Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/primes and update to discord.js v14.12 #38

Merged
merged 22 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cc5b812
chore(package.json): update dependencies
Arcoz0308 Aug 2, 2023
d2a6c00
feat(env.z.ts): add GOOGLE_MAIL, GOOGLE_PRIVATE_KEY, and GOOGLE_SHEET…
Arcoz0308 Aug 2, 2023
ad0ebba
feat(global.config.ts): add owners array to global configuration
Arcoz0308 Aug 2, 2023
caf4afc
feat(admin.util.ts): add ownerOnly function to check if user is owner
Arcoz0308 Aug 2, 2023
ecbd581
feat(admin.class.ts): add support for PrimeStaff command in admin class
Arcoz0308 Aug 2, 2023
0fe9d2c
feat(commands.config.ts): add support for 'primeStaff' command
Arcoz0308 Aug 2, 2023
702ad8d
feat(button.util.ts): add support for sending button replies to inter…
Arcoz0308 Aug 2, 2023
9a35328
feat(confirm.const.ts): add support for primeStaff button in confirmI…
Arcoz0308 Aug 2, 2023
0163ab3
fix(reverse_xp_movement.class.ts): fix owner-only check in run method…
Arcoz0308 Aug 2, 2023
a5f0cd6
feat(prime_staff.type.ts): add PrimeInfos type
Arcoz0308 Aug 2, 2023
83b31b8
feat(prime_staff.util.ts): add getSavedPrimes and getPrimes functions
Arcoz0308 Aug 2, 2023
23ac7c8
feat(prime_staff.class.ts): add PrimeStaff class to handle the 'prime…
Arcoz0308 Aug 2, 2023
0ab0df9
feat(confirm_buttons/prime_staff.button.ts): add confirm button handl…
Arcoz0308 Aug 2, 2023
5810304
chore(package.json): update devDependencies
Arcoz0308 Aug 3, 2023
8638e88
fix(xp.func.ts): change return type of xpMovement function to remove …
Arcoz0308 Aug 3, 2023
c675781
add edits for discord.js v14.12.0
Arcoz0308 Aug 3, 2023
65551bc
feat(guild.type.ts): add GlobalGuild type with primeChannel property
Arcoz0308 Aug 3, 2023
e7e0dbc
fix(config): fix import and type errors in global/base.config.ts and …
Arcoz0308 Aug 3, 2023
9bb06b2
feat(commands.config.ts): add final message configuration for the "st…
Arcoz0308 Aug 3, 2023
2aadd47
feat(confirm_buttons/prime_staff.button.ts): add support for processi…
Arcoz0308 Aug 3, 2023
d114247
Merge branch 'master' into feature/primes
Arcoz0308 Aug 3, 2023
a018885
fix eslint error
Arcoz0308 Aug 3, 2023
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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@
"vitest": "^0.31.4"
},
"dependencies": {
"@google-cloud/local-auth": "2.1.0",
"@prisma/client": "^4.15.0",
"@swc-node/register": "^1.6.5",
"cron": "^2.4.0",
"dayjs": "^1.11.8",
"discord.js": "^14.11.0",
"discord.js": "^14.12.1",
"dotenv": "^16.1.4",
"google-auth-library": "^9.0.0",
"googleapis": "105",
"i": "^0.3.7",
"npm": "^9.7.1",
"rustic-error": "^0.2.1",
Expand Down
5 changes: 4 additions & 1 deletion src/commands/globals/other/admin/admin.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ export const builder = new SlashCommandBuilder()
.setDescription(config.subcmds.reverseXpMovement.description)
.addIntegerOption(option => option.setName(config.subcmds.reverseXpMovement.options.id.name)
.setDescription(config.subcmds.reverseXpMovement.options.id.description)
.setRequired(true)));
.setRequired(true)))
.addSubcommand(subCommand => subCommand
.setName(config.subcmds.primeStaff.name)
.setDescription(config.subcmds.primeStaff.description));
4 changes: 3 additions & 1 deletion src/commands/globals/other/admin/admin.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {builder} from './admin.builder';
import {commandsConfig} from '$core/config/message/command';
import {ReverseXpMovement} from '$core/commands/globals/other/admin/reverse_xp_movement/reverse_xp_movement.class';
import {Dev} from '$core/utils/dev';
import {PrimeStaff} from '$core/commands/globals/other/admin/prime_staff/prime_staff.class';

