-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add converter for discord-formatted timestamps (#102)
- Loading branch information
Showing
6 changed files
with
191 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
...n/kotlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package com.kotlindiscord.kord.extensions.commands.converters.impl | ||
|
||
import com.kotlindiscord.kord.extensions.DiscordRelayedException | ||
import com.kotlindiscord.kord.extensions.commands.Argument | ||
import com.kotlindiscord.kord.extensions.commands.CommandContext | ||
import com.kotlindiscord.kord.extensions.commands.converters.SingleConverter | ||
import com.kotlindiscord.kord.extensions.commands.converters.Validator | ||
import com.kotlindiscord.kord.extensions.modules.annotations.converters.Converter | ||
import com.kotlindiscord.kord.extensions.modules.annotations.converters.ConverterType | ||
import com.kotlindiscord.kord.extensions.parser.StringParser | ||
import com.kotlindiscord.kord.extensions.time.TimestampType | ||
import com.kotlindiscord.kord.extensions.time.toDiscord | ||
import dev.kord.common.annotation.KordPreview | ||
import dev.kord.core.entity.interaction.OptionValue | ||
import dev.kord.rest.builder.interaction.OptionsBuilder | ||
import dev.kord.rest.builder.interaction.StringChoiceBuilder | ||
import kotlinx.datetime.Instant | ||
|
||
private const val TIMESTAMP_PREFIX = "<t:" | ||
private const val TIMESTAMP_SUFFIX = ">" | ||
|
||
/** | ||
* Argument converter for discord-formatted timestamp arguments. | ||
*/ | ||
@Converter( | ||
"timestamp", | ||
|
||
types = [ConverterType.DEFAULTING, ConverterType.LIST, ConverterType.OPTIONAL, ConverterType.SINGLE] | ||
) | ||
@OptIn(KordPreview::class) | ||
public class TimestampConverter( | ||
override var validator: Validator<FormattedTimestamp> = null | ||
) : SingleConverter<FormattedTimestamp>() { | ||
override val signatureTypeString: String = "converters.timestamp.signatureType" | ||
|
||
override suspend fun parse(parser: StringParser?, context: CommandContext, named: String?): Boolean { | ||
val arg: String = named ?: parser?.parseNext()?.data ?: return false | ||
this.parsed = parseFromString(arg) ?: throw DiscordRelayedException( | ||
context.translate( | ||
"converters.timestamp.error.invalid", | ||
replacements = arrayOf(arg) | ||
) | ||
) | ||
|
||
return true | ||
} | ||
|
||
override suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder = | ||
StringChoiceBuilder(arg.displayName, arg.description).apply { required = true } | ||
|
||
override suspend fun parseOption(context: CommandContext, option: OptionValue<*>): Boolean { | ||
val optionValue = (option as? OptionValue.StringOptionValue)?.value ?: return false | ||
this.parsed = parseFromString(optionValue) ?: throw DiscordRelayedException( | ||
context.translate( | ||
"converters.timestamp.error.invalid", | ||
replacements = arrayOf(optionValue) | ||
) | ||
) | ||
|
||
return true | ||
} | ||
|
||
internal companion object { | ||
internal fun parseFromString(string: String): FormattedTimestamp? { | ||
if (string.startsWith(TIMESTAMP_PREFIX) && string.endsWith(TIMESTAMP_SUFFIX)) { | ||
val inner = string.removeSurrounding(TIMESTAMP_PREFIX, TIMESTAMP_SUFFIX).split(":") | ||
val epochSeconds = inner.getOrNull(0) | ||
val format = inner.getOrNull(1) | ||
|
||
return FormattedTimestamp( | ||
Instant.fromEpochSeconds(epochSeconds?.toLongOrNull() ?: return null), | ||
TimestampType.fromFormatSpecifier(format) ?: return null | ||
) | ||
} else { | ||
return null | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Container class for a timestamp and format, as expected by Discord. | ||
* | ||
* @param instant The timestamp this represents | ||
* @param format Which format to display the timestamp in | ||
*/ | ||
public data class FormattedTimestamp(val instant: Instant, val format: TimestampType) { | ||
/** | ||
* Format the timestamp using the format into Discord's special format. | ||
*/ | ||
public fun toDiscord(): String = instant.toDiscord(format) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...tlin/com/kotlindiscord/kord/extensions/commands/converters/impl/TimestampConverterTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.kotlindiscord.kord.extensions.commands.converters.impl | ||
|
||
import com.kotlindiscord.kord.extensions.time.TimestampType | ||
import kotlinx.datetime.Instant | ||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class TimestampConverterTest { | ||
|
||
@Test | ||
fun `timestamp without format`() { | ||
val timestamp = "<t:1420070400>" // 1st second of 2015 | ||
val parsed = TimestampConverter.parseFromString(timestamp)!! | ||
assertEquals(Instant.fromEpochSeconds(1_420_070_400), parsed.instant) | ||
assertEquals(TimestampType.Default, parsed.format) | ||
} | ||
|
||
@Test | ||
fun `timestamp with format`() { | ||
val timestamp = "<t:1420070400:R>" | ||
val parsed = TimestampConverter.parseFromString(timestamp)!! | ||
assertEquals(Instant.fromEpochSeconds(1_420_070_400), parsed.instant) | ||
assertEquals(TimestampType.RelativeTime, parsed.format) | ||
} | ||
|
||
@Test | ||
fun `empty timestamp`() { | ||
val timestamp = "<t::>" | ||
val parsed = TimestampConverter.parseFromString(timestamp) | ||
assertNull(parsed) | ||
} | ||
|
||
@Test | ||
fun `timestamp with empty format`() { | ||
val timestamp = "<t:1420070400:>" | ||
val parsed = TimestampConverter.parseFromString(timestamp) | ||
assertNull(parsed) | ||
} | ||
} |