GithubHelp home page GithubHelp logo

hopperelec / hopperbot-java Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 0.0 110 KB

Current release of my Discord bot 'HopperBot'. Originally created in Python but now re-created in Java!

Home Page: https://www.hopperelec.co.uk/projects/HopperBot/

License: Creative Commons Attribution Share Alike 4.0 International

Java 100.00%

hopperbot-java's People

Stargazers

 avatar

Watchers

 avatar

hopperbot-java's Issues

Voice channels made by AutoVCGen aren't always placed in the correct position

channel.createCopy().setName(autoVCJoined[0]+" "+(count+1)).setPosition(channel.getPosition()).queue();

These new voice channels are supposed to be moved to directly after the last channel of it's type. However, channels re-created after a user moves from a solo channel to the open channel get moved one place too far forward and channels channels created after two already exist get moved one place too far back.

Add song duration field to HopperBotPlaylistSong.songInfoEmbed

private static class HopperBotPlaylistSong {
@NotNull final String filename;
@NotNull final String title;
@NotNull final List<String> authors = new ArrayList<>();
@NotNull final List<String> singers = new ArrayList<>();
@NotNull final String url;
@Nullable final String note;
@Nullable final String lyrics;
@Nullable final List<MessageEmbed> lyricEmbeds;
final MessageEmbed songInfoEmbed;
HopperBotPlaylistSong(@NotNull String filename, @NotNull Map<String,JsonNode> songJsonData, @NotNull PlaylistFeature playlistFeature) {
this.filename = filename;
title = songJsonData.get("Title").textValue();
songJsonData.get("Authors").elements().forEachRemaining(author -> authors.add(author.textValue()));
songJsonData.get("Singers").elements().forEachRemaining(singer -> singers.add(singer.textValue()));
url = songJsonData.get("URL").textValue();
final String note = songJsonData.get("Note").textValue();
this.note = note.equals("") ? null : note;
final Iterator<JsonNode> lyricsIterator = songJsonData.get("Lyrics").elements();
if (lyricsIterator.hasNext()) {
final List<String> lyricsLines = new ArrayList<>();
lyricsIterator.forEachRemaining(lyric -> lyricsLines.add(lyric.textValue()));
lyrics = String.join("\n",lyricsLines);
lyricEmbeds = new ArrayList<>();
final List<String> lyricPages = new ArrayList<>();
for (String verse : lyrics.split("\n\n")) {
if (verse.length() >= 1024) {
String lastItem = "";
lyricPages.add(lastItem);
for (String line : verse.split("\n")) {
if (lastItem.length() + line.length() <= 1024) {
lyricPages.set(lyricPages.size()-1,lastItem+"\n"+line);
} else {
lyricPages.add(line);
lastItem = line;
}
}
} else {
lyricPages.add(verse);
}
}
int index = 0;
for (String lyricPage : lyricPages) {
final EmbedBuilder embedBuilder = playlistFeature.pagedEmbedBase("Lyrics",index++,lyricPages.size());
lyricEmbeds.add(embedBuilder.addField(strippedFilename(),lyricPage,false).build());
}
} else {
lyrics = null;
lyricEmbeds = null;
}
songInfoEmbed = addBooleanField(
addNullableField(
addListField(
addListField(
getEmbedBase()
.setTitle("Song info")
.setAuthor(strippedFilename() + " (Click for YouTube video)",fullURL())
.setImage(thumbnail())
.addField("Title",title,false),
"Author", authors, true
), "Singer", singers, true
), "Note", note, false
), "Lyrics available (/lyrics [song])", lyrics != null, false
).build();
}
@NotNull
public String strippedFilename() {
return removeExtension(filename);
}
@NotNull
public String thumbnail() {
return "https://i.ytimg.com/vi/"+url+"/hqdefault.jpg";
}
@NotNull
public String fullURL() {
return "https://www.youtube.com/watch?v="+url;
}
@NotNull
private EmbedBuilder addListField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, @NotNull List<String> values, boolean inline) {
if (values.isEmpty()) {
return embedBuilder;
}
if (values.size() == 1) {
return embedBuilder.addField(name,values.get(0),true);
}
return embedBuilder.addField(name+"s",String.join(", ",values),inline);
}
@NotNull
private EmbedBuilder addNullableField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, @Nullable String value, boolean inline) {
if (value == null || value.equals("")) {
return embedBuilder;
}
return embedBuilder.addField(name,value,inline);
}
@NotNull
private EmbedBuilder addBooleanField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, boolean value, boolean inline) {
return embedBuilder.addField(name+"?",value ? "Yes" : "No",inline);
}
}

Audio file duration can be found like this https://stackoverflow.com/a/3009973/

Make BOT_OWNER part of HopperBotConfig