@Dev
export default class Admin extends BaseCommand {
builder = builder.toJSON();

getSubCommands(): SubCommandOptions {
return {
[commandsConfig.admin.subcmds.reverseXpMovement.name]: new ReverseXpMovement()
[commandsConfig.admin.subcmds.reverseXpMovement.name]: new ReverseXpMovement(),
[commandsConfig.admin.subcmds.primeStaff.name]: new PrimeStaff()
};
}
}
18 changes: 18 additions & 0 deletions src/commands/globals/other/admin/admin.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type {ChatInputCommandInteraction} from 'discord.js';
import type {Result} from 'rustic-error';
import {ok} from 'rustic-error';
import type {CommandError} from '$core/utils/error';
import {globalConfig} from '$core/config/global';
import {sendCommandReply} from '$core/handlers/commands';
import {errorEmbed} from '$core/utils/discord';
import {commandsConfig} from '$core/config/message/command';

/**
* If is owner, return false for code facility
*/
export const ownerOnly = async (interaction: ChatInputCommandInteraction, defer = true): Promise<Result<boolean, CommandError>> => {
if (!globalConfig.owners.includes(interaction.user.id)) {
return sendCommandReply(interaction, {embeds: [errorEmbed(commandsConfig.admin.exec.ownerOnly)]}, defer);
}
return ok(false);
};
89 changes: 89 additions & 0 deletions src/commands/globals/other/admin/prime_staff/prime_staff.class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {SubCommand} from '$core/handlers/commands';
import {commandsConfig} from '$core/config/message/command';
import type {Result} from 'rustic-error';
import {error, ok, resultify} from 'rustic-error';
import {CommandError} from '$core/utils/error';
import type {ChatInputCommandInteraction, EmbedBuilder} from 'discord.js';
import {userMention} from 'discord.js';
import {ownerOnly} from '$core/commands/globals/other/admin/admin.util';
import {getPrimes} from '$core/commands/globals/other/admin/prime_staff/prime_staff.util';
import {errorEmbed, simpleEmbed} from '$core/utils/discord';
import {msgParams} from '$core/utils/function/string';
import {confirmIds, getConfirmButtons} from '$core/handlers/buttons/confirm';

const config = commandsConfig.admin.exec.primeStaff;

export class PrimeStaff extends SubCommand {
name = commandsConfig.admin.subcmds.primeStaff.name;
preReply = {
enable: true,
ephemeral: false
};

async run(interaction: ChatInputCommandInteraction): Promise<Result<boolean, CommandError>> {
const ownerOnlyResult = await ownerOnly(interaction);
if (!ownerOnlyResult.ok) {
return error(ownerOnlyResult.error);
}
if (ownerOnlyResult.value) {
return ok(false);
}

const primeResult = await getPrimes();
if (!primeResult.ok) {
return error(new CommandError('failed to get staff primes', interaction, primeResult.error));
}

if (typeof primeResult.value === 'string') {
return this.sendReply(interaction, {embeds: [errorEmbed(primeResult.value)]});
}

const infos: string[] = [];
for (const prime of primeResult.value) {
infos.push(msgParams(config.primeInfo, [
userMention(prime.userId),
prime.role,
prime.username,
prime.totalPrime,
prime.prime,
prime.associationPrime
]));
}

const embeds: EmbedBuilder[] = [];


// thx https://stackoverflow.com/a/8495740
const chunkSize = 30;
for (let i = 0; i < infos.length; i += chunkSize) {
const chunk = infos.slice(i, i + chunkSize);
embeds.push(simpleEmbed(chunk.join('\n')));
}

const replyResult = await this.sendReply(interaction, {
embeds: [embeds[0].setTitle(config.primeInfoTitle)]
});
if (!replyResult.ok) {
return error(replyResult.error);
}

for (const embed of embeds.slice(1)) {
const result = await resultify(() => interaction.followUp({
embeds: [embed]
}));
if (!result.ok) {
return error(new CommandError('failed to followUp interaction', interaction, result.error));
}
}

const result = await resultify(() => interaction.followUp({
embeds: [simpleEmbed(config.primeDescription)],
components: getConfirmButtons(confirmIds.primeStaff)
}));
if (!result.ok) {
return error(new CommandError('failed to followUp interaction', interaction, result.error));
}
return ok(true);

}
}
10 changes: 10 additions & 0 deletions src/commands/globals/other/admin/prime_staff/prime_staff.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {Snowflake} from 'discord-api-types/globals';

