This commit is contained in:
UnrealValentin
2021-05-28 18:39:48 +02:00
parent 288e56f035
commit f0f6015d6f
8 changed files with 266 additions and 160 deletions

65
.idea/workspace.xml generated
View File

@@ -5,14 +5,14 @@
</component>
<component name="ChangeListManager">
<list default="true" id="1aabf22b-2f57-46ac-9973-367d8668ffd3" name="Default Changelist" comment="no idea what that did">
<change afterPath="$PROJECT_DIR$/.idea/.name" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/libraries-with-intellij-classes.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/extensions/Jsoup.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/model/JobListingPreview.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/web/JobListingParser.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/org/hmcore/TwitterJob.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/build.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gradle/wrapper/gradle-wrapper.properties" beforeDir="false" afterPath="$PROJECT_DIR$/gradle/wrapper/gradle-wrapper.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Admin.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Admin.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Channels.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Channels.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DataIO.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DataIO.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DiscordRpc.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DiscordRpc.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Main.kt" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -37,45 +37,6 @@
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="build" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="help" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="other" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="verification" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Dependencies" type="6de06a37:ExternalSystemViewDefaultContributor$MyDependenciesNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Run Configurations" type="7b0102dc:RunConfigurationsNode" />
</path>
</expand>
<select />
</tree_state>
@@ -117,10 +78,10 @@
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/build/libs" />
<property name="project.structure.last.edited" value="Project" />
<property name="project.structure.last.edited" value="Modules" />
<property name="project.structure.proportion" value="0.15" />
<property name="project.structure.side.proportion" value="0.2" />
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
<property name="settings.editor.selected.configurable" value="reference.settingsdialog.project.gradle" />
</component>
<component name="RecentsManager">
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
@@ -135,7 +96,7 @@
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\src\main\kotlin\de\wulkanat" />
</key>
</component>
<component name="RunManager" selected="Gradle.HytaleUpdateBot [build]">
<component name="RunManager" selected="Application.MainKt">
<configuration name="HytaleUpdateBot [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
@@ -204,8 +165,14 @@
<method v="2" />
</configuration>
<configuration name="MainKt" type="JetRunConfigurationType" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="de.wulkanat.MainKt" />
<module name="HytaleUpdateBot.main" />
<option name="VM_PARAMETERS" />
<option name="PROGRAM_PARAMETERS" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="de.wulkanat.MainKt" />
<option name="WORKING_DIRECTORY" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -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 {

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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<DiscordChannel> = refreshChannelsFromDisk()
var serviceChannels: MutableList<ServiceChannel> = 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<String> {
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<String> {
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? {

View File

@@ -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)

View File

@@ -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

View File

@@ -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<JDA>()
Channels.jda = builder
Admin.jda = builder
DiscordRpc.jda = builder
Admin.info()
@JvmStatic
fun main(args: Array<String>) {
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)
}
}