Skip to content

Commit 4a689d3

Browse files
committed
💥 Fix not removing webhook after the user is kicked from the guild
1 parent 6ccd78d commit 4a689d3

File tree

7 files changed

+80
-25
lines changed

7 files changed

+80
-25
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = "net.azisaba"
8-
version = "1.0.0"
8+
version = "2.0.0"
99

1010
repositories {
1111
mavenCentral()

src/main/kotlin/net/azisaba/guildchatdiscord/InterChatPacketListener.kt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,45 @@ object InterChatPacketListener : PacketListener {
3939
)
4040
val formattedComponent = LEGACY_COMPONENT_SERIALIZER.deserialize(formattedText)
4141
val plainText = PLAIN_TEXT_COMPONENT_SERIALIZER.serialize(formattedComponent)
42-
DatabaseManager.getWebhooksByGuildId(packet.guildId()).forEach { (webhookId, webhookToken) ->
42+
val members = guild.members.join()
43+
DatabaseManager.getWebhooksByGuildId(packet.guildId()).forEach { info ->
4344
InterChatDiscord.asyncExecutor.execute {
4445
runBlocking {
46+
// make sure that member is still in the guild
47+
val uuid = DatabaseManager.getMinecraftUUIDByDiscordId(info.createdUserId)
48+
if (uuid == null || members.all { it.uuid() != uuid }) {
49+
// delete webhook
50+
InterChatDiscord.logger.info("Removing webhook ${info.webhookId} because the user is not in the guild")
51+
try {
52+
restClient.webhook.deleteWebhook(
53+
Snowflake(info.webhookId.toULong()),
54+
"${info.createdUserId} was removed from the guild"
55+
)
56+
} catch (e: Exception) {
57+
InterChatDiscord.logger.info("Failed to delete webhook", e)
58+
}
59+
DatabaseManager.query("DELETE FROM `channels` WHERE `webhook_id` = ?") {
60+
it.setLong(1, info.webhookId)
61+
it.executeUpdate()
62+
}
63+
DatabaseManager.getWebhooksByGuildId.forget(packet.guildId())
64+
return@runBlocking
65+
}
66+
// execute webhook
4567
try {
46-
restClient.webhook.executeWebhook(Snowflake(webhookId.toULong()), webhookToken) {
68+
restClient.webhook.executeWebhook(Snowflake(info.webhookId.toULong()), info.webhookToken) {
4769
content = plainText
4870
allowedMentions = AllowedMentionsBuilder()
4971
}
5072
} catch (e: RestRequestException) {
51-
if (e.status.code == 404) {
52-
// webhook is gone
73+
if (e.status.code == 404 || e.status.code == 403) {
74+
// invalid webhook url?
75+
InterChatDiscord.logger.info("Removing webhook ${info.webhookId} because it is invalid (404 or 403)")
5376
DatabaseManager.query("DELETE FROM `channels` WHERE `webhook_id` = ?") {
54-
it.setLong(1, webhookId)
77+
it.setLong(1, info.webhookId)
5578
it.executeUpdate()
5679
}
80+
DatabaseManager.getWebhooksByGuildId.forget(packet.guildId())
5781
}
5882
}
5983
}

src/main/kotlin/net/azisaba/guildchatdiscord/commands/ConnectCommand.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package net.azisaba.guildchatdiscord.commands
22

33
import dev.kord.common.entity.Permission
44
import dev.kord.common.entity.Permissions
5-
import dev.kord.core.behavior.interaction.respondEphemeral
6-
import dev.kord.core.behavior.interaction.respondPublic
5+
import dev.kord.core.behavior.interaction.response.respond
76
import dev.kord.core.entity.channel.TopGuildChannel
87
import dev.kord.core.entity.interaction.ApplicationCommandInteraction
98
import dev.kord.rest.builder.interaction.GlobalMultiApplicationCommandBuilder
@@ -16,9 +15,10 @@ import java.util.UUID
1615

1716
object ConnectCommand : CommandHandler {
1817
override suspend fun handle0(interaction: ApplicationCommandInteraction) {
18+
val defer = interaction.deferPublicResponse()
1919
val channel = interaction.channel.asChannel() as TopGuildChannel
2020
if (!channel.getEffectivePermissions(interaction.kord.selfId).contains(Permission.ManageWebhooks)) {
21-
interaction.respondEphemeral { content = "BotがこのチャンネルにWebhookを作成する権限がありません。" }
21+
defer.respond { content = "BotがこのチャンネルにWebhookを作成する権限がありません。" }
2222
return
2323
}
2424
val minecraftUuid = DatabaseManager.query("SELECT `minecraft_uuid` FROM `users` WHERE `discord_id` = ?") {
@@ -32,18 +32,18 @@ object ConnectCommand : CommandHandler {
3232
}
3333
}
3434
if (minecraftUuid == null) {
35-
interaction.respondEphemeral { content = "Minecraftアカウントと連携されていません。" }
35+
defer.respond { content = "Minecraftアカウントと連携されていません。" }
3636
return
3737
}
3838
val user: User = InterChatDiscord.userManager.fetchUser(minecraftUuid).join()
3939
val guildName = interaction.optString("guild")!!
4040
val guild = InterChatDiscord.guildManager.fetchGuildByName(guildName).exceptionally { null }.join()
4141
if (guild == null || guild.deleted()) {
42-
interaction.respondEphemeral { content = "ギルド`$guildName`に参加していません。" }
42+
defer.respond { content = "ギルド`$guildName`に参加していません。" }
4343
return
4444
}
4545
if (guild.getMember(user).exceptionally { null }.join() == null) {
46-
interaction.respondEphemeral { content = "ギルド`$guildName`に参加していません。" }
46+
defer.respond { content = "ギルド`$guildName`に参加していません。" }
4747
return
4848
}
4949
val linkedGuildId = DatabaseManager.query("SELECT `guild_id` FROM `channels` WHERE `channel_id` = ?") {
@@ -59,25 +59,28 @@ object ConnectCommand : CommandHandler {
5959
if (linkedGuildId != -1L) {
6060
val linkedGuild = InterChatDiscord.guildManager.fetchGuildById(linkedGuildId).exceptionally { null }.join()
6161
if (linkedGuild != null && !linkedGuild.deleted()) {
62-
interaction.respondEphemeral {
62+
defer.respond {
6363
content = "このチャンネルは既にギルド`${linkedGuild.name()}`と連携されています。\n`/unconnect`で連携を解除できます。"
6464
}
6565
} else {
66-
interaction.respondEphemeral { content = "`/unconnect`を実行して連携を解除してからもう一度お試しください。" }
66+
defer.respond { content = "`/unconnect`を実行して連携を解除してからもう一度お試しください。" }
6767
}
6868
return
6969
}
7070
val webhook = channel.kord.rest.webhook.createWebhook(channel.id, "ギルドチャット (${guild.name()})") {
7171
reason = "/connect command from ${interaction.user.tag} (${interaction.user.id})"
7272
}
73-
DatabaseManager.query("INSERT INTO `channels` (`guild_id`, `channel_id`, `webhook_id`, `webhook_token`) VALUES (?, ?, ?, ?)") {
73+
DatabaseManager.query("INSERT INTO `channels` (`guild_id`, `channel_id`, `webhook_id`, `webhook_token`, `created_user_id`) VALUES (?, ?, ?, ?, ?)") {
7474
it.setString(1, guild.id().toString())
7575
it.setLong(2, channel.id.value.toLong())
7676
it.setLong(3, webhook.id.value.toLong())
7777
it.setString(4, webhook.token.value)
78+
it.setLong(5, interaction.user.id.value.toLong())
7879
it.executeUpdate()
7980
}
80-
interaction.respondPublic { content = "ギルドチャットを`${guild.name()}`と連携しました。\nメッセージが受信できるようになるまで最大60秒程度かかります。" }
81+
DatabaseManager.getGuildIdByChannelId.forget(channel.id.value.toLong())
82+
DatabaseManager.getWebhooksByGuildId.forget(guild.id())
83+
defer.respond { content = "ギルドチャットを`${guild.name()}`と連携しました。\nメッセージが受信できるようになるまで最大60秒程度かかります。" }
8184
}
8285

8386
override fun register(builder: GlobalMultiApplicationCommandBuilder) {

src/main/kotlin/net/azisaba/guildchatdiscord/commands/LinkCommand.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ object LinkCommand : CommandHandler {
4545
stmt.setString(2, code)
4646
stmt.executeUpdate()
4747
}
48+
DatabaseManager.getMinecraftUUIDByDiscordId.forget(interaction.user.id.value.toLong())
4849
interaction.respondEphemeral { content = "`$name` (`$uuid`)と連携しました。" }
4950
}
5051

src/main/kotlin/net/azisaba/guildchatdiscord/util/DatabaseManager.kt

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,30 @@ object DatabaseManager {
2929
`channel_id` BIGINT NOT NULL UNIQUE,
3030
`webhook_id` BIGINT NOT NULL,
3131
`webhook_token` VARCHAR(128) NOT NULL,
32+
`created_user_id` BIGINT NOT NULL,
3233
UNIQUE KEY `webhook` (`webhook_id`, `webhook_token`)
3334
)
3435
""".trimIndent()) { it.executeUpdate() }
3536
}
3637

37-
val getWebhooksByGuildId = Functions.memoize<Long, List<Pair<Long, String>>>(1000 * 60) { guildId ->
38-
query("SELECT `webhook_id`, `webhook_token` FROM `channels` WHERE `guild_id` = ?") {
38+
val getWebhooksByGuildId = Functions.memoize<Long, List<WebhookInfo>>(1000 * 60) { guildId ->
39+
query("SELECT `webhook_id`, `webhook_token`, `created_user_id` FROM `channels` WHERE `guild_id` = ?") {
3940
it.setLong(1, guildId)
4041
it.executeQuery().use { rs ->
41-
val list = mutableListOf<Pair<Long, String>>()
42+
val list = mutableListOf<WebhookInfo>()
4243
while (rs.next()) {
43-
list.add(rs.getLong("webhook_id") to rs.getString("webhook_token"))
44+
list += WebhookInfo(
45+
rs.getLong("webhook_id"),
46+
rs.getString("webhook_token"),
47+
rs.getLong("created_user_id"),
48+
)
4449
}
4550
list
4651
}
4752
}
48-
}.toKotlin()
53+
}.toMemoizeFunction<Long, List<WebhookInfo>>()
4954

50-
val getGuildIdByChannelId: (Long) -> Long? = Functions.memoize<Long, Long>(1000 * 60) { channelId ->
55+
val getGuildIdByChannelId = Functions.memoize<Long, Long>(1000 * 60) { channelId ->
5156
query("SELECT `guild_id` FROM `channels` WHERE `channel_id` = ?") {
5257
it.setLong(1, channelId)
5358
it.executeQuery().use { rs ->
@@ -58,9 +63,9 @@ object DatabaseManager {
5863
}
5964
}
6065
}
61-
}.toKotlin()
66+
}.toMemoizeFunction<Long, Long?>()
6267

63-
val getMinecraftUUIDByDiscordId: (Long) -> UUID? = Functions.memoize<Long, UUID>(1000 * 30) { discordId ->
68+
val getMinecraftUUIDByDiscordId = Functions.memoize<Long, UUID>(1000 * 30) { discordId ->
6469
query("SELECT `minecraft_uuid` FROM `users` WHERE `discord_id` = ?") {
6570
it.setLong(1, discordId)
6671
it.executeQuery().use { rs ->
@@ -71,7 +76,7 @@ object DatabaseManager {
7176
}
7277
}
7378
}
74-
}.toKotlin()
79+
}.toMemoizeFunction<Long, UUID?>()
7580

7681
inline fun <R> query(@Language("SQL") sql: String, block: (PreparedStatement) -> R): R =
7782
dataSource.connection.use { connection ->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
11
package net.azisaba.guildchatdiscord.util
22

33
fun <T, R> java.util.function.Function<T, R>.toKotlin(): Function1<T, R> = this::apply
4+
5+
fun <T, R> java.util.function.Function<T, R>.toMemoizeFunction(): MemoizeFunction<T, R> = MemoizeFunction(this)
6+
7+
class MemoizeFunction<T, R>(private val f: java.util.function.Function<T, R>) : (T) -> R {
8+
override operator fun invoke(t: T): R = f.apply(t)
9+
10+
fun forgetAll() = getMap().clear()
11+
12+
fun forget(key: T) = getMap().remove(key)
13+
14+
private fun getMap(): MutableMap<T, *> {
15+
@Suppress("UNCHECKED_CAST")
16+
return f::class.java.getDeclaredField("cache").apply { isAccessible = true }[f] as MutableMap<T, *>
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package net.azisaba.guildchatdiscord.util
2+
3+
data class WebhookInfo(
4+
val webhookId: Long,
5+
val webhookToken: String,
6+
val createdUserId: Long,
7+
)

0 commit comments

Comments
 (0)