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.StringArgumentType.getString;
|
||||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
|
||||||
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
|
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 land.chipmunk.chipmunkbot.command.arguments.TimestampArgumentType.timestamp;
|
||||||
import static com.mojang.brigadier.arguments.LongArgumentType.getLong;
|
import static com.mojang.brigadier.arguments.LongArgumentType.getLong;
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
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.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.JoinConfiguration;
|
import net.kyori.adventure.text.JoinConfiguration;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class MusicCommand extends Command {
|
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 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 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 () {
|
public MusicCommand () {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
Path root = Path.of(SongPlayer.SONG_DIR.getPath());
|
||||||
|
|
||||||
this.node(
|
this.node(
|
||||||
literal("music")
|
literal("music")
|
||||||
.then(
|
.then(
|
||||||
literal("play")
|
literal("play")
|
||||||
.then(
|
.then(
|
||||||
argument("location", greedyString())
|
argument("location", location(root))
|
||||||
.executes(this::play)
|
.executes(this::play)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -40,7 +48,15 @@ public class MusicCommand extends Command {
|
||||||
.then(literal("stop").executes(this::stop))
|
.then(literal("stop").executes(this::stop))
|
||||||
.then(literal("skip").executes(this::skip))
|
.then(literal("skip").executes(this::skip))
|
||||||
.then(literal("pause").executes(this::pause))
|
.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(
|
.then(
|
||||||
literal("loop")
|
literal("loop")
|
||||||
|
@ -62,8 +78,12 @@ public class MusicCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int play (CommandContext<CommandSource> context) {
|
public int play (CommandContext<CommandSource> context) {
|
||||||
final String location = getString(context, "location");
|
final SongPlayer songPlayer = context.getSource().client().songPlayer();
|
||||||
context.getSource().client().songPlayer().loadSong(location);
|
|
||||||
|
final Path path = getPath(context, "location");
|
||||||
|
|
||||||
|
if (path != null) songPlayer.loadSong(path);
|
||||||
|
else songPlayer.loadSong(getUrl(context, "location"));
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -111,13 +131,16 @@ public class MusicCommand extends Command {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int list (CommandContext<CommandSource> context) {
|
public int list (CommandContext<CommandSource> context, Path path) throws CommandSyntaxException {
|
||||||
final CommandSource source = context.getSource();
|
final CommandSource source = context.getSource();
|
||||||
final SongPlayer songPlayer = source.client().songPlayer();
|
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<>();
|
final List<Component> list = new ArrayList<>();
|
||||||
int i = 0;
|
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));
|
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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.LinkedList;
|
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) {
|
if (loaderThread != null) {
|
||||||
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
client.chat().tellraw(Component.translatable("Already loading a song, cannot load another", NamedTextColor.RED));
|
||||||
return;
|
return;
|
||||||
|
@ -50,7 +54,24 @@ public class SongPlayer extends SessionAdapter {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final SongLoaderThread _loaderThread = new SongLoaderThread(location);
|
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.start();
|
||||||
loaderThread = _loaderThread;
|
loaderThread = _loaderThread;
|
||||||
} catch (SongLoaderException e) {
|
} catch (SongLoaderException e) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import net.kyori.adventure.text.Component;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@ -18,28 +19,14 @@ public class SongLoaderThread extends Thread {
|
||||||
|
|
||||||
private boolean isUrl = false;
|
private boolean isUrl = false;
|
||||||
|
|
||||||
public SongLoaderThread (String location) throws SongLoaderException {
|
public SongLoaderThread (URL location) throws SongLoaderException {
|
||||||
this.location = location;
|
|
||||||
if (location.startsWith("http://") || location.startsWith("https://")) {
|
|
||||||
isUrl = true;
|
isUrl = true;
|
||||||
try {
|
songUrl = location;
|
||||||
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 (Path location) throws SongLoaderException {
|
||||||
|
isUrl = false;
|
||||||
|
songPath = location.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run () {
|
public void run () {
|
||||||
|
|
Loading…
Reference in a new issue