Skip to content

Commit

Permalink
feat: non recursive replacers (#109)
Browse files Browse the repository at this point in the history
* feat: non recursive replacers

* fix: wrong index

* fix: accurate comment

* feat: parse command

* feat: allow alias search update

* feat: distinguish system messages
  • Loading branch information
yuuahp authored Jun 9, 2024
1 parent 7a20304 commit 1b29ef1
Show file tree
Hide file tree
Showing 21 changed files with 437 additions and 107 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ data/
logs/

config.yml

store-test/
2 changes: 2 additions & 0 deletions src/main/kotlin/com/jaoafa/vcspeaker/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class Main : CliktCommand() {
override fun run() {
logger.info { "Starting VCSpeaker..." }

logger.info { "Reading config: $configPath" }

// Options > Config > Default
val config = Config {
addSpec(TokenSpec)
Expand Down
36 changes: 26 additions & 10 deletions src/main/kotlin/com/jaoafa/vcspeaker/commands/AliasCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AliasCommand : Extension() {
}

inner class UpdateOptions : Options() {
val search by string {
val alias by string {
name = "alias"
description = "更新するエイリアス"

Expand All @@ -61,6 +61,11 @@ class AliasCommand : Extension() {
choice(aliasType.displayName, aliasType.name)
}

val search by optionalString {
name = "search"
description = "置き換える条件"
}

val replace by optionalString {
name = "replace"
description = "置き換える文字列"
Expand Down Expand Up @@ -121,32 +126,44 @@ class AliasCommand : Extension() {

publicSubCommand("update", "エイリアスを更新します。", ::UpdateOptions) {
action {
val aliasData = AliasStore.find(guild!!.id, arguments.search)
val aliasData = AliasStore.find(guild!!.id, arguments.alias)
if (aliasData != null) {
val (_, _, type, search, replace) = aliasData

val updatedType = arguments.type?.let { typeString -> AliasType.valueOf(typeString) } ?: type
val updatedSearch = arguments.search ?: search
val updatedReplace = arguments.replace ?: replace

AliasStore.remove(aliasData)
AliasStore.create(
aliasData.copy(
userId = user.id,
type = updatedType,
search = updatedSearch,
replace = updatedReplace
)
)

respondEmbed(
":repeat: Alias Updated",
"${type.displayName}のエイリアスを更新しました"
"エイリアスを更新しました"
) {
authorOf(user)

fieldAliasFrom(updatedType, search)
fun searchDisplay(type: AliasType, search: String) = when (type) {
AliasType.Text -> search
AliasType.Regex -> "`$search`"
AliasType.Emoji -> "$search `$search`"
}

field("${updatedType.emoji} ${updatedType.displayName}", true) {
searchDisplay(type, search) + if (replace != updatedReplace)
" → **${searchDisplay(updatedType, updatedSearch)}**"
else ""
}

field(":arrows_counterclockwise: 置き換える文字列", true) {
"$replace**${updatedReplace}**"
if (replace != updatedReplace) "$replace」→「**$updatedReplace**」" else "$replace"
}

successColor()
Expand All @@ -158,14 +175,14 @@ class AliasCommand : Extension() {
} else {
respondEmbed(
":question: Alias Not Found",
"置き換え条件が「${arguments.search}」のエイリアスは見つかりませんでした。"
"置き換え条件が「${arguments.alias}」のエイリアスは見つかりませんでした。"
) {
authorOf(user)
errorColor()
}

log(logger) { guild, user ->
"[${guild.name}] Alias Not Found: @${user.username} searched for alias contains \"${arguments.search}\" but not found"
"[${guild.name}] Alias Not Found: @${user.username} searched for alias contains \"${arguments.alias}\" but not found"
}
}
}
Expand Down Expand Up @@ -238,9 +255,8 @@ class AliasCommand : Extension() {

title = ":information_source: Aliases"

description = chunkedAliases.joinToString("\n") { (_, userId, type, from, to) ->
val fromDisplay = if (type == AliasType.Regex) "`$from`" else from
"${type.emoji} ${type.displayName} | 「$fromDisplay$to」 | <@${userId}>"
description = chunkedAliases.joinToString("\n") {
it.toDisplayWithEmoji()
}

successColor()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class IgnoreCommand : Extension() {
val guildId = event.interaction.getChannel().data.guildId.value

suggestStringCollection(
IgnoreStore.filter(guildId).map { it.text },
IgnoreStore.filter(guildId).map { it.search },
FilterStrategy.Contains
)
}
Expand Down Expand Up @@ -144,8 +144,8 @@ class IgnoreCommand : Extension() {

title = ":information_source: Ignores"

description = chunkedIgnores.joinToString("\n") { (_, userId, type, text) ->
"${type.emoji} ${type.displayName} | 「$text」 | <@${userId}>"
description = chunkedIgnores.joinToString("\n") {
it.toDisplayWithEmoji()
}

successColor()
Expand Down
176 changes: 176 additions & 0 deletions src/main/kotlin/com/jaoafa/vcspeaker/commands/ParseCommand.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.jaoafa.vcspeaker.commands

import com.jaoafa.vcspeaker.stores.IgnoreStore
import com.jaoafa.vcspeaker.stores.IgnoreType
import com.jaoafa.vcspeaker.tools.Emoji.containsEmojis
import com.jaoafa.vcspeaker.tools.Emoji.getEmojis
import com.jaoafa.vcspeaker.tools.discord.DiscordExtensions.errorColor
import com.jaoafa.vcspeaker.tools.discord.DiscordExtensions.respondEmbed
import com.jaoafa.vcspeaker.tools.discord.DiscordExtensions.successColor
import com.jaoafa.vcspeaker.tools.discord.Options
import com.jaoafa.vcspeaker.tools.discord.SlashCommandExtensions.publicSlashCommand
import com.jaoafa.vcspeaker.tts.TextProcessor
import com.jaoafa.vcspeaker.tts.Token
import com.kotlindiscord.kord.extensions.checks.anyGuild
import com.kotlindiscord.kord.extensions.commands.converters.impl.string
import com.kotlindiscord.kord.extensions.extensions.Extension

class ParseCommand : Extension() {
override val name = this::class.simpleName!!

inner class ParseOptions : Options() {
val text by string {
name = "message"
description = "試すメッセージ"
}
}

override suspend fun setup() {
publicSlashCommand("parse", "読み上げる文章の処理をテストします", ::ParseOptions) {
check { anyGuild() }
action {
val guildId = guild!!.id
val text = arguments.text

fun effectiveIgnores(text: String) = IgnoreStore.filter(guildId).filter {
when (it.type) {
IgnoreType.Equals -> text == it.search
IgnoreType.Contains -> text.contains(it.search)
}
}

val effectiveIgnores = effectiveIgnores(text)

suspend fun respondStepEmbed(
checkIgnore: String? = null,
applyAlias: String? = null,
recheckIgnore: String? = null,
replaceEmoji: String? = null,
result: String,
ignored: Boolean
) = respondEmbed(
":alembic: Text Parsed"
) {
field(":a: 入力") {
"$text"
}

fun stepField(step: Int, title: String, text: String?) {
val emojis = listOf(":one:", ":two:", ":three:", ":four:")
val emoji = if (text != null) emojis.getOrNull(step - 1) else ":white_large_square:"

field("$emoji $title") {
text ?: "*スキップされました。"
}
}

stepField(
step = 1,
title = "無視するか確認",
text = checkIgnore
)

stepField(
step = 2,
title = "エイリアスを適用",
text = applyAlias
)

stepField(
step = 3,
title = "無視するか再確認",
text = recheckIgnore
)

stepField(
step = 4,
title = "Unicode 絵文字を置き換え",
text = replaceEmoji
)

field(":white_check_mark: 結果") {
result
}

if (ignored) errorColor() else successColor()
}

// step 1: check ignore
if (effectiveIgnores.isNotEmpty()) {
respondStepEmbed(
checkIgnore = effectiveIgnores.joinToString("\n") {
it.toDisplay()
},
result = "*無視されました。",
ignored = true
)

return@action
}

// step 2: apply alias
val tokens = TextProcessor.replacers.fold(mutableListOf(Token(text))) { tokens, replacer ->
replacer.replace(tokens, guildId)
}

// annotate text with alias index
var aliasIndex = 1
val annotatedText = tokens.joinToString("") {
it.text + if (it.replaced()) {
val annotation = " `[$aliasIndex]` "
aliasIndex++
annotation
} else ""
}

// step 3: recheck ignore
val annotatedEffectiveIgnores = effectiveIgnores(annotatedText)

val replacedTokens = tokens.filter { it.replaced() }

val replaceResult = "$annotatedText\n" + replacedTokens.withIndex()
.joinToString("\n") { (i, token) -> "$i. ${token.replacer}" }

if (annotatedEffectiveIgnores.isNotEmpty()) {
respondStepEmbed(
checkIgnore = "*無視されませんでした。",
applyAlias = replaceResult,
recheckIgnore = annotatedEffectiveIgnores.joinToString("\n") {
it.toDisplay()
},
result = "*無視されました。",
ignored = true
)

return@action
}

// step 4: replace emoji
val appliedText = tokens.joinToString("") { it.text }
val emojis = appliedText.getEmojis()

var emojiIndex = 1
val (annotatedAppliedText, result) = emojis.fold(appliedText to appliedText) { (annotated, result), emoji ->
val newAnnotated = annotated.replace(emoji.unicode, "${emoji.name} `[$emojiIndex]`")
val newResult = result.replace(emoji.unicode, emoji.name)

emojiIndex++

newAnnotated to newResult
}

val emojiReplaceResult = "$annotatedAppliedText\n" + emojis
.withIndex().joinToString("\n") { (i, emoji) -> "$i. ${emoji.unicode}${emoji.name}" }

respondStepEmbed(
checkIgnore = "*無視されませんでした。",
applyAlias = if (replacedTokens.isNotEmpty()) replaceResult else "*置き換えられませんでした。",
recheckIgnore = "*無視されませんでした。",
replaceEmoji = if (appliedText.containsEmojis()) emojiReplaceResult else "*絵文字は含まれていませんでした。",
result = "$result",
ignored = false
)
}
}
}
}
8 changes: 4 additions & 4 deletions src/main/kotlin/com/jaoafa/vcspeaker/features/Alias.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ object Alias {
)
}

fun EmbedBuilder.fieldAliasFrom(type: AliasType, from: String) =
fun EmbedBuilder.fieldAliasFrom(type: AliasType, search: String) =
this.field("${type.emoji} ${type.displayName}", true) {
when (type) {
AliasType.Text -> from
AliasType.Regex -> "`$from`"
AliasType.Emoji -> "$from `$from`"
AliasType.Text -> search
AliasType.Regex -> "`$search`"
AliasType.Emoji -> "$search `$search`"
}
}
}
7 changes: 6 additions & 1 deletion src/main/kotlin/com/jaoafa/vcspeaker/stores/AliasStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ data class AliasData(
val type: AliasType,
val search: String,
val replace: String
)
) {
private val searchDisplay = if (type == AliasType.Regex) " `$search` " else "$search"
fun toDisplay() = "${type.displayName}${searchDisplay}→「$replace」<@$userId>"

fun toDisplayWithEmoji() = "${type.emoji} ${toDisplay()}"
}

object AliasStore : StoreStruct<AliasData>(
VCSpeaker.Files.aliases.path,
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/com/jaoafa/vcspeaker/stores/IgnoreStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ data class IgnoreData(
val guildId: Snowflake,
val userId: Snowflake,
val type: IgnoreType,
val text: String
)
val search: String
) {
fun toDisplay() = "${type.displayName}$search」<@$userId>"

fun toDisplayWithEmoji() = "${type.emoji} ${toDisplay()}"
}

object IgnoreStore : StoreStruct<IgnoreData>(
VCSpeaker.Files.ignores.path,
IgnoreData.serializer(),
{ Json.decodeFromString(this) }
) {
fun find(guildId: Snowflake, text: String) = data.find { it.guildId == guildId && it.text == text }
fun find(guildId: Snowflake, text: String) = data.find { it.guildId == guildId && it.search == text }

fun filter(guildId: Snowflake?) = data.filter { it.guildId == guildId }
}
Loading

0 comments on commit 1b29ef1

Please sign in to comment.