feat: support webhooks

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-11-05 02:01:15 +01:00
parent 197c6db56a
commit 7e2eccf387
9 changed files with 150 additions and 10 deletions

View File

@ -20,6 +20,7 @@ data class Message(
val mentions: List<String>? = null,
val masquerade: Masquerade? = null,
val system: SystemInfo? = null,
val webhook: WebHook? = null,
val type: String? = null, // this is _only_ used for websocket events!
val tail: Boolean? = null // this is used to determine if the message is the last in a message group
) {
@ -110,3 +111,9 @@ data class SystemInfo(
val to: String? = null,
val content: String? = null
)
@Serializable
data class WebHook(
val avatar: String? = null,
val name: String? = null,
)

View File

@ -14,6 +14,7 @@ sealed class Action {
data class ReportUser(val userId: String) : Action()
data class ReportMessage(val messageId: String) : Action()
data class OpenVoiceChannelOverlay(val channelId: String) : Action()
data object OpenWebhookSheet : Action()
}
val ActionChannel = Channel<Action>(

View File

@ -13,7 +13,8 @@ enum class InlineBadge {
Bot,
Bridge,
PlatformModeration,
TeamMember
TeamMember,
Webhook
}
@Composable
@ -50,6 +51,13 @@ fun InlineBadge(
tint = colour,
modifier = modifier
)
InlineBadge.Webhook -> Icon(
painter = painterResource(id = R.drawable.ic_hook_24dp),
contentDescription = stringResource(id = R.string.badge_webhook_alt),
tint = colour,
modifier = modifier
)
}
}
@ -60,6 +68,7 @@ fun InlineBadges(
bridge: Boolean = false,
platformModeration: Boolean = false,
teamMember: Boolean = false,
webhook: Boolean = false,
colour: Color = Color.Unspecified,
precedingIfAny: @Composable () -> Unit = {},
followingIfAny: @Composable () -> Unit = {}
@ -99,6 +108,13 @@ fun InlineBadges(
colour = colour
)
}
if (webhook) {
InlineBadge(
badge = InlineBadge.Webhook,
modifier = modifier,
colour = colour
)
}
}
if (hasBadges) {

View File

@ -180,6 +180,8 @@ fun Message(
canReply: Boolean = false,
onReply: () -> Unit = {},
onAddReaction: () -> Unit = {},
fromWebhook: Boolean = false,
webhookName: String? = null
) {
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
val context = LocalContext.current
@ -304,7 +306,7 @@ fun Message(
if (message.tail == false) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = authorName(message),
text = webhookName ?: authorName(message),
style = LocalTextStyle.current.copy(
fontWeight = FontWeight.Bold,
brush = authorColour(message)
@ -328,6 +330,7 @@ fun Message(
bridge = message.masquerade != null && author.bot != null,
platformModeration = author.id == SpecialUsers.PLATFORM_MODERATION_USER,
teamMember = author.id in SpecialUsers.TEAM_MEMBER_FLAIRS.keys,
webhook = fromWebhook,
colour = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f),
modifier = Modifier.size(16.dp),
precedingIfAny = {

View File

@ -78,6 +78,7 @@ import chat.revolt.sheets.ReactionInfoSheet
import chat.revolt.sheets.ServerContextSheet
import chat.revolt.sheets.StatusSheet
import chat.revolt.sheets.UserInfoSheet
import chat.revolt.sheets.WebHookUserSheet
import com.airbnb.lottie.RenderMode
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
@ -229,6 +230,8 @@ fun ChatRouterScreen(
var userContextSheetTarget by remember { mutableStateOf("") }
var userContextSheetServer by remember { mutableStateOf<String?>(null) }
var showWebhookInfoSheet by remember { mutableStateOf(false) }
var showChannelUnavailableAlert by remember { mutableStateOf(false) }
var showLinkInfoSheet by remember { mutableStateOf(false) }
@ -389,6 +392,10 @@ fun ChatRouterScreen(
voiceChannelOverlayChannelId = action.channelId
voiceChannelOverlay = true
}
is Action.OpenWebhookSheet -> {
showWebhookInfoSheet = true
}
}
}
}
@ -554,6 +561,19 @@ fun ChatRouterScreen(
}
}
if (showWebhookInfoSheet) {
val webhookInfoSheetState = rememberModalBottomSheetState()
ModalBottomSheet(
sheetState = webhookInfoSheetState,
onDismissRequest = {
showWebhookInfoSheet = false
}
) {
WebHookUserSheet()
}
}
if (showReportUser) {
ReportUserDialog(
onDismiss = { showReportUser = false },

View File

@ -134,7 +134,6 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import java.io.File
import kotlin.math.max
import kotlin.math.min
sealed class ChannelScreenItem {
data class RegularMessage(val message: Message) : ChannelScreenItem()
@ -603,14 +602,20 @@ fun ChannelScreen(
}
},
onAvatarClick = {
item.message.author?.let { author ->
if (item.message.webhook != null) {
scope.launch {
ActionChannel.send(
Action.OpenUserSheet(
author,
viewModel.channel?.server
ActionChannel.send(Action.OpenWebhookSheet)
}
} else {
item.message.author?.let { author ->
scope.launch {
ActionChannel.send(
Action.OpenUserSheet(
author,
viewModel.channel?.server
)
)
)
}
}
}
},
@ -631,7 +636,9 @@ fun ChannelScreen(
reactSheetTarget = messageId
reactSheetShown = true
}
}
},
fromWebhook = item.message.webhook != null,
webhookName = item.message.webhook?.name
)
}

View File

@ -0,0 +1,72 @@
package chat.revolt.sheets
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.components.generic.NonIdealState
@Composable
fun WebHookUserSheet(modifier: Modifier = Modifier) {
val context = LocalContext.current
NonIdealState(
icon = {
Icon(
painter = painterResource(R.drawable.ic_hook_24dp),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
title = {
Text(
text = stringResource(R.string.user_info_sheet_webhook)
)
},
description = {
Text(
text = stringResource(R.string.user_info_sheet_webhook_description),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
)
Column(Modifier.padding(horizontal = 16.dp)) {
Text(
text = stringResource(R.string.user_info_sheet_webhook_description_2),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(32.dp))
Button(
onClick = {
Toast(context).apply {
setText(context.getString(R.string.comingsoon_toast))
duration = Toast.LENGTH_SHORT
show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.user_info_sheet_webhook_learn_more))
}
}
Spacer(Modifier.height(48.dp))
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M18,6C18,7.82 16.76,9.41 15,9.86V17A5,5 0 0,1 10,22A5,5 0 0,1 5,17V12L10,17H7A3,3 0 0,0 10,20A3,3 0 0,0 13,17V9.86C11.23,9.4 10,7.8 10,5.97C10,3.76 11.8,2 14,2C16.22,2 18,3.79 18,6M14,8A2,2 0 0,0 16,6A2,2 0 0,0 14,4A2,2 0 0,0 12,6A2,2 0 0,0 14,8Z" />
</vector>

View File

@ -125,6 +125,7 @@
<string name="badge_masquerade_alt">From linked channel</string>
<string name="badge_platform_moderation_alt">Platform moderation</string>
<string name="badge_team_member_alt">Revolt team member</string>
<string name="badge_webhook_alt">Webhook</string>
<string name="unknown">Unknown</string>
@ -352,6 +353,10 @@
<string name="user_info_sheet_open">Open user info</string>
<string name="user_info_sheet_user_not_found">Can\'t resolve this user</string>
<string name="user_info_sheet_user_not_found_description">This user may have been deleted or you may not have permission to view them.</string>
<string name="user_info_sheet_webhook">Is it a bird, is it a plane?</string>
<string name="user_info_sheet_webhook_description">No, it\'s a webhook!</string>
<string name="user_info_sheet_webhook_description_2">Webhooks are automated broadcasts from other services on the internet. They can\'t chat back, but you can still see what they have to say.</string>
<string name="user_info_sheet_webhook_learn_more">Learn more</string>
<string name="user_info_sheet_category_bio">Bio</string>
<string name="user_info_sheet_bio_empty">This user hasn\'t set a bio yet.</string>
<string name="user_info_sheet_bio_not_found">This user\'s bio could not be fetched. Please verify you share a server or are friends.</string>