diff --git a/build.gradle.kts b/build.gradle.kts index f2c04b0..6902bdc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,21 +1,26 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { + application id("java") id("com.github.johnrengelman.shadow") version "8.1.1" } group = "fr.melaine.gerard.kiss.shot.acerola" version = "1.0-SNAPSHOT" - +application { + mainClass = "org.camelia.studio.kiss.shot.acerola.KissShotAcerola" +} repositories { mavenCentral() + maven { setUrl("https://jitpack.io") } } dependencies { implementation("io.github.cdimascio:dotenv-kotlin:6.4.2") implementation("net.dv8tion:JDA:5.2.1") implementation("ch.qos.logback:logback-classic:1.5.12") + implementation ("dev.arbjerg:lavaplayer:2.2.2") } diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/AudioPlayerSendHandler.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/AudioPlayerSendHandler.java new file mode 100644 index 0000000..467533b --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/AudioPlayerSendHandler.java @@ -0,0 +1,36 @@ +package org.camelia.studio.kiss.shot.acerola.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame; +import net.dv8tion.jda.api.audio.AudioSendHandler; +import java.nio.Buffer; +import java.nio.ByteBuffer; + +public class AudioPlayerSendHandler implements AudioSendHandler { + private final AudioPlayer audioPlayer; + private final ByteBuffer buffer; + private final MutableAudioFrame frame; + + public AudioPlayerSendHandler(AudioPlayer audioPlayer) { + this.audioPlayer = audioPlayer; + this.buffer = ByteBuffer.allocate(1024); + this.frame = new MutableAudioFrame(); + this.frame.setBuffer(buffer); + } + + @Override + public boolean canProvide() { + return audioPlayer.provide(frame); + } + + @Override + public ByteBuffer provide20MsAudio() { + ((Buffer) buffer).flip(); + return buffer; + } + + @Override + public boolean isOpus() { + return true; + } +} diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/GuildMusicManager.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/GuildMusicManager.java new file mode 100644 index 0000000..2b8ec63 --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/GuildMusicManager.java @@ -0,0 +1,21 @@ +package org.camelia.studio.kiss.shot.acerola.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; + +public class GuildMusicManager { + public final AudioPlayer audioPlayer; + public final TrackScheduler scheduler; + private final AudioPlayerSendHandler sendHandler; + + public GuildMusicManager(AudioPlayerManager manager) { + this.audioPlayer = manager.createPlayer(); + this.scheduler = new TrackScheduler(this.audioPlayer); + this.audioPlayer.addListener(this.scheduler); + this.sendHandler = new AudioPlayerSendHandler(this.audioPlayer); + } + + public AudioPlayerSendHandler getSendHandler() { + return sendHandler; + } +} diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/PlayerManager.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/PlayerManager.java new file mode 100644 index 0000000..2542377 --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/PlayerManager.java @@ -0,0 +1,70 @@ +package org.camelia.studio.kiss.shot.acerola.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; + +import java.util.HashMap; +import java.util.Map; + +public class PlayerManager { + private static PlayerManager INSTANCE; + private final Map musicManagers; + private final AudioPlayerManager audioPlayerManager; + + public PlayerManager() { + this.musicManagers = new HashMap<>(); + this.audioPlayerManager = new DefaultAudioPlayerManager(); + AudioSourceManagers.registerRemoteSources(audioPlayerManager); + } + + public static PlayerManager getInstance() { + if (INSTANCE == null) { + INSTANCE = new PlayerManager(); + } + return INSTANCE; + } + + public GuildMusicManager getMusicManager(Guild guild) { + return musicManagers.computeIfAbsent(guild.getIdLong(), (guildId) -> { + final GuildMusicManager guildMusicManager = new GuildMusicManager(audioPlayerManager); + guild.getAudioManager().setSendingHandler(guildMusicManager.getSendHandler()); + return guildMusicManager; + }); + } + + public void loadAndPlay(TextChannel channel, String url) { + final GuildMusicManager musicManager = getMusicManager(channel.getGuild()); + + audioPlayerManager.loadItemOrdered(musicManager, url, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack track) { + musicManager.scheduler.queue(track); + channel.sendMessage("Ajout à la file d'attente: `" + track.getInfo().title + "`").queue(); + } + + @Override + public void playlistLoaded(AudioPlaylist playlist) { + final AudioTrack track = playlist.getTracks().get(0); + musicManager.scheduler.queue(track); + channel.sendMessage("Ajout à la file d'attente: `" + track.getInfo().title + "`").queue(); + } + + @Override + public void noMatches() { + channel.sendMessage("Aucun résultat trouvé pour: " + url).queue(); + } + + @Override + public void loadFailed(FriendlyException e) { + channel.sendMessage("Erreur lors du chargement: " + e.getMessage()).queue(); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/TrackScheduler.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/TrackScheduler.java new file mode 100644 index 0000000..46bf097 --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/audio/TrackScheduler.java @@ -0,0 +1,25 @@ +package org.camelia.studio.kiss.shot.acerola.audio; + +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; + +public class TrackScheduler extends AudioEventAdapter { + private final AudioPlayer player; + + public TrackScheduler(AudioPlayer player) { + this.player = player; + } + + public void queue(AudioTrack track) { + player.startTrack(track, false); + } + + @Override + public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + if (endReason.mayStartNext) { + // Ici vous pouvez gérer la lecture de la prochaine piste si vous implémentez une file d'attente + } + } +} \ No newline at end of file diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/PlayAudioCommand.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/PlayAudioCommand.java new file mode 100644 index 0000000..4868ad8 --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/PlayAudioCommand.java @@ -0,0 +1,56 @@ +package org.camelia.studio.kiss.shot.acerola.commands.utils; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.GuildVoiceState; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.entities.channel.unions.AudioChannelUnion; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.managers.AudioManager; + +import java.util.List; + +import org.camelia.studio.kiss.shot.acerola.audio.PlayerManager; +import org.camelia.studio.kiss.shot.acerola.interfaces.ISlashCommand; + +public class PlayAudioCommand implements ISlashCommand { + @Override + public String getName() { + return "playaudio"; + } + + @Override + public String getDescription() { + return "Permet de lancer une musique en .mp3 dans un salon vocal"; + } + + @Override + public List getOptions() { + return List.of( + new OptionData(OptionType.STRING, "url", "URL de la musique à jouer", true)); + } + + @Override + public void execute(SlashCommandInteractionEvent event) { + event.deferReply().queue(); + String url = event.getOption("url").getAsString(); + Member member = event.getMember(); + GuildVoiceState voiceState = member.getVoiceState(); + + if (!member.getVoiceState().inAudioChannel()) { + event.getHook().editOriginal("Vous devez être connecté à un salon vocal pour utiliser cette commande !") + .queue(); + return; + } + + VoiceChannel channel = voiceState.getChannel().asVoiceChannel(); + + AudioManager audioManager = event.getGuild().getAudioManager(); + audioManager.openAudioConnection(channel); + PlayerManager.getInstance().getMusicManager(event.getGuild()).audioPlayer.setVolume(25); + PlayerManager.getInstance().loadAndPlay(event.getChannel().asTextChannel(), url); + event.getHook().editOriginal("Chargement du fichier audio en cours...").queue(); + } +} \ No newline at end of file diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/StopCommand.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/StopCommand.java new file mode 100644 index 0000000..b611556 --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/commands/utils/StopCommand.java @@ -0,0 +1,54 @@ +package org.camelia.studio.kiss.shot.acerola.commands.utils; + +import org.camelia.studio.kiss.shot.acerola.audio.GuildMusicManager; +import org.camelia.studio.kiss.shot.acerola.audio.PlayerManager; +import org.camelia.studio.kiss.shot.acerola.interfaces.ISlashCommand; + +import net.dv8tion.jda.api.entities.GuildVoiceState; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.managers.AudioManager; + +public class StopCommand implements ISlashCommand{ + + @Override + public String getName() { + return "stopaudio" + } + + @Override + public String getDescription() { + return "Permet de stopper la musique en cours"; + } + + @Override + public void execute(SlashCommandInteractionEvent event) { + // Vérifier si l'utilisateur est dans un canal vocal + GuildVoiceState voiceState = event.getMember().getVoiceState(); + if (!voiceState.inAudioChannel()) { + event.reply("Vous devez être dans un canal vocal pour utiliser cette commande !").queue(); + return; + } + + // Vérifier si le bot est dans le même canal vocal + AudioManager audioManager = event.getGuild().getAudioManager(); + if (!audioManager.isConnected()) { + event.reply("Je ne suis pas connecté à un canal vocal !").queue(); + return; + } + + if (voiceState.getChannel() != audioManager.getConnectedChannel()) { + event.reply("Vous devez être dans le même canal vocal que moi !").queue(); + return; + } + + // Arrêter la musique + GuildMusicManager musicManager = PlayerManager.getInstance().getMusicManager(event.getGuild()); + musicManager.audioPlayer.stopTrack(); + + // Déconnecter le bot + audioManager.closeAudioConnection(); + + event.reply("Musique arrêtée et déconnexion du canal vocal.").queue(); + } + +} diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/listeners/VoiceLeaveListener.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/listeners/VoiceLeaveListener.java new file mode 100644 index 0000000..c10becd --- /dev/null +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/listeners/VoiceLeaveListener.java @@ -0,0 +1,38 @@ +package org.camelia.studio.kiss.shot.acerola.listeners; + +import org.camelia.studio.kiss.shot.acerola.audio.PlayerManager; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.managers.AudioManager; + +public class VoiceLeaveListener extends ListenerAdapter { + @Override + public void onGuildVoiceUpdate(GuildVoiceUpdateEvent event) { + Guild guild = event.getGuild(); + AudioManager audioManager = guild.getAudioManager(); + + // Vérifie si le bot est connecté à un canal vocal + if (!audioManager.isConnected()) { + return; + } + + VoiceChannel botChannel = audioManager.getConnectedChannel().asVoiceChannel(); + + // Compte le nombre de membres dans le canal (excluant les bots) + long realMembersCount = botChannel.getMembers().stream() + .filter(member -> !member.getUser().isBot()) + .count(); + + // Si plus personne dans le salon + if (realMembersCount == 0) { + // Arrête la musique + PlayerManager.getInstance().getMusicManager(guild).audioPlayer.stopTrack(); + + // Déconnecte le bot + audioManager.closeAudioConnection(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/camelia/studio/kiss/shot/acerola/managers/ListenerManager.java b/src/main/java/org/camelia/studio/kiss/shot/acerola/managers/ListenerManager.java index 19cdb16..019c8bd 100644 --- a/src/main/java/org/camelia/studio/kiss/shot/acerola/managers/ListenerManager.java +++ b/src/main/java/org/camelia/studio/kiss/shot/acerola/managers/ListenerManager.java @@ -4,6 +4,7 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.camelia.studio.kiss.shot.acerola.listeners.GuildMemberJoinListener; import org.camelia.studio.kiss.shot.acerola.listeners.SlashCommandListener; +import org.camelia.studio.kiss.shot.acerola.listeners.VoiceLeaveListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,11 +14,13 @@ import java.util.List; public class ListenerManager { private final List listener; private final Logger logger = LoggerFactory.getLogger(ListenerManager.class.getName()); + public ListenerManager() { listener = new ArrayList<>(); addListener(new SlashCommandListener()); addListener(new GuildMemberJoinListener()); + addListener(new VoiceLeaveListener()); } public void registerListeners(JDA jda) {