Skip to content

Commit

Permalink
Merge pull request #299 from the-hideout/player-search
Browse files Browse the repository at this point in the history
Player search
  • Loading branch information
Razzmatazzz authored Jul 31, 2024
2 parents bdf2bc4 + 909b50e commit 425016e
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.14.2
20.16.0
3 changes: 3 additions & 0 deletions bot.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ discordClient.on('interactionCreate', async interaction => {
options = options.splice(0, 25);

await interaction.respond(options.map(name => {
if (typeof name === 'object' && name.name && name.value) {
return name;
}
return {
name: name,
value: name,
Expand Down
28 changes: 26 additions & 2 deletions commands/ammo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,40 @@ const defaultFunction = {
if (!caliberLabel) caliberLabel = caliber.replace('Caliber', '');
embed.setTitle(`${caliberLabel} ${t('Ammo Table')}`);

let maxNameLength = 11;
if (ammos.length > 0) {
embed.setThumbnail(ammos[0].iconLink);
//embed.setThumbnail(ammos[0].iconLink);
embed.setThumbnail(ammos.reduce((currentThumb, ammo) => {
if (!currentThumb) {
currentThumb = ammo.iconLink;
}
if (ammo.name?.toLowerCase() === searchString.toLowerCase()) {
currentThumb = ammo.iconLink;
}
const longWidths = [
ammo.properties.penetrationPower >= 100 ? 1 : 0,
ammo.properties.totalDamage >= 100 ? 1 : 0,
ammo.properties.armorDamage >= 100 ? 1 : 0,
ammo.properties.fragmentationChance >= 1 ? 1 : 0,
ammo.properties.initialSpeed >= 100 ? 1 : 0,
].reduce((total, current) => {
return total + current;
}, 0);
if (longWidths > 2) {
// more than 2 numeric fields will be display with triple digits
// reduce the name field length to compensate
maxNameLength = maxNameLength - longWidths - 2;
}
return currentThumb;
}, null));
}

for (const ammo of ammos) {
if (!ammo.shortName) {
continue;
}
tableData.push([
ammo.shortName.substring(0, 11),
ammo.shortName.substring(0, maxNameLength),
ammo.properties.penetrationPower,
ammo.properties.totalDamage,
ammo.properties.armorDamage,
Expand Down
170 changes: 170 additions & 0 deletions commands/player.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
import moment from 'moment';

import gameData from '../modules/game-data.mjs';
import { getFixedT, getCommandLocalizations } from '../modules/translations.mjs';
import progress from '../modules/progress-shard.mjs';

const getPlayerLevel = (exp, levels) => {
if (exp === 0) {
return 0;
}
let expTotal = 0;
for (let i = 0; i < levels.length; i++) {
const levelData = levels[i];
expTotal += levelData.exp;
if (expTotal === exp) {
return levelData.level;
}
if (expTotal > exp) {
return levels[i - 1].level;
}

}
return levels[levels.length-1].level;
};

const defaultFunction = {
data: new SlashCommandBuilder()
.setName('player')
.setDescription('Get player profile information')
.setNameLocalizations(getCommandLocalizations('player'))
.setDescriptionLocalizations(getCommandLocalizations('player_desc'))
.addStringOption(option => option
.setName('account')
.setDescription('Account to retrieve')
.setNameLocalizations(getCommandLocalizations('account'))
.setDescriptionLocalizations(getCommandLocalizations('account_seach_desc'))
.setAutocomplete(true)
.setRequired(true)
.setMinLength(3)
.setMaxLength(15)
),

async execute(interaction) {
await interaction.deferReply();
const { lang } = await progress.getInteractionSettings(interaction);
const t = getFixedT(lang);
const accountId = interaction.options.getString('account');
if (isNaN(accountId)) {
return interaction.editReply({
content: `❌ ${t('{{accountId}} is not a valid account id', {accountId})}`
});
}

const profile = await fetch(`https://players.tarkov.dev/profile/${accountId}.json`).then(r => r.json()).catch(error => {
return {
err: error.message,
errmsg: error.message,
};
});

if (profile.err) {
return interaction.editReply({
content: `❌ ${t('Error retrieving account {{accountId}}: {{errorMessage}}', {accountId, errorMessage: profile.errmsg})}`,
});
}

const [playerLevels, items, achievements] = await Promise.all([
gameData.playerLevels.getAll(),
gameData.items.getAll(lang),
gameData.achievements.getAll(lang),
]);

const playerLevel = getPlayerLevel(profile.info.experience, playerLevels);

const dogtagIds = {
Usec: '59f32c3b86f77472a31742f0',
Bear: '59f32bb586f774757e1e8442'
};

const dogtagItem = items.find(i => i.id === dogtagIds[profile.info.side]);

const embeds = [];

const embed = new EmbedBuilder();
embeds.push(embed);

// Construct the embed
embed.setTitle(`${profile.info.nickname} (${playerLevel} ${t(profile.info.side)})`);
embed.setThumbnail(dogtagItem.iconLink);
/*embed.setAuthor({
name: trader.name,
iconURL: trader.imageLink,
url: `https://tarkov.dev/trader/${trader.normalizedName}`,
});*/
embed.setURL(`https://tarkov.dev/player/${accountId}`);
const descriptionParts = [`${t('Hours Played')}: ${Math.round(profile.pmcStats.eft.totalInGameTime / 60 / 60)}`];
const lastActive = profile.skills.Common.reduce((mostRecent, skill) => {
if (skill.LastAccess > mostRecent) {
return skill.LastAccess;
}
return mostRecent;
}, 0);
if (lastActive > 0) {
descriptionParts.push(`${t('Last Active')}: ${new Date(lastActive * 1000).toLocaleString(lang)}`);
}
/*if (task.minPlayerLevel) {
descriptionParts.push(`${t('Minimum Level')}: ${task.minPlayerLevel}`);
}*/
embed.setDescription(descriptionParts.join('\n'));
moment.locale(lang);
const footerText = t('Updated {{updateTimeAgo}}', {updateTimeAgo: moment(new Date(profile.updated)).fromNow()});

const statTypes = {
pmc: 'PMC',
scav: 'Scav',
};
for (const statType in statTypes) {
const sideLabel = statTypes[statType];
const raidCount = profile[`${statType}Stats`].eft.overAllCounters.Items?.find(i => i.Key.includes('Sessions'))?.Value ?? 0
const raidsSurvived = profile[`${statType}Stats`].eft.overAllCounters.Items?.find(i => i.Key.includes('Survived'))?.Value ?? 0;
const raidsDied = profile[`${statType}Stats`].eft.overAllCounters.Items?.find(i => i.Key.includes('Killed'))?.Value ?? 0;
const raidSurvivalRatio = raidCount > 0 ? raidsSurvived / raidCount : 0;
const raidDiedRatio = raidCount > 0 ? raidsDied / raidCount : 0;
const kills = profile[`${statType}Stats`].eft.overAllCounters.Items?.find(i => i.Key.includes('Kills'))?.Value ?? 0;
const kdr = raidsDied > 0 ? (kills / raidsDied).toFixed(2) : '∞';
const survivalStreak = profile[`${statType}Stats`].eft.overAllCounters.Items?.find(i => i.Key.includes('LongestWinStreak'))?.Value ?? 0;
const fieldValue = `${t('Survive Rate')}: ${raidSurvivalRatio.toFixed(2)} (${raidsSurvived}/${raidCount})
${t('Death Rate')}: ${raidDiedRatio.toFixed(2)} (${raidsDied}/${raidCount})
${t('K:D', {nsSeparator: '|'})}: ${kdr} (${kills}/${raidsDied})
${t('Longest Survival Streak')}: ${survivalStreak}`;
embed.addFields(
{ name: t('{{side}} Stats', {side: t(sideLabel)}), value: fieldValue, inline: true },
);
}

const completedAchievements = [];
for (const achievementId in profile.achievements) {
const achievement = achievements.find(a => a.id === achievementId);
if (!achievement) {
continue;
}
completedAchievements.push({...achievement, completed: profile.achievements[achievementId]});
}
if (completedAchievements.length > 0) {
const achievementsEmbed = new EmbedBuilder();
embeds.push(achievementsEmbed);
achievementsEmbed.setTitle(t('Achievements'));
for (const achievement of completedAchievements) {
const completed = new Date(achievement.completed * 1000);
achievementsEmbed.addFields(
{ name: achievement.name, value: completed.toLocaleString(lang), inline: true },
);
}
achievementsEmbed.setFooter({text: footerText});
} else {
embed.setFooter({text: footerText});
}

return interaction.editReply({
embeds,
});
},
examples: [
'/$t(player) Nikita',
'/$t(player) Prapor'
]
};

export default defaultFunction;
4 changes: 2 additions & 2 deletions commands/quest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ const defaultFunction = {
});
},
examples: [
'/$t(map) Woods',
'/$t(map) customs'
'/$t(quest) Debut',
'/$t(quest) Supplier'
]
};

Expand Down
5 changes: 3 additions & 2 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fork } from 'child_process';

import got from 'got';
import cron from 'cron';
import { CronJob } from 'cron';
import { ShardingManager } from 'discord.js';

import progress from './modules/progress.mjs';
Expand Down Expand Up @@ -45,7 +45,7 @@ manager.spawn().then(shards => {
if (process.env.NODE_ENV === 'production') {
// A healthcheck cron to send a GET request to our status server
// The cron schedule is expressed in seconds for the first value
healthcheckJob = new cron.CronJob('*/45 * * * * *', () => {
healthcheckJob = new CronJob('*/45 * * * * *', () => {
got(
`https://status.tarkov.dev/api/push/${process.env.HEALTH_ENDPOINT}?msg=OK`,
{
Expand Down Expand Up @@ -105,6 +105,7 @@ gameData.updateAll().then(() => {
}
});
});
gameData.updateProfileIndex();

process.on('uncaughtException', (error) => {
try {
Expand Down
8 changes: 8 additions & 0 deletions modules/autocomplete.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ async function autocomplete(interaction) {
console.error(getError);
}
let cacheKey = interaction.commandName;
if (cacheKey === 'player') {
const nameResults = await gameData.profiles.search(interaction.options.getString('account'));
const names = [];
for (const id in nameResults) {
names.push({name: nameResults[id], value: id});
}
return names;
}
if (cacheKey === 'progress' && interaction.options.getSubcommand() === 'hideout') {
cacheKey = interaction.options.getSubcommand();
searchString = interaction.options.getString('station');
Expand Down
Loading

0 comments on commit 425016e

Please sign in to comment.