// SPDX-License-Identifier: AGPL-3.0-or-later import type {ChannelID, GuildID, MessageID, UserID, WebhookID, WebhookToken} from '@app/api/BrandedTypes'; import {createUserID} from '@app/api/BrandedTypes'; import {Config} from '@app/api/Config'; import type {ChannelRepository} from '@app/api/channel/ChannelRepository'; import type {MessageUpdateRequest} from '@app/api/channel/MessageTypes'; import {createMessageResponseDataService} from '@app/api/channel/services/message/MessageResponseDataService'; import {maskThreadArtifactsFor} from '@app/api/channel/services/message/ThreadMessageResponses'; import {SYSTEM_THREAD_VIEWER, type ThreadViewer} from '@app/api/experiment/ChannelThreadsGate'; import type {IGatewayService} from '@app/api/infrastructure/IGatewayService'; import type {LiveKitWebhookService} from '@app/api/infrastructure/LiveKitWebhookService'; import type {UserCacheService} from '@app/api/infrastructure/UserCacheService'; import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware'; import type {Message} from '@app/api/models/Message'; import type {SweegoWebhookService} from '@app/api/webhook/SweegoWebhookService'; import {transformSlackWebhookRequest} from '@app/api/webhook/transformers/SlackTransformer'; import { createWebhookSourceResolver, mapWebhooksToResponse, mapWebhookToResponseWithCache, mapWebhookToTokenResponse, type WebhookSourceResolver, } from '@app/api/webhook/WebhookModel'; import type {WebhookExecuteMessageData, WebhookService} from '@app/api/webhook/WebhookService'; import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas'; import type {GitHubWebhook} from '@fluxer/schema/src/domains/webhook/GitHubWebhookSchemas'; import type {InstatusWebhook} from '@fluxer/schema/src/domains/webhook/InstatusWebhookSchemas'; import type {LibredeskWebhook} from '@fluxer/schema/src/domains/webhook/LibredeskWebhookSchemas'; import type { SlackWebhookRequest, WebhookCreateRequest, WebhookTokenUpdateRequest, WebhookUpdateRequest, } from '@fluxer/schema/src/domains/webhook/WebhookRequestSchemas'; import type {WebhookResponse, WebhookTokenResponse} from '@fluxer/schema/src/domains/webhook/WebhookSchemas'; type WebhookExecutionResponse = MessageResponse | null; interface WebhookListGuildParams { userId: UserID; viewer: ThreadViewer; guildId: GuildID; requestCache: RequestCache; } interface WebhookListChannelParams { userId: UserID; viewer: ThreadViewer; channelId: ChannelID; requestCache: RequestCache; } interface WebhookCreateParams { userId: UserID; viewer: ThreadViewer; channelId: ChannelID; data: WebhookCreateRequest; requestCache: RequestCache; auditLogReason?: string | null; } interface WebhookGetByUserParams { userId: UserID; viewer: ThreadViewer; webhookId: WebhookID; requestCache: RequestCache; } interface WebhookGetByTokenParams { webhookId: WebhookID; token: WebhookToken; requestCache: RequestCache; } type WebhookGetParams = WebhookGetByUserParams | WebhookGetByTokenParams; interface WebhookUpdateByUserParams { userId: UserID; viewer: ThreadViewer; webhookId: WebhookID; data: WebhookUpdateRequest; requestCache: RequestCache; auditLogReason?: string | null; } interface WebhookUpdateByTokenParams { webhookId: WebhookID; token: WebhookToken; data: WebhookTokenUpdateRequest; requestCache: RequestCache; } type WebhookUpdateParams = WebhookUpdateByUserParams | WebhookUpdateByTokenParams; interface WebhookDeleteByUserParams { userId: UserID; viewer: ThreadViewer; webhookId: WebhookID; auditLogReason?: string | null; } interface WebhookDeleteByTokenParams { webhookId: WebhookID; token: WebhookToken; } type WebhookDeleteParams = WebhookDeleteByUserParams | WebhookDeleteByTokenParams; interface WebhookExecuteParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; data: WebhookExecuteMessageData; wait: boolean; requestCache: RequestCache; } interface WebhookGetMessageParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; messageId: MessageID; requestCache: RequestCache; } interface WebhookEditMessageParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; messageId: MessageID; data: MessageUpdateRequest; requestCache: RequestCache; } interface WebhookDeleteMessageParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; messageId: MessageID; requestCache: RequestCache; } interface WebhookExecuteGitHubParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; event: string; delivery: string; data: GitHubWebhook; requestCache: RequestCache; } interface WebhookExecuteSlackParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; data: SlackWebhookRequest; requestCache: RequestCache; } interface WebhookExecuteInstatusParams { webhookId: WebhookID; token: WebhookToken; data: InstatusWebhook; requestCache: RequestCache; } interface WebhookExecuteLibredeskParams { webhookId: WebhookID; token: WebhookToken; threadId?: string; data: LibredeskWebhook; requestCache: RequestCache; } interface LiveKitWebhookParams { body: string; authHeader?: string; } interface SweegoWebhookParams { body: string; webhookId?: string; timestamp?: string; signature?: string; } export class WebhookRequestService { constructor( private readonly webhookService: WebhookService, private readonly channelRepository: ChannelRepository, private readonly userCacheService: UserCacheService, private readonly liveKitWebhookService: LiveKitWebhookService | null, private readonly sweegoWebhookService: SweegoWebhookService, private readonly gatewayService: IGatewayService, ) {} private sourceResolver(requestCache: RequestCache): WebhookSourceResolver { return createWebhookSourceResolver({ channelRepository: this.channelRepository, gatewayService: this.gatewayService, requestCache, }); } async listGuildWebhooks(params: WebhookListGuildParams): Promise> { const webhooks = await this.webhookService.getGuildWebhooks({ userId: params.userId, viewer: params.viewer, guildId: params.guildId, }); return mapWebhooksToResponse({ webhooks, userCacheService: this.userCacheService, requestCache: params.requestCache, resolveSource: this.sourceResolver(params.requestCache), }); } async listChannelWebhooks(params: WebhookListChannelParams): Promise> { const webhooks = await this.webhookService.getChannelWebhooks({ userId: params.userId, viewer: params.viewer, channelId: params.channelId, }); return mapWebhooksToResponse({ webhooks, userCacheService: this.userCacheService, requestCache: params.requestCache, resolveSource: this.sourceResolver(params.requestCache), }); } async createWebhook(params: WebhookCreateParams): Promise { const webhook = await this.webhookService.createWebhook( { userId: params.userId, viewer: params.viewer, channelId: params.channelId, data: params.data, }, params.auditLogReason ?? null, ); return mapWebhookToResponseWithCache({ webhook, userCacheService: this.userCacheService, requestCache: params.requestCache, }); } async getWebhook(params: WebhookGetByUserParams): Promise; async getWebhook(params: WebhookGetByTokenParams): Promise; async getWebhook(params: WebhookGetParams): Promise { if ('token' in params) { const webhook = await this.webhookService.getWebhookByToken({webhookId: params.webhookId, token: params.token}); return mapWebhookToTokenResponse(webhook); } const webhook = await this.webhookService.getWebhook({ userId: params.userId, viewer: params.viewer, webhookId: params.webhookId, }); return mapWebhookToResponseWithCache({ webhook, userCacheService: this.userCacheService, requestCache: params.requestCache, resolveSource: this.sourceResolver(params.requestCache), }); } async updateWebhook(params: WebhookUpdateByUserParams): Promise; async updateWebhook(params: WebhookUpdateByTokenParams): Promise; async updateWebhook(params: WebhookUpdateParams): Promise { if ('token' in params) { const webhook = await this.webhookService.updateWebhookByToken({ webhookId: params.webhookId, token: params.token, data: params.data, }); return mapWebhookToTokenResponse(webhook); } const webhook = await this.webhookService.updateWebhook( { userId: params.userId, viewer: params.viewer, webhookId: params.webhookId, data: params.data, }, params.auditLogReason ?? null, ); return mapWebhookToResponseWithCache({ webhook, userCacheService: this.userCacheService, requestCache: params.requestCache, resolveSource: this.sourceResolver(params.requestCache), }); } async deleteWebhook(params: WebhookDeleteParams): Promise { if ('token' in params) { await this.webhookService.deleteWebhookByToken({webhookId: params.webhookId, token: params.token}); return; } await this.webhookService.deleteWebhook( { userId: params.userId, viewer: params.viewer, webhookId: params.webhookId, }, params.auditLogReason ?? null, ); } async executeWebhook(params: WebhookExecuteParams): Promise { const message = await this.webhookService.executeWebhook({ webhookId: params.webhookId, token: params.token, threadId: params.threadId, data: params.data, requestCache: params.requestCache, }); if (!params.wait) { return null; } return this.mapMessageResponse(message, params.requestCache); } async getWebhookMessage(params: WebhookGetMessageParams): Promise { const message = await this.webhookService.getWebhookMessage({ webhookId: params.webhookId, token: params.token, threadId: params.threadId, messageId: params.messageId, }); return this.mapMessageResponse(message, params.requestCache); } async editWebhookMessage(params: WebhookEditMessageParams): Promise { const message = await this.webhookService.editWebhookMessage({ webhookId: params.webhookId, token: params.token, threadId: params.threadId, messageId: params.messageId, data: params.data, requestCache: params.requestCache, }); return this.mapMessageResponse(message, params.requestCache); } async deleteWebhookMessage(params: WebhookDeleteMessageParams): Promise { await this.webhookService.deleteWebhookMessage(params); } async executeGitHubWebhook(params: WebhookExecuteGitHubParams): Promise { await this.webhookService.executeGitHubWebhook(params); } async executeInstatusWebhook(params: WebhookExecuteInstatusParams): Promise { await this.webhookService.executeInstatusWebhook(params); } async executeLibredeskWebhook(params: WebhookExecuteLibredeskParams): Promise { await this.webhookService.executeLibredeskWebhook(params); } async executeSlackWebhook(params: WebhookExecuteSlackParams): Promise { await this.webhookService.executeWebhook({ webhookId: params.webhookId, token: params.token, threadId: params.threadId, data: transformSlackWebhookRequest(params.data), requestCache: params.requestCache, service: true, }); } async handleLiveKitWebhook(params: LiveKitWebhookParams): Promise { if (!Config.voice.enabled) { return new Response('Voice not enabled', {status: 404}); } if (!this.liveKitWebhookService) { return new Response('LiveKit webhook service not available', {status: 503}); } const response = await this.liveKitWebhookService.handleWebhookRequest({ body: params.body, authHeader: params.authHeader, }); return new Response(response.body, {status: response.status}); } async handleSweegoWebhook(params: SweegoWebhookParams): Promise { if (!Config.email.enabled) { return new Response('Email not enabled', {status: 404}); } const response = await this.sweegoWebhookService.handleWebhook({ body: params.body, webhookId: params.webhookId, timestamp: params.timestamp, signature: params.signature, secret: Config.email.webhookSecret, }); return new Response(response.body, {status: response.status}); } private async mapMessageResponse(message: Message, _requestCache: RequestCache): Promise { const channel = await this.channelRepository.channelData.findUnique(message.channelId); const response = await createMessageResponseDataService().buildMessage({ userId: createUserID(0n), message, access: { sourceGuildId: channel?.guildId ?? null, messageHistoryCutoff: null, canReadMessageHistory: true, }, }); const [shaped] = maskThreadArtifactsFor(SYSTEM_THREAD_VIEWER, channel?.guildId ?? null, [response]); return shaped ?? response; } }