Début intégration invocation de membre + rattrapage (Version peu visuelle)

This commit is contained in:
Melaine Gérard 2024-10-31 11:26:59 +01:00
parent f5879c849e
commit fc1c9c90c9
16 changed files with 3210 additions and 7 deletions

View File

@ -1,2 +1,12 @@
TOKEN="BOT_TOKEN" TOKEN="BOT_TOKEN"
GUILD_ID="1234567890" GUILD_ID="1234567890"
DB_DIALECT="mysql"
DB_HOST="localhost"
DB_NAME="gachamelia"
DB_USERNAME="gachamelia"
DB_PASSWORD="gachamelia"
DB_PORT=3310
SSR_ROLE=""
SR_ROLE=""
R_ROLE=""
WELCOME_CHANNEL=""

15
docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
services:
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: gachamelia
MYSQL_USER: gachamelia
MYSQL_PASSWORD: gachamelia
ports:
- "3310:3306"
volumes:
- mysql_gachamelia_data:/var/lib/mysql
volumes:
mysql_gachamelia_data:

2842
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,9 +13,18 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/node": "^22.8.5",
"discord.js": "^14.16.2", "discord.js": "^14.16.2",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"glob": "^11.0.0", "glob": "^11.0.0",
"mariadb": "^3.4.0",
"mysql2": "^3.11.3",
"pg": "^8.13.1",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"sequelize-typescript": "^2.1.6",
"sqlite3": "^5.1.7",
"tedious": "^18.6.1",
"typescript": "^5.6.2" "typescript": "^5.6.2"
} }
} }

View File