export type PrimeInfos = {
username: string;
role: string;
prime: number;
associationPrime: number;
totalPrime: number;
userId: Snowflake;
}
95 changes: 95 additions & 0 deletions src/commands/globals/other/admin/prime_staff/prime_staff.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {google} from 'googleapis';
import {env} from '$core/config/env';
import type {PrimeInfos} from '$core/commands/globals/other/admin/prime_staff/prime_staff.type';
import {msgParams} from '$core/utils/function/string';
import {commandsConfig} from '$core/config/message/command';
import type {Result} from 'rustic-error';
import {error, ok} from 'rustic-error';
import {anyToError} from '$core/utils/error';

let lastSavedPrimes: PrimeInfos[] | undefined;

export const getSavedPrimes = () => lastSavedPrimes;

const getIndexes = (header: string[]): Record<keyof PrimeInfos, number> | string => {
const baseIndexes = {
'Adhesion Asso': header.findIndex(v => v === 'Adhésion Asso'),
'Primes': header.findIndex(v => v === 'Primes'),
'Poste': header.findIndex(v => v === 'Poste'),
'TOTAL Sans Bonus': header.findIndex(v => v === 'TOTAL Sans Bonus'),
'Idd': header.findIndex(v => v === 'Idd'),
'Pseudo': header.findIndex(v => v === 'Pseudo')
};
let msg = '';
for (const [key, value] of Object.entries(baseIndexes)) {
if (value < 0) {
msg += `${msgParams(commandsConfig.admin.exec.primeStaff.columnNotFound, [key])}\n`;
}
}
if (msg !== '') {
return msg;
}

return {
associationPrime: baseIndexes['Adhesion Asso'],
prime: baseIndexes.Primes,
role: baseIndexes.Poste,
totalPrime: baseIndexes['TOTAL Sans Bonus'],
userId: baseIndexes.Idd,
username: baseIndexes.Pseudo
};
};

export const getPrimes = async (): Promise<Result<PrimeInfos[] | string, Error>> => {
try {
const auth = new google.auth.GoogleAuth({
scopes: [
'https://www.googleapis.com/auth/spreadsheets'
],
credentials: {
private_key: env.GOOGLE_PRIVATE_KEY.split(String.raw`\n`).join('\n'),
client_email: env.GOOGLE_MAIL
}
});
const sheets = google.sheets({
version: 'v4',
auth: auth
});
const res = await sheets.spreadsheets.values.get({
spreadsheetId: env.GOOGLE_SHEETS_ID,
range: 'Registre Staff!A:I'
});

if (res.data.values === null || typeof res.data.values === 'undefined' ||
res.data.values.length === 0 || res.data.values[0].length === 0) {

return error(new Error('No rows gets'));
}
const indexes = getIndexes(res.data.values[0]);
if (typeof indexes === 'string') {
return ok(indexes);
}

const primes: PrimeInfos[] = [];

for (const row of res.data.values.slice(1)) {
const prime: PrimeInfos = {
associationPrime: parseInt(row[indexes.associationPrime], 10),
prime: parseInt(row[indexes.prime], 10),
role: row[indexes.role],
totalPrime: parseInt(row[indexes.totalPrime], 10),
userId: row[indexes.userId],
username: row[indexes.username]
};
if (!Object.values(prime).includes('')) {
primes.push(prime);
}
}
lastSavedPrimes = primes;
return ok(primes);
} catch (e) {
return error(new Error(`failed to get prime staff infos, error : ${anyToError(e).message}`));
}
};


Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import type {ChatInputCommandInteraction} from 'discord.js';
import {time, userMention} from 'discord.js';
import {CommandError} from '$core/utils/error';
import type {Result} from 'rustic-error';
import {error} from 'rustic-error';
import {error, ok} from 'rustic-error';
import {commandsConfig} from '$core/config/message/command';
import {getXpMovement} from '$core/handlers/database/xp_movement/xp_movement.func';
import {errorEmbed, simpleEmbed} from '$core/utils/discord';
import {msgParams} from '$core/utils/function/string';
import {confirmIds, getConfirmButtons} from '$core/handlers/buttons/confirm';
import {ownerOnly} from '$core/commands/globals/other/admin/admin.util';

