Skip to content

Commit

Permalink
Allow users to disable their IRC forwarding per-guild
Browse files Browse the repository at this point in the history
Intended for use with PluralKit
  • Loading branch information
randomnetcat committed Nov 19, 2023
1 parent 5882412 commit a14f033
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 5 deletions.
72 changes: 72 additions & 0 deletions src/main/kotlin/org/randomcat/agorabot/commands/RelayCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.randomcat.agorabot.commands

import org.randomcat.agorabot.commands.base.BaseCommand
import org.randomcat.agorabot.commands.base.BaseCommandImplReceiver
import org.randomcat.agorabot.commands.base.BaseCommandStrategy
import org.randomcat.agorabot.commands.base.help.help
import org.randomcat.agorabot.commands.base.requirements.discord.InGuildSimple
import org.randomcat.agorabot.commands.base.requirements.discord.currentGuildId
import org.randomcat.agorabot.commands.base.requirements.discord.currentMessageEvent
import org.randomcat.agorabot.commands.base.requires
import org.randomcat.agorabot.guild_state.UserState
import org.randomcat.agorabot.guild_state.UserStateMap
import org.randomcat.agorabot.guild_state.update
import org.randomcat.agorabot.irc.RELAY_USER_STATE_KEY
import org.randomcat.agorabot.irc.RelayUserState

private fun UserState.updateRelayState(mapper: (RelayUserState) -> RelayUserState) {
update<RelayUserState>(RELAY_USER_STATE_KEY) {
mapper(it ?: RelayUserState.DEFAULT)
}
}

