✨ Début intégration invocation de membre + rattrapage (Version peu visuelle)
This commit is contained in:
parent
f5879c849e
commit
fc1c9c90c9
10
.env.example
10
.env.example
@ -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
15
docker-compose.yml
Normal 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
2842
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
55
src/base/classes/Database.ts
Normal file
55
src/base/classes/Database.ts
Normal 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
6
src/base/enums/Rank.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum Rank {
|
||||||
|
SSR = 'SSR',
|
||||||
|
SR = 'SR',
|
||||||
|
R = 'R',
|
||||||
|
}
|
||||||
|
|
6
src/base/enums/RankChance.ts
Normal file
6
src/base/enums/RankChance.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum RankChance {
|
||||||
|
SSR = 3,
|
||||||
|
SR = 17,
|
||||||
|
R = 80,
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
18
src/base/interfaces/IDatabaseConfig.ts
Normal file
18
src/base/interfaces/IDatabaseConfig.ts
Normal 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
34
src/base/models/User.ts
Normal 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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
66
src/base/utils/GachaUtils.ts
Normal file
66
src/base/utils/GachaUtils.ts
Normal 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éé');
|
||||||
|
}
|
||||||
|
}
|
16
src/base/utils/RandomUtils.ts
Normal file
16
src/base/utils/RandomUtils.ts
Normal 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;
|
||||||
|
}
|
56
src/commands/RankCommand.ts
Normal file
56
src/commands/RankCommand.ts
Normal 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]});
|
||||||
|
}
|
||||||
|
}
|
@ -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[] {
|
||||||
|
50
src/events/guild/GuildMemberJoin.ts
Normal file
50
src/events/guild/GuildMemberJoin.ts
Normal 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]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user