Skip to content

Commit

Permalink
feat: non recursive replacers
Browse files Browse the repository at this point in the history
  • Loading branch information
yuuahp committed May 25, 2024
1 parent a6d3be3 commit faecea4
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 67 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/
6 changes: 3 additions & 3 deletions src/main/kotlin/com/jaoafa/vcspeaker/tts/TextProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ object TextProcessor {
suspend fun processText(guildId: Snowflake, text: String): String? {
if (text.shouldIgnoreOn(guildId)) return null

val replacedText = replacers.fold(text) { replacedText, replacer ->
replacer.replace(replacedText, guildId)
}.replaceEmojiToName()
val replacedText = replacers.fold(mutableListOf(Token(text))) { tokens, replacer ->
replacer.replace(tokens, guildId)
}.joinToString("") { it.text }.replaceEmojiToName()

if (replacedText.shouldIgnoreOn(guildId)) return null

Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/com/jaoafa/vcspeaker/tts/Token.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.jaoafa.vcspeaker.tts

data class Token(val text: String, val replaced: Boolean = false)
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.stores.AliasType
import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* エイリアスを置換するクラス
*/
object AliasReplacer : BaseReplacer {
override val priority = ReplacerPriority.Normal
override val priority = ReplacerPriority.Low

override suspend fun replace(text: String, guildId: Snowflake) =
replaceText(text, guildId, AliasType.Text) { alias, replacedText ->
replacedText.replace(alias.search, alias.replace)
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) =
replaceText(tokens, guildId, AliasType.Text) { alias, replacedTokens ->
buildList {
for (replacedToken in replacedTokens) {
val text = replacedToken.text

if (replacedToken.replaced || !text.contains(alias.search)) {
add(replacedToken)
continue
}

val splitTexts = text.split(alias.search)

val additions = splitTexts.mixin {
Token(alias.replace, true)
}

addAll(additions)
}
}.toMutableList()
}
}
57 changes: 43 additions & 14 deletions src/main/kotlin/com/jaoafa/vcspeaker/tts/replacers/BaseReplacer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,75 @@ import com.jaoafa.vcspeaker.VCSpeaker
import com.jaoafa.vcspeaker.stores.AliasData
import com.jaoafa.vcspeaker.stores.AliasStore
import com.jaoafa.vcspeaker.stores.AliasType
import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import kotlinx.coroutines.runBlocking

/**
* テキストを置換する基底クラス
*/
interface BaseReplacer {
val priority: ReplacerPriority

suspend fun replace(text: String, guildId: Snowflake): String
suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake): MutableList<Token>

fun replaceText(
text: String,
tokens: MutableList<Token>,
guildId: Snowflake,
type: AliasType,
transform: (AliasData, String) -> String
): String {
transform: (AliasData, MutableList<Token>) -> MutableList<Token>
): MutableList<Token> {
val aliases = AliasStore.filter(guildId).filter { it.type == type }

val replacedText = aliases.fold(text) { replacedText, alias ->
transform(alias, replacedText)
val replacedText = aliases.fold(tokens) { replacedTokens, alias ->
transform(alias, replacedTokens)
}

return replacedText
}

suspend fun replaceMentionable(
text: String,
tokens: MutableList<Token>,
regex: Regex,
nameSupplier: suspend (Kord, Snowflake) -> String
): String {
val matches = regex.findAll(text)
): MutableList<Token> {
val newTokens = mutableListOf<Token>()

val replacedText = matches.fold(text) { replacedText, match ->
val id = Snowflake(match.groupValues[1]) // 0 is for whole match
val name = nameSupplier(VCSpeaker.kord, id)
for (token in tokens) {
val text = token.text

replacedText.replace(match.value, name)
if (token.replaced || !text.partialMatch(regex)) {
newTokens.add(token)
continue
}

val matches = regex.findAll(text).toList()

val splitTexts = text.split(regex)

val additions = splitTexts.mixin { index ->
val match = matches[index]
val id = Snowflake(match.groupValues[1]) // 0 is for whole match
val name = nameSupplier(VCSpeaker.kord, id)

Token(name, true)
}

newTokens.addAll(additions)
}

return replacedText
return newTokens
}


fun List<String>.mixin(provider: suspend (Int) -> Token) = buildList {
// ["Token1", "Token2", "Token3"] -> ["Token1", provider(1), "Token2", provider(2), "Token3"]
for (index in 0..(this@mixin.size * 2 - 2)) {
if (index % 2 == 0) add(Token(this@mixin[index / 2]))
else add(runBlocking { provider((index + 1) / 2) })
}
}

fun String.partialMatch(regex: Regex) = regex.findAll(this).toList().isNotEmpty()
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* チャンネルメンションを置換するクラス
*/
object ChannelMentionReplacer : BaseReplacer {
override val priority = ReplacerPriority.High
override val priority = ReplacerPriority.Normal

override suspend fun replace(text: String, guildId: Snowflake) =
replaceMentionable(text, Regex("<#(\\d+)>")) { kord, id ->
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) =
replaceMentionable(tokens, Regex("<#(\\d+)>")) { kord, id ->
kord.getChannel(id)?.data?.name?.value ?: "不明なチャンネル"
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.stores.AliasType
import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* 絵文字エイリアスを置換するクラス
*/
object EmojiReplacer : BaseReplacer {
override val priority = ReplacerPriority.High
override val priority = ReplacerPriority.Normal

override suspend fun replace(text: String, guildId: Snowflake) =
replaceText(text, guildId, AliasType.Emoji) { alias, replacedText ->
replacedText.replace(alias.search, alias.replace)
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) =
replaceText(tokens, guildId, AliasType.Emoji) { alias, replacedTokens ->
buildList {
for (token in replacedTokens) {
val text = token.text

if (token.replaced || !text.contains(alias.search)) {
add(token)
continue
}

val splitTexts = text.split(alias.search)

val additions = splitTexts.mixin {
Token(alias.replace, true)
}

addAll(additions)
}
}.toMutableList()
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* Guildの絵文字を置換するクラス
*/
object GuildEmojiReplacer : BaseReplacer {
override val priority = ReplacerPriority.High
override val priority = ReplacerPriority.Normal

override suspend fun replace(text: String, guildId: Snowflake): String {
val matches = Regex("<a?:(\\w+):(\\d+)>").findAll(text)
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) = buildList {
val regex = Regex("<a?:(\\w+):(\\d+)>")
for (token in tokens) {
val text = token.text

val replacedText = matches.fold(text) { replacedText, match ->
val emojiName = match.groupValues[1]
if (token.replaced || !text.partialMatch(regex)) {
add(token)
continue
}

replacedText.replace(match.value, emojiName)
}
val matches = regex.findAll(text).toList()

val splitTexts = text.split(regex)

val additions = splitTexts.mixin { index ->
val match = matches[index]
val emojiName = match.groupValues[1]

return replacedText
}
Token(emojiName, true)
}

addAll(additions)
}
}.toMutableList()
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.stores.AliasType
import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* 正規表現エイリアスを置換するクラス
*/
object RegexReplacer : BaseReplacer {
override val priority = ReplacerPriority.Normal
override val priority = ReplacerPriority.Low

override suspend fun replace(text: String, guildId: Snowflake) =
replaceText(text, guildId, AliasType.Regex) { alias, replacedText ->
replacedText.replace(Regex(alias.search), alias.replace)
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) =
replaceText(tokens, guildId, AliasType.Regex) { alias, replacedTokens ->
buildList {
val regex = Regex(alias.search)

for (replacedToken in replacedTokens) {
val text = replacedToken.text

if (replacedToken.replaced || !text.partialMatch(regex)) {
add(replacedToken)
continue
}

val splitTexts = text.split(regex)

val additions = splitTexts.mixin {
Token(alias.replace, true)
}

addAll(additions)
}
}.toMutableList()
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.jaoafa.vcspeaker.tts.replacers

import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.Snowflake

/**
* ロールメンションを置換するクラス
*/
object RoleMentionReplacer : BaseReplacer {
override val priority = ReplacerPriority.High
override val priority = ReplacerPriority.Normal

override suspend fun replace(text: String, guildId: Snowflake) =
replaceMentionable(text, Regex("<@&(\\d+)>")) { kord, id ->
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake) =
replaceMentionable(tokens, Regex("<@&(\\d+)>")) { kord, id ->
kord.getGuildOrNull(guildId)?.getRole(id)?.data?.name ?: "不明なロール"
}
}
39 changes: 21 additions & 18 deletions src/main/kotlin/com/jaoafa/vcspeaker/tts/replacers/UrlReplacer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.jaoafa.vcspeaker.tools.Steam
import com.jaoafa.vcspeaker.tools.Twitter
import com.jaoafa.vcspeaker.tools.YouTube
import com.jaoafa.vcspeaker.tools.discord.DiscordExtensions.isThread
import com.jaoafa.vcspeaker.tts.Token
import dev.kord.common.entity.ChannelType
import dev.kord.common.entity.Snowflake
import dev.kord.core.behavior.channel.asChannelOf
Expand All @@ -34,24 +35,28 @@ import kotlin.text.String
object UrlReplacer : BaseReplacer {
override val priority = ReplacerPriority.High

override suspend fun replace(text: String, guildId: Snowflake): String {
override suspend fun replace(tokens: MutableList<Token>, guildId: Snowflake): MutableList<Token> {
suspend fun replaceUrl(vararg replacers: suspend (String, Snowflake) -> String) =
replacers.fold(text) { replacedText, replacer ->
replacers.fold(tokens.joinToString("") { it.text }) { replacedText, replacer ->
replacer(replacedText, guildId)
}

return replaceUrl(
::replaceMessageUrl,
::replaceChannelUrl,
::replaceEventDirectUrl,
::replaceEventInviteUrl,
::replaceTweetUrl,
::replaceInviteUrl,
::replaceSteamAppUrl,
::replaceYouTubeUrl,
::replaceYouTubePlaylistUrl,
::replaceUrlToTitle,
::replaceUrl,
return mutableListOf(
Token(
replaceUrl(
::replaceMessageUrl,
::replaceChannelUrl,
::replaceEventDirectUrl,
::replaceEventInviteUrl,
::replaceTweetUrl,
::replaceInviteUrl,
::replaceSteamAppUrl,
::replaceYouTubeUrl,
::replaceYouTubePlaylistUrl,
::replaceUrlToTitle,
::replaceUrl,
)
)
)
}

Expand Down Expand Up @@ -523,11 +528,9 @@ object UrlReplacer : BaseReplacer {
)

// 動画タイトルが20文字を超える場合は、20文字に短縮して「以下略」を付ける
val videoTitle = video.title.substring(0, 15.coerceAtMost(video.title.length)) +
if (video.title.length > 15) " 以下略" else ""
val videoTitle = video.title.shorten(20)
// 投稿者名が15文字を超える場合は、15文字に短縮して「以下略」を付ける
val authorName = video.authorName.substring(0, 13.coerceAtMost(video.authorName.length)) +
if (video.authorName.length > 15) " 以下略" else ""
val authorName = video.authorName.shorten(15)

// URLからアイテムの種別を断定できる場合は、それに応じたテンプレートを使用する
val replaceTo = when (videoType) {
Expand Down
Loading

0 comments on commit faecea4

Please sign in to comment.