From ea4c80a57d75a09b8e1c7e4d77737ca97c65c979 Mon Sep 17 00:00:00 2001 From: Helium9 <80259183+KeinsWolfi@users.noreply.github.com> Date: Sun, 16 Mar 2025 14:17:43 +0100 Subject: [PATCH 1/8] Test? --- .../hannibal2/skyhanni/discord/command/PullRequestCommand.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index c49bc8b..e670d10 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -103,6 +103,11 @@ object PullRequestCommand : BaseCommand() { append("\n") append("> Last Updated: $lastUpdate") append("\n") + if (pr.merged) { + val merged = passedSince(pr.mergedAt ?: "") + append("> Merged: $merged") + append("\n") + } } val lastCommit = head.sha From ee6e482f37bbfed830156604bc9394e01639e35f Mon Sep 17 00:00:00 2001 From: Helium9 <80259183+KeinsWolfi@users.noreply.github.com> Date: Sun, 16 Mar 2025 17:09:52 +0100 Subject: [PATCH 2/8] Added hint if PR is in latest beta --- .../discord/command/PullRequestCommand.kt | 24 ++++++ .../skyhanni/discord/github/GitHubClient.kt | 16 ++-- .../discord/json/discord/ReleasesResponse.kt | 75 +++++++++++++++++++ 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/at/hannibal2/skyhanni/discord/json/discord/ReleasesResponse.kt diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index e670d10..298584e 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -107,6 +107,24 @@ object PullRequestCommand : BaseCommand() { val merged = passedSince(pr.mergedAt ?: "") append("> Merged: $merged") append("\n") + + val releases = try { + github.getReleases() + } catch (e: Exception) { + null + } + val lastRelease = releases?.firstOrNull() + val lastReleaseTime = passedSince(lastRelease?.publishedAt ?: "") + append("> Last Release: $lastReleaseTime") + append("\n") + + if (releaseSinceMerge(pr.mergedAt ?: "", lastRelease?.publishedAt ?: "")) { + append("> This PR is in the latest beta.") + append("\n") + } else { + append("> This PR is not in the latest beta.") + append("\n") + } } } @@ -172,6 +190,12 @@ object PullRequestCommand : BaseCommand() { private fun passedSince(stringTime: String): String = "" + private fun releaseSinceMerge(stringTimeMerge: String, stringTimeLastRelease: String): Boolean { + val timeMerge = parseToUnixTime(stringTimeMerge) + val timeLastRelease = parseToUnixTime(stringTimeLastRelease) + return timeMerge < timeLastRelease + } + @Suppress("unused") // TODO implement once we can upload the file private fun MessageReceivedEvent.pullRequestArtifactCommand(args: List) { if (args.size != 2) { diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/github/GitHubClient.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/github/GitHubClient.kt index d3478bc..1f4b719 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/github/GitHubClient.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/github/GitHubClient.kt @@ -1,12 +1,6 @@ package at.hannibal2.skyhanni.discord.github -import at.hannibal2.skyhanni.discord.json.discord.Artifact -import at.hannibal2.skyhanni.discord.json.discord.ArtifactResponse -import at.hannibal2.skyhanni.discord.json.discord.CheckRun -import at.hannibal2.skyhanni.discord.json.discord.CheckRunsResponse -import at.hannibal2.skyhanni.discord.json.discord.Job -import at.hannibal2.skyhanni.discord.json.discord.JobsResponse -import at.hannibal2.skyhanni.discord.json.discord.PullRequestJson +import at.hannibal2.skyhanni.discord.json.discord.* import com.google.gson.Gson import okhttp3.OkHttpClient import okhttp3.Request @@ -69,7 +63,8 @@ class GitHubClient(user: String, repo: String, private val token: String) { private inline fun readJson(url: String, crossinline block: (T) -> R): R? = readBody(url) { body -> - block(gson.fromJson(body.string(), T::class.java)) + val type = object : com.google.gson.reflect.TypeToken() {}.type + block(gson.fromJson(body.string(), type)) } inline fun readBody(url: String, block: (ResponseBody) -> T): T? { @@ -84,4 +79,9 @@ class GitHubClient(user: String, repo: String, private val token: String) { val request = Request.Builder().url(url).header("Authorization", "token $token").build() return client.newCall(request).execute() } + + fun getReleases(): List? { + val url = "$base/releases" + return readJson, List?>(url) { it } + } } \ No newline at end of file diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/json/discord/ReleasesResponse.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/json/discord/ReleasesResponse.kt new file mode 100644 index 0000000..425dbfe --- /dev/null +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/json/discord/ReleasesResponse.kt @@ -0,0 +1,75 @@ +package at.hannibal2.skyhanni.discord.json.discord + +import com.google.gson.annotations.SerializedName + +data class Release ( + @SerializedName("url") val url: String, + @SerializedName("assets_url") val assetsUrl: String, + @SerializedName("upload_url") val uploadUrl: String, + @SerializedName("html_url") val htmlUrl: String, + @SerializedName("id") val id: Int, + @SerializedName("author") val author: User, + @SerializedName("node_id") val nodeId: String, + @SerializedName("tag_name") val tagName: String, + @SerializedName("target_commitish") val targetCommitish: String?, + @SerializedName("name") val name: String, + @SerializedName("draft") val draft: Boolean, + @SerializedName("prerelease") val prerelease: Boolean, + @SerializedName("created_at") val createdAt: String, + @SerializedName("published_at") val publishedAt: String, + @SerializedName("assets") val assets: List, + @SerializedName("tarball_url") val tarballUrl: String, + @SerializedName("zipball_url") val zipballUrl: String, + @SerializedName("body") val body: String, + @SerializedName("reactions") val reactions: Reactions, +) + +data class Asset ( + @SerializedName("url") val url: String, + @SerializedName("id") val id: Int, + @SerializedName("node_id") val nodeId: String, + @SerializedName("name") val name: String, + @SerializedName("label") val label: String?, + @SerializedName("uploader") val uploader: User, + @SerializedName("content_type") val contentType: String, + @SerializedName("state") val state: String, + @SerializedName("size") val size: Int, + @SerializedName("download_count") val downloadCount: Int, + @SerializedName("created_at") val createdAt: String, + @SerializedName("updated_at") val updatedAt: String, + @SerializedName("browser_download_url") val browserDownloadUrl: String, +) + +data class User ( + @SerializedName("login") val login: String, + @SerializedName("id") val id: Int, + @SerializedName("node_id") val nodeId: String, + @SerializedName("avatar_url") val avatarUrl: String, + @SerializedName("gravatar_id") val gravatarId: String, + @SerializedName("url") val url: String, + @SerializedName("html_url") val htmlUrl: String, + @SerializedName("followers_url") val followersUrl: String, + @SerializedName("following_url") val followingUrl: String, + @SerializedName("gists_url") val gistsUrl: String, + @SerializedName("starred_url") val starredUrl: String, + @SerializedName("subscriptions_url") val subscriptionsUrl: String, + @SerializedName("organizations_url") val organizationsUrl: String, + @SerializedName("repos_url") val reposUrl: String, + @SerializedName("events_url") val eventsUrl: String, + @SerializedName("received_events_url") val receivedEventsUrl: String, + @SerializedName("type") val type: String, + @SerializedName("site_admin") val siteAdmin: Boolean, +) + +data class Reactions ( + @SerializedName("url") val url: String, + @SerializedName("total_count") val totalCount: Int, + @SerializedName("+1") val plusOne: Int, + @SerializedName("-1") val minusOne: Int, + @SerializedName("laugh") val laugh: Int, + @SerializedName("hooray") val hooray: Int, + @SerializedName("confused") val confused: Int, + @SerializedName("heart") val heart: Int, + @SerializedName("rocket") val rocket: Int, + @SerializedName("eyes") val eyes: Int +) \ No newline at end of file From fe28770ae37d3d1c6a6e82f42ca7c2294be32492 Mon Sep 17 00:00:00 2001 From: Helium9 <80259183+KeinsWolfi@users.noreply.github.com> Date: Sun, 16 Mar 2025 17:19:51 +0100 Subject: [PATCH 3/8] Formatting --- .../skyhanni/discord/command/PullRequestCommand.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index 298584e..ab1ac45 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -99,11 +99,12 @@ object PullRequestCommand : BaseCommand() { val time = buildString { val lastUpdate = passedSince(pr.updatedAt) val created = passedSince(pr.createdAt) - append("> Created: $created") - append("\n") - append("> Last Updated: $lastUpdate") - append("\n") - if (pr.merged) { + if (!pr.merged) { + append("> Created: $created") + append("\n") + append("> Last Updated: $lastUpdate") + append("\n") + } else { val merged = passedSince(pr.mergedAt ?: "") append("> Merged: $merged") append("\n") From 681fd0b06811d92db48634ae69210386e5cdb0c6 Mon Sep 17 00:00:00 2001 From: Helium9 <80259183+KeinsWolfi@users.noreply.github.com> Date: Sun, 16 Mar 2025 17:35:47 +0100 Subject: [PATCH 4/8] Formatting --- .../hannibal2/skyhanni/discord/DiscordBot.kt | 2 + .../discord/command/PullRequestCommand.kt | 47 ++++++++++++++----- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/DiscordBot.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/DiscordBot.kt index 78f1ac0..346f830 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/DiscordBot.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/DiscordBot.kt @@ -31,6 +31,8 @@ class DiscordBot(val jda: JDA, val config: BotConfig) { } const val PLEADING_FACE = "🥺" +const val BIG_X = "❌" +const val CHECK_MARK = "✅" const val PING_HANNIBAL = "<@239858538959077376>" fun main() { diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index 0a493fa..d1c46f3 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -1,11 +1,7 @@ package at.hannibal2.skyhanni.discord.command -import at.hannibal2.skyhanni.discord.BOT -import at.hannibal2.skyhanni.discord.Option -import at.hannibal2.skyhanni.discord.PLEADING_FACE -import at.hannibal2.skyhanni.discord.SimpleTimeMark +import at.hannibal2.skyhanni.discord.* import at.hannibal2.skyhanni.discord.SimpleTimeMark.Companion.asTimeMark -import at.hannibal2.skyhanni.discord.Utils import at.hannibal2.skyhanni.discord.Utils.createParentDirIfNotExist import at.hannibal2.skyhanni.discord.Utils.embed import at.hannibal2.skyhanni.discord.Utils.format @@ -96,6 +92,8 @@ object PullRequestCommand : BaseCommand() { append("\n") } + var inBeta: Boolean = false + val time = buildString { val lastUpdate = passedSince(pr.updatedAt) val created = passedSince(pr.createdAt) @@ -115,15 +113,13 @@ object PullRequestCommand : BaseCommand() { null } val lastRelease = releases?.firstOrNull() - val lastReleaseTime = passedSince(lastRelease?.publishedAt ?: "") - append("> Last Release: $lastReleaseTime") - append("\n") if (releaseSinceMerge(pr.mergedAt ?: "", lastRelease?.publishedAt ?: "")) { - append("> This PR is in the latest beta.") + append("> This PR is in the latest beta $CHECK_MARK") append("\n") + inBeta = true } else { - append("> This PR is not in the latest beta.") + append("> This PR is not in the latest beta $BIG_X") append("\n") } } @@ -132,7 +128,14 @@ object PullRequestCommand : BaseCommand() { val lastCommit = head.sha val job = github.getRun(lastCommit, "Build and test") ?: run { - val text = "${title}${time} \nArtifact does not exist $PLEADING_FACE (expired or first pr of contributor)" + val text = buildString { + append(title) + append(time) + if (!inBeta) { + append("\n") + append("Artifact does not exist $PLEADING_FACE (expired or first pr of contributor)") + } + } reply(embed(embedTitle, text, readColor(pr))) return } @@ -151,7 +154,17 @@ object PullRequestCommand : BaseCommand() { Status.PENDING -> "Run is pending $PLEADING_FACE" else -> "" } - reply(embed(embedTitle, "${title}${time} \n $text", readColor(pr))) + + val embedBody = buildString { + append(title) + append(time) + if (!inBeta) { + append("\n") + append(text) + } + } + + reply(embed(embedTitle, embedBody, readColor(pr))) return } @@ -174,7 +187,15 @@ object PullRequestCommand : BaseCommand() { append("> (updated ${passedSince(job.completedAt ?: "")})") } - reply(embed(embedTitle, "$title$time$artifactDisplay", readColor(pr))) + val embedBody = buildString { + append(title) + append(time) + if (!inBeta) { + append(artifactDisplay) + } + } + + reply(embed(embedTitle, embedBody, readColor(pr))) } // Colors picked from GitHub From 5bf0a66220f470bef53102adb1df05e1c08c43e8 Mon Sep 17 00:00:00 2001 From: Helium9 <80259183+KeinsWolfi@users.noreply.github.com> Date: Sun, 16 Mar 2025 18:51:58 +0100 Subject: [PATCH 5/8] Fixed --- .../discord/command/PullRequestCommand.kt | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index 3a26134..d64c3be 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -60,7 +60,7 @@ object PullRequestCommand : BaseCommand() { loadPrInfos(prNumber) } - private fun MessageReceivedEvent.loadPrInfos(prNumber: Int) { + private fun MessageReceivedEvent.loadPrInfos(prNumber: Long) { logAction("loads pr infos for #$prNumber") val prLink = "$BASE/pull/$prNumber" @@ -102,22 +102,12 @@ object PullRequestCommand : BaseCommand() { val created = passedSince(pr.createdAt) append("> Created: $created") append("\n") - append("> Last Updated: $lastUpdate") - append("\n") - appendLabelCategory("Type", labels, this) - appendLabelCategory("State", labels, this) - appendLabelCategory("Milestone", labels, this, pr.milestone?.let { " `${it.title}`" } ?: "") - } - - if (toTimeMark(pr.updatedAt).passedSince() > 400.days) { - val text = "${title}${time} \nBuild download has expired $PLEADING_FACE" - reply(embed(embedTitle, text, readColor(pr))) - return if (!pr.merged) { - append("> Created: $created") - append("\n") append("> Last Updated: $lastUpdate") append("\n") + appendLabelCategory("Type", labels, this) + appendLabelCategory("State", labels, this) + appendLabelCategory("Milestone", labels, this, pr.milestone?.let { " `${it.title}`" } ?: "") } else { val merged = passedSince(pr.mergedAt ?: "") append("> Merged: $merged") @@ -128,6 +118,7 @@ object PullRequestCommand : BaseCommand() { } catch (e: Exception) { null } + val lastRelease = releases?.firstOrNull() if (releaseSinceMerge(pr.mergedAt ?: "", lastRelease?.publishedAt ?: "")) { @@ -141,6 +132,12 @@ object PullRequestCommand : BaseCommand() { } } + if (toTimeMark(pr.updatedAt).passedSince() > 400.days && !inBeta) { + val text = "${title}${time} \nBuild download has expired $PLEADING_FACE" + reply(embed(embedTitle, text, readColor(pr))) + return + } + val lastCommit = head.sha val job = github.getRun(lastCommit, "Build and test") ?: run { @@ -149,16 +146,15 @@ object PullRequestCommand : BaseCommand() { append(time) if (!inBeta) { append("\n") - append("Artifact does not exist $PLEADING_FACE (expired or first pr of contributor)") + append("Build needs approval $PLEADING_FACE") } } - val text = "${title}${time} \nBuild needs approval $PLEADING_FACE" reply(embed(embedTitle, text, readColor(pr))) return } - if (job.startedAt?.let { toTimeMark(it).passedSince() > 90.days } == true) { + if (job.startedAt?.let { toTimeMark(it).passedSince() > 90.days } == true && !inBeta) { reply(embed(embedTitle, "${title}${time} \nBuild download has expired $PLEADING_FACE", readColor(pr))) return @@ -187,7 +183,7 @@ object PullRequestCommand : BaseCommand() { return } - if (job.conclusion != Conclusion.SUCCESS) { + if (job.conclusion != Conclusion.SUCCESS && !inBeta) { reply(embed(embedTitle, "$title$time\nLast development build failed $PLEADING_FACE", Color.red)) return } @@ -195,8 +191,8 @@ object PullRequestCommand : BaseCommand() { val match = job.htmlUrl?.let { runIdRegex.matchEntire(it) } val runId = match?.groups?.get("RunId")?.value - val artifactLink = "$base/actions/runs/$runId?pr=$prNumber" - val nightlyLink = "https://nightly.link/$user/$repo/actions/runs/$runId/Development%20Build.zip" + val artifactLink = "$BASE/actions/runs/$runId?pr=$prNumber" + val nightlyLink = "https://nightly.link/$USER/$REPO/actions/runs/$runId/Development%20Build.zip" val artifactLine = "GitHub".linkTo(artifactLink) val nightlyLine = "Nightly".linkTo(nightlyLink) From 9cfcfa33ce58dfe529db45dd883c26c68309a5f7 Mon Sep 17 00:00:00 2001 From: DavidArthurCole Date: Wed, 19 Mar 2025 21:43:42 -0400 Subject: [PATCH 6/8] Done --- .../skyhanni/discord/CommandListener.kt | 6 +- .../discord/command/PullRequestCommand.kt | 186 ++++++++++-------- .../discord/command/RepoPullRequestCommand.kt | 14 ++ 3 files changed, 121 insertions(+), 85 deletions(-) create mode 100644 src/main/kotlin/at/hannibal2/skyhanni/discord/command/RepoPullRequestCommand.kt diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt index 17d66a4..f7a6ee2 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/CommandListener.kt @@ -7,6 +7,7 @@ import at.hannibal2.skyhanni.discord.Utils.reply import at.hannibal2.skyhanni.discord.command.BaseCommand import at.hannibal2.skyhanni.discord.command.HelpCommand import at.hannibal2.skyhanni.discord.command.PullRequestCommand +import at.hannibal2.skyhanni.discord.command.RepoPullRequestCommand import at.hannibal2.skyhanni.discord.command.ServerCommands import at.hannibal2.skyhanni.discord.command.TagCommands import at.hannibal2.skyhanni.discord.command.TagUndo @@ -17,6 +18,8 @@ import java.lang.reflect.Modifier object CommandListener { private const val BOT_ID = "1343351725381128193" + private val pullRequestCommand: PullRequestCommand = PullRequestCommand() + var commands = listOf() private set private var commandsMap = mapOf() @@ -49,7 +52,8 @@ object CommandListener { } if (ServerCommands.isKnownServerUrl(this, message)) return - if (PullRequestCommand.isPullRequest(this, message)) return + if (pullRequestCommand.isPullRequest(this, message)) return + if (RepoPullRequestCommand.isPullRequest(this, message)) return if (!isCommand(message)) return diff --git a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt index d64c3be..0763dea 100644 --- a/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt +++ b/src/main/kotlin/at/hannibal2/skyhanni/discord/command/PullRequestCommand.kt @@ -26,26 +26,25 @@ import java.time.format.DateTimeFormatter import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.seconds -object PullRequestCommand : BaseCommand() { +open class PullRequestCommand : BaseCommand() { - override val name: String = "pr" + open val disableBuildInfo: Boolean = false + open val repo get() = "SkyHanni" + private val user get() = "hannibal002" + private val base get() = "https://github.com/$user/$repo" + + private val pullRequestPattern = "$base/pull/(?\\d+)".toPattern() + private val github by lazy { GitHubClient(user, repo, BOT.config.githubToken) } + private val runIdRegex = + Regex("https://github\\.com/[\\w.]+/[\\w.]+/actions/runs/(?\\d+)/job/(?\\d+)") + override val name: String = "pr" override val description: String = "Displays useful information about a pull request on Github." override val options: List