diff --git a/api/src/main/kotlin/com/gmtkgamejam/bot/BotMessageBuilder.kt b/api/src/main/kotlin/com/gmtkgamejam/bot/BotMessageBuilder.kt new file mode 100644 index 00000000..540d5aa3 --- /dev/null +++ b/api/src/main/kotlin/com/gmtkgamejam/bot/BotMessageBuilder.kt @@ -0,0 +1,52 @@ +package com.gmtkgamejam.bot + +import com.gmtkgamejam.services.PostService +import org.javacord.api.entity.message.embed.EmbedBuilder +import org.javacord.api.entity.user.User + +class BotMessageBuilder { + + private val postService = PostService() + + fun canBuildEmbedFromUser(sender: User): Boolean = postService.getPostByAuthorId(sender.id.toString()) != null + + fun embedMessage(recipient: User, sender: User): EmbedBuilder { + val post = postService.getPostByAuthorId(sender.id.toString())!! + + val shortDescription = if (post.description.length > 240) post.description.take(237) + "..." else post.description + + val embed = EmbedBuilder() + .setTitle("${sender.name} wants to get in contact!") + .setDescription("Hey there ${recipient.name}! ${sender.name} wants to get in touch - this is a summary of their current post on the Team Finder!") + .setAuthor("GMTK Team Finder", "https://findyourjam.team/", "https://findyourjam.team/logos/jam-logo-stacked.webp") + .addField("Description", shortDescription) + + // Add optional fields - turns out this includes timezones + if (post.skillsSought?.isNotEmpty() == true) { + embed.addField("${sender.name} is looking for:", post.skillsSought.toString()) + } + if (post.skillsPossessed?.isNotEmpty() == true) { + embed.addField("${sender.name} can bring:", post.skillsPossessed.toString()) + } + if (post.preferredTools?.isNotEmpty() == true) { + embed.addField("Engine(s)", post.preferredTools.toString()) + } + if (post.timezoneOffsets.isNotEmpty()) { + embed.addField("Timezone(s)", post.timezoneOffsets.map { if (it < 0) "UTC-$it" else "UTC+$it" }.toString()) + } + + embed + .addField("Like what you see?", "Check out their full post here to see more! https://findyourjam.team/gmtk/${post.id}/") + .setFooter("Feedback? DM @dotwo in the #developing-gtmk-team-finder-app channel") + + return embed + } + + // TODO: Add a variety of messages to mix things up a bit? + fun basicMessage(recipient: User, sender: User): String { + return """ + Hey ${recipient.mentionTag}, ${sender.mentionTag} wants to get in contact about your Team Finder post!" + They don't have a post on the Team Finder yet, so why not drop them a message and find out more? + """.trimIndent() + } +} diff --git a/api/src/main/kotlin/com/gmtkgamejam/bot/DiscordBot.kt b/api/src/main/kotlin/com/gmtkgamejam/bot/DiscordBot.kt index d0b357bf..682b0f64 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/bot/DiscordBot.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/bot/DiscordBot.kt @@ -1,7 +1,9 @@ package com.gmtkgamejam.bot import com.gmtkgamejam.Config +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.await +import kotlinx.coroutines.withContext import org.javacord.api.DiscordApi import org.javacord.api.DiscordApiBuilder import org.javacord.api.entity.channel.ServerTextChannel @@ -13,6 +15,7 @@ import org.javacord.api.exception.DiscordException import org.javacord.api.exception.MissingPermissionsException import org.slf4j.Logger import org.slf4j.LoggerFactory +import kotlin.jvm.optionals.getOrElse class DiscordBot { @@ -24,6 +27,8 @@ class DiscordBot { private lateinit var channel: ServerTextChannel + private val messageBuilder = BotMessageBuilder() + private val approvedUsers: MutableList = mutableListOf() init { @@ -51,6 +56,26 @@ class DiscordBot { val recipient: User = api.getUserById(recipientUserId).await() val sender: User = api.getUserById(senderUserId).await() + val dmChannel = recipient.privateChannel.getOrElse { recipient.openPrivateChannel().get() } + + val messageSendAttempt = if (messageBuilder.canBuildEmbedFromUser(sender)) { + dmChannel.sendMessage(messageBuilder.embedMessage(recipient, sender)) + } else { + dmChannel.sendMessage(messageBuilder.basicMessage(recipient, sender)) + } + + try { + withContext(Dispatchers.IO) { + messageSendAttempt.get() + } + } catch (ex: InterruptedException) { + createFallbackChannelPingMessage(recipient, sender) + } catch (ex: java.util.concurrent.ExecutionException) { + createFallbackChannelPingMessage(recipient, sender) + } + } + + private suspend fun createFallbackChannelPingMessage(recipient: User, sender: User) { val messageContents = "Hey ${recipient.mentionTag}, ${sender.mentionTag} wants to get in contact about your Team Finder post!" // TODO: Validate message actually sent, give error otherwise channel.sendMessage(messageContents).await() diff --git a/api/src/main/kotlin/com/gmtkgamejam/models/posts/Skills.kt b/api/src/main/kotlin/com/gmtkgamejam/models/posts/Skills.kt index 70111fb0..df82a772 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/models/posts/Skills.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/models/posts/Skills.kt @@ -1,13 +1,17 @@ package com.gmtkgamejam.models.posts -enum class Skills { - ART_2D, - ART_3D, - CODE, - DESIGN_PRODUCTION, - SFX, - MUSIC, - TESTING_SUPPORT, - TEAM_LEAD, - OTHER; +enum class Skills(private var readableName: String) { + ART_2D("2D Art"), + ART_3D("3D Art"), + CODE("Code"), + DESIGN_PRODUCTION("Design/Production"), + SFX("SFX"), + MUSIC("Music"), + TESTING_SUPPORT("Testing/Support"), + TEAM_LEAD("Team lead"), + OTHER("Other"); + + override fun toString(): String { + return readableName; + } } diff --git a/api/src/main/kotlin/com/gmtkgamejam/models/posts/Tools.kt b/api/src/main/kotlin/com/gmtkgamejam/models/posts/Tools.kt index 1bbc8838..0fa81a27 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/models/posts/Tools.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/models/posts/Tools.kt @@ -1,15 +1,19 @@ package com.gmtkgamejam.models.posts // For the time being, Tool == Engine -enum class Tools { - UNITY, - CONSTRUCT, - GAME_MAKER_STUDIO, - GODOT, - TWINE, - BITSY, - UNREAL, - RPG_MAKER, - PICO_8, - OTHER, +enum class Tools(private var readableName: String) { + UNITY("Unity"), + CONSTRUCT("Construct"), + GAME_MAKER_STUDIO("Game Maker Studio"), + GODOT("Godot"), + TWINE("Twine"), + BITSY("Bitsy"), + UNREAL("Unreal"), + RPG_MAKER("RPG Maker"), + PICO_8("PICO 8"), + OTHER("Other"); + + override fun toString(): String { + return readableName; + } } \ No newline at end of file diff --git a/ui/src/pages/post/Post.tsx b/ui/src/pages/post/Post.tsx index 6d28ec88..509fe0dc 100644 --- a/ui/src/pages/post/Post.tsx +++ b/ui/src/pages/post/Post.tsx @@ -154,7 +154,7 @@ const MessageOnDiscordButton: React.FC<{ {canPostAuthorBeDMd && } {inDiscordServer - ? + ? : <>

Sorry, you can't contact this user right now.

diff --git a/ui/src/pages/post/components/DiscordPingButton.tsx b/ui/src/pages/post/components/DiscordPingButton.tsx index b11ae20d..bb7ebcd4 100644 --- a/ui/src/pages/post/components/DiscordPingButton.tsx +++ b/ui/src/pages/post/components/DiscordPingButton.tsx @@ -1,14 +1,29 @@ import React from "react"; +import {toast} from "react-hot-toast"; export const DiscordPingButton: React.FC<{ authorId: string, + authorName: string, createBotDmMutation: any, message: string }> = ({ authorId, + authorName, createBotDmMutation, message, }) => { + + const onClick = () => { + createBotDmMutation.mutate({ + recipientId: authorId, + }, { + onSuccess: () => { + toast(`${authorName} has just been notified that you want to get in touch!`); + }, + + }); + }; + return ( <>

{message}

@@ -16,7 +31,7 @@ export const DiscordPingButton: React.FC<{ createBotDmMutation.mutate({ recipientId: authorId })} + onClick={onClick} className="text-sm" > Ping them on Discord