Skip to content

Commit

Permalink
Merge pull request #224 from Pan-nav/master
Browse files Browse the repository at this point in the history
Discord server wrapped command
  • Loading branch information
stephendotgg authored Jan 1, 2024
2 parents 73d451e + 2531444 commit 4a770e4
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 99 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/com/learnspigot/bot/Bot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import net.dv8tion.jda.api.utils.ChunkingFilter
import net.dv8tion.jda.api.utils.MemberCachePolicy

class Bot {
private val profileRegistry = ProfileRegistry()
val profileRegistry = ProfileRegistry()

init {
jda = JDABuilder.createDefault(Environment.get("BOT_TOKEN"))
Expand Down
201 changes: 103 additions & 98 deletions src/main/kotlin/com/learnspigot/bot/reputation/LeaderboardMessage.kt
Original file line number Diff line number Diff line change
@@ -1,119 +1,124 @@
package com.learnspigot.bot.reputation

import com.learnspigot.bot.profile.ProfileRegistry
import com.learnspigot.bot.Server
import com.learnspigot.bot.profile.ProfileRegistry
import com.learnspigot.bot.util.embed
import net.dv8tion.jda.api.entities.Message
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.MessageHistory
import java.time.Instant
import java.time.YearMonth
import java.time.ZoneOffset
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
import net.dv8tion.jda.api.entities.Message
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.MessageHistory

class LeaderboardMessage(private val profileRegistry: ProfileRegistry) {

private val medals: List<String> = listOf(":first_place:", ":second_place:", ":third_place:")

private val executorService = Executors.newSingleThreadScheduledExecutor()

private val monthlyRewardMessage: Message
private val lifetimeMessage: Message
private val monthlyMessage: Message

init {
Server.leaderboardChannel.apply {
MessageHistory.getHistoryFromBeginning(this).complete().retrievedHistory.apply {
/*
* If all 3 messages aren't there, delete any existing ones and send the new 3
* Otherwise, just get them, edit to update, and store for constant updating like normal
*/
if (size != 3) {
forEach { it.delete().queue() }
monthlyRewardMessage = sendMessageEmbeds(buildPrizeEmbed()).complete()
lifetimeMessage = sendMessageEmbeds(buildLeaderboard(false)).complete()
monthlyMessage = sendMessageEmbeds(buildLeaderboard(true)).complete()
} else {
monthlyRewardMessage = get(2).editMessageEmbeds(buildPrizeEmbed()).complete()
lifetimeMessage = get(1).editMessageEmbeds(buildLeaderboard(false)).complete()
monthlyMessage = get(0).editMessageEmbeds(buildLeaderboard(true)).complete()
}
}
private val medals: List<String> = listOf(":first_place:", ":second_place:", ":third_place:")

private val executorService = Executors.newSingleThreadScheduledExecutor()

private val monthlyRewardMessage: Message
private val lifetimeMessage: Message
private val monthlyMessage: Message

init {
Server.leaderboardChannel.apply {
MessageHistory.getHistoryFromBeginning(this).complete().retrievedHistory.apply {
/*
* If all 3 messages aren't there, delete any existing ones and send the new 3
* Otherwise, just get them, edit to update, and store for constant updating like normal
*/
if (size != 3) {
forEach { it.delete().queue() }
monthlyRewardMessage = sendMessageEmbeds(buildPrizeEmbed()).complete()
lifetimeMessage = sendMessageEmbeds(buildLeaderboard(false)).complete()
monthlyMessage = sendMessageEmbeds(buildLeaderboard(true)).complete()
} else {
monthlyRewardMessage = get(2).editMessageEmbeds(buildPrizeEmbed()).complete()
lifetimeMessage = get(1).editMessageEmbeds(buildLeaderboard(false)).complete()
monthlyMessage = get(0).editMessageEmbeds(buildLeaderboard(true)).complete()
}

executorService.scheduleAtFixedRate({
lifetimeMessage.editMessageEmbeds(buildLeaderboard(false)).queue()
monthlyMessage.editMessageEmbeds(buildLeaderboard(true)).queue()

if (isLastMin()){
Server.managerChannel.sendMessageEmbeds(buildLeaderboard(true)).queue {println("Manager channel leaderboard message sent.")}
}
}, 1L, 1L, TimeUnit.MINUTES)
}
}

private fun buildLeaderboard(monthly: Boolean): MessageEmbed {
val builder = StringBuilder()

val i = AtomicInteger(1)
top10(monthly).forEach(Consumer { (id, reputation): ReputationWrapper ->
builder.append(
if (i.get() <= medals.size) medals[i.get() - 1] else i.get().toString() + "."
).append(" <@").append(id).append("> - ").append(reputation.size).append("\n")
i.getAndIncrement()
})

return embed()
.setTitle((if (monthly) "Monthly" else "All-Time") + " Leaderboard")
.setDescription((if (monthly) "These stats are reset on the 1st of every month." else "These stats are never reset.") + "\n\n$builder")
.setFooter("Last updated")
.setTimestamp(Instant.now())
.build()
}

private fun buildPrizeEmbed() : MessageEmbed{
return embed()
.setTitle("Current Monthly Rewards")
.setDescription("The top 3 on the Monthly Leaderboard will earn these rewards:" +
"\n\n${medals[0]} - $50 PayPal!" +
"\n${medals[1]} - \$20 PayPal!" +
"\n${medals[2]} - \$10 PayPal!")
.setFooter("* To qualify, you must be part of the Support Team. Message a Manager to apply.", "https://cdn.discordapp.com/avatars/928124622564655184/54b6c4735aff20a92a5bc6881fab4d64.webp?size=128")
.build()
}

private fun top10(monthly: Boolean): List<ReputationWrapper> {
val reputation = mutableListOf<ReputationWrapper>()
for ((key, profile) in profileRegistry.profileCache) {
var repList = ArrayList(profile.reputation.values)
if (repList.isEmpty()) continue

if (monthly) {
repList = repList.filter { rep ->
YearMonth.now().atDay(1).atStartOfDay().toInstant(ZoneOffset.UTC)
.isBefore(Instant.ofEpochSecond(rep.timestamp))
} as ArrayList<Reputation>
}
reputation.add(ReputationWrapper(key, repList))
executorService.scheduleAtFixedRate(
{
lifetimeMessage.editMessageEmbeds(buildLeaderboard(false)).queue()
monthlyMessage.editMessageEmbeds(buildLeaderboard(true)).queue()
},
1L,
1L,
TimeUnit.MINUTES)
}

private fun buildLeaderboard(monthly: Boolean): MessageEmbed {
val builder = StringBuilder()

val i = AtomicInteger(1)
top10(profileRegistry, monthly)
.forEach(
Consumer { (id, reputation): ReputationWrapper ->
builder
.append(
if (i.get() <= medals.size) medals[i.get() - 1] else i.get().toString() + ".")
.append(" <@")
.append(id)
.append("> - ")
.append(reputation.size)
.append("\n")
i.getAndIncrement()
})

return embed()
.setTitle((if (monthly) "Monthly" else "All-Time") + " Leaderboard")
.setDescription(
(if (monthly) "These stats are reset on the 1st of every month."
else "These stats are never reset.") + "\n\n$builder")
.setFooter("Last updated")
.setTimestamp(Instant.now())
.build()
}

private fun buildPrizeEmbed(): MessageEmbed {
return embed()
.setTitle("Current Monthly Rewards")
.setDescription(
"The top 3 on the Monthly Leaderboard will earn these rewards:" +
"\n\n${medals[0]} - $50 PayPal!" +
"\n${medals[1]} - \$20 PayPal!" +
"\n${medals[2]} - \$10 PayPal!")
.setFooter(
"* To qualify, you must be part of the Support Team. Message a Manager to apply.",
"https://cdn.discordapp.com/avatars/928124622564655184/54b6c4735aff20a92a5bc6881fab4d64.webp?size=128")
.build()
}

companion object {
fun top10(profileRegistry: ProfileRegistry, monthly:Boolean): List<ReputationWrapper> {
val reputation = mutableListOf<ReputationWrapper>()
for ((key, profile) in profileRegistry.profileCache) {
var repList = ArrayList(profile.reputation.values)
if (repList.isEmpty()) continue

if (monthly) {
repList =
repList.filter { rep ->
YearMonth.now()
.atDay(1)
.atStartOfDay()
.toInstant(ZoneOffset.UTC)
.isBefore(Instant.ofEpochSecond(rep.timestamp))
} as ArrayList<Reputation>
}
reputation.sortByDescending { it.reputation.size }
return reputation.take(10)
reputation.add(ReputationWrapper(key, repList))
}
reputation.sortByDescending { it.reputation.size }
return reputation.take(10)
}

private fun isLastMin(): Boolean {
val now = Instant.now()
val startOfNextMonth = YearMonth.now().plusMonths(1).atDay(1).atStartOfDay().toInstant(ZoneOffset.UTC)
val lastMinOfCurrentMonth = startOfNextMonth.minusSeconds(60)

val isLastMin = now.isAfter(lastMinOfCurrentMonth)
if (isLastMin){ println("This is the last minute of the month!")}

return isLastMin
}


}
data class ReputationWrapper(val id: String, val reputation: List<Reputation>)

}
}
147 changes: 147 additions & 0 deletions src/main/kotlin/com/learnspigot/bot/stats/WrappedCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.learnspigot.bot.stats

import com.learnspigot.bot.Bot
import com.learnspigot.bot.Environment
import com.learnspigot.bot.Server
import com.learnspigot.bot.reputation.LeaderboardMessage
import gg.flyte.neptune.annotation.Command
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
import java.awt.Color
import java.util.*

class WrappedCommand {

private val guild = Bot.jda.getGuildById(Environment.get("GUILD_ID"))!!
private val currentYear = 2023
private val helpForum: ForumChannel = Bot.jda.getForumChannelById(Server.helpChannel.id)!!

private val uniqueUsers = mutableMapOf<String, Int>()
private var totalMessages = 0
private val channelsMessageCount = mutableMapOf<String, Int>()
private val emojisUsageCount = mutableMapOf<String, Int>()
private val wordsUsageCount = mutableMapOf<String, Int>()
private var studentsHelped = 0
private val highestContributor: LeaderboardMessage.ReputationWrapper by lazy {
LeaderboardMessage.top10(Bot().profileRegistry(), false).firstOrNull()!!
}

private lateinit var mostReactedMessage: net.dv8tion.jda.api.entities.Message

private val blacklist = mutableListOf(
"the", "to", "it", "i", "a", "and", "is", "in", "that", "you",
"was", "for", "on", "are", "with", "as", "at", "be", "this",
"have", "from", "or", "an", "but", "not", "by", "we", "can",
"if", "they", "he", "she", "will", "all", "no", "there", "do",
"just", "has", "so", "what", "about", "up", "out", "up", "one",
"down", "into", "some", "your", "how", "like", "when", "his",
"her", "their", "would", "who", "which", "time", "than", "them",
":", "of", "count", "last", "since"
)

@Command(
name = "wrapped",
description = "get this year's discord wrapped",
permissions = [Permission.MANAGE_ROLES]
)
fun onWrappedCommand(
event: SlashCommandInteractionEvent,
) {
event.reply("Fetching the stats, please hold a moment!").setEphemeral(true).queue()
performMessageIteration()
performHelpIteration()
event.channel.sendMessageEmbeds(buildRecapMessage().build()).queue()
}

private fun performMessageIteration() {
guild.textChannels.forEach { textChannel ->
textChannel.iterableHistory.forEach { message ->
val messageYear = message.timeCreated.year
if (messageYear == currentYear) {
totalMessages++
uniqueUsers.merge(message.author.id, 1, Int::plus)

val reactions = message.reactions.size
if (!::mostReactedMessage.isInitialized ||
reactions > mostReactedMessage.reactions.size
) {
mostReactedMessage = message
}

channelsMessageCount.merge(textChannel.id, 1, Int::plus)

val emojis = guild.emojis
emojis.forEach { emoji ->
val emojiId = emoji.id
if (message.contentRaw.contains(emojiId)) {
emojisUsageCount.merge(emojiId, 1, Int::plus)
}
}

val words = message.contentRaw.split("\\s+".toRegex())
words.forEach { word ->
wordsUsageCount.merge(word, 1, Int::plus)
}
}
}
}
}

private fun performHelpIteration() {
studentsHelped = helpForum.threadChannels.count { it.isArchived && it.timeCreated.year == currentYear }
}

private fun buildRecapMessage(): EmbedBuilder {
val topUsers = uniqueUsers.entries.sortedByDescending { it.value }.take(5)
val topChannels = channelsMessageCount.entries.sortedByDescending { it.value }.take(3)
val topEmojis = emojisUsageCount.entries.sortedByDescending { it.value }.take(5)
val topWords = wordsUsageCount.entries
.filter { it.key.lowercase(Locale.getDefault()) !in blacklist }
.sortedByDescending { it.value }
.take(5)


val codeBlock = buildString {
append("```\n")

append("Total Messages: $totalMessages\n\n")

append("Top 5 Most Active Users:\n")
topUsers.forEach { entry ->
append("${Bot.jda.getUserById(entry.key)!!.asMention}: ${entry.value} messages\n")
}

append("\nTop 3 Most Active Channels:\n")
topChannels.forEach { entry ->
val channel = guild.getTextChannelById(entry.key)
append("${channel!!.asMention}: ${entry.value} messages\n")
}

append("\nTop 5 Most Used Emojis:\n")
topEmojis.forEach { entry ->
val emojiId = entry.key
val emoji = guild.getEmojiById(emojiId)
append("${emoji?.asMention ?: "<:$emojiId>"}: ${entry.value} uses\n")
}

append("\nTop 5 Most Used Words:\n")
topWords.forEach { entry -> append("${entry.key}: ${entry.value} uses\n") }

append("\nHelp Post Statistics:\n")
append("Help posts successfully closed: $studentsHelped\n")
append(
"Highest Contributor: ${Bot.jda.getUserById(highestContributor.id)!!.asMention} with ${highestContributor.reputation.size} reps!\n"
)

append("```\n")
}

return EmbedBuilder()
.setColor(Color.BLUE)
.setTitle("Discord Wrapped $currentYear")
.setDescription("Here's a recap of this year's Discord activity:")
.addField("LearnSpigot Discord Wrapped $currentYear", codeBlock, false)
}
}

0 comments on commit 4a770e4

Please sign in to comment.