commit 1320fec1439d9947595402434fa9adf77a3014e1 Author: Mysaa Date: Thu May 27 22:31:20 2021 +0200 Premier commit - Introudction au système git diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5e5214 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +dependencies/ +bin/ +.gradle +.settings +.classpath +.project diff --git a/src/com/bernard/djulia/DJuLIA.java b/src/com/bernard/djulia/DJuLIA.java new file mode 100644 index 0000000..70ee6d3 --- /dev/null +++ b/src/com/bernard/djulia/DJuLIA.java @@ -0,0 +1,419 @@ +package com.bernard.djulia; + +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import com.bernard.juliabot.api.Command; +import com.bernard.juliabot.api.Discord; +import com.bernard.juliabot.api.DiscordCCommande; +import com.bernard.juliabot.api.JuLIAddon; +import com.bernard.juliabot.api.Trukilie; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; +import com.sedmelluq.discord.lavaplayer.player.event.AudioEvent; +import com.sedmelluq.discord.lavaplayer.player.event.AudioEventListener; +import com.sedmelluq.discord.lavaplayer.player.event.PlayerPauseEvent; +import com.sedmelluq.discord.lavaplayer.player.event.PlayerResumeEvent; +import com.sedmelluq.discord.lavaplayer.player.event.TrackEndEvent; +import com.sedmelluq.discord.lavaplayer.player.event.TrackExceptionEvent; +import com.sedmelluq.discord.lavaplayer.player.event.TrackStartEvent; +import com.sedmelluq.discord.lavaplayer.player.event.TrackStuckEvent; +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame; + +import net.dv8tion.jda.core.EmbedBuilder; +import net.dv8tion.jda.core.audio.AudioSendHandler; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.Message; +import net.dv8tion.jda.core.entities.MessageChannel; +import net.dv8tion.jda.core.entities.MessageEmbed.Field; +import net.dv8tion.jda.core.entities.PrivateChannel; +import net.dv8tion.jda.core.entities.TextChannel; +import net.dv8tion.jda.core.entities.User; +import net.dv8tion.jda.core.entities.VoiceChannel; +import net.dv8tion.jda.core.events.message.react.GenericMessageReactionEvent; +import net.dv8tion.jda.core.managers.AudioManager; + + +@JuLIAddon(devs = "Mysaa", name = "djulia", version = "beta") +public class DJuLIA { + + public static final String MUSIC = "musique"; + + Map guilds = new HashMap<>(); + + Map localGuilds = new HashMap<>(); + + AudioPlayerManager audioManager; + + public DJuLIA() { + audioManager = new DefaultAudioPlayerManager(); + AudioSourceManagers.registerRemoteSources(audioManager); + } + + @Command(admin = false, description = "Permet de jouer un ou plusieurs morceaux", name = "play") + public void playCommand(DiscordCCommande commande) { + Guild g = getGuild(commande); + if(g==null) { + bMsg(commande.getChannel(), "Impossible de deviner dans quel serveur vous voulez éxecuter cette commande (y a la commande soundGuild)"); + return; + } + } + + + @Command(admin = false, description = "Définis le serveur utilisé pour les commandes de son de cet utilisateur", name = "soundGuild", synopsis = "!!soundGuild [null|long guildid|string guildName]") + public void soundGuild(DiscordCCommande commande) { + String arg = commande.getStringCommand().substring(commande.getStringCommand().split(" ")[0].length()); + if(!arg.isEmpty())arg = arg.substring(1);// On enlève l'espace en trop + if(arg.equalsIgnoreCase("null") || (arg.isEmpty() && !(commande.getChannel() instanceof TextChannel))) { + localGuilds.remove(commande.getUser().getIdLong()); + bMsg(commande.getChannel(), "Très bien, aucun serveur n'est séléctionné"); + return; + } + if(arg.isEmpty()) { + bMsg(commande.getChannel(), "Vous devez indiquer un serveur ou lancer cette commande dans un serveur"); + return; + } + Long l = null; + Guild g = null; + try { + l = Long.parseLong(arg); + g = Trukilie.jda().getGuildById(l); + }catch(NumberFormatException e) { + + } + if(g!=null) { + localGuilds.put(commande.getUser().getIdLong(), g.getIdLong()); + bMsg(commande.getChannel(), "Très bien, le serveur `"+g.getName()+"` a été séléctionné *(id "+g.getIdLong()+")*"); + return; + } + List guilds = Trukilie.jda().getGuildsByName(arg, true); + if(guilds.isEmpty()) { + bMsg(commande.getChannel(), "Excuse-moi, `"+arg+"` ne correspond à aucun nom ou id de serveur (que je connaisse, du moins :wink:)"); + return; + } + int index = 0; + if(guilds.size() > 1) { + index = selector(commande.getChannel(),commande.getUser(),"Quel serveur électionnez-vous ?",guilds.stream().map(gu->"`"+ gu.getName()+"` *(id:"+gu.getId()+")*").collect(Collectors.toList())); + if(index==-1) { + bMsg(commande.getChannel(), "Tu n'as pas séléctionné de serveur, donc je ne t'en assigne pas"); + return; + } + } + localGuilds.put(commande.getUser().getIdLong(), guilds.get(index).getIdLong()); + } + + public Guild getGuild(DiscordCCommande commande) { + + if(commande.getChannel() instanceof TextChannel) + return ((TextChannel)commande.getChannel()).getGuild(); + + if(localGuilds.containsKey(commande.getUser().getIdLong())) + return Trukilie.jda().getGuildById(localGuilds.get(commande.getUser().getIdLong())); + + List mutualGuilds = commande.getUser().getMutualGuilds(); + if(mutualGuilds.size() == 1) + return mutualGuilds.get(0); + + Set vocalGuilds = mutualGuilds.stream() + .filter(gu->gu.getMember(commande.getUser()).getVoiceState().inVoiceChannel()) + .collect(Collectors.toSet()); + if(vocalGuilds.size() == 1) + return vocalGuilds.iterator().next(); + + Set vocalGuildsWithJulia = vocalGuilds.stream() + .filter(g->guilds.containsKey(g.getIdLong())) + .filter(g->guilds.get(g.getIdLong()).currentChannel.getId().equals(g.getMember(commande.getUser()).getVoiceState().getAudioChannel().getId())) + .collect(Collectors.toSet()); + if(vocalGuildsWithJulia.size() == 1) + return vocalGuildsWithJulia.iterator().next(); + + return null; + } + + + + Map waitingEmojis = new HashMap<>(); + Map everyEventLocks = new HashMap<>(); + + public int selector(MessageChannel chan,String title,List choices) { + List authorized = new ArrayList<>(); + if(chan instanceof TextChannel) + authorized = ((TextChannel)chan).getMembers().stream().map(Member::getUser).collect(Collectors.toList()); + else + authorized = List.of(((PrivateChannel)chan).getUser()); + return selector(chan,authorized,title,choices); + } + public int selector(MessageChannel chan,User authorized,String title,List choices) { + return selector(chan,List.of(authorized),title,choices); + } + public int selector(MessageChannel chan,List authorized,String title,List choices) { + String[] enames; + if(choices.size() > 20) { + throw new IllegalArgumentException("Je ne gère pas (encore) les trop nombreux choix >20"); + } + if(choices.size() <= 10) + enames = new String[] {"one","two","three","four","five","six","seven","eight","nine","ten"}; + else + enames = new String[] {"regional_indicator_a","regional_indicator_b","regional_indicator_c","regional_indicator_d","regional_indicator_e","regional_indicator_f","regional_indicator_g","regional_indicator_h","regional_indicator_i","regional_indicator_j","regional_indicator_k","regional_indicator_l","regional_indicator_m","regional_indicator_n","regional_indicator_o","regional_indicator_p","regional_indicator_q","regional_indicator_r","regional_indicator_s","regional_indicator_t","regional_indicator_u","regional_indicator_v","regional_indicator_w","regional_indicator_x","regional_indicator_y","regional_indicator_z"}; + + EmbedBuilder builder = new EmbedBuilder(); + builder.setTitle(title); + for (int i = 0; i < choices.size(); i++) + builder.addField(":"+enames[i]+":", choices.get(i), false); + + Calendar c = Calendar.getInstance(); + c.add(Calendar.HOUR, 1); + String expiration = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(c.getTime()); + + builder.setFooter("Éxpire le "+expiration, null); + + Message m = chan.sendMessage(builder.build()).complete(); + + for (int i = 0; i < choices.size(); i++) + m.addReaction(enames[i]).queue(); + + + Object o = new Object(); + waitingEmojis.put(m.getIdLong(), o); + ReentrantLock lock = new ReentrantLock(); + everyEventLocks.put(m.getIdLong(), lock); + int index = -1; + do { + lock.unlock(); + try { + o.wait(c.getTimeInMillis() - Calendar.getInstance().getTimeInMillis()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + lock.lock(); + + //Check for emoji + Message mm = chan.getMessageById(m.getIdLong()).complete(); + index = mm.getReactions().stream() + .filter(mr->!inter(mr.getUsers().complete(),authorized).isEmpty()) + .mapToInt(mr->Arrays.binarySearch(enames,0,choices.size(),mr.getReactionEmote().getName())) + .filter(i->i>=0) + .findAny() + .orElse(-1); + }while(index==-1 || Calendar.getInstance().after(c)); + + waitingEmojis.remove(m.getIdLong()); + everyEventLocks.remove(m.getIdLong()); + + lock.unlock(); + o.notifyAll(); + + return index; + } + + @Discord(description = "Regarde les réactions pour le sélecteur") + public void reaction(GenericMessageReactionEvent event) { + long messageID = event.getMessageIdLong(); + if(!waitingEmojis.containsKey(messageID)) + return; + Optional.ofNullable(everyEventLocks.get(messageID)).ifPresent(Lock::lock); + if(!waitingEmojis.containsKey(messageID)) + return; + everyEventLocks.get(messageID).unlock(); + waitingEmojis.get(messageID).notifyAll(); + } + + public static final Set inter(Collection c1,Collection c2) { + return c1.stream() + .distinct() + .filter(c2::contains) + .collect(Collectors.toSet()); + } + + public Guild targetGuild(DiscordCCommande commande) { + Guild g = getGuild(commande); + if(g==null) { + bMsg(commande.getChannel(),"Je ne sais pas pour quel serveur vous voulez lancer ces commandes. Veuillez utiliser `!!soundGuild` pour séléctionner le serveur"); + } + return g; + } + public static final void bMsg(MessageChannel chan,String text) { + EmbedBuilder builder = new EmbedBuilder(); + builder.addField(new Field("text", text, true)); + chan.sendMessage(builder.build()).queue(); + } + + public class JuliaAudioSendHandler implements AudioSendHandler{ + VoiceChannel currentChannel; + Guild currentGuild; + AudioManager manager; + Map chans = new HashMap<>(); + + + + public JuliaAudioSendHandler(Guild g) { + currentChannel = g.getMember(Trukilie.jda().getSelfUser()).getVoiceState().getChannel(); + manager = g.getAudioManager(); + manager.setSendingHandler(this); + } + + public JuliaAudioChannel getChan(String chanName) { + if(chans.containsKey(chanName)) + return chans.get(chanName); + JuliaAudioChannel chan = new JuliaAudioChannel(); + chans.put(chanName, chan); + return chan; + } + + public void goTo(VoiceChannel chan) { + currentChannel = chan; + manager.openAudioConnection(chan); + } + + public class JuliaAudioChannel implements AudioEventListener{ + AudioPlayer player; + AudioFrame frame; + List playlist; + int playlistIndex = 0; + boolean looping = false; + + public JuliaAudioChannel() { + player = audioManager.createPlayer(); + player.addListener(this); + playlist = new ArrayList<>(); + } + + public void play(JuliaAudioTrack track) { + playlist.add(playlistIndex+1, track); + playlistIndex++; + player.playTrack(playlist.get(playlistIndex).track); + player.setPaused(false); + } + + public void queue(JuliaAudioTrack track) { + playlist.add(track); + } + + public void pause() { + player.setPaused(!player.isPaused()); + } + + public AudioTrack currentTrack() { + return player.getPlayingTrack(); + } + + public List getPlaylist() { + return playlist; + } + + @Override + public void onEvent(AudioEvent event) { + + if(event instanceof TrackEndEvent) { + if(playlist.size()-1>=playlistIndex) { + if(!looping) + playlistIndex++; + player.playTrack(playlist.get(playlistIndex).track); + player.setPaused(false); + } + }else if(event instanceof TrackExceptionEvent) { + + }else if(event instanceof TrackStuckEvent) { + + }else if(event instanceof PlayerPauseEvent) { + + }else if(event instanceof PlayerResumeEvent) { + + }else if(event instanceof TrackStartEvent) { + + } + } + + + + } + + + + + + + + + + + + + + @Override + public boolean canProvide() { + chans.values().stream().forEach(c->c.frame = c.player.provide()); + return chans.values().stream().anyMatch(c->c.frame!=null); + } + + @Override + public byte[] provide20MsAudio() { + int dataL = chans.values().stream().map(c->c.frame).filter(f->f!=null).findAny().get().getDataLength(); + Set afs = chans.values().stream() + .map(c->c.frame) + .filter(af->af!=null) + .map(af->ByteBuffer.wrap(af.getData()).asShortBuffer()) + .collect(Collectors.toSet()); + int n = afs.size(); + + byte[] result = new byte[dataL]; + ShortBuffer resultBuffer = ByteBuffer.wrap(result).asShortBuffer(); + + int samples = dataL / 2; + + for (int i = 0; i < samples; i++) { + int j = i; + resultBuffer.put(i, (short) (afs.stream().mapToInt(sb->sb.get(j)/n).sum())); + } + return result; + } + + @Override + public boolean isOpus() { + return true; + } + + } + + public static class JuliaAudioTrack{ + AudioTrack track; + Type type; + String identifier; + User querier; + + + + public JuliaAudioTrack(AudioTrack track, Type type, String identifier, User querier) { + this.track = track; + this.type = type; + this.identifier = identifier; + this.querier = querier; + } + + + + public static enum Type{ + LOCAL, + YOUTUBE; + } + } + +}