Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 98 additions & 90 deletions bun.lock

Large diffs are not rendered by default.

112 changes: 57 additions & 55 deletions commands/f1Standings.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,69 @@
import * as Discord from 'discord.js';
import * as cheerio from 'cheerio';
import { logError } from '../utils/log';
import { Command } from './classes/Command';

function safeParseInt(text: string): number {
const result = parseInt(text, 10);
return Number.isNaN(result) ? 0 : result;
}

function extractStandings(html: string, type: string): any[] {
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
const { JSDOM } = require('jsdom') as { JSDOM: any };
const dom = new JSDOM(html);
const rows: any[] = dom.window.document.getElementById('results-table')
?.querySelector('tbody')
?.querySelectorAll('tr') || [];
return Array.from(rows)
.map((row: any) => {
const columns = row.querySelectorAll('td');
if (type === 'drivers' && columns.length === 5) {
const driverLinkEl = columns[1].querySelector('a');
let driverName = '';

if (driverLinkEl) {
const nameContainerSpanCandidates = [
driverLinkEl.querySelectorAll('span')[1],
driverLinkEl.querySelectorAll('span')[0],
];

let nameContainerSpan: Element | null = null;
nameContainerSpanCandidates.forEach((nameContainerSpanCandidate) => {
if (nameContainerSpanCandidate && nameContainerSpanCandidate.querySelector('.max-lg\\:hidden')) {
nameContainerSpan = nameContainerSpanCandidate;
}
});

if (nameContainerSpan) {
const firstNameEl = (nameContainerSpan as Element).querySelector('.max-lg\\:hidden');
const lastNameEl = (nameContainerSpan as Element).querySelector('.max-md\\:hidden');

const firstName = firstNameEl ? firstNameEl.textContent!.trim() : '';
const lastName = lastNameEl ? lastNameEl.textContent!.trim() : '';
driverName = `${firstName} ${lastName}`.trim();
} else {
driverName = driverLinkEl.textContent!.trim().replace(/\s+/g, ' ');
const $ = cheerio.load(html);
const rows = $('#results-table tbody tr').toArray();

return rows.map((rowEl) => {
const row = $(rowEl);
const columns = row.find('td');

if (type === 'drivers' && columns.length === 5) {
const driverLinkEl = $(columns[1]).find('a');
let driverName = '';

if (driverLinkEl.length) {
const nameContainerSpanCandidates = [
driverLinkEl.find('span').eq(1),
driverLinkEl.find('span').eq(0),
];

let nameContainerSpan: any = null;
for (const candidate of nameContainerSpanCandidates) {
if (candidate.length && candidate.find('.max-lg\\:hidden').length) {
nameContainerSpan = candidate;
break;
}
} else {
driverName = columns[1].textContent!.trim().replace(/\s+/g, ' ');
}

return {
position: parseInt(columns[0].textContent!.trim(), 10),
driver: driverName,
nationality: columns[2].textContent!.trim(),
car: columns[3].textContent!.trim(),
points: parseInt(columns[4].textContent!.trim(), 10),
};
if (nameContainerSpan) {
const firstName = nameContainerSpan.find('.max-lg\\:hidden').text().trim();
const lastName = nameContainerSpan.find('.max-md\\:hidden').text().trim();
driverName = `${firstName} ${lastName}`.trim();
} else {
driverName = driverLinkEl.text().trim().replace(/\s+/g, ' ');
}
} else {
driverName = $(columns[1]).text().trim().replace(/\s+/g, ' ');
}

if (type === 'teams' && columns.length === 3) {
return {
position: parseInt(columns[0].textContent!.trim(), 10),
team: columns[1].textContent!.trim(),
points: parseInt(columns[2].textContent!.trim(), 10),
};
}
return {
position: safeParseInt($(columns[0]).text().trim()),
driver: driverName,
nationality: $(columns[2]).text().trim(),
car: $(columns[3]).text().trim(),
points: safeParseInt($(columns[4]).text().trim()),
};
}

return null;
})
.filter(Boolean);
if (type === 'teams' && columns.length === 3) {
return {
position: safeParseInt($(columns[0]).text().trim()),
team: $(columns[1]).text().trim(),
points: safeParseInt($(columns[2]).text().trim()),
};
}

return null;
}).filter(Boolean);
}

function buildEmbed(data: any[], type: string, year: number): Discord.EmbedBuilder {
Expand Down Expand Up @@ -114,10 +115,11 @@ class F1Standings extends Command {
const minYear = type === 'drivers' ? 1950 : 1958;

if (year > currentYear || year < minYear) {
interaction.editReply({
await interaction.editReply({
content: `Invalid year for ${type} standings. Must be between ${minYear} and ${currentYear}.`,
ephemeral: true,
});
return;
}

const endpoint = type === 'drivers' ? 'drivers' : 'team';
Expand Down
51 changes: 37 additions & 14 deletions commands/gacha.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
/* eslint-disable no-unreachable */
import path from 'path';
import {
EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle,
} from 'discord.js';
import { Command } from './classes/Command';
import charactersRaw from '../data/hsrCharacters.json';
import lightconesRaw from '../data/hsrLC.json';
import hsrNames from '../data/hsr.json';

type Pools = { namesData: any; characterPool: any[]; lightconePool: any[] };
let pools: Pools | null = null;
let poolsPromise: Promise<Pools> | null = null;

async function getPools() {
if (pools) return pools;
if (!poolsPromise) {
poolsPromise = Promise.all([
Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(),
Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(),
Bun.file(path.join(__dirname, '../data/hsr.json')).json(),
]).then(([charactersRaw, lightconesRaw, hsrNames]) => {
pools = {
namesData: hsrNames,
characterPool: Object.values(charactersRaw),
lightconePool: Object.values(lightconesRaw),
};
poolsPromise = null;
return pools;
});
}
return poolsPromise;
}

class Gacha extends Command {
namesData: any;
Expand All @@ -26,9 +47,9 @@ class Gacha extends Command {
},
], { blame: 'xei' });

this.namesData = hsrNames;
this.characterPool = Object.values(charactersRaw);
this.lightconePool = Object.values(lightconesRaw);
this.namesData = null;
this.characterPool = [];
this.lightconePool = [];
}

getRandomItem(pool: any[]) {
Expand All @@ -40,7 +61,7 @@ class Gacha extends Command {
}

getItemDetails(item: any) {
if (!item) return { name: 'Unknown', imagePath: null, rarity: 'Unknown' };
if (!item) return { name: 'Unknown', imageUrl: null, rarity: 'Unknown' };

const nameHash = item.AvatarName?.Hash?.toString() || item.EquipmentName?.Hash?.toString();
const name = this.namesData.en[nameHash] || 'Unknown';
Expand All @@ -49,14 +70,16 @@ class Gacha extends Command {

return {
name,
imageUrl: imagePath ? `https://enka.network/ui/hsr/${imagePath}` : null,
imageUrl: imagePath ? `https://enka.network${imagePath}` : null,
rarity,
};
}

async run(interaction: any): Promise<void> {
await interaction.editReply({ content: 'gacha is not ready yet', ephemeral: true });
return;
const loaded = await getPools();
this.namesData = loaded.namesData;
this.characterPool = loaded.characterPool;
this.lightconePool = loaded.lightconePool;

const amount = interaction.options.getInteger('amount');
const userId = interaction.user.id;
Expand Down Expand Up @@ -105,7 +128,7 @@ class Gacha extends Command {
const itemDetails = this.getItemDetails(rollResult);
results.push(itemDetails);

const itemType = this.characterPool.some((c) => c.name === itemDetails.name) ? 'Character' : 'Lightcone';
const itemType = rollResult?.AvatarName ? 'Character' : 'Lightcone';
this.client.db.gacha.addGachaItem(userId, itemDetails.name, itemType, itemDetails.rarity);
}

Expand Down Expand Up @@ -162,12 +185,12 @@ class Gacha extends Command {
const collector = message.createMessageComponentCollector({ time: 60000 });

collector.on('collect', async (i: any) => {
if (i.customId === 'next_roll') {
if (i.customId === 'nextRoll') {
currentIndex += 1;
if (currentIndex < results.length) {
await updateMessage(i);
}
} else if (i.customId === 'skip_results') {
} else if (i.customId === 'skipResults') {
collector.stop();
}
});
Expand Down
32 changes: 24 additions & 8 deletions commands/genshinProfile.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import path from 'path';
import { Command } from './classes/Command';
import { logError } from '../utils/log';
import _profilePictures from '../data/genshinPfps.json';
import _namecards from '../data/genshinNamecards.json';

const profilePictures: any = _profilePictures;
const namecards: any = _namecards;
type GenshinData = { profilePictures: any; namecards: any };
let dataCache: GenshinData | null = null;
let dataCachePromise: Promise<GenshinData> | null = null;

async function loadGenshinData() {
if (dataCache) return dataCache;
if (!dataCachePromise) {
dataCachePromise = Promise.all([
Bun.file(path.join(__dirname, '../data/genshinPfps.json')).json(),
Bun.file(path.join(__dirname, '../data/genshinNamecards.json')).json(),
]).then(([profilePictures, namecards]) => {
dataCache = { profilePictures, namecards };
dataCachePromise = null;
return dataCache;
});
}
return dataCachePromise;
}

class GenshinProfile extends Command {
constructor(client: any) {
Expand All @@ -26,6 +41,7 @@ class GenshinProfile extends Command {
};

try {
const { profilePictures, namecards } = await loadGenshinData();
const response = await fetch(url, { headers });
if (!response.ok) {
logError(`HTTP Error Response: Status ${response.status} ${response.statusText}`);
Expand All @@ -44,15 +60,15 @@ class GenshinProfile extends Command {
const profilePictureId = playerInfo.profilePicture?.id;
let profilePictureUrl = null;
if (profilePictureId && profilePictures[profilePictureId]) {
const { iconPath } = profilePictures[profilePictureId];
profilePictureUrl = `https://enka.network/ui/${iconPath}.png`;
const { IconPath } = profilePictures[profilePictureId];
profilePictureUrl = `https://enka.network${IconPath}`;
}

const { nameCardId } = playerInfo;
let namecardUrl = null;
if (nameCardId && namecards[nameCardId]) {
const namecardPath = namecards[nameCardId].icon;
namecardUrl = `https://enka.network/ui/${namecardPath}.png`;
const namecardPath = namecards[nameCardId].Icon;
namecardUrl = `https://enka.network${namecardPath}`;
}

const embed = {
Expand Down
39 changes: 31 additions & 8 deletions commands/hsrProfile.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
import path from 'path';
import { Command } from './classes/Command';
import { logError } from '../utils/log';
import _avatarData from '../data/hsrAvartars.json';
import _characterData from '../data/hsrCharacters.json';
import _namesData from '../data/hsr.json';
import _lightconeData from '../data/hsrLC.json';

const avatarData: any = _avatarData;
const characterData: any = _characterData;
const namesData: any = _namesData;
const lightconeData: any = _lightconeData;
type HsrData = {
avatarData: any;
characterData: any;
namesData: any;
lightconeData: any;
};
let dataCache: HsrData | null = null;
let dataCachePromise: Promise<HsrData> | null = null;

async function loadHsrData() {
if (dataCache) return dataCache;
if (!dataCachePromise) {
dataCachePromise = Promise.all([
Bun.file(path.join(__dirname, '../data/hsrAvartars.json')).json(),
Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(),
Bun.file(path.join(__dirname, '../data/hsr.json')).json(),
Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(),
]).then(([avatarData, characterData, namesData, lightconeData]) => {
dataCache = {
avatarData, characterData, namesData, lightconeData,
};
dataCachePromise = null;
return dataCache;
});
}
return dataCachePromise;
}

class HsrProfile extends Command {
constructor(client: any) {
Expand All @@ -30,6 +50,9 @@ class HsrProfile extends Command {
};

try {
const {
avatarData, characterData, namesData, lightconeData,
} = await loadHsrData();
const response = await fetch(url, { headers });
if (!response.ok) {
logError(`HTTP Error Response: Status ${response.status} ${response.statusText}`);
Expand Down
13 changes: 11 additions & 2 deletions commands/nword.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import path from 'path';
import { Command } from './classes/Command';
import wordlist from '../data/words_dictionary.json';

let nwordsCache: string[] | null = null;

async function getNwords(): Promise<string[]> {
if (!nwordsCache) {
nwordsCache = await Bun.file(path.join(__dirname, '../data/nwords.json')).json();
}
return nwordsCache!;
}

class NWord extends Command {
constructor(client: any) {
super(client, 'nword', 'say an n-word', [], { blame: 'ei' });
}

async run(interaction: any): Promise<void> {
const nwords = Object.keys(wordlist).filter((word) => word.startsWith('n'));
const nwords = await getNwords();
const nword = nwords[Math.floor(Math.random() * nwords.length)];
await interaction.editReply(nword);
}
Expand Down
Loading