feat: added little note at boot

fix!: fixed compilation issues on Winblows (This resulted in a new base package.)

Signed-off-by: moonleay <contact@moonleay.net>
This commit is contained in:
moonleay 2024-02-13 18:47:24 +01:00
parent d928258028
commit ab7016cdb3
Signed by: moonleay
GPG key ID: 82667543CCD715FB
100 changed files with 259 additions and 246 deletions

View file

@ -0,0 +1,256 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features
import com.kotlindiscord.kord.extensions.utils.isNullOrBot
import dev.inmo.krontab.buildSchedule
import dev.inmo.krontab.doInfinity
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.UserBehavior
import dev.kord.core.behavior.createRole
import dev.kord.core.behavior.requestMembers
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.MessageChannel
import dev.kord.gateway.PrivilegedIntent
import dev.kord.rest.builder.message.EmbedBuilder
import net.moonleay.liljudd.Bot
import net.moonleay.liljudd.data.database.entry.PlanningNotifierRolesData
import net.moonleay.liljudd.data.database.entry.TimePlanningMessagesData
import net.moonleay.liljudd.data.database.repository.PlanningNotifierRolesRepository
import net.moonleay.liljudd.data.database.repository.TimePlanningMessagesRepository
import net.moonleay.liljudd.extensions.FeatureManageExtension
import net.moonleay.liljudd.features.component.FeatureEnum
import net.moonleay.liljudd.features.component.IFeature
import net.moonleay.liljudd.util.*
import java.time.ZonedDateTime
object AvailabilityManager : IFeature {
// This runs during the cronjob.
suspend fun runThread() {
Logger.out("Starting to update roles...")
// ChannelID, Data
val messages = TimePlanningMessagesRepository.getWeek(TimeUtil.getWeekStamp().toEpochSecond())
.associateBy { it.channelID }
val targetedRoles = PlanningNotifierRolesRepository.getAll().associateBy { it.channelID }
if (targetedRoles.isEmpty()) {
Logger.out("No saved roles. Canceling.")
return
}
for (id in messages.keys) {
val snf = Snowflake(id) // snf = Snowflake
val data = messages[id]!! // this is the data of the table
if (Bot.bot.kordRef.getChannel(Snowflake(data.channelID)) == null) {
// This channel does not exist anymore.
Logger.out("Warning: Channel ${data.channelID} does not exist anymore. Skipping.")
continue
}
val roleData = targetedRoles[data.channelID] // Get the role data
if (roleData == null) {
Logger.out("Role for channel ${data.channelID} does not exist")
continue // this took way to long to find out that this was the issue
}
this.updateInChannel(snf, data, roleData)
}
Logger.out("Done! Until tomorrow! <3 ")
}
suspend fun updateInChannel(snf: Snowflake) {
val stamp = TimeUtil.getWeekStamp().toEpochSecond()
Logger.out("Weekstamp: $stamp")
val messageData = TimePlanningMessagesRepository.getWeekInChannel(
stamp,
snf.value.toLong()
)
if (messageData == null) {
Logger.out("Could not find data for channel ${snf.value}")
return
}
val roleData = PlanningNotifierRolesRepository.getForChannel(snf.value.toLong())
if (roleData == null) {
Logger.out("Role for channel ${messageData.channelID} does not exist")
return // this took way to long to find out that this was the issue
}
updateInChannel(snf, messageData, roleData)
}
@OptIn(PrivilegedIntent::class)
suspend fun updateInChannel(snf: Snowflake, tpmd: TimePlanningMessagesData, pnrd: PlanningNotifierRolesData) {
if (Bot.bot.kordRef.getChannel(snf) == null)
return // This channel does not exist anymore.
val c = Bot.bot.kordRef.getChannelOf<MessageChannel>(snf)!! // Get the channel as MessageChannel
val weekday = ZonedDateTime.now().dayOfWeek // The current week day
val weekStamp = TimeUtil.getWeekStamp().toEpochSecond() // The current week time stamp
Logger.out("It is week ${weekStamp} and day ${weekday}/${TimeUtil.getDayOfMonthInt(weekday)} of the week.")
val g = Bot.bot.kordRef.getGuild(Snowflake(tpmd.serverID))
// Get all members with the role
val mce = g.requestMembers {
this.requestAllMembers()
}
mce.collect { memberchunkevent ->
memberchunkevent.members.forEach {
if (!it.isNullOrBot()) { // Check if the member is a bot
Logger.out("Checking member ${it.id.value} (${it.username})")
if (it.roleIds.contains(Snowflake(pnrd.hasTimeRoleID))) {
it.removeRole(Snowflake(pnrd.hasTimeRoleID))
Logger.out("Removed role from ${it.username}") // Removed the role
}
}
// I cant use continue here, because it does not work with .forEach
}
}
Logger.out("Got through all members")
// This stores the ids of the messages.
// The format is weekdaNR:ID
// The last entry (nr 8) is empty, so we can ignore it
val messageIdSplit = tpmd.messageIDs.split(";").subList(0, 7)
for (mid in messageIdSplit) {
Logger.out("Checking id $mid")
if (!mid.startsWith((TimeUtil.getDayOfMonthInt(weekday) - 1).toString(), true))
continue// This is not the right message, check the next one
val idFiltered = mid.split(":")[1] // This is the target message id
val message = c.getMessageOrNull(Snowflake(idFiltered)) // Get the message from the channel
if (message == null) {
Logger.out("Could not find message.")
return // This message does not exist anymore. Nothing we can do about that.
}
if (message.data.embeds.isEmpty()) {
Logger.out("There are no embeds.")
return // There are no embeds or there are not enough embeds
}
val targets = EmbedUtil.getAllUsersInTheFirstXTables(2, message.embeds[0])
for (tid in targets) {
Logger.out("Checking id $tid")
if (Bot.bot.kordRef.getGuildOrNull(Snowflake(tpmd.serverID))!!
.getMemberOrNull(Snowflake(tid)) == null
)
continue// This member does not exist anymore.
val member = Bot.bot.kordRef.getGuild(Snowflake(tpmd.serverID))
.getMember(Snowflake(tid)) // Get the member
if (member.roleIds.contains(Snowflake(pnrd.hasTimeRoleID)))
continue // This member already has the role
member.addRole(Snowflake(pnrd.hasTimeRoleID)) // Add the role
Logger.out("Added role to ${member.username}")
}
Logger.out("Done with message. Moving on...")
// We found the right message. We don't need to check the others.
break
}
}
override val feat: FeatureEnum
get() = FeatureEnum.PLANNINGROLES
// Register the cronjob to run at 1AM UTC every day
override suspend fun registerThread() {
Logger.out("Adding availability scheduler...")
val scheduler = buildSchedule("0 0 2 * * *") // 0 0 4 * * * 0o 1w // 0o is UTC
scheduler.doInfinity {
this.runThread()
}
}
override suspend fun enable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder {
var alreadyExists = PlanningNotifierRolesRepository.existsInChannel(cID)
// Check if the channel and guild already exist in the db
if (!alreadyExists) {
// Create the roles in Discord
val hasTimeRole = Bot.bot.kordRef.getGuild(Snowflake(gID)).createRole {
this.name = "available [${ch.data.name.value}]"
this.mentionable = true
}
val htr = hasTimeRole.id.value.toLong()
val wantsNotifsRole = Bot.bot.kordRef.getGuild(Snowflake(gID)).createRole {
this.name = "notifications [${ch.data.name.value}]"
this.mentionable = true
}
val wnr = wantsNotifsRole.id.value.toLong()
// Save the role ids to db
PlanningNotifierRolesRepository.write(
PlanningNotifierRolesData(
id = -1,
serverID = gID,
channelID = cID,
hasTimeRoleID = htr,
wantsToBeNotifiedID = wnr
)
)
return MessageUtil.getEmbed(
EmbedColor.SUCCESS,
"200: Success",
"The feature was enabled in channel ${args.channel.data.name.value} with roles ${hasTimeRole.mention} & ${wantsNotifsRole.mention}.",
u.asUser().username + "#" + u.asUser().discriminator
)
}
// They exist, do not add them
return MessageUtil.getEmbed(
EmbedColor.ERROR,
"403: Forbidden",
"The feature is already enabled in this channel.",
u.asUser().username + "#" + u.asUser().discriminator
)
}
override suspend fun disable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder {
// Check if entry exists in db
if (PlanningNotifierRolesRepository.existsInChannelFromSever(cID, gID)) {
val entry = PlanningNotifierRolesRepository.getForChannelInServer(cID, gID)!!
// delete all entries for this guild and channel combo
Bot.bot.kordRef.getGuild(Snowflake(gID))
.getRoleOrNull(Snowflake(entry.hasTimeRoleID))?.delete()
Bot.bot.kordRef.getGuild(Snowflake(gID))
.getRoleOrNull(Snowflake(entry.wantsToBeNotifiedID))?.delete()
// delete all found entries
PlanningNotifierRolesRepository.delete(entry.id)
return MessageUtil.getEmbed(
EmbedColor.SUCCESS,
"200: Success",
"The feature was disabled.",
u.asUser().username + "#" + u.asUser().discriminator
)
}
// not in db, do nothing
return MessageUtil.getEmbed(
EmbedColor.ERROR,
"403: Forbidden",
"The feature is already disabled in this channel.",
u.asUser().username + "#" + u.asUser().discriminator
)
}
}

