2019-06-08 21:14:57 +02:00
package moe.kageru.kagebot
2019-11-12 14:02:31 +01:00
import arrow.core.*
import arrow.core.extensions.either.monad.flatMap
2019-11-11 23:52:14 +01:00
import arrow.core.extensions.list.foldable.find
2019-11-12 14:02:31 +01:00
import arrow.typeclasses.Monad
2019-07-13 15:39:50 +02:00
import moe.kageru.kagebot.config.Config
2019-11-11 23:52:14 +01:00
import moe.kageru.kagebot.extensions.*
2019-07-13 15:39:50 +02:00
import moe.kageru.kagebot.config.Config.server
2019-06-12 23:43:36 +02:00
import org.javacord.api.entity.channel.TextChannel
2019-06-10 09:19:03 +02:00
import org.javacord.api.entity.message.MessageAuthor
2019-06-15 12:27:20 +02:00
import org.javacord.api.entity.message.embed.EmbedBuilder
2019-06-12 23:43:36 +02:00
import org.javacord.api.entity.permission.Role
2019-07-11 22:16:02 +02:00
import org.javacord.api.entity.user.User
import org.javacord.api.event.message.MessageCreateEvent
2019-06-15 12:27:20 +02:00
import java.awt.Color
2019-07-13 15:39:50 +02:00
import java.util.*
2019-06-13 00:19:02 +02:00
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
2019-06-08 23:50:05 +02:00
2019-06-08 21:14:57 +02:00
object Util {
2019-07-17 22:47:47 +02:00
inline fun < T > T . applyIf ( condition : Boolean , op : ( T ) -> T ) : T {
return if ( condition ) op ( this ) else this
2019-06-08 21:14:57 +02:00
}
2019-06-08 23:50:05 +02:00
2019-06-12 23:43:36 +02:00
/ * *
* Mimics the behavior of [ Optional . ifPresent ] , but returns null if the optional is empty ,
* allowing easier fallback behavior via Kotlin ’ s ?: operator .
* /
2019-09-17 22:59:55 +02:00
internal inline fun < T , R > Optional < T > . ifNotEmpty ( op : ( T ) -> R ) : R ? =
if ( this . isPresent ) op ( this . get ( ) ) else null
2019-06-10 09:19:03 +02:00
2019-06-12 23:43:36 +02:00
fun hasOneOf ( messageAuthor : MessageAuthor , roles : Set < Role > ) : Boolean {
2019-11-11 23:52:14 +01:00
return messageAuthor . asUser ( ) . asOption ( ) . flatMap { user ->
user . roles ( ) . find { it in roles }
} . nonEmpty ( )
2019-06-10 09:19:03 +02:00
}
2019-06-12 23:43:36 +02:00
private val channelIdRegex = Regex ( " \\ d{18} " )
2019-06-15 11:47:11 +02:00
private fun String . isEntityId ( ) = channelIdRegex . matches ( this )
2019-06-12 23:43:36 +02:00
2019-11-12 14:02:31 +01:00
fun findRole ( idOrName : String ) : Either < String , Role > {
2019-06-12 23:43:36 +02:00
return when {
2019-11-12 14:02:31 +01:00
idOrName . isEntityId ( ) -> server . getRoleById ( idOrName ) . asOption ( ) . toEither { 0 }
else -> server . rolesByName ( idOrName ) . getOnly ( )
} . mapLeft { " Found $it results, expected 1 " }
2019-09-17 22:59:55 +02:00
}
2019-11-12 14:02:31 +01:00
private fun < T > ListK < T > . getOnly ( ) : Either < Int , T > {
2019-09-17 22:59:55 +02:00
return when ( size ) {
2019-11-12 14:02:31 +01:00
1 -> Either . right ( first ( ) )
else -> Either . left ( size )
2019-06-12 23:43:36 +02:00
}
}
2019-07-23 21:50:55 +02:00
private fun < T > Optional < T > . toNullable ( ) : T ? {
2019-09-17 22:04:52 +02:00
return orElse ( null )
2019-07-23 21:50:55 +02:00
}
fun findUser ( idOrName : String ) : User ? {
return when {
idOrName . isEntityId ( ) -> server . getMemberById ( idOrName ) . toNullable ( )
else -> {
when {
idOrName . contains ( '#' ) -> server . getMemberByDiscriminatedNameIgnoreCase ( idOrName ) . toNullable ( )
else -> server . getMembersByName ( idOrName ) . firstOrNull ( )
}
}
}
}
2019-11-12 14:02:31 +01:00
fun < T > CompletableFuture < T > . asOption ( ) : Option < T > {
return try {
Option . just ( join ( ) )
} catch ( e : CompletionException ) {
Option . empty ( )
}
}
fun < T > Either < * , T > . unwrap ( ) : T = this . getOrElse { error ( " Attempted to unwrap Either.left " ) }
2019-07-17 23:17:16 +02:00
fun < T > CompletableFuture < T > . failed ( ) : Boolean {
2019-06-13 00:19:02 +02:00
try {
2019-07-17 23:17:16 +02:00
join ( )
2019-06-13 00:19:02 +02:00
} catch ( e : CompletionException ) {
2019-09-17 22:59:55 +02:00
// we don’t care about this error, but I at least want to log it for debugging
Log . info (
2019-09-17 22:04:52 +02:00
""" Error during CompletableFuture:
2019-08-05 21:38:41 +02:00
| $ e
| $ { e . localizedMessage }
| $ { e . stackTrace . joinToString ( " \n \t " ) }
2019-09-17 22:04:52 +02:00
""" .trimMargin()
)
2019-06-13 00:19:02 +02:00
}
2019-07-17 23:17:16 +02:00
return isCompletedExceptionally
2019-06-13 00:19:02 +02:00
}
2019-11-12 14:02:31 +01:00
fun findChannel ( idOrName : String ) : Either < String , TextChannel > {
2019-06-12 23:43:36 +02:00
return when {
2019-11-12 14:02:31 +01:00
idOrName . isEntityId ( ) -> server . getTextChannelById ( idOrName ) . asOption ( ) . toEither { " Channel $idOrName not found " }
idOrName . startsWith ( '@' ) -> Globals . api . getCachedUserByDiscriminatedName ( idOrName . removePrefix ( " @ " ) ) . asOption ( )
. toEither { " User $idOrName not found " }
. flatMap { user ->
user . openPrivateChannel ( ) . asOption ( ) . toEither { " Can’t DM user $idOrName " }
}
else -> server . channelByName ( idOrName ) . getOnly ( ) . mapLeft { " Found $it channels for $idOrName , expected 1 " }
2019-06-12 23:43:36 +02:00
}
}
2019-06-15 12:27:20 +02:00
2019-11-11 23:52:14 +01:00
fun < T > Optional < T > . asOption ( ) : Option < T > = if ( this . isPresent ) Option . just ( this . get ( ) ) else Option . empty ( )
2019-06-15 12:27:20 +02:00
inline fun checked ( op : ( ( ) -> Unit ) ) {
try {
op ( )
} catch ( e : Exception ) {
2019-07-17 21:16:17 +02:00
Log . warn ( " An uncaught exception occurred. \n $e " )
2019-07-23 21:50:55 +02:00
Log . warn ( e . stackTrace . joinToString ( " \n " ) )
2019-07-17 21:16:17 +02:00
MessageUtil . sendEmbed (
Globals . api . owner . get ( ) ,
2019-06-15 12:27:20 +02:00
EmbedBuilder ( )
. setColor ( Color . RED )
. addField ( " Error " , " kagebot has encountered an error " )
. addField (
" $e " , """ ```
$ { e . stackTrace . joinToString ( " \n " ) }
2019-07-23 21:50:55 +02:00
` ` ` """ .trimIndent().run { applyIf(length > 1800) { substring(1..1800) } }
2019-06-15 12:27:20 +02:00
)
)
}
}
2019-07-11 22:16:02 +02:00
2019-09-17 22:59:55 +02:00
fun MessageCreateEvent . getUser ( ) : User ? = Config . server . getMemberById ( messageAuthor . id ) . toNullable ( )
2019-08-05 21:39:00 +02:00
/ * *
* Convert a list of elements to pairs , retaining order .
* The last element is dropped if the input size is odd .
* [ 1 , 2 , 3 , 4 , 5 ] -> [ [ 1 , 2 ] , [ 3 , 4 ] ]
* /
fun < T > Collection < T > . toPairs ( ) : List < Pair < T , T > > = this . iterator ( ) . run {
( 0 until size / 2 ) . map {
Pair ( next ( ) , next ( ) )
}
}
2019-09-17 22:59:55 +02:00
private inline fun < T > CompletableFuture < T > . joinOr ( op : ( ) -> Nothing ) : T {
val value = join ( )
if ( isCompletedExceptionally ) {
op ( )
}
return value
}
2019-07-13 14:52:05 +02:00
}