public static final long BOT_OWNER_ID = 348083986989449216L;
@NotNull public static final String BOT_OWNER_DEFAULT_NAME = "hopperelec#3060";
@NotNull public static final String BOT_OWNER_DEFAULT_ICON = "https://www.hopperelec.co.uk/resources/hopper.png?scale=0.2&padding=30&padding-side=-8&type3D=2&shadow=32&fill=100&outline-width=6&bg=1";

public HopperBotConfig(HopperBotFeatures[] enabledFeatures, String logFormat, Map<Long,HopperBotServerConfig> servers) {

This will allow them to be changed if anyone else uses the bot

Start EconomyFeature

https://github.com/HopperElec/HopperBot-Java/blob/5f530850cb6a1393e57a72e00393c852d1cccb25/src/main/java/uk/co/hopperelec/hopperbot/features/economy/EconomyFeature.java#L14

This feature will be one node in the wider 'HopperEconomy' connected to StockBlock. It will have most of the features included in the original HopperBot-Python:
https://github.com/HopperElec/HopperBot-Python/blob/master/Cogs/Shared/Economy/economy.py
as well as a whole lot more, potentially including a storyline and multiple characters

ReactionRoles Feature

Reaction roles are roles given by users reacting to a message with a list of roles to choose from. For example, a channel called #roles could exist and inside that channel is a message listing 10 different colours to choose from. If a user reacts to the message with one of the listed colours, they should be automatically given the corresponding 'colour role'. In most bots for reaction roles, you must type the message and create the roles yourself. However, since most roles are cosmetic (such as for gender, pronouns or favourite colour) or view access to one or a few channels (such as for various games people may want to play in the guild), this can be tedious, so I would like HopperBot to handle this automatically

Various minigames

  • Whitelisted word guessing game
    Multiplayer game. One player is given a random word/phrase to describe and several other random words to describe with (they can only describe the first word using the rest of the words). Other players have to try and guess the word

  • Hangman
    Solo or multiplayer game. A word/phrase is chosen (either randomly by the bot or by another member). Spaces and symbols are given but letters are hidden (only their positions are shown so players know the length of each word in it). The player(s) must guess which letters are in the word/phrase and if not in the word/phrase, they lose a point, otherwise they are shown where in the word/phrase the guessed letters appear. Once they think they know the word/phrase, they can guess it to win.

Boolean config option for pre-loading playlist songs

Depending on

  • the number of songs in the playlist
  • the speed of the storage device used for storing the songs in the playlist
  • whether the user of the bot is concerned about the lifespan of the storage device used for storing the songs
  • how much memory the bot can be allocated
    Some hosts may want to read the songs into memory only when they are being played, whereas other hosts may want to pre-load them all.

Currently, the former is the case, and is done here

playerManager.loadItem(songFileLocation, new AudioLoadResultHandler() {
@Override
public void trackLoaded(AudioTrack track) {
logGlobally("Successfully loaded "+song.filename,featureEnum);
player.playTrack(track);
lastThreeSongs.add(song);
if (lastThreeSongs.size() == 4) {
lastThreeSongs.remove(0);
}
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
// Only individual tracks are loaded; playlists are not expected
}
@Override
public void noMatches() {
logGlobally("Couldn't find song at "+songFileLocation.replace(File.separator,"\\"+File.separator),featureEnum);
playNextSong(attempts+1);
}
@Override
public void loadFailed(FriendlyException exception) {
logGlobally("Failed loading "+song.filename,featureEnum);
playNextSong(attempts+1);
}
});

But the AudioTrack could be stored as a property of HopperBotPlaylistSong

private static class HopperBotPlaylistSong {
@NotNull final String filename;
@NotNull final String title;
@NotNull final List<String> authors = new ArrayList<>();
@NotNull final List<String> singers = new ArrayList<>();
@NotNull final String url;
@Nullable final String note;
@Nullable final String lyrics;
@Nullable final List<MessageEmbed> lyricEmbeds;
final MessageEmbed songInfoEmbed;
HopperBotPlaylistSong(@NotNull String filename, @NotNull Map<String,JsonNode> songJsonData, @NotNull PlaylistFeature playlistFeature) {
this.filename = filename;
title = songJsonData.get("Title").textValue();
songJsonData.get("Authors").elements().forEachRemaining(author -> authors.add(author.textValue()));
songJsonData.get("Singers").elements().forEachRemaining(singer -> singers.add(singer.textValue()));
url = songJsonData.get("URL").textValue();
final String note = songJsonData.get("Note").textValue();
this.note = note.equals("") ? null : note;
final Iterator<JsonNode> lyricsIterator = songJsonData.get("Lyrics").elements();
if (lyricsIterator.hasNext()) {
final List<String> lyricsLines = new ArrayList<>();
lyricsIterator.forEachRemaining(lyric -> lyricsLines.add(lyric.textValue()));
lyrics = String.join("\n",lyricsLines);
lyricEmbeds = new ArrayList<>();
final List<String> lyricPages = new ArrayList<>();
for (String verse : lyrics.split("\n\n")) {
if (verse.length() >= 1024) {
String lastItem = "";
lyricPages.add(lastItem);
for (String line : verse.split("\n")) {
if (lastItem.length() + line.length() <= 1024) {
lyricPages.set(lyricPages.size()-1,lastItem+"\n"+line);
} else {
lyricPages.add(line);
lastItem = line;
}
}
} else {
lyricPages.add(verse);
}
}
int index = 0;
for (String lyricPage : lyricPages) {
final EmbedBuilder embedBuilder = playlistFeature.pagedEmbedBase("Lyrics",index++,lyricPages.size());
lyricEmbeds.add(embedBuilder.addField(strippedFilename(),lyricPage,false).build());
}
} else {
lyrics = null;
lyricEmbeds = null;
}
songInfoEmbed = addBooleanField(
addNullableField(
addListField(
addListField(
getEmbedBase()
.setTitle("Song info")
.setAuthor(strippedFilename() + " (Click for YouTube video)",fullURL())
.setImage(thumbnail())
.addField("Title",title,false),
"Author", authors, true
), "Singer", singers, true
), "Note", note, false
), "Lyrics available (/lyrics [song])", lyrics != null, false
).build();
}
@NotNull
public String strippedFilename() {
return removeExtension(filename);
}
@NotNull
public String thumbnail() {
return "https://i.ytimg.com/vi/"+url+"/hqdefault.jpg";
}
@NotNull
public String fullURL() {
return "https://www.youtube.com/watch?v="+url;
}
@NotNull
private EmbedBuilder addListField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, @NotNull List<String> values, boolean inline) {
if (values.isEmpty()) {
return embedBuilder;
}
if (values.size() == 1) {
return embedBuilder.addField(name,values.get(0),true);
}
return embedBuilder.addField(name+"s",String.join(", ",values),inline);
}
@NotNull
private EmbedBuilder addNullableField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, @Nullable String value, boolean inline) {
if (value == null || value.equals("")) {
return embedBuilder;
}
return embedBuilder.addField(name,value,inline);
}
@NotNull
private EmbedBuilder addBooleanField(@NotNull EmbedBuilder embedBuilder, @NotNull String name, boolean value, boolean inline) {
return embedBuilder.addField(name+"?",value ? "Yes" : "No",inline);
}
}