@ -5,6 +5,10 @@ import {Handler} from "./Handler";
import {Command} from "./Command"; import {Command} from "./Command";
import {SubCommand} from "./SubCommand"; import {SubCommand} from "./SubCommand";
import {Config} from "./LoadConfig"; import {Config} from "./LoadConfig";
import {Database} from "./Database";
import {IDatabaseConfig} from "../interfaces/IDatabaseConfig";
import {Dialect} from "sequelize";
import {User} from "../models/User";
export class CustomClient extends Client implements ICustomClient { export class CustomClient extends Client implements ICustomClient {
config: IConfig; config: IConfig;
@ -12,6 +16,7 @@ export class CustomClient extends Client implements ICustomClient {
commands: Collection<string, Command>; commands: Collection<string, Command>;
subCommands: Collection<string, SubCommand>; subCommands: Collection<string, SubCommand>;
cooldowns: Collection<string, Collection<string, number>>; cooldowns: Collection<string, Collection<string, number>>;
database: Database;
constructor() { constructor() {
super({ super({
@ -24,12 +29,28 @@ export class CustomClient extends Client implements ICustomClient {
// On charge dynamiquement la config // On charge dynamiquement la config
this.config = Config.load(); this.config = Config.load();
const dbConfig: IDatabaseConfig = {
dialect: this.config.dbDialect as Dialect,
host: this.config.dbHost,
port: this.config.dbPort,
username: this.config.dbUsername,
password: this.config.dbPassword,
database: this.config.dbName
};
this.database = new Database(dbConfig);
this.handler = new Handler(this); this.handler = new Handler(this);
this.commands = new Collection(); this.commands = new Collection();
this.subCommands = new Collection(); this.subCommands = new Collection();
this.cooldowns = new Collection(); this.cooldowns = new Collection();
} }
async init(): Promise<void> { async init(): Promise<void> {
await this.database.connect();
User.initModel(this.database.getSequelize());
await this.database.sync({
alter: true
});
await this.LoadHandlers(); await this.LoadHandlers();
this.login(this.config.token).catch(console.error); this.login(this.config.token).catch(console.error);
} }

View File

@ -0,0 +1,55 @@
import {Sequelize} from "sequelize-typescript";
import {Options} from "sequelize";
import {IDatabaseConfig} from "../interfaces/IDatabaseConfig";
export class Database {
private readonly sequelize: Sequelize;
constructor(config: IDatabaseConfig) {
const options: Options = {
dialect: config.dialect,
host: config.host,
port: config.port,
username: config.username,
password: config.password,
database: config.database,
logging: false,
pool: {
max: config.poolOptions?.max ?? 5,
min: config.poolOptions?.min ?? 0,
acquire: config.poolOptions?.acquire ?? 30000,
idle: config.poolOptions?.idle ?? 10000
}
};
this.sequelize = new Sequelize(options);
}
public getSequelize(): Sequelize {
return this.sequelize;
}
public async connect(): Promise<void> {
try {
await this.sequelize.authenticate();
console.log('Connexion à la base de données établie avec succès.');
} catch (error) {
console.error('Impossible de se connecter à la base de données:', error);
throw error;
}
}
public async close(): Promise<void> {
try {
await this.sequelize.close();
console.log('Connexion à la base de données fermée avec succès.');
} catch (error) {
console.error('Erreur lors de la fermeture de la connexion:', error);
throw error;
}
}
public async sync(options?: { force?: boolean; alter?: boolean }): Promise<void> {
await this.sequelize.sync(options);
}
}

6
src/base/enums/Rank.ts Normal file
View File

@ -0,0 +1,6 @@
export enum Rank {
SSR = 'SSR',
SR = 'SR',
R = 'R',
}

View File

@ -0,0 +1,6 @@
export enum RankChance {
SSR = 3,
SR = 17,
R = 80,
}

View File

@ -1,4 +1,14 @@
export interface IConfig { export interface IConfig {
token: string; token: string;
guildId: string; guildId: string;
dbDialect: string;
dbHost: string;
dbPort: number;
dbUsername: string;
dbPassword: string;
dbName: string;
ssrRole: string;
srRole: string;
rRole: string;
welcomeChannel: string;
} }

View File

@ -0,0 +1,18 @@
import {Dialect} from "sequelize";
export interface IDatabaseConfig {
dialect: Dialect;
host: string;
port: number;
username: string;
password: string;
database: string;
logging?: boolean | ((sql: string, timing?: number) => void);
poolOptions?: {
max?: number;
min?: number;
acquire?: number;
idle?: number;
};
}

34
src/base/models/User.ts Normal file
View File

@ -0,0 +1,34 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
import { Rank } from '../enums/Rank';
export class User extends Model {
declare id: number;
declare discordId: string;
declare rank: Rank;
declare createdAt: Date;
declare updatedAt: Date;
public static initModel(sequelize: Sequelize): void {
User.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
discordId: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
rank: {
type: DataTypes.STRING,
allowNull: false
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
}, {
sequelize,
tableName: 'users'
});
}
}

View File

@ -0,0 +1,66 @@
import {CustomClient} from "../classes/CustomClient";
import {User} from "../models/User";
import {getRandomRank} from "./RandomUtils";
import {Rank} from "../enums/Rank";
import {GuildMember} from "discord.js";
export async function addRole(member: GuildMember, user: User, client: CustomClient): Promise<void> {
try {
let rRoleId = client.config.rRole;
let srRoleId = client.config.srRole;
let ssrRoleId = client.config.ssrRole;
if (user.rank === Rank.R) {
const rRole = await member.guild.roles.fetch(rRoleId);
if (rRole) {
await member.roles.add(rRole);
}
} else if (user.rank === Rank.SR) {
const srRole = await member.guild.roles.fetch(srRoleId);
if (srRole) {
await member.roles.add(srRole);
}
} else if (user.rank === Rank.SSR) {
const ssrRole = await member.guild.roles.fetch(ssrRoleId);
if (ssrRole) {
await member.roles.add(ssrRole);
}
}
} catch (e: Error | any) {
console.log(`Impossible d'ajouter les rôles à ${member.displayName}`);
console.error(e.message);
}
}
export async function createAllUsers(client: CustomClient): Promise<void> {
let count = 0;
const guild = await client.guilds.fetch(client.config.guildId)
const members = await guild.members.fetch();
for (const member of members.values()) {
let user = await User.findOne({
where: {
discordId: member.id
}
})
if (!user) {
user = await User.create({
discordId: member.id,
rank: getRandomRank()
});
count++;
}
await addRole(member, user, client);
}
if (count > 0) {
console.log(`Création de ${count} nouveaux utilisateurs`);
} else {
console.log('Aucun nouvel utilisateur créé');
}
}

View File

@ -0,0 +1,16 @@
import {Rank} from "../enums/Rank";
import {RankChance} from "../enums/RankChance";
export function getRandomRank(): Rank {
const rand = Math.random() * 100;
let cumulativeChance = 0;
for (const rank of Object.values(Rank)) {
cumulativeChance += RankChance[rank as keyof typeof RankChance];
if (rand <= cumulativeChance) {
return rank as Rank;
}
}
return Rank.R;
}

View File

@ -0,0 +1,56 @@
import {Command} from "../base/classes/Command";
import {CustomClient} from "../base/classes/CustomClient";
import {Category} from "../base/enums/Category";
import {EmbedBuilder, GuildMember, PermissionsBitField, SlashCommandUserOption} from "discord.js";
import {User} from "../base/models/User";
import {Rank} from "../base/enums/Rank";
import {getRandomRank} from "../base/utils/RandomUtils";
export class PingCommand extends Command {
constructor(client: CustomClient) {
super(client, {
name: 'rank',
description: 'Permet de voir son rang',
category: Category.UTILITIES,
options: [
new SlashCommandUserOption()
.setName('user')
.setDescription('L\'utilisateur dont vous voulez voir le rang')
.setRequired(false)
],
default_member_permissions: PermissionsBitField.Flags.UseApplicationCommands,
dm_permission: false,
cooldown: 1
});
}
async execute(interaction: any): Promise<void> {
let discordUser = (interaction.options.getMember('user') || interaction.member) as GuildMember;
console.log(discordUser.id);
let user = await User.findOne(
{
where: {
discordId: discordUser.id
}
}
);
if (!user) {
user = await User.create({
discordId: discordUser.id,
rank: getRandomRank()
});
}
// TODO: Remplacer la description par une image générée par le bot
let embed = new EmbedBuilder()
.setTitle(`Rang de ${discordUser.displayName}`)
.setThumbnail(discordUser.displayAvatarURL())
.setDescription(`Cet utilisateur est de rang : ${Rank[user.rank]}`)
.setTimestamp(new Date())
.setColor('#0078DE')
;
await interaction.reply({embeds: [embed]});
}
}

View File

@ -2,6 +2,7 @@ import {Event} from '../../base/classes/Event';
import {CustomClient} from "../../base/classes/CustomClient"; import {CustomClient} from "../../base/classes/CustomClient";
import {Collection, Events, REST, Routes} from "discord.js"; import {Collection, Events, REST, Routes} from "discord.js";
import {Command} from "../../base/classes/Command"; import {Command} from "../../base/classes/Command";
import {createAllUsers} from "../../base/utils/GachaUtils";
export class Ready extends Event { export class Ready extends Event {
constructor(client: CustomClient) { constructor(client: CustomClient) {
super(client, { super(client, {
@ -28,7 +29,7 @@ export class Ready extends Event {
console.log(`${setCommands.length} Commandes mises à jours avec succès !`); console.log(`${setCommands.length} Commandes mises à jours avec succès !`);
await createAllUsers(this.client);
} }
private getJson(commands: Collection<string, Command>): object[] { private getJson(commands: Collection<string, Command>): object[] {

View File

@ -0,0 +1,50 @@
import {EmbedBuilder, Events, GuildMember} from "discord.js";
import {CustomClient} from "../../base/classes/CustomClient";
import {Event} from "../../base/classes/Event";
import {User} from "../../base/models/User";
import {getRandomRank} from "../../base/utils/RandomUtils";
import {Rank} from "../../base/enums/Rank";
import {addRole} from "../../base/utils/GachaUtils";
export class GuildMemberJoin extends Event {
constructor(client: CustomClient) {
super(client, {
name: Events.GuildMemberAdd,
once: false,
description: 'Event se déclenchant lorsqu\'un utilisateur rejoint le serveur'
});
}
async execute(member: GuildMember): Promise<void> {
let user = await User.findOne({
where: {
discordId: member.id
}
});
if (!user) {
user = await User.create({
discordId: member.id,
rank: getRandomRank()
});
}
await addRole(member, user, this.client);
const channel = await member.guild.channels.fetch(this.client.config.welcomeChannel);
if (channel && channel.isTextBased()) {
// TODO : Remplacer la description par une image générée par le bot
let embed = new EmbedBuilder()
.setTitle(`Bienvenue sur le serveur ${member.displayName} !`)
.setThumbnail(member.displayAvatarURL())
.setDescription(`Bien joué ! Tu as obtenu le rang : ${Rank[user.rank]}`)
.setTimestamp(new Date())
.setColor('#0078DE')
;
await channel.send({embeds: [embed]});
}
}
}