View file

@ -0,0 +1,64 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features
import dev.kord.common.entity.Snowflake
import net.moonleay.liljudd.Bot
import net.moonleay.liljudd.data.database.entry.MatchPlanningDataData
import net.moonleay.liljudd.data.database.repository.MatchPlanningDataRepository
import net.moonleay.liljudd.jobs.MatchJob
import net.moonleay.liljudd.jobs.component.JobManager
import net.moonleay.liljudd.util.Logger
object MatchManager {
suspend fun update() {
Logger.out("Updating match roles...")
val dataList = MatchPlanningDataRepository.getAll()
for (data in dataList) {
Logger.out("Checking match role ${data.id}...")
if (data.timestamp < System.currentTimeMillis()) {
Logger.out("Match role ${data.id} is expired, removing...")
this.removeRoleFromGuild(data.serverID, data.roleID)
MatchPlanningDataRepository.delete(data.id)
continue
}
this.registerJob(data)
}
Logger.out("Done. Until next time! <3 ")
}
private fun registerJob(data: MatchPlanningDataData) {
JobManager.addJob(
MatchJob(
data.jobString,
data.id,
"fromdb-${data.matchType}_Vs_${data.opponentName}_[${data.id}]-${data.serverID}_${data.channelID}"
)
)
Logger.out("Registered job for match ${data.id}...")
}
private suspend fun removeRoleFromGuild(gid: Long, rid: Long): Boolean {
val guild = Bot.bot.kordRef.getGuildOrNull(Snowflake(gid)) ?: return false
val role = guild.getRoleOrNull(Snowflake(rid)) ?: return false
role.delete()
return true
}
}

