Rewrite filepath parsing
This commit is contained in:
parent
b4039a5965
commit
e30ed17c44
4 changed files with 150 additions and 31 deletions
|
@ -0,0 +1,88 @@
|
|||
package land.chipmunk.chipmunkbot.command.arguments;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import land.chipmunk.chipmunkbot.command.ComponentMessage;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LocationArgumentType implements ArgumentType<Object> {
|
||||
private static final Collection<String> EXAMPLES = Arrays.<String>asList("songs/amogus.mid", "images/cat.jpg", "videos/badapple.mp4");
|
||||
|
||||
private static final SimpleCommandExceptionType OOB_FILEPATH = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified file path is outside of the allowed directory")));
|
||||
|
||||
private boolean allowsUrls = false;
|
||||
private boolean allowsPaths = false;
|
||||
private Path root;
|
||||
|
||||
private LocationArgumentType (boolean allowsUrls, boolean allowsPaths, Path root) {
|
||||
this.allowsUrls = allowsUrls;
|
||||
this.allowsPaths = allowsPaths;
|
||||
this.root = root.toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static LocationArgumentType location (Path rootPath) { return new LocationArgumentType(true, true, rootPath); }
|
||||
public static LocationArgumentType url () { return new LocationArgumentType(true, false, null); }
|
||||
public static LocationArgumentType filepath (Path rootPath) { return new LocationArgumentType(false, true, rootPath); }
|
||||
|
||||
@Override
|
||||
public Object parse (StringReader reader) throws CommandSyntaxException {
|
||||
final String remaining = reader.getString().substring(reader.getCursor());
|
||||
if (allowsUrls && isUrlStart(remaining)) return parseUrl(reader);
|
||||
if (allowsPaths) return parsePath(reader);
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isUrlStart (String string) { return string.startsWith("http://") || string.startsWith("https://") || string.startsWith("ftp://"); }
|
||||
|
||||
public URL parseUrl (StringReader reader) throws CommandSyntaxException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (reader.canRead() && reader.peek() != ' ') {
|
||||
sb.append(reader.read());
|
||||
}
|
||||
|
||||
try {
|
||||
return new URL(sb.toString());
|
||||
} catch (MalformedURLException exception) {
|
||||
throw new SimpleCommandExceptionType(ComponentMessage.wrap(Component.text(exception.getMessage()))).create();
|
||||
}
|
||||
}
|
||||
|
||||
public Path parsePath (StringReader reader) throws CommandSyntaxException {
|
||||
final String pathString = reader.readString();
|
||||
final Path path = Path.of(root.toString(), pathString).toAbsolutePath().normalize();
|
||||
if (!path.startsWith(root)) throw OOB_FILEPATH.create();
|
||||
return path;
|
||||
}
|
||||
|
||||
private static Object getLocation (CommandContext<?> context, String name) {
|
||||
return context.getArgument(name, Object.class);
|
||||
}
|
||||
|
||||
public static URL getUrl (CommandContext<?> context, String name) {
|
||||
final Object location = getLocation(context, name);
|
||||
if (location instanceof URL) return (URL) location;
|
||||
try {
|
||||
if (location instanceof Path) return new URL("file", "", -1, location.toString());
|
||||
} catch (MalformedURLException ignored) {
|
||||
return null; // The real question is whether this will actually ever get called
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Path getPath (CommandContext<?> context, String name) {
|
||||
final Object location = getLocation(context, name);
|
||||
if (location instanceof Path) return (Path) location;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getExamples () { return EXAMPLES; }
|
||||
}
|
|
@ -9,6 +9,10 @@ import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
|||
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
|
||||
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.location;
|
||||
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.filepath;
|
||||
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.getPath;
|
||||
import static land.chipmunk.chipmunkbot.command.arguments.LocationArgumentType.getUrl;
|
||||
import static land.chipmunk.chipmunkbot.command.arguments.TimestampArgumentType.timestamp;
|
||||
import static com.mojang.brigadier.arguments.LongArgumentType.getLong;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
|
@ -17,22 +21,26 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
|||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.JoinConfiguration;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MusicCommand extends Command {
|
||||
private static SimpleCommandExceptionType NO_SONG_IS_CURRENTLY_PLAYING = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("No song is currently playing")));
|
||||
private static SimpleCommandExceptionType OOB_TIMESTAMP = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("Invalid timestamp for the current song")));
|
||||
private static SimpleCommandExceptionType DIRECTORY_DOES_NOT_EXIST = new SimpleCommandExceptionType(ComponentMessage.wrap(Component.translatable("The specified directory does not exist")));
|
||||
|
||||
public MusicCommand () {
|
||||
super();
|
||||
|
||||
Path root = Path.of(SongPlayer.SONG_DIR.getPath());
|
||||
|
||||
this.node(
|
||||
literal("music")
|
||||
.then(
|
||||
literal("play")
|
||||
.then(
|
||||
argument("location", greedyString())
|
||||
argument("location", location(root))
|
||||
.executes(this::play)
|
||||
)
|
||||
)
|
||||
|
@ -40,7 +48,15 @@ public class MusicCommand extends Command {
|
|||
.then(literal("stop").executes(this::stop))
|
||||
.then(literal("skip").executes(this::skip))
|
||||
.then(literal("pause").executes(this::pause))
|
||||
.then(literal("list").executes(this::list))
|
||||
|
||||
.then(
|
||||
literal("list")
|
||||
.executes(c -> list(c, root))
|
||||
.then(
|
||||
argument("location", filepath(root))
|
||||
.executes(c -> list(c, getPath(c, "location")))
|
||||
)
|
||||
)
|
||||
|
||||
.then(
|
||||
literal("loop")
|
||||
|
@ -62,8 +78,12 @@ public class MusicCommand extends Command {
|
|||
}
|
||||
|
||||
public int play (CommandContext<CommandSource> context) {
|
||||
final String location = getString(context, "location");
|
||||
context.getSource().client().songPlayer().loadSong(location);
|
||||
final SongPlayer songPlayer = context.getSource().client().songPlayer();
|
||||
|
||||
final Path path = getPath(context, "location");
|
||||
|
||||
if (path != null) songPlayer.loadSong(path);
|
||||
else songPlayer.loadSong(getUrl(context, "location"));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -111,13 +131,16 @@ public class MusicCommand extends Command {
|
|||
return 1;
|
||||
}
|
||||
|
||||
public int list (CommandContext<CommandSource> context) {
|
||||
public int list (CommandContext<CommandSource> context, Path path) throws CommandSyntaxException {
|
||||
final CommandSource source = context.getSource();
|
||||
final SongPlayer songPlayer = source.client().songPlayer();
|
||||
|
||||
final String[] filenames = path.toFile().list();
|
||||
if (filenames == null) throw DIRECTORY_DOES_NOT_EXIST.create();
|
||||
|
||||
final List<Component> list = new ArrayList<>();
|
||||
int i = 0;
|
||||
for (String filename : songPlayer.SONG_DIR.list()) {
|
||||
for (String filename : filenames) {
|
||||
list.add(Component.text(filename, (i++ & 1) == 0 ? NamedTextColor.DARK_GREEN : NamedTextColor.GREEN));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.net.URL;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.LinkedList;
|
||||
|
@ -42,7 +44,9 @@ public class SongPlayer extends SessionAdapter {
|
|||
});
|
||||
}
|
||||
|
||||
public void loadSong (String location) {
|
||||
// TODO: Less duplicate code
|
||||
|
||||
public void loadSong (Path location) {
|
||||
if (loaderThread != null) {
|
||||
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
||||
return;
|
||||
|
@ -50,7 +54,24 @@ public class SongPlayer extends SessionAdapter {
|
|||
|
||||
try {
|
||||
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
|
||||
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location, NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location.getFileName().toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
_loaderThread.start();
|
||||
loaderThread = _loaderThread;
|
||||
} catch (SongLoaderException e) {
|
||||
client.chat().tellraw(Component.translatable("Failed to load song: %s", e.message()).color(NamedTextColor.RED));
|
||||
loaderThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSong (URL location) {
|
||||
if (loaderThread != null) {
|
||||
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
|
||||
client.chat().tellraw(Component.translatable("Loading %s", Component.text(location.toString(), NamedTextColor.DARK_GREEN)).color(NamedTextColor.GREEN));
|
||||
_loaderThread.start();
|
||||
loaderThread = _loaderThread;
|
||||
} catch (SongLoaderException e) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import net.kyori.adventure.text.Component;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
|
@ -18,30 +19,16 @@ public class SongLoaderThread extends Thread {
|
|||
|
||||
private boolean isUrl = false;
|
||||
|
||||
public SongLoaderThread (String location) throws SongLoaderException {
|
||||
this.location = location;
|
||||
if (location.startsWith("http://") || location.startsWith("https://")) {
|
||||
isUrl = true;
|
||||
try {
|
||||
songUrl = new URL(location);
|
||||
} catch (IOException exception) {
|
||||
throw new SongLoaderException(Component.text(exception.getMessage()), exception);
|
||||
}
|
||||
} else if (location.contains("/") || location.contains("\\")) {
|
||||
throw new SongLoaderException(Component.translatable("Invalid characters in song name: %s", Component.text(location)));
|
||||
} else if (getSongFile(location).exists()) {
|
||||
songPath = getSongFile(location);
|
||||
} else if (getSongFile(location+".mid").exists()) {
|
||||
songPath = getSongFile(location+".mid");
|
||||
} else if (getSongFile(location+".midi").exists()) {
|
||||
songPath = getSongFile(location+".midi");
|
||||
} else if (getSongFile(location+".nbs").exists()) {
|
||||
songPath = getSongFile(location+".nbs");
|
||||
} else {
|
||||
throw new SongLoaderException(Component.translatable("Could not find song: %s", Component.text(location)));
|
||||
}
|
||||
public SongLoaderThread (URL location) throws SongLoaderException {
|
||||
isUrl = true;
|
||||
songUrl = location;
|
||||
}
|
||||
|
||||
|
||||
public SongLoaderThread (Path location) throws SongLoaderException {
|
||||
isUrl = false;
|
||||
songPath = location.toFile();
|
||||
}
|
||||
|
||||
public void run () {
|
||||
byte[] bytes;
|
||||
String name;
|
||||
|
|
Loading…
Reference in a new issue