const config = commandsConfig.admin;

Expand All @@ -21,6 +22,15 @@ export class ReverseXpMovement extends SubCommand {
};

async run(interaction: ChatInputCommandInteraction): Promise<Result<boolean, CommandError>> {

const ownerOnlyResult = await ownerOnly(interaction);
if (!ownerOnlyResult.ok) {
return error(ownerOnlyResult.error);
}
if (ownerOnlyResult.value) {
return ok(false);
}

const id = interaction.options.getInteger(config.subcmds.reverseXpMovement.options.id.name);
if (!id) {
return error(new CommandError('No value in option id', interaction));
Expand Down
5 changes: 4 additions & 1 deletion src/config/env/env.z.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ import {z} from 'zod';
export const envDTO = z.object({
TOKEN: z.string().nonempty(),
BRAWL_STARS_TOKEN: z.string().nonempty(),
GOOGLE_MAIL: z.string().email().nonempty(),
GOOGLE_PRIVATE_KEY: z.string().nonempty(),
GOOGLE_SHEETS_ID: z.string().nonempty(),
WEBHOOK_DISCORD_URL: z.string().nonempty()
});
});
10 changes: 9 additions & 1 deletion src/config/global/global.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ export const globalConfig = {
bumpBot: '302050872383242240',
bumpCommand: 'bump',
maxBump: 5,
xpPerBump: 100
xpPerBump: 100,
owners: [
'457144873859022858',
'712579966373724222',
'622389865765404684',
'615664538423001118',
'364010473559031811',
'276817823639273472'
]
};


Expand Down
7 changes: 4 additions & 3 deletions src/config/guilds/_dev/dev.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {BaseGuild, BrawlStarsGuild} from '$core/config/guilds/guild.type';
import type {BaseGuild, BrawlStarsGuild, GlobalGuild} from '$core/config/guilds/guild.type';
import {LevelUpRoleType} from '$core/config/guilds/guild.type';
import type {Overwrite} from '$core/utils/type/type';
import type {GuildAlias} from '$core/handlers/commands';
Expand Down Expand Up @@ -36,6 +36,7 @@ const devGuilds = {
}
]
},
primeChannel: '1096122859178426423'
},
guildSection: {
name: 'global',
Expand Down Expand Up @@ -93,9 +94,9 @@ const devGuilds = {
}
}
]
},
}
}
} satisfies Record<'guildMain' | 'guildSection', Overwrite<BrawlStarsGuild, {
} satisfies Record<'guildMain' | 'guildSection', Overwrite<BrawlStarsGuild & GlobalGuild, {
name: GuildAlias
}> | Overwrite<BaseGuild, { name: GuildAlias }>>;

Expand Down
4 changes: 4 additions & 0 deletions src/config/guilds/guild.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export type BrawlStarsGuild = BaseGuild & {
clubs: BrawlStarsClub[];
}

export type GlobalGuild = BaseGuild & {
primeChannel: Snowflake;
};

export enum BrawlStarsClubType {
LADDER = 'Leader',
LDC = 'Ldc',
Expand Down
5 changes: 3 additions & 2 deletions src/config/guilds/guilds_configs/global/base.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {BaseGuild} from '../../guild.type';
import type {GlobalGuild} from '../../guild.type';
import {xpConfig} from './xp.config';
import {pubMessage} from './pub_message.config';

export const globalGuildConfig: BaseGuild = {
export const globalGuildConfig: GlobalGuild = {
name: 'global',
guildId: '346989473143455744',
eventAnnouncements: {
Expand All @@ -14,4 +14,5 @@ export const globalGuildConfig: BaseGuild = {
xp: xpConfig,
bumpChannel: '979400972315000852',
pubMessages: pubMessage,
primeChannel: '1083522288441368596'
};
Loading