View file

@ -0,0 +1,206 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features
import dev.inmo.krontab.buildSchedule
import dev.inmo.krontab.doInfinity
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.UserBehavior
import dev.kord.core.behavior.channel.createMessage
import dev.kord.core.entity.channel.Channel
import dev.kord.core.entity.channel.MessageChannel
import dev.kord.rest.builder.message.EmbedBuilder
import dev.kord.rest.builder.message.actionRow
import dev.kord.rest.builder.message.embed
import kotlinx.coroutines.delay
import net.moonleay.liljudd.Bot
import net.moonleay.liljudd.data.database.entry.TimePlanningChannelsData
import net.moonleay.liljudd.data.database.entry.TimePlanningMessagesData
import net.moonleay.liljudd.data.database.repository.PlanningNotifierRolesRepository
import net.moonleay.liljudd.data.database.repository.TimePlanningChannelsRepository
import net.moonleay.liljudd.data.database.repository.TimePlanningMessagesRepository
import net.moonleay.liljudd.extensions.FeatureManageExtension
import net.moonleay.liljudd.features.component.FeatureEnum
import net.moonleay.liljudd.features.component.IFeature
import net.moonleay.liljudd.util.EmbedColor
import net.moonleay.liljudd.util.EmbedUtil
import net.moonleay.liljudd.util.Logger
import net.moonleay.liljudd.util.MessageUtil
import java.time.ZoneId
import java.time.ZonedDateTime
object TimeManager : IFeature {
override val feat: FeatureEnum
get() = FeatureEnum.TIMEPLANNINGFEATURE
// Register the cronjob to run at 0:01 AM UTC every Monday
override suspend fun registerThread() {
Logger.out("Adding message scheduler...")
val scheduler = buildSchedule("0 0 1 * * * 0o 1w") // 0 0 4 * * * 0o 1w // 0o is UTC
scheduler.doInfinity {
this.runThread()
}
}
private suspend fun runThread() {
Logger.out("Starting to notify...")
// ChannelID -> Data
val targetedChannels = TimePlanningChannelsRepository.getAll().associateBy { it.channelID }
val targetedRoles = PlanningNotifierRolesRepository.getAll().associateBy { it.channelID }
lateinit var msgStr: String
Logger.out("${targetedChannels.count()} Channels to notify with ${targetedRoles.count()} Roles to ping!")
for (ch2 in targetedChannels.keys) {
val ch = Snowflake(ch2)
if (Bot.bot.kordRef.getChannel(ch) == null)
continue // TODO: Check if the channel is valid in another shard
val c = Bot.bot.kordRef.getChannelOf<MessageChannel>(ch)!!
msgStr = ""
if (targetedRoles != null && targetedRoles.keys.contains(ch2) && targetedRoles[ch2] != null) {
c.createMessage {
this.content =
"The weekly planning starts now <@&${Snowflake(targetedRoles[ch2]?.wantsToBeNotifiedID!!)}>"
this.embed {
this.color = EmbedColor.INFO.color
this.title = "Time Planning Feature"
this.description = "Do you have time on the following Days?"
this.footer {
this.icon = Bot.bot.kordRef.getSelf().avatar?.cdnUrl?.toUrl()
this.text = MessageUtil.getFooter()
}
}
}
} else {
c.createMessage {
this.embed {
this.color = EmbedColor.INFO.color
this.title = "Time Planning Feature"
this.description = "Do you have time on the following Days?"
this.footer {
this.icon = Bot.bot.kordRef.getSelf().avatar?.cdnUrl?.toUrl()
this.text = MessageUtil.getFooter()
}
}
}
}
delay(2000)
var then = ZonedDateTime.now(ZoneId.of("Europe/Berlin")).withHour(4).withMinute(0).withSecond(0)
repeat(7) {
val eb = MessageUtil.getEmbedWithTable(
EmbedColor.INFO,
"",
"${then.dayOfWeek.name}, ${then.dayOfMonth}.${then.monthValue}.${then.year} /${it + 1}. weekday",
mapOf(
"Is available" to listOf(),
"May be available" to listOf(),
"Is not available" to listOf()
)
)
val msg = c.createMessage {
this.embed {
this.color = eb.color
this.title = eb.title
this.description = eb.description
this.fields = eb.fields
this.footer {
this.icon = Bot.bot.kordRef.getSelf().avatar?.cdnUrl?.toUrl()
this.text = MessageUtil.getFooter()
}
}
this.actionRow {
this.components.addAll(EmbedUtil.getTimePlannerButtons().components)
}
}
msgStr += "${it}:${msg.id.value};"
then = then.plusDays(1).withHour(4).withMinute(0).withSecond(0)
Logger.out("Finished sending day $it")
delay(1000)
}
// Save the message ids
TimePlanningMessagesRepository.write(
TimePlanningMessagesData(
id = -1,
serverID = c.data.guildId.value?.value!!.toLong(),
channelID = c.data.id.value.toLong(),
weekstamp = then.minusDays(7).toEpochSecond(),
messageIDs = msgStr
)
)
Logger.out("Finished with ${c.data.guildId.value}")
}
Logger.out("Done! Until next Monday! <3 ")
}
override suspend fun enable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder {
if (!TimePlanningChannelsRepository.exists(cID, gID)) {
TimePlanningChannelsRepository.write(TimePlanningChannelsData(id = -1, serverID = gID, channelID = cID))
return MessageUtil.getEmbed(
EmbedColor.SUCCESS,
"200: Success",
"The feature was enabled in channel ${args.channel.data.name.value}",
u.asUser().username
)
}
return MessageUtil.getEmbed(
EmbedColor.ERROR,
"409: Conflict",
"The feature is already enabled in this channel.",
u.asUser().username
)
}
override suspend fun disable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder {
// Check if entry exists in db
if (TimePlanningChannelsRepository.exists(cID, gID)) {
// delete all entrys for this channel
TimePlanningChannelsRepository.deleteFromChannelInServer(cID, gID)
return MessageUtil.getEmbed(
EmbedColor.SUCCESS,
"200: Success",
"The feature was disabled.",
u.asUser().username
)
}
// Do nothing; not in db
return MessageUtil.getEmbed(
EmbedColor.ERROR,
"409: Conflict",
"The feature is already disabled in this channel.",
u.asUser().username
)
}
}

