diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f33409c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# Discord Radio Bot
+
+> [!IMPORTANT]
+> You are currently viewing the Java rewrite of the bot. I am not done jet and some features are missing. If you are looking for the old Node.js based version, click here.
+
+Plays radio streams directly inside your Discord server.
+This bot has no commands, it's for playing radio streams only.
+You can specify your own radio stream in the config.
+
+
+Important: The provided url MUST be a link to a DIRECT MEDIA STREAM. This means https://radioXYZ.fm is not a valid url!
+Stream urls normally look like https://play.radioXYZ.fm/source.mp3
+
+
+## How to
+### Requirements
+You need this.
+
+1. Create a new Bot User
+ First you need to create a new bot account.
+ Head over to the Discord Developer Portal, create a new bot instance and get the bot token.
+2. A working JRE setup
+ If you do not have one already, get it here: AdoptOpenJDK
+
+### Running the Bot
+Download the latest release.
+
+Then you have to create a file called ``application.properties`` in the same directory as the bot.
+Fill it like so:
+```properties
+discord.token=<>
+discord.channel=<>
+```
+
+And run it: ``java -jar radiobot.jar``
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..427cfdf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,87 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.3
+
+
+ space.parzival.discord
+ radiobot
+ 0.0.1-SNAPSHOT
+ radiobot
+ Plays radio streams directly inside your Discord server.
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ net.dv8tion
+ JDA
+ 5.0.0-beta.12
+
+
+ com.github.walkyst
+ lavaplayer-fork
+ 1.4.3
+
+
+ org.apache.maven
+ maven-artifact
+ 3.9.4
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ build-info
+
+ build-info
+
+
+
+
+
+
+
+
+
+ jitpack
+ https://jitpack.io
+
+
+
+
diff --git a/src/main/java/space/parzival/discord/radiobot/ClientConfiguration.java b/src/main/java/space/parzival/discord/radiobot/ClientConfiguration.java
new file mode 100644
index 0000000..997da5d
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/ClientConfiguration.java
@@ -0,0 +1,29 @@
+package space.parzival.discord.radiobot;
+
+import lombok.extern.slf4j.Slf4j;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.JDABuilder;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import space.parzival.discord.radiobot.properties.ClientProperties;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class ClientConfiguration {
+
+ @Bean
+ public JDA discordInstance(ClientProperties clientProperties, List extends ListenerAdapter> events) {
+ JDA client = JDABuilder
+ .createDefault(clientProperties.getToken())
+ .build();
+
+ events.forEach(client::addEventListener);
+
+ log.debug("New Discord instance created.");
+ return client;
+ }
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/RadioBotApplication.java b/src/main/java/space/parzival/discord/radiobot/RadioBotApplication.java
new file mode 100644
index 0000000..479399a
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/RadioBotApplication.java
@@ -0,0 +1,25 @@
+package space.parzival.discord.radiobot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+import space.parzival.discord.radiobot.properties.ClientProperties;
+import space.parzival.discord.radiobot.properties.HttpProperties;
+import space.parzival.discord.radiobot.properties.StreamProperties;
+
+@SpringBootApplication
+@EnableConfigurationProperties
+@Import({
+ ClientProperties.class,
+ StreamProperties.class,
+ HttpProperties.class
+})
+public class RadioBotApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RadioBotApplication.class, args);
+ }
+
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/VersionCheck.java b/src/main/java/space/parzival/discord/radiobot/VersionCheck.java
new file mode 100644
index 0000000..93d2c18
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/VersionCheck.java
@@ -0,0 +1,59 @@
+package space.parzival.discord.radiobot;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.message.BasicHeader;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.stereotype.Component;
+import org.apache.maven.artifact.versioning.ComparableVersion;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+import space.parzival.discord.radiobot.properties.HttpProperties;
+import space.parzival.discord.radiobot.model.GitHubRelease;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class VersionCheck implements InitializingBean {
+
+ @Autowired
+ private HttpProperties httpProperties;
+
+ @Autowired
+ private BuildProperties buildProperties;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ ComparableVersion latest = this.getLatestVersion();
+ ComparableVersion current = new ComparableVersion(buildProperties.getVersion());
+
+ if (current.compareTo(latest) > 0) {
+ log.warn("------------------------------------------------------------------");
+ log.warn("You are currently running version {}, but {} ", current, latest);
+ log.warn("is already available. Please consider updating.");
+ log.warn("------------------------------------------------------------------");
+ }
+ }
+
+ private ComparableVersion getLatestVersion() {
+ WebClient githubClient = WebClient.builder()
+ .defaultHeader("User-Agent", httpProperties.getUserAgent())
+ .baseUrl("https://api.github.com")
+ .build();
+
+ Mono response = githubClient
+ .get()
+ .uri(httpProperties.getVersionUrl())
+ .retrieve()
+ .bodyToMono(GitHubRelease[].class);
+
+ GitHubRelease[] releases = response.block();
+ if (releases != null && releases.length >= 1)
+ return new ComparableVersion(releases[0].tag_name);
+
+ return new ComparableVersion("0.0.0");
+ }
+
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/events/Ready.java b/src/main/java/space/parzival/discord/radiobot/events/Ready.java
new file mode 100644
index 0000000..053edf7
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/events/Ready.java
@@ -0,0 +1,80 @@
+package space.parzival.discord.radiobot.events;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
+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.source.AudioSourceManagers;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import lombok.extern.slf4j.Slf4j;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.audio.AudioSendHandler;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+import net.dv8tion.jda.api.events.session.ReadyEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import space.parzival.discord.radiobot.icecast.IcyAudioLoadResultHandler;
+import space.parzival.discord.radiobot.icecast.IcyAudioPlayerManager;
+import space.parzival.discord.radiobot.player.AudioPlayerSendHandler;
+import space.parzival.discord.radiobot.properties.ClientProperties;
+
+import java.nio.ByteBuffer;
+
+@Slf4j
+@Component
+public class Ready extends ListenerAdapter {
+
+ @Autowired
+ private IcyAudioPlayerManager playerManager;
+
+ @Autowired
+ private ClientProperties clientProperties;
+
+ public void onReady(@NotNull ReadyEvent event) {
+ log.info("This bot is now ready and connected to {} guilds.", event.getGuildTotalCount());
+
+ JDA client = event.getJDA();
+ VoiceChannel channel = client.getVoiceChannelById(clientProperties.getChannel());
+ assert channel != null;
+ Guild guild = channel.getGuild();
+ guild.getAudioManager().openAudioConnection(channel);
+
+ AudioPlayer player = playerManager.createPlayer();
+ guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player));
+
+ playerManager.loadIcyStream("https://play.sas-media.ru/play_256", new IcyAudioLoadResultHandler() {
+ @Override
+ public void metadataUpdated(String metadata) {
+
+ }
+
+ @Override
+ public void trackLoaded(AudioTrack audioTrack) {
+ log.info("Playing {} by {}", audioTrack.getInfo().title, audioTrack.getInfo().author);
+ player.playTrack(audioTrack);
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist audioPlaylist) {
+
+ }
+
+ @Override
+ public void noMatches() {
+ log.error("No matches.");
+ }
+
+ @Override
+ public void loadFailed(FriendlyException e) {
+ log.error("Load failed:", e);
+ }
+ });
+ }
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioLoadResultHandler.java b/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioLoadResultHandler.java
new file mode 100644
index 0000000..2046258
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioLoadResultHandler.java
@@ -0,0 +1,9 @@
+package space.parzival.discord.radiobot.icecast;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
+
+public interface IcyAudioLoadResultHandler extends AudioLoadResultHandler {
+
+ void metadataUpdated(String metadata);
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioPlayerManager.java b/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioPlayerManager.java
new file mode 100644
index 0000000..c4f3535
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/icecast/IcyAudioPlayerManager.java
@@ -0,0 +1,26 @@
+package space.parzival.discord.radiobot.icecast;
+
+import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
+import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
+import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.CompletableFuture;
+
+@Component
+public class IcyAudioPlayerManager extends DefaultAudioPlayerManager {
+
+ public IcyAudioPlayerManager() {
+ super();
+
+ // we do not need support for YouTube, so only http sources
+ this.registerSourceManager(new HttpAudioSourceManager());
+ }
+
+
+ public void loadIcyStream(final String url, final IcyAudioLoadResultHandler resultHandler) {
+ this.loadItem(url, resultHandler);
+
+ // todo: implement logic for current track info
+ }
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/model/GitHubRelease.java b/src/main/java/space/parzival/discord/radiobot/model/GitHubRelease.java
new file mode 100644
index 0000000..c138510
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/model/GitHubRelease.java
@@ -0,0 +1,5 @@
+package space.parzival.discord.radiobot.model;
+
+public class GitHubRelease {
+ public String tag_name;
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/player/AudioPlayerSendHandler.java b/src/main/java/space/parzival/discord/radiobot/player/AudioPlayerSendHandler.java
new file mode 100644
index 0000000..60ba44f
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/player/AudioPlayerSendHandler.java
@@ -0,0 +1,35 @@
+package space.parzival.discord.radiobot.player;
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
+import net.dv8tion.jda.api.audio.AudioSendHandler;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Helper to allow AudioPlayers to stream to Discord.
+ */
+public class AudioPlayerSendHandler implements AudioSendHandler {
+ private final AudioPlayer audioPlayer;
+ private AudioFrame lastFrame;
+
+ public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
+ this.audioPlayer = audioPlayer;
+ }
+
+ @Override
+ public boolean canProvide() {
+ lastFrame = audioPlayer.provide();
+ return lastFrame != null;
+ }
+
+ @Override
+ public ByteBuffer provide20MsAudio() {
+ return ByteBuffer.wrap(lastFrame.getData());
+ }
+
+ @Override
+ public boolean isOpus() {
+ return true;
+ }
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/properties/ClientProperties.java b/src/main/java/space/parzival/discord/radiobot/properties/ClientProperties.java
new file mode 100644
index 0000000..04603f4
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/properties/ClientProperties.java
@@ -0,0 +1,21 @@
+package space.parzival.discord.radiobot.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter @Setter
+@ConfigurationProperties(prefix = "discord")
+public class ClientProperties {
+
+ /**
+ * The Discord Bot Token
+ */
+ private String token;
+
+ /**
+ * The channel id where the bot is playing.
+ */
+ private String channel;
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/properties/HttpProperties.java b/src/main/java/space/parzival/discord/radiobot/properties/HttpProperties.java
new file mode 100644
index 0000000..f2da7a0
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/properties/HttpProperties.java
@@ -0,0 +1,18 @@
+package space.parzival.discord.radiobot.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "http")
+public class HttpProperties {
+
+ /**
+ * Useragent used to identify on webservers.
+ */
+ private String UserAgent = "Discord Radio Bot (https://github.com/parzival-space/discord-radio-bot)";
+
+ private String VersionUrl = "/repos/parzival-space/discord-radio-bot/releases";
+}
diff --git a/src/main/java/space/parzival/discord/radiobot/properties/StreamProperties.java b/src/main/java/space/parzival/discord/radiobot/properties/StreamProperties.java
new file mode 100644
index 0000000..ff5299f
--- /dev/null
+++ b/src/main/java/space/parzival/discord/radiobot/properties/StreamProperties.java
@@ -0,0 +1,16 @@
+package space.parzival.discord.radiobot.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "stream")
+public class StreamProperties {
+
+ /**
+ * The url to the audio stream.
+ */
+ private String url = "";
+}
diff --git a/src/test/java/space/parzival/discord/radiobot/RadioBotApplicationTests.java b/src/test/java/space/parzival/discord/radiobot/RadioBotApplicationTests.java
new file mode 100644
index 0000000..b130a6d
--- /dev/null
+++ b/src/test/java/space/parzival/discord/radiobot/RadioBotApplicationTests.java
@@ -0,0 +1,13 @@
+package space.parzival.discord.radiobot;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class RadioBotApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}