import subDays from 'date-fns/subDays';
import requestCreator from './libs/requestCreator';
import { generatePaginationItems, setPage, getPage } from './utils/pagination';
import getIDFromURL from './utils/getIDFromURL';
import getCurrentServer from './utils/getCurrentServer';
import formatDate from './utils/formatDate';
import renderPopup from './utils/renderPopup';
import { formatPlayerURL } from './utils/twstats';
import {
formatTribeURL,
formatPlayerURL as formatPlayerURLTribalWars,
formatVillageName,
formatVillageURL,
loadInADayData,
} from './utils/tribalwars';
import { setItem, getItem } from './utils/localStorage';
// ==UserScript==
// @name Extended Player Profile
// @namespace https://github.com/tribalwarshelp/scripts
// @updateURL https://raw.githubusercontent.com/tribalwarshelp/scripts/master/dist/extendedPlayerProfile.js
// @downloadURL https://raw.githubusercontent.com/tribalwarshelp/scripts/master/dist/extendedPlayerProfile.js
// @version 0.71
// @description Extended Player Profile
// @author Kichiyaki http://dawid-wysokinski.pl/
// @match *://*/game.php*&screen=info_player*
// @grant none
// @run-at document-end
// ==/UserScript==
const SERVER = getCurrentServer();
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 pageData($server: String!, $id: Int!, $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, filter: $filter) {
items {
rank
rankAtt
rankDef
rankSup
rankTotal
points
scoreAtt
scoreAtt
scoreDef
scoreSup
scoreTotal
villages
}
}
}
`;
const TRIBE_CHANGES_QUERY = `
query tribeChanges($server: String!, $filter: TribeChangeFilter!) {
tribeChanges(server: $server, 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!) {
playerHistory(server: $server, filter: $playerHistoryFilter) {
total
items {
totalVillages
points
rank
scoreAtt
rankAtt
scoreDef
rankDef
scoreSup
rankSup
scoreTotal
rankTotal
tribe {
id
tag
}
createDate
}
}
dailyPlayerStats(server: $server, filter: $dailyPlayerStatsFilter) {
items {
points
scoreAtt
scoreAtt
scoreDef
scoreSup
scoreTotal
villages
createDate
}
}
}
`;
const PLAYER_HISTORY_PAGINATION_CONTAINER_ID = 'playerHistoryPagination';
const PLAYER_HISTORY_PER_PAGE = 15;
const ENNOBLEMENTS_QUERY = `
query ennoblements($server: String!, $filter: EnnoblementFilter!) {
ennoblements(server: $server, filter: $filter) {
total
items {
village {
id
name
x
y
}
oldOwner {
id
name
}
oldOwnerTribe {
id
tag
}
newOwner {
id
name
}
newOwnerTribe {
id
tag
}
ennobledAt
}
}
}
`;
const ENNOBLEMENTS_PAGINATION_CONTAINER_ID = 'ennoblementsPagination';
const ENNOBLEMENTS_PER_PAGE = 15;
const profileInfoTBody = document.querySelector('#player_info > tbody');
const actionsContainer =
PLAYER_ID === CURRENT_PLAYER_ID
? profileInfoTBody
: document.querySelector(
'#content_value > table > tbody > tr > td:nth-child(1) > table:nth-child(2) > tbody'
);
const otherElementsContainer = 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 loadDataFromCache = () => {
return getItem(LOCAL_STORAGE_KEY);
};
const cachePlayerData = (data = {}) => {
setItem(LOCAL_STORAGE_KEY, data);
};
const loadData = async () => {
const data = await requestCreator({
query: PLAYER_QUERY,
variables: {
server: SERVER,
id: PLAYER_ID,
filter: {
sort: 'createDate DESC',
limit: 1,
playerID: [PLAYER_ID],
},
},
});
if (data.player) {
const inADay = {};
inADay.att = await loadInADayData('kill_att', {
name: data.player.name,
playerID: data.player.id,
});
inADay.def = await loadInADayData('kill_def', {
name: data.player.name,
playerID: data.player.id,
});
inADay.sup = await loadInADayData('kill_sup', {
name: data.player.name,
playerID: data.player.id,
});
inADay.lootRes = await loadInADayData('loot_res', {
name: data.player.name,
playerID: data.player.id,
});
inADay.lootVil = await loadInADayData('loot_vil', {
name: data.player.name,
playerID: data.player.id,
});
inADay.scavenge = await loadInADayData('scavenge', {
name: data.player.name,
playerID: data.player.id,
});
inADay.conquer = await loadInADayData('conquer', {
name: data.player.name,
playerID: data.player.id,
});
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 = `
Player's servers
|
|
`;
otherElementsContainer.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%';
otherElementsContainer.prepend(playerOtherNames);
}
playerOtherNames.innerHTML = `
Old name
|
New name
|
Date
|
${player.nameChanges
.map((nameChange) => {
return `
${nameChange.oldName}
|
${nameChange.newName}
|
${formatDate(nameChange.changeDate, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})}
|
`;
})
.join('')}
`;
};
const getTodaysStatsTdStyle = (value) => {
const statIncreaseStyle = 'color: #000; background-color: #0f0';
const statDecreaseStyle = 'color: #000; background-color: #f00';
const defaultStyle = 'color: #000; background-color: #808080';
return value > 0
? statIncreaseStyle
: value < 0
? statDecreaseStyle
: defaultStyle;
};
const renderTodaysStats = (stats) => {
let todaysStats = document.querySelector('#todaysStats');
if (!todaysStats) {
todaysStats = document.createElement('div');
todaysStats.id = 'todaysStats';
todaysStats.width = '100%';
otherElementsContainer.prepend(todaysStats);
}
todaysStats.innerHTML = `
Today's stats
|
Points:
|
${Math.abs(stats.points).toLocaleString()}
|
Rank:
|
${Math.abs(stats.rank)}
|
Villages:
|
${Math.abs(stats.villages).toLocaleString()}
|
ODA:
|
${Math.abs(stats.scoreAtt).toLocaleString()}
|
ODA Rank:
|
${Math.abs(stats.rankAtt)}
|
ODD:
|
${Math.abs(stats.scoreDef).toLocaleString()}
|
ODD Rank:
|
${Math.abs(stats.rankDef)}
|
ODS:
|
${Math.abs(stats.scoreSup).toLocaleString()}
|
ODS Rank:
|
${Math.abs(stats.rankSup)}
|
OD:
|
${Math.abs(stats.scoreTotal).toLocaleString()}
|
OD Rank:
|
${Math.abs(stats.rankTotal)}
|
`;
};
const renderInADayRanks = (player) => {
let inADayRanks = document.querySelector('#inADayRanks');
if (!inADayRanks) {
inADayRanks = document.createElement('div');
inADayRanks.id = 'inADayRanks';
inADayRanks.width = '100%';
otherElementsContainer.prepend(inADayRanks);
}
inADayRanks.innerHTML = `
'In a day' best scores
|
Units defeated while attacking:
|
${player.inADay.att.score.toLocaleString()} (${
player.inADay.att.rank
}.)
|
Units defeated while defending:
|
${player.inADay.def.score.toLocaleString()} (${
player.inADay.def.rank
}.)
|
Units defeated while supporting:
|
${player.inADay.sup.score.toLocaleString()} (${
player.inADay.sup.rank
}.)
|
Resources plundered:
|
${player.inADay.lootRes.score.toLocaleString()} (${
player.inADay.lootRes.rank
}.)
|
Villages plundered:
|
${player.inADay.lootVil.score.toLocaleString()} (${
player.inADay.lootVil.rank
}.)
|
Resources gathered:
|
${player.inADay.scavenge.score.toLocaleString()} (${
player.inADay.scavenge.rank
}.)
|
Villages conquered:
|
${player.inADay.conquer.score.toLocaleString()} (${
player.inADay.conquer.rank
}.)
|
`;
};
const render = ({ player, dailyPlayerStats }) => {
[
{
title: 'Joined at:',
data: formatDate(player.joinedAt),
id: 'joined_at',
},
{
title: 'Daily growth:',
data: player.dailyGrowth.toLocaleString(),
id: 'dg',
},
{
title: 'Best rank:',
data: player.bestRank + ' ' + `(${formatDate(player.bestRankAt)})`,
id: 'best_rank',
},
{
title: 'Most points:',
data:
player.mostPoints.toLocaleString() +
' ' +
`(${formatDate(player.mostPointsAt)})`,
id: 'most_points',
},
{
title: 'Most villages:',
data:
player.mostVillages + ' ' + `(${formatDate(player.mostVillagesAt)})`,
id: 'most_villages',
},
].forEach((data) => {
renderTr(data);
});
renderInADayRanks(player);
if (dailyPlayerStats && dailyPlayerStats.items.length > 0) {
renderTodaysStats(dailyPlayerStats.items[0]);
}
if (player.nameChanges.length > 0) {
renderPlayerOtherNames(player);
}
if (player.servers.length > 0) {
renderPlayerServers(player);
}
};
const addPaginationListeners = (id, fn) => {
document.querySelectorAll('#' + id + ' a').forEach((el) => {
el.addEventListener('click', fn);
});
};
const renderTribeChanges = (e, currentPage, tribeChanges) => {
const paginationItems = generatePaginationItems({
total: tribeChanges.total,
limit: TRIBE_CHANGES_PER_PAGE,
currentPage,
});
const html = `
Date
|
New tribe
|
Old tribe
|
${tribeChanges.items
.map((tribeChange) => {
let rowHTML =
'' + `${formatDate(tribeChange.createdAt)} | `;
if (tribeChange.newTribe) {
rowHTML += `${tribeChange.newTribe.tag} | `;
} else {
rowHTML += '- | ';
}
if (tribeChange.oldTribe) {
rowHTML += `${tribeChange.oldTribe.tag} | `;
} else {
rowHTML += '- | ';
}
return rowHTML + '
';
})
.join('')}
`;
renderPopup({
e,
title: `Tribe changes`,
id: 'tribeChanges',
html,
});
addPaginationListeners(
TRIBE_CHANGES_PAGINATION_CONTAINER_ID,
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],
offset: TRIBE_CHANGES_PER_PAGE * (page - 1),
limit: TRIBE_CHANGES_PER_PAGE,
sort: 'createdAt DESC',
},
server: SERVER,
},
});
renderTribeChanges(e, page, data.tribeChanges);
}
};
const addMathSymbol = (v) => {
return v > 0 ? '+' + v : v;
};
const renderPlayerHistory = (
e,
currentPage,
playerHistory,
playerDailyStats
) => {
const paginationItems = generatePaginationItems({
total: playerHistory.total,
limit: PLAYER_HISTORY_PER_PAGE,
currentPage,
});
const html = `
Date
|
Tribe
|
Points
|
Villages
|
OD
|
ODA
|
ODD
|
ODS
|
${playerHistory.items
.map((playerHistory) => {
const subtracted =
subDays(new Date(playerHistory.createDate), 1)
.toISOString()
.split('.')[0] + 'Z';
const stats = playerDailyStats.items.find((stats) => {
return stats.createDate === subtracted;
});
let rowHTML =
'' +
`${formatDate(playerHistory.createDate, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})} | `;
if (playerHistory.tribe) {
rowHTML += `${playerHistory.tribe.tag} | `;
} else {
rowHTML += '- | ';
}
rowHTML +=
`
${playerHistory.points.toLocaleString()} (${
playerHistory.rank
})
|
${playerHistory.totalVillages}
|
${playerHistory.scoreTotal.toLocaleString()} (${
playerHistory.rankTotal
})
|
${playerHistory.scoreAtt.toLocaleString()} (${
playerHistory.rankAtt
})
|
${playerHistory.scoreDef.toLocaleString()} (${
playerHistory.rankDef
})
|
${playerHistory.scoreSup.toLocaleString()} (${
playerHistory.rankSup
})
|
` + '
';
return rowHTML;
})
.join('')}
`;
renderPopup({
e,
title: `Player history`,
id: 'playerHistory',
html,
});
addPaginationListeners(
PLAYER_HISTORY_PAGINATION_CONTAINER_ID,
handleShowPlayerHistoryClick
);
};
const handleShowPlayerHistoryClick = async (e) => {
e.preventDefault();
const page = getPage(e.target);
if (!isNaN(page)) {
try {
const filter = {
playerID: [PLAYER_ID],
offset: PLAYER_HISTORY_PER_PAGE * (page - 1),
limit: PLAYER_HISTORY_PER_PAGE,
sort: 'createDate DESC',
};
const { playerHistory, dailyPlayerStats } = await requestCreator({
query: PLAYER_HISTORY_AND_PLAYER_DAILY_STATS_QUERY,
variables: {
server: SERVER,
playerHistoryFilter: filter,
dailyPlayerStatsFilter: {
...filter,
offset: filter.offset + 1,
},
},
});
renderPlayerHistory(e, page, playerHistory, dailyPlayerStats);
} catch (error) {
console.log('cannot load player history', error);
}
}
};
const renderPlayerEnnoblements = (e, currentPage, ennoblements) => {
const paginationItems = generatePaginationItems({
total: ennoblements.total,
limit: ENNOBLEMENTS_PER_PAGE,
currentPage,
});
const getPlayerTd = (player, tribe) => {
if (player) {
return `${
player.name
} (${
tribe ? `${tribe.tag}` : '-'
}) | `;
}
return '- | ';
};
const html = `
Date
|
Village
|
New Owner
|
Old Owner
|
${ennoblements.items
.map((ennoblement) => {
let rowHTML =
'' + `${formatDate(ennoblement.ennobledAt)} | `;
if (ennoblement.village) {
rowHTML += `${formatVillageName(
ennoblement.village.name,
ennoblement.village.x,
ennoblement.village.y
)} | `;
} else {
rowHTML += '- | ';
}
rowHTML += getPlayerTd(
ennoblement.newOwner,
ennoblement.newOwnerTribe
);
rowHTML += getPlayerTd(
ennoblement.oldOwner,
ennoblement.oldOwnerTribe
);
return rowHTML + '
';
})
.join('')}
`;
renderPopup({
e,
title: `Ennoblements`,
id: 'ennoblements',
html,
});
addPaginationListeners(
ENNOBLEMENTS_PAGINATION_CONTAINER_ID,
handleShowPlayerEnnoblementsClick
);
};
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,
},
});
renderPlayerEnnoblements(e, page, data.ennoblements);
}
};
const handleExportPlayerVillagesButtonClick = (e) => {
e.preventDefault();
Dialog.show(
'Exported villages',
``
);
};
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 showTribeChanges = document.createElement('a');
showTribeChanges.href = '#';
setPage(showTribeChanges, '1');
showTribeChanges.innerHTML = 'Show tribe changes';
showTribeChanges.addEventListener('click', handleShowTribeChangesButtonClick);
actionsContainer.appendChild(wrapAction(showTribeChanges));
const showPlayerHistory = document.createElement('a');
showPlayerHistory.href = '#';
setPage(showPlayerHistory, '1');
showPlayerHistory.innerHTML = 'Show player history';
showPlayerHistory.addEventListener('click', handleShowPlayerHistoryClick);
actionsContainer.appendChild(wrapAction(showPlayerHistory));
const showEnnoblements = document.createElement('a');
showEnnoblements.href = '#';
setPage(showEnnoblements, '1');
showEnnoblements.innerHTML = 'Show player ennoblements';
showEnnoblements.addEventListener('click', handleShowPlayerEnnoblementsClick);
actionsContainer.appendChild(wrapAction(showEnnoblements));
const exportPlayerVillages = document.createElement('a');
exportPlayerVillages.href = '#';
exportPlayerVillages.innerHTML = `Export player's villages`;
exportPlayerVillages.addEventListener(
'click',
handleExportPlayerVillagesButtonClick
);
actionsContainer.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);
}
})();