View file

@ -0,0 +1,26 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features.component
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.ChoiceEnum
enum class FeatureEnum(override val readableName: String) : ChoiceEnum {
TIMEPLANNINGFEATURE("Time Planning Feature"),
PLANNINGROLES("Planning Roles")
}

View file

@ -0,0 +1,30 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features.component
import net.moonleay.liljudd.features.AvailabilityManager
import net.moonleay.liljudd.features.TimeManager
object FeatureManager {
val features = mutableListOf(AvailabilityManager, TimeManager) // Stores all features
fun getFeature(feat: FeatureEnum): IFeature? {
return features.find { it.feat == feat }
}
}

View file

@ -0,0 +1,45 @@
/*
* lilJudd
* Copyright (C) 2023 moonleay
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.moonleay.liljudd.features.component
import dev.kord.core.behavior.UserBehavior
import dev.kord.core.entity.channel.Channel
import dev.kord.rest.builder.message.EmbedBuilder
import net.moonleay.liljudd.extensions.FeatureManageExtension
interface IFeature {
val feat: FeatureEnum
suspend fun registerThread()
suspend fun enable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder
suspend fun disable(
u: UserBehavior,
gID: Long,
cID: Long,
ch: Channel,
args: FeatureManageExtension.FeatureManagerArgs
): EmbedBuilder
}