diff --git a/.gitignore b/.gitignore
index d376959..2ed1642 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
servers.json
admin.json
+test.json
*.hprof
/build
/.gradle
\ No newline at end of file
diff --git a/.idea/dictionaries/wulkanat.xml b/.idea/dictionaries/wulkanat.xml
new file mode 100644
index 0000000..9612e8b
--- /dev/null
+++ b/.idea/dictionaries/wulkanat.xml
@@ -0,0 +1,7 @@
+
+
+
+ crosspost
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 3163861..dea8515 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,5 +1,6 @@
+
-
+
@@ -137,7 +142,7 @@
true
-
+
@@ -148,7 +153,7 @@
-
+
@@ -160,21 +165,30 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -189,13 +203,25 @@
1597322033373
+
+ 1597437833375
+
+
+
+ 1597437833375
+
+
+
+
+
+
-
+
-
+
@@ -204,6 +230,10 @@
+
+
+
+
@@ -212,51 +242,59 @@
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
+
+
+
+
diff --git a/README.md b/README.md
index a3cecf9..bb6fd90 100644
--- a/README.md
+++ b/README.md
@@ -3,17 +3,39 @@ A bot that automatically polls the newest blogpost from [Hytale News Tab](https:
## Setup
Okay, this isn't really meant for you to setup, if you want it though it first is easier to just dm me on Twitter [@tale_talk](https://twitter.com/tale_talk) so I can add you to the server list.
If you *really* want to set it up yourself, fine.
-1. first go to the release tab, download the jar, and put it in a folder
-2. Add two files in the root of the repo, an `admin.json` and a `servers.json`.
+* first go to the release tab, download the jar, and put it in a folder
+* Add two files in the root of the repo, an `admin.json` and a `servers.json`.
Add your Discord ID (not name), Bot token, and update frequency to the `admin.json`:
```json
-{"adminId": 12345678910,"token": "AOGH@(AKnjsfjiJijaig3ijgG92jaij","updateMs":30000}
+{
+ "adminId": 12345678910,
+ "token": "AOGH@(AKnjsfjiJijaig3ijgG92jaij",
+ "updateMs":30000
+}
```
-3. add an empty array to your `servers.json`
+* add your servers to `servers.json`
```json
-[]
+[
+ {
+ "id": 15050067772322222,
+ "mentionedRole": "everyone",
+ "autoPublish":true
+ },
+ {
+ "id": 74050067772325222,
+ "mentionedRole": null,
+ "autoPublish":false
+ },
+ {
+ "id": 74050067772325222,
+ "mentionedRole": "74036067771625222",
+ "autoPublish":false
+ }
+]
```
-Not sure, but it might be that multiline JSON doesn't work.
+* add a `test.json` with the same schema as the `server.json`. When
+you enable test mode, the servers from there will be used instead allowing
+you to test if it works.
## Compiling yourself
I developed it under Windows, and had some trouble compiling it on Linux. You mileage may vary.
@@ -22,14 +44,22 @@ I developed it under Windows, and had some trouble compiling it on Linux. You mi
Start the server with `java -jar [server-file-name]` If you put in everything correctly, the bot should message you on Discord.
### Adding Servers
+Please edit the JSON file.
+You can force an update by calling
```
-%!addChannel [channelID] [roleID/everyone]
+%!refreshList
```
-Second argument is optional.
-### Cause a fake update (test if it works)
+### Testing
+Switching between test and production files
```
+%!testMode
%!fakeUpdate
```
+```
+%!productionMode
+```
+**WARNING**: Initiating a fake update is not being cancelled by switching
+to production.
### Stop the server from within Discord
```
%!stop
@@ -45,3 +75,8 @@ It will also print errors directly in a Discord private message.
## TODO
Mainly reaction roles for convenience, self setup on invite to server, Twitter integration.
+
+## Other
+
+Thanks to [Forcellrus](https://github.com/Forcellrus/Discord-Auto-Publisher) for discovering a way to auto publish messages
+in news channels
\ No newline at end of file
diff --git a/src/main/java/Inaccessibles.java b/src/main/java/Inaccessibles.java
new file mode 100644
index 0000000..fa19041
--- /dev/null
+++ b/src/main/java/Inaccessibles.java
@@ -0,0 +1,27 @@
+import net.dv8tion.jda.internal.requests.Method;
+import net.dv8tion.jda.internal.requests.Route;
+
+import java.lang.reflect.Constructor;
+
+public class Inaccessibles {
+ /**
+ * This is private by default
+ *
+ * @param method look
+ * @param route somewhere
+ * @return else
+ */
+ public static Route getRoute(Method method, String route) {
+ try {
+ Constructor> constructor = Route.class.getDeclaredConstructor(Method.class, String.class);
+ constructor.setAccessible(true);
+ return (Route) constructor.newInstance(method, route);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static String toUnsignedString(long num) {
+ return Long.toUnsignedString(num);
+ }
+}
diff --git a/src/main/kotlin/de/wulkanat/Admin.kt b/src/main/kotlin/de/wulkanat/Admin.kt
index 2c38f57..a78f885 100644
--- a/src/main/kotlin/de/wulkanat/Admin.kt
+++ b/src/main/kotlin/de/wulkanat/Admin.kt
@@ -4,17 +4,33 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.JDA
+import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.User
import java.awt.Color
-import java.sql.Time
-import java.util.concurrent.TimeUnit
object Admin {
val userId: Long
val token: String
val updateMs: Long
+ var testModeEnabled: Boolean = false
+ set(value) {
+ if (field == value)
+ return
+
+ field = value
+
+ if (value) {
+ jda?.presence?.setPresence(Activity.of(Activity.ActivityType.DEFAULT, "Testing mode, hold on..."), true)
+ } else {
+ jda?.presence?.setPresence(Activity.watching("for new Blogposts"), false)
+ }
+
+ Channels.channels = Channels.refreshFromDisk()
+ Admin.info()
+ }
+
init {
val admin = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText())
userId = admin.adminId
@@ -55,14 +71,14 @@ object Admin {
)
}
- fun error(msg: String, error: Exception) {
+ fun error(msg: String, error: String) {
sendDevMessage(
EmbedBuilder()
.setTitle(msg)
- .setDescription(error.message)
+ .setDescription(error)
.setColor(Color.RED)
.build()
- , "$msg\n\n${error.message}"
+ , "$msg\n\n${error}"
)
}
diff --git a/src/main/kotlin/de/wulkanat/Bot.kt b/src/main/kotlin/de/wulkanat/Bot.kt
deleted file mode 100644
index 68bacfc..0000000
--- a/src/main/kotlin/de/wulkanat/Bot.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package de.wulkanat
-
-import net.dv8tion.jda.api.events.message.MessageReceivedEvent
-import net.dv8tion.jda.api.hooks.ListenerAdapter
-
-class Bot : ListenerAdapter() {
- override fun onMessageReceived(event: MessageReceivedEvent) {
- val message = event.message
-
- if (message.contentRaw == "!ping") {
- val channel = message.channel
- val time = System.currentTimeMillis()
-
- channel.sendMessage("Pong!")
- .queue {
- it.editMessageFormat("Pong: %d ms", System.currentTimeMillis() - time)
- .queue()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/de/wulkanat/Channels.kt b/src/main/kotlin/de/wulkanat/Channels.kt
index e37735e..f536813 100644
--- a/src/main/kotlin/de/wulkanat/Channels.kt
+++ b/src/main/kotlin/de/wulkanat/Channels.kt
@@ -1,5 +1,6 @@
package de.wulkanat
+import de.wulkanat.extensions.crosspost
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
@@ -15,8 +16,7 @@ object Channels {
/**
* List of (ServerID, ChannelID)
*/
- val channels: MutableList =
- json.parse(DiscordChannel.serializer().list, SERVERS_FILE.readText()).toMutableList()
+ var channels: MutableList = refreshFromDisk()
fun sentToAll(messageEmbed: MessageEmbed) {
if (jda == null)
@@ -33,7 +33,11 @@ object Channels {
}
channel.sendMessage(message).queue()
}
- channel.sendMessage(messageEmbed).queue()
+ channel.sendMessage(messageEmbed).queue {
+ if (channel_pair.autoPublish) {
+ it.crosspost().queue()
+ }
+ }
}
}
@@ -52,6 +56,16 @@ object Channels {
}
}
+ fun refreshFromDisk(): MutableList {
+ return json.parse(
+ DiscordChannel.serializer().list, (if (Admin.testModeEnabled) {
+ TEST_FILE
+ } else {
+ SERVERS_FILE
+ }).readText()
+ ).toMutableList()
+ }
+
fun getServerNames(): List {
if (jda == null)
return listOf()
@@ -63,12 +77,10 @@ object Channels {
return@map "**${it.id}** *(inactive)*"
}
- val role = if (it.mentionedRole == null) {
- ""
- } else if (it.mentionedRole == "everyone") {
- " @everyone"
- } else {
- " @${channel.guild.getRoleById(it.mentionedRole)?.name}"
+ val role = when (it.mentionedRole) {
+ null -> ""
+ "everyone" -> " @everyone"
+ else -> " @${channel.guild.getRoleById(it.mentionedRole)?.name}"
}
"**${channel.guild.name}**\n#${channel.name}${role}"
}
@@ -88,6 +100,7 @@ object Channels {
json.stringify(
DiscordChannel.serializer().list,
channels
- ))
+ )
+ )
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/de/wulkanat/Cli.kt b/src/main/kotlin/de/wulkanat/Cli.kt
index 01b847c..0a9a8af 100644
--- a/src/main/kotlin/de/wulkanat/Cli.kt
+++ b/src/main/kotlin/de/wulkanat/Cli.kt
@@ -4,59 +4,47 @@ import de.wulkanat.model.BlogPostPreview
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import de.wulkanat.web.SiteWatcher
+import net.dv8tion.jda.api.events.ExceptionEvent
+import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent
import kotlin.system.exitProcess
class Cli : ListenerAdapter() {
- override fun onMessageReceived(event: MessageReceivedEvent) {
+ override fun onPrivateMessageReceived(event: PrivateMessageReceivedEvent) {
val msg = event.message.contentRaw
if (event.author.idLong != Admin.userId ||
- !msg.startsWith("%!")
+ !msg.startsWith("!")
) {
return
}
- val command = msg.removePrefix("%!").split(" ")
+ val command = msg.removePrefix("!").split(Regex("\\s+"))
- try {
- when (command[0]) {
- "stop" -> exitProcess(1)
- "fakeUpdate" -> {
- SiteWatcher.newestBlog = BlogPostPreview(
- title = "FakePost",
- imgUrl = "",
- fullPostUrl = "",
- author = "wulkanat",
- date = "now",
- description = "Lorem Ipsum"
- )
+ when (command[0]) {
+ "stop" -> exitProcess(1)
+ "fakeUpdate" -> {
+ SiteWatcher.newestBlog = BlogPostPreview(
+ title = "FakePost",
+ imgUrl = "",
+ fullPostUrl = "",
+ author = "wulkanat",
+ date = "now",
+ description = "Lorem Ipsum"
+ )
- Admin.println("Posting on next update cycle.")
- }
- "addChannel" -> {
- val channel = command[1].toLong()
- var role: String? = null
- if (command.size == 3) {
- role = command[2]
- }
- val serverChannel = Channels.testServerId(channel)
- val roleName = serverChannel?.guild?.getRoleById(role ?: "")
-
- if (serverChannel != null) {
- if (roleName != null || role == null || role == "everyone") {
- Channels.addChannel(channel, role)
- Admin.println("Added server '${serverChannel.name}' for role '${roleName ?: role}'")
- } else {
- Admin.warning("Unknown Role ID")
- }
- } else {
- Admin.warning("Unknown Channel ID")
- }
- }
- "info" -> {
- Admin.info()
- }
+ Admin.println("Posting on next update cycle.")
+ }
+ "info" -> {
+ Admin.info()
+ }
+ "refreshList" -> {
+ Channels.channels = Channels.refreshFromDisk()
+ Admin.info()
+ }
+ "testMode" -> {
+ Admin.testModeEnabled = true
+ }
+ "productionMode" -> {
+ Admin.testModeEnabled = false
}
- } catch (e: ArrayIndexOutOfBoundsException) {
- // noop
}
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/de/wulkanat/DataIO.kt b/src/main/kotlin/de/wulkanat/DataIO.kt
index 910a9f7..1c2d1dd 100644
--- a/src/main/kotlin/de/wulkanat/DataIO.kt
+++ b/src/main/kotlin/de/wulkanat/DataIO.kt
@@ -6,7 +6,8 @@ import java.io.File
@Serializable
data class DiscordChannel(
val id: Long,
- val mentionedRole: String? = null
+ val mentionedRole: String? = null,
+ val autoPublish: Boolean = false
)
@Serializable
@@ -17,4 +18,5 @@ data class AdminFile(
)
val SERVERS_FILE = File("servers.json")
+val TEST_FILE = File("test.json")
val ADMIN_FILE = File("admin.json")
diff --git a/src/main/kotlin/de/wulkanat/ErrorHandler.kt b/src/main/kotlin/de/wulkanat/ErrorHandler.kt
new file mode 100644
index 0000000..6663b6f
--- /dev/null
+++ b/src/main/kotlin/de/wulkanat/ErrorHandler.kt
@@ -0,0 +1,10 @@
+package de.wulkanat
+
+import net.dv8tion.jda.api.events.ExceptionEvent
+import net.dv8tion.jda.api.hooks.ListenerAdapter
+
+class ErrorHandler : ListenerAdapter() {
+ override fun onException(event: ExceptionEvent) {
+ Admin.error(event.cause.message ?: event.cause.localizedMessage, event.cause.stackTrace.toString())
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/wulkanat/Main.kt b/src/main/kotlin/de/wulkanat/Main.kt
index 5e787fa..58b9b90 100644
--- a/src/main/kotlin/de/wulkanat/Main.kt
+++ b/src/main/kotlin/de/wulkanat/Main.kt
@@ -7,15 +7,14 @@ import de.wulkanat.web.SiteWatcher
import kotlin.concurrent.timer
fun main() {
- // TODO: move toke into file
val builder = JDABuilder.createLight(
Admin.token,
GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
- .addEventListeners(Bot())
.setActivity(Activity.watching("for new Blogposts"))
.build()
builder.addEventListener(Cli())
+ builder.addEventListener(ErrorHandler())
builder.awaitReady()
Channels.jda = builder
diff --git a/src/main/kotlin/de/wulkanat/extensions/Message.kt b/src/main/kotlin/de/wulkanat/extensions/Message.kt
new file mode 100644
index 0000000..2198a1c
--- /dev/null
+++ b/src/main/kotlin/de/wulkanat/extensions/Message.kt
@@ -0,0 +1,28 @@
+package de.wulkanat.extensions
+
+import Inaccessibles
+import net.dv8tion.jda.api.entities.Message
+import net.dv8tion.jda.api.entities.MessageChannel
+import net.dv8tion.jda.api.requests.restaction.MessageAction
+import net.dv8tion.jda.internal.requests.Method
+import net.dv8tion.jda.internal.requests.Route
+import net.dv8tion.jda.internal.requests.restaction.MessageActionImpl
+import net.dv8tion.jda.internal.utils.Checks
+
+fun MessageChannel.crosspostById(messageId: String): MessageAction {
+ Checks.isSnowflake(messageId, "Message ID")
+
+ val route = CROSSPOST_MESSAGE.compile(id, messageId)
+ return MessageActionImpl(jda, route, this).append("This is not of your interest.")
+}
+
+fun Message.crosspost(): MessageAction {
+ val messageId = Inaccessibles.toUnsignedString(idLong)
+
+ return channel.crosspostById(messageId)
+}
+
+val CROSSPOST_MESSAGE: Route = Inaccessibles.getRoute(
+ Method.POST,
+ "channels/{channel_id}/messages/{message_id}/crosspost"
+)
\ No newline at end of file
diff --git a/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt b/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt
index 36893de..e5af26c 100644
--- a/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt
+++ b/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt
@@ -27,7 +27,7 @@ object SiteWatcher {
newestBlog = newBlog
}
} catch (e: IOException) {
- Admin.error("Connection to Hytale Server failed", e)
+ Admin.error("Connection to Hytale Server failed", e.message ?: e.localizedMessage)
return false
}