diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 2e722da..b7569c0 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,14 +5,14 @@
-
-
-
-
-
+
-
+
+
+
+
+
@@ -37,45 +37,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -117,10 +78,10 @@
-
+
-
+
@@ -135,7 +96,7 @@
-
+
@@ -204,8 +165,14 @@
-
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 9008d53..4863a66 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,6 +17,9 @@ dependencies {
implementation 'net.dv8tion:JDA:4.2.0_189'
implementation 'org.jsoup:jsoup:1.13.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
+
+ implementation 'com.github.redouane59.twitter:twittered:1.20'
+ implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2'
}
compileKotlin {
diff --git a/src/main/java/org/hmcore/TwitterJob.java b/src/main/java/org/hmcore/TwitterJob.java
new file mode 100644
index 0000000..be4cb6a
--- /dev/null
+++ b/src/main/java/org/hmcore/TwitterJob.java
@@ -0,0 +1,49 @@
+package org.hmcore;
+
+import com.github.redouane59.twitter.TwitterClient;
+import com.github.redouane59.twitter.dto.tweet.Tweet;
+import com.github.redouane59.twitter.signature.TwitterCredentials;
+import de.wulkanat.Admin;
+import de.wulkanat.Channels;
+import net.dv8tion.jda.api.MessageBuilder;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+import java.util.Objects;
+
+public class TwitterJob implements Job {
+
+ public static TwitterClient twitterClient = new TwitterClient(TwitterCredentials.builder()
+ .accessToken(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getAccessToken())
+ .accessTokenSecret(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getAccessTokenSecret())
+ .apiKey(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getApiKey())
+ .apiSecretKey(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getApiKeySecret())
+ .build());
+
+ public static String hytaleTwitterID = twitterClient.getUserFromUserName("Hytale").getId();
+
+ public static String lastTweetID = twitterClient.getUserTimeline(hytaleTwitterID, 20).get(0).getId();
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+
+ try {
+
+ Tweet tweet = twitterClient.getUserTimeline(hytaleTwitterID, 20).get(0);
+ String tweetID = tweet.getId();
+
+ if(!lastTweetID.equalsIgnoreCase(tweetID)) {
+ lastTweetID = tweetID;
+
+ Channels.INSTANCE.sentToAll(new MessageBuilder().append(" You got mail! <:blobmail:817758108503769130> https://twitter.com/Hytale/status/").append(tweetID).build());
+
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+}
diff --git a/src/main/kotlin/de/wulkanat/Admin.kt b/src/main/kotlin/de/wulkanat/Admin.kt
index c442d41..359b369 100644
--- a/src/main/kotlin/de/wulkanat/Admin.kt
+++ b/src/main/kotlin/de/wulkanat/Admin.kt
@@ -1,3 +1,4 @@
+@file:JvmName("Admin")
package de.wulkanat
import kotlinx.serialization.json.Json
@@ -10,26 +11,19 @@ import net.dv8tion.jda.api.entities.User
import java.awt.Color
object Admin {
- val userId: Long
- val token: String
- val updateMs: Long
- val message: String
- val offlineMessage: String
+ @JvmField
+ val adFile = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText())
+ val userId: Long = adFile.adminId
+ val token: String = adFile.token
+ val updateMs: Long = adFile.updateMs
+ val message: String = adFile.watchingMessage
+ val offlineMessage: String = adFile.offlineMessage
- init {
- val admin = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText())
- userId = admin.adminId
- token = admin.token
- updateMs = admin.updateMs
- message = admin.watchingMessage
- offlineMessage = admin.offlineMessage
- }
-
- var jda: JDA? = null
- set(value) {
- field = value
-
- admin = value?.retrieveUserById(userId)?.complete()
+ fun connectToUser() {
+ Main.jdas.forEach {
+ if(admin != null) return;
+ admin = it.retrieveUserById(userId)?.complete()
+ }
if (admin == null) {
kotlin.io.println("Connection to de.wulkanat.Admin failed!")
} else {
@@ -118,7 +112,6 @@ object Admin {
}
private fun senDevMessageBlocking(messageEmbed: MessageEmbed, fallback: String) {
- admin = jda!!.retrieveUserById(userId).complete()
val devChannel = admin?.openPrivateChannel() ?: kotlin.run {
kotlin.io.println(fallback)
return
diff --git a/src/main/kotlin/de/wulkanat/Channels.kt b/src/main/kotlin/de/wulkanat/Channels.kt
index aaef499..07f7405 100644
--- a/src/main/kotlin/de/wulkanat/Channels.kt
+++ b/src/main/kotlin/de/wulkanat/Channels.kt
@@ -1,3 +1,4 @@
+@file:JvmName("Channels")
package de.wulkanat
import de.wulkanat.extensions.crosspost
@@ -5,12 +6,12 @@ import kotlinx.serialization.list
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.Permission
+import net.dv8tion.jda.api.entities.Message
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.TextChannel
import java.awt.Color
object Channels {
- var jda: JDA? = null
/**
* List of (ServerID, ChannelID)
@@ -18,40 +19,39 @@ object Channels {
var channels: MutableList = refreshChannelsFromDisk()
var serviceChannels: MutableList = refreshServiceChannelsFromDisk()
- fun sentToAll(messageEmbed: MessageEmbed) {
- if (jda == null)
- return
+ fun sentToAll(messageEmbed: Message) {
+ Main.jdas.forEach { jda ->
+ for (channel_pair in channels) {
+ try {
+ val channel = jda.getTextChannelById(channel_pair.id) ?: continue
+ val customMessage = channel_pair.message?.message ?: ""
- for (channel_pair in channels) {
- try {
- val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
- val customMessage = channel_pair.message?.message ?: ""
-
- if (channel_pair.mentionedRole != null) {
- val message = if (channel_pair.mentionedRole == "everyone") {
- "@everyone $customMessage"
- } else {
- "<@&${channel_pair.mentionedRole}> $customMessage"
+ if (channel_pair.mentionedRole != null) {
+ val message = if (channel_pair.mentionedRole == "everyone") {
+ "@everyone $customMessage"
+ } else {
+ "<@&${channel_pair.mentionedRole}> $customMessage"
+ }
+ channel.sendMessage(message).queue {
+ if (channel_pair.message?.pushAnnouncement == true) {
+ it.crosspost().queue()
+ }
+ }
+ } else if (channel_pair.message != null) {
+ channel.sendMessage(customMessage).queue {
+ if (channel_pair.message?.pushAnnouncement == true) {
+ it.crosspost().queue()
+ }
+ }
}
- channel.sendMessage(message).queue {
- if (channel_pair.message?.pushAnnouncement == true) {
- it.crosspost().queue()
- }
- }
- } else if (channel_pair.message != null) {
- channel.sendMessage(customMessage).queue {
- if (channel_pair.message?.pushAnnouncement == true) {
+ channel.sendMessage(messageEmbed).queue {
+ if (channel_pair.autoPublish) {
it.crosspost().queue()
}
}
+ } catch (e: Exception) {
+ Admin.error("Error in server ${channel_pair.id}", e.message ?: e.localizedMessage)
}
- channel.sendMessage(messageEmbed).queue {
- if (channel_pair.autoPublish) {
- it.crosspost().queue()
- }
- }
- } catch (e: Exception) {
- Admin.error("Error in server ${channel_pair.id}", e.message ?: e.localizedMessage)
}
}
}
@@ -66,9 +66,12 @@ object Channels {
.build()
for (channelInfo in serviceChannels) {
- val channel = jda!!.getTextChannelById(channelInfo.id)
+ Main.jdas.forEach {
+ val channel = it.getTextChannelById(channelInfo.id)
+
+ channel?.sendMessage(serviceMessage)?.queue()
+ }
- channel?.sendMessage(serviceMessage)?.queue()
}
Admin.println("Service message distributed to ${serviceChannels.size} channels.")
@@ -84,15 +87,17 @@ object Channels {
}
fun checkEveryonePermission() {
- for (channel_pair in channels) {
- val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
+ Main.jdas.forEach {
+ for (channel_pair in channels) {
+ val channel = it.getTextChannelById(channel_pair.id) ?: continue
- if (channel_pair.mentionedRole == "everyone" &&
- channel.guild.selfMember.hasPermission(Permission.MESSAGE_MENTION_EVERYONE)
- ) {
- Admin.warning("Cannot mention everyone on ${channel.guild.name}")
- } else if (channel.guild.selfMember.hasPermission(Permission.MESSAGE_WRITE)) {
- Admin.warning("Cannot send any messages on ${channel.guild.name}")
+ if (channel_pair.mentionedRole == "everyone" &&
+ channel.guild.selfMember.hasPermission(Permission.MESSAGE_MENTION_EVERYONE)
+ ) {
+ Admin.warning("Cannot mention everyone on ${channel.guild.name}")
+ } else if (channel.guild.selfMember.hasPermission(Permission.MESSAGE_WRITE)) {
+ Admin.warning("Cannot send any messages on ${channel.guild.name}")
+ }
}
}
@@ -111,43 +116,49 @@ object Channels {
}
fun getServerNames(server: Long? = null): List {
- if (jda == null)
- return listOf()
- return channels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
- val channel = jda!!.getTextChannelById(it.id)
- if (channel == null) {
- Admin.warning("Channel ${it.id} is no longer active!")
- return@map "**${it.id}** *(inactive)*"
- }
+ return Main.jdas.flatMap { jda ->
+ channels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
+ val channel = jda!!.getTextChannelById(it.id)
+ if (channel == null) {
+ Admin.warning("Channel ${it.id} is no longer active!")
+ return@map "**${it.id}** *(inactive)*"
+ }
- val role = when (it.mentionedRole) {
- null -> ""
- "everyone" -> " @everyone"
- else -> " @${channel.guild.getRoleById(it.mentionedRole ?: "")?.name}"
+ val role = when (it.mentionedRole) {
+ null -> ""
+ "everyone" -> " @everyone"
+ else -> " @${channel.guild.getRoleById(it.mentionedRole ?: "")?.name}"
+ }
+ val publish = if (it.autoPublish) " (publish)" else ""
+ "**${channel.guild.name}** #${channel.name}${role}${publish}${
+ if (it.message == null) {
+ ""
+ } else {
+ "\n*${it.message!!.message}*${if (it.message!!.pushAnnouncement) " (publish)" else ""}"
+ }
+ }"
}
- val publish = if (it.autoPublish) " (publish)" else ""
- "**${channel.guild.name}** #${channel.name}${role}${publish}${if (it.message == null) {
- ""
- } else {
- "\n*${it.message!!.message}*${if (it.message!!.pushAnnouncement) " (publish)" else ""}"
- }
- }"
}
}
fun getServiceChannelServers(server: Long? = null): List {
- if (jda == null)
- return listOf()
- return serviceChannels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
- val channel = jda!!.getTextChannelById(it.id)
- "**${channel?.guild?.name ?: it.id}** #${channel?.name ?: "(inactive)"}"
+ return Main.jdas.flatMap { jda ->
+ serviceChannels.filter { server == null || (jda.getTextChannelById(it.id)?.guild?.idLong == server) }
+ .map {
+ val channel = jda.getTextChannelById(it.id)
+ "**${channel?.guild?.name ?: it.id}** #${channel?.name ?: "(inactive)"}"
+ }
}
}
fun testServerId(id: Long): TextChannel? {
- return jda?.getTextChannelById(id)
+
+ return Main.jdas.map {
+ it.getTextChannelById(id)
+ }.firstOrNull()
+
}
fun addChannel(id: Long, role: String?): DiscordChannel? {
diff --git a/src/main/kotlin/de/wulkanat/DataIO.kt b/src/main/kotlin/de/wulkanat/DataIO.kt
index d84571a..7b5e9c5 100644
--- a/src/main/kotlin/de/wulkanat/DataIO.kt
+++ b/src/main/kotlin/de/wulkanat/DataIO.kt
@@ -1,3 +1,4 @@
+@file:JvmName("DataIO")
package de.wulkanat
import de.wulkanat.extensions.ensureExists
@@ -32,7 +33,16 @@ data class AdminFile(
val token: String = "12345",
val updateMs: Long = 30000,
val watchingMessage: String = "for new Blogposts",
- val offlineMessage: String = "CONNECTION FAILED"
+ val offlineMessage: String = "CONNECTION FAILED",
+ var twitterApi: TwitterApi? = TwitterApi()
+)
+
+@Serializable
+data class TwitterApi(
+ val accessToken: String = "accessTokenHere",
+ val accessTokenSecret: String = "accessTokenSecretHere",
+ val apiKey: String = "apiKeyHere",
+ val apiKeySecret: String = "Api Key secret here"
)
val json = Json(JsonConfiguration.Stable)
diff --git a/src/main/kotlin/de/wulkanat/DiscordRpc.kt b/src/main/kotlin/de/wulkanat/DiscordRpc.kt
index b4d3dfd..20351b5 100644
--- a/src/main/kotlin/de/wulkanat/DiscordRpc.kt
+++ b/src/main/kotlin/de/wulkanat/DiscordRpc.kt
@@ -4,7 +4,6 @@ import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.entities.Activity
object DiscordRpc {
- var jda: JDA? = null
fun updatePresence(available: Boolean) {
// jda ?: return
diff --git a/src/main/kotlin/de/wulkanat/Main.kt b/src/main/kotlin/de/wulkanat/Main.kt
index f409e59..09ea5f9 100644
--- a/src/main/kotlin/de/wulkanat/Main.kt
+++ b/src/main/kotlin/de/wulkanat/Main.kt
@@ -1,40 +1,114 @@
package de.wulkanat
+import de.wulkanat.web.SiteWatcher
+import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.JDABuilder
+import net.dv8tion.jda.api.MessageBuilder
import net.dv8tion.jda.api.entities.Activity
import net.dv8tion.jda.api.requests.GatewayIntent
-import de.wulkanat.web.SiteWatcher
+import net.dv8tion.jda.api.utils.ChunkingFilter
+import net.dv8tion.jda.api.utils.MemberCachePolicy
+import net.dv8tion.jda.api.utils.cache.CacheFlag
+import org.hmcore.TwitterJob
+import org.quartz.CronScheduleBuilder.cronSchedule
+import org.quartz.JobBuilder.newJob
+import org.quartz.JobDetail
+import org.quartz.Trigger
+import org.quartz.TriggerBuilder.newTrigger
+import org.quartz.impl.StdSchedulerFactory
+import javax.security.auth.login.LoginException
import kotlin.concurrent.timer
-fun main() {
- val builder = JDABuilder.createLight(
- Admin.token,
- GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
- .setActivity(Activity.watching(Admin.message))
- .build()
- builder.addEventListener(AdminCli())
- builder.addEventListener(ErrorHandler())
- builder.addEventListener(OwnerCli())
- builder.awaitReady()
+object Main {
+ @JvmField
+ var jdas = mutableListOf()
- Channels.jda = builder
- Admin.jda = builder
- DiscordRpc.jda = builder
- Admin.info()
+ @JvmStatic
+ fun main(args: Array) {
+ val builder = JDABuilder.createLight(
+ Admin.token,
+ GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
+ .setActivity(Activity.watching(Admin.message))
- Runtime.getRuntime().addShutdownHook(object : Thread() {
- override fun run() {
- println("Shutting down...")
- println("Sending shutdown notice to Admin, waiting 5s...")
- Admin.println("Shutting down")
- sleep(5000)
+ configureMemoryUsage(builder)
+
+ for (i in 0 until 6) {
+ try {
+ jdas.add(
+ builder.useSharding(i, 6)
+ .build()
+ )
+ } catch (loginException: LoginException) {
+ println("!!! Shard $i could not login !!!")
+ }
}
- })
- timer("Updater", daemon = true, initialDelay = 0L, period = Admin.updateMs) {
- if (SiteWatcher.hasNewBlogPost()) {
- Channels.sentToAll(SiteWatcher.newestBlog!!.toMessageEmbed())
+ val jda = builder.build()
+
+ jda.addEventListener(AdminCli())
+ jda.addEventListener(ErrorHandler())
+ jda.addEventListener(OwnerCli())
+ jda.awaitReady()
+
+ Admin.connectToUser()
+
+ Admin.info()
+
+ Runtime.getRuntime().addShutdownHook(object : Thread() {
+ override fun run() {
+ println("Shutting down...")
+ println("Sending shutdown notice to Admin, waiting 5s...")
+ Admin.println("Shutting down")
+ sleep(5000)
+ }
+ })
+
+ timer("Updater", daemon = true, initialDelay = 0L, period = Admin.updateMs) {
+ if (SiteWatcher.hasNewBlogPost()) {
+ Channels.sentToAll(MessageBuilder().setEmbed(SiteWatcher.newestBlog!!.toMessageEmbed()).build())
+ }
}
+
+ // Grab the Scheduler instance from the Factory
+ val scheduler = StdSchedulerFactory.getDefaultScheduler()
+ // and start it off
+ scheduler.start();
+ // define the job and tie it to our TwitterJob class
+ val job: JobDetail = newJob(TwitterJob::class.java)
+ .withIdentity("job1", "group1")
+ .build()
+
+ // Trigger the job to run now, and then repeat every 5 minutes
+ val trigger: Trigger = newTrigger()
+ .withIdentity("trigger1", "group1")
+ .startNow()
+ .withSchedule(cronSchedule("0 0/5 * 1/1 * ? *"))
+ .build()
+
+ // Tell quartz to schedule the job using our trigger
+ scheduler.scheduleJob(job, trigger);
+
+
}
+
+
+ fun configureMemoryUsage(builder: JDABuilder) {
+ // Disable cache for member activities (streaming/games/spotify)
+ builder.disableCache(CacheFlag.ACTIVITY)
+
+ // Only cache members who are either in a voice channel or owner of the guild
+ builder.setMemberCachePolicy(MemberCachePolicy.VOICE.or(MemberCachePolicy.OWNER))
+
+ // Disable member chunking on startup
+ builder.setChunkingFilter(ChunkingFilter.NONE)
+
+ // Disable presence updates and typing events
+ builder.disableIntents(GatewayIntent.GUILD_PRESENCES, GatewayIntent.GUILD_MESSAGE_TYPING)
+
+ // Consider guilds with more than 50 members as "large".
+ // Large guilds will only provide online members in their setup and thus reduce bandwidth if chunking is disabled.
+ builder.setLargeThreshold(50)
+ }
+
}
\ No newline at end of file