class RelayCommand(
strategy: BaseCommandStrategy,
private val userStateMap: UserStateMap,
) : BaseCommand(strategy) {
override fun BaseCommandImplReceiver.impl() {
subcommands {
subcommand("self") {
subcommand("disable") {
noArgs().help("Disables forwarding of messages from this account in this Guild")
.requires(InGuildSimple) {
val guildId = currentGuildId

userStateMap.stateForUser(currentMessageEvent.author.id).updateRelayState { old ->
when (old) {
is RelayUserState.Version0 -> {
RelayUserState.Version0(disabledGuilds = buildSet {
addAll(old.disabledGuilds)
add(guildId)
})
}
}
}

respond("Disabled relay forwarding for your account in this Guild")
}
}

subcommand("enable") {
noArgs().help("Enables forwarding of messages from this account in this Guild")
.requires(InGuildSimple) {
val guildId = currentGuildId

userStateMap.stateForUser(currentMessageEvent.author.id).updateRelayState { old ->
when (old) {
is RelayUserState.Version0 -> {
RelayUserState.Version0(disabledGuilds = buildSet {
addAll(old.disabledGuilds)
remove(guildId)
})
}
}
}

respond("Enabled relay forwarding for your account in this Guild")
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package org.randomcat.agorabot.features
import kotlinx.collections.immutable.toPersistentList
import net.dv8tion.jda.api.JDA
import org.randomcat.agorabot.*
import org.randomcat.agorabot.commands.RelayCommand
import org.randomcat.agorabot.config.CommandOutputMappingTag
import org.randomcat.agorabot.config.RelayConnectedEndpointMapTag
import org.randomcat.agorabot.guild_state.feature.UserStateStorageTag
import org.randomcat.agorabot.irc.*

private fun ircAndDiscordMapping(
Expand Down Expand Up @@ -70,14 +72,15 @@ private fun ircAndDiscordMapping(
private val ircDep = FeatureDependency.AtMostOne(IrcSetupTag)
private val jdaDep = FeatureDependency.Single(JdaTag)
private val coroutineScopeDep = FeatureDependency.Single(CoroutineScopeTag)
private val userStateMapDep = FeatureDependency.Single(UserStateStorageTag)

@FeatureSourceFactory
fun relayProviderFeatureSource(): FeatureSource<*> = object : FeatureSource.NoConfig {
override val featureName: String
get() = "relay_data_provider"

override val dependencies: List<FeatureDependency<*>>
get() = listOf(ircDep, jdaDep, coroutineScopeDep)
get() = listOf(ircDep, jdaDep, coroutineScopeDep, userStateMapDep)

override val provides: List<FeatureElementTag<*>>
get() = listOf(CommandOutputMappingTag, RelayConnectedEndpointMapTag)
Expand All @@ -86,6 +89,7 @@ fun relayProviderFeatureSource(): FeatureSource<*> = object : FeatureSource.NoCo
val jda = context[jdaDep]
val ircConfig = context[ircDep]
val coroutineScope = context[coroutineScopeDep]
val userStateMap = context[userStateMapDep]

if (ircConfig != null) {
val relayConnectedEndpointMap = connectToRelayEndpoints(
Expand All @@ -94,6 +98,7 @@ fun relayProviderFeatureSource(): FeatureSource<*> = object : FeatureSource.NoCo
ircClientMap = ircConfig.clients,
jda = jda,
coroutineScope = coroutineScope,
userStateMap = userStateMap,
),
)

Expand Down Expand Up @@ -123,3 +128,17 @@ fun relayProviderFeatureSource(): FeatureSource<*> = object : FeatureSource.NoCo
}
}
}

@FeatureSourceFactory
fun relayCommandsSource(): FeatureSource<*> = FeatureSource.ofBaseCommands(
name = "relay_commands",
extraDependencies = listOf(userStateMapDep),
block = { strategy, context ->
mapOf(
"relay" to RelayCommand(
strategy,
userStateMap = context[userStateMapDep],
)
)
},
)
37 changes: 35 additions & 2 deletions src/main/kotlin/org/randomcat/agorabot/irc/DiscordEndpoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.SubscribeEvent
import net.dv8tion.jda.api.utils.SplitUtil
import org.randomcat.agorabot.CommandOutputSink
import org.randomcat.agorabot.guild_state.UserStateMap
import org.randomcat.agorabot.guild_state.get
import org.randomcat.agorabot.util.DiscordMessage
import org.randomcat.agorabot.util.await
import org.randomcat.agorabot.util.disallowMentions
Expand All @@ -21,9 +23,28 @@ private fun formatRawNameForDiscord(name: String): String {

private val logger = LoggerFactory.getLogger("RelayDiscord")

private fun shouldRelayMessage(
userStateMap: UserStateMap,
guildId: String,
userId: String,
content: String,
): Boolean {
// Disable ignoring if message starts with a backslash
if (content.startsWith("\\")) return true

return when (val state = userStateMap.stateForUser(userId).get<RelayUserState>(RELAY_USER_STATE_KEY)) {
is RelayUserState.Version0 -> {
!state.disabledGuilds.contains(guildId)
}

null -> true
}
}

private fun addDiscordRelay(
jda: JDA,
coroutineScope: CoroutineScope,
userStateMap: UserStateMap,
channelId: String,
endpoints: List<RelayConnectedEndpoint>,
) {
Expand All @@ -37,6 +58,16 @@ private fun addDiscordRelay(
if (event.channel.id != channelId) return
if (event.author.id == event.jda.selfUser.id) return

if (!shouldRelayMessage(
userStateMap = userStateMap,
guildId = event.guild.id,
userId = event.author.id,
content = event.message.contentRaw,
)
) {
return
}

coroutineScope.launch {
forEachEndpoint {
launch {
Expand All @@ -55,6 +86,7 @@ private fun addDiscordRelay(
data class RelayConnectedDiscordEndpoint(
val jda: JDA,
val coroutineScope: CoroutineScope,
val userStateMap: UserStateMap,
val channelId: String,
) : RelayConnectedEndpoint() {
companion object {
Expand Down Expand Up @@ -99,14 +131,14 @@ data class RelayConnectedDiscordEndpoint(
val replySection = if (referencedMessage != null) {
val replyName = referencedMessage.retrieveEffectiveSenderName().await()

"In reply to ${formatRawNameForDiscord(replyName)} saying: ${referencedMessage.contentRaw}\n"
"In reply to ${formatRawNameForDiscord(replyName)} saying: ${formatRelayDiscordContent(referencedMessage.contentRaw)}\n"
} else {
""
}

val textSection = run {
val saysVerb = if (referencedMessage != null) "replies" else "says"
"${formatRawNameForDiscord(senderName)} $saysVerb: ${message.contentRaw}"
"${formatRawNameForDiscord(senderName)} $saysVerb: ${formatRelayDiscordContent(message.contentRaw)}"
}

val attachmentsSection = if (message.attachments.isNotEmpty()) {
Expand All @@ -129,6 +161,7 @@ data class RelayConnectedDiscordEndpoint(
addDiscordRelay(
jda = jda,
coroutineScope = coroutineScope,
userStateMap = userStateMap,
channelId = channelId,
endpoints = otherEndpoints,
)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/randomcat/agorabot/irc/IrcEndpoint.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private suspend fun IrcChannel.sendDiscordMessage(message: DiscordMessage) {
val replySection = run {
if (referencedMessage != null) {
val replyName = referencedMessage.retrieveEffectiveSenderName().await()
"In response to $replyName saying: ${referencedMessage.contentRaw}\n"
"In response to $replyName saying: ${formatRelayDiscordContent(referencedMessage.contentRaw)}\n"
} else {
""
}
Expand All @@ -44,7 +44,7 @@ private suspend fun IrcChannel.sendDiscordMessage(message: DiscordMessage) {
?: ""

val saysVerb = if (referencedMessage != null) "replies" else "says"
val textSection = "$senderName $saysVerb: ${message.contentRaw}"
val textSection = "$senderName $saysVerb: ${formatRelayDiscordContent(message.contentRaw)}"

val fullMessage = replySection + textSection + attachmentSection

Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/org/randomcat/agorabot/irc/IrcRelay.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import net.dv8tion.jda.api.JDA
import org.randomcat.agorabot.CommandOutputSink
import org.randomcat.agorabot.guild_state.UserStateMap
import org.randomcat.agorabot.listener.CommandRegistry
import org.randomcat.agorabot.util.DiscordMessage
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -37,6 +38,7 @@ data class RelayConnectionContext(
val ircClientMap: IrcClientMap,
val jda: JDA,
val coroutineScope: CoroutineScope,
val userStateMap: UserStateMap,
)

data class RelayConnectedEndpointMap(
Expand All @@ -62,6 +64,7 @@ fun connectToRelayEndpoints(
RelayConnectedDiscordEndpoint(
jda = context.jda,
coroutineScope = context.coroutineScope,
userStateMap = context.userStateMap,
channelId = config.channelId,
)
}
Expand Down Expand Up @@ -110,3 +113,8 @@ fun initializeIrcRelay(
}
}
}

fun formatRelayDiscordContent(content: String): String {
// Remove backslash used to indicate that message should always be forwarded
return content.removePrefix("\\")
}
19 changes: 19 additions & 0 deletions src/main/kotlin/org/randomcat/agorabot/irc/State.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.randomcat.agorabot.irc

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

const val RELAY_USER_STATE_KEY = "relay"

@Serializable
sealed class RelayUserState {
companion object {
val DEFAULT = Version0()
}

@Serializable
@SerialName("RelayUserStateV0")
data class Version0(
val disabledGuilds: Set<String> = setOf(),
) : RelayUserState()
}

0 comments on commit a14f033

Please sign in to comment.