When the songs are loaded should be based on a config option.

~songprogress command

The command would show the progress (AudioTrack::getPosition) into the duration (#15) of the currently playing song. There would be no need for a search option because this command would only be effective on the currently playing song.

Read messages from lang files

Currently, all messages except for the basic log_format are hard-coded. They should be moved into language specific files such as /lang/en_us.yml and read from there, where the language is chosen in config.yml

Feature for handling playlist suggestions

Currently, in the hopperelec Discord server, a channel called #playlist-suggestions exists where people send links to YouTube videos as song suggestions to be added for PlaylistFeature and I will react to each suggestion with either a thumbs_down, man_shrugging or thumbs_up based on whether I like the song. Then, every now and again, I will run this Python repl.it which, among other things, searches for any messages I have approved using the thumbs_up and automatically downloads them and produces some songs.yml data for it which can then be copied into the production file. Shrugged suggestions are also reported in the console.

However, this could be improved by integrating this system into HopperBot-Java. For example, the bot would restrict the suggestions channel to only working YouTube video links, create a thread for all suggestions for discussion and add a poll using this function

public static void createPoll(@NotNull TextChannel channel, @NotNull String question) {
channel.sendMessage(question).queue(message -> {
message.addReaction("\uD83D\uDC4D").queue();
message.addReaction("\uD83D\uDC4E").queue();
message.addReaction("\uD83E\uDD37").queue();
});
}

where I can select the appropriate rating. If I approve a suggestion, the bot can then try and product the songs.yml data for it and ask me if the details are correct, in which case a backup of songs.yml will be made, the new song will be appended to it and the song will be added to PlaylistFeature::songs

Member activity tracker

With consent, members' online status and game activity changes would be stored to a database allowing the bot to produce various data samples using it, such as predictions to their current activity and graphs of their past activity. This was originally done as a suggestion for TheFuturisticIdiot's LynxBot (written in Python), but was never completed (atleast to what I was imagining) so I would like to create it for HopperBot

Improve PlaylistFeature::searchSongs

@NotNull
@CheckReturnValue
private Map.Entry<HopperBotPlaylistSong,Double> searchSongs(@NotNull String search) {
final Map<HopperBotPlaylistSong,Double> results = new HashMap<>();
for (HopperBotPlaylistSong song : songs) {
results.put(song, jaroWinklerSimilarity.apply(search,song.filename));
}
return Collections.max(results.entrySet(), Comparator.comparingDouble(Map.Entry::getValue));
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.