✨ 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"
|
||||
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": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.8.5",
|
||||
"discord.js": "^14.16.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@ import {Handler} from "./Handler";
|
||||
import {Command} from "./Command";
|
||||
import {SubCommand} from "./SubCommand";
|
||||
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 {
|
||||
config: IConfig;
|
||||
@ -12,6 +16,7 @@ export class CustomClient extends Client implements ICustomClient {
|
||||
commands: Collection<string, Command>;
|
||||
subCommands: Collection<string, SubCommand>;
|
||||
cooldowns: Collection<string, Collection<string, number>>;
|
||||
database: Database;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
@ -24,12 +29,28 @@ export class CustomClient extends Client implements ICustomClient {
|
||||
|
||||
// On charge dynamiquement la config
|
||||
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.commands = new Collection();
|
||||
this.subCommands = new Collection();
|
||||
this.cooldowns = new Collection();
|
||||
}
|
||||
async init(): Promise<void> {
|
||||
await this.database.connect();
|
||||
User.initModel(this.database.getSequelize());
|
||||
await this.database.sync({
|
||||
alter: true
|
||||
});
|
||||
await this.LoadHandlers();
|
||||
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 {
|
||||
token: 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 {Collection, Events, REST, Routes} from "discord.js";
|
||||
import {Command} from "../../base/classes/Command";
|
||||
import {createAllUsers} from "../../base/utils/GachaUtils";
|
||||
export class Ready extends Event {
|
||||
constructor(client: CustomClient) {
|
||||
super(client, {
|
||||
@ -28,7 +29,7 @@ export class Ready extends Event {
|
||||
|
||||
console.log(`${setCommands.length} Commandes mises à jours avec succès !`);
|
||||
|
||||
|
||||
await createAllUsers(this.client);
|
||||
}
|
||||
|
||||
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