Skip to content

Commit 53fc9d1

Browse files
author
Joan Reyero
authored
Get forum channels from Discord (#405)
1 parent b28de43 commit 53fc9d1

9 files changed

Lines changed: 148 additions & 29 deletions

File tree

backend/src/serverless/integrations/services/integrations/discordIntegrationService.ts

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
DiscordMembers,
66
DiscordMention,
77
DiscordStreamProcessResult,
8+
ProcessedChannel,
9+
ProcessedChannels,
810
} from '../../types/discordTypes'
911
import { DISCORD_CONFIG } from '../../../../config'
1012
import { DiscordMemberAttributes } from '../../../../database/attributes/member/discord'
@@ -20,7 +22,6 @@ import { IntegrationType, PlatformType } from '../../../../types/integrationEnum
2022
import { timeout } from '../../../../utils/timing'
2123
import Operations from '../../../dbOperations/operations'
2224
import { DiscordGrid } from '../../grid/discordGrid'
23-
import { Channels } from '../../types/regularTypes'
2425
import getChannels from '../../usecases/discord/getChannels'
2526
import getMembers from '../../usecases/discord/getMembers'
2627
import getMessages from '../../usecases/discord/getMessages'
@@ -29,6 +30,7 @@ import { sendNodeWorkerMessage } from '../../../utils/nodeWorkerSQS'
2930
import { NodeWorkerIntegrationProcessMessage } from '../../../../types/mq/nodeWorkerIntegrationProcessMessage'
3031
import { AddActivitiesSingle } from '../../types/messageTypes'
3132
import { singleOrDefault } from '../../../../utils/arrays'
33+
import getThreads from '../../usecases/discord/getThreads'
3234

3335
/* eslint class-methods-use-this: 0 */
3436

@@ -83,18 +85,24 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
8385
async preprocess(context: IStepContext): Promise<void> {
8486
const guildId = context.integration.integrationIdentifier
8587

86-
let channelsFromDiscordAPI: Channels = await getChannels(
88+
const fromDiscordApi: ProcessedChannels = await getChannels(
8789
{
8890
guildId,
8991
token: this.getToken(context),
9092
},
9193
this.logger(context),
9294
)
9395

96+
let channelsFromDiscordAPI: ProcessedChannel[] = fromDiscordApi.channels
97+
9498
const channels = context.integration.settings.channels
9599
? context.integration.settings.channels
96100
: []
97101

102+
const forumChannels = context.integration.settings.forumChannels
103+
? context.integration.settings.forumChannels
104+
: []
105+
98106
// Add bool new property to new channels
99107
channelsFromDiscordAPI = channelsFromDiscordAPI.map((c) => {
100108
if (channels.filter((a) => a.id === c.id).length <= 0) {
@@ -103,16 +111,46 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
103111
return c
104112
})
105113

114+
const threads = await getThreads(
115+
{
116+
guildId,
117+
token: this.getToken(context),
118+
},
119+
this.logger(context),
120+
)
121+
122+
const forumChannelsFromDiscordAPi = []
123+
124+
for (const thread of threads) {
125+
const forumChannel: any = lodash.find(fromDiscordApi.forumChannels, { id: thread.parentId })
126+
if (forumChannel) {
127+
forumChannelsFromDiscordAPi.push({
128+
...forumChannel,
129+
threadId: thread.id,
130+
new: forumChannels.filter((c) => c.id === forumChannel.id).length <= 0,
131+
threadName: thread.name,
132+
})
133+
}
134+
}
135+
106136
context.pipelineData = {
107137
settingsChannels: channels,
108138
channels: channelsFromDiscordAPI,
139+
forumChannels: forumChannelsFromDiscordAPi,
109140
channelsInfo: channelsFromDiscordAPI.reduce((acc, channel) => {
110141
acc[channel.id] = {
111142
name: channel.name,
112143
new: !!channel.new,
113144
}
114145
return acc
115146
}, {}),
147+
forumChannelsInfo: forumChannelsFromDiscordAPi.reduce((acc, forumChannel) => {
148+
acc[forumChannel.id] = {
149+
name: forumChannel.name,
150+
new: !!forumChannel.new,
151+
}
152+
return acc
153+
}, {}),
116154
guildId: context.integration.integrationIdentifier,
117155
}
118156
}
@@ -132,15 +170,27 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
132170
},
133171
]
134172

135-
return predefined.concat(
136-
context.pipelineData.channels.map((c) => ({
137-
value: 'channel',
138-
metadata: {
139-
id: c.id,
140-
page: '',
141-
},
142-
})),
143-
)
173+
return predefined
174+
.concat(
175+
context.pipelineData.channels.map((c) => ({
176+
value: 'channel',
177+
metadata: {
178+
id: c.id,
179+
page: '',
180+
},
181+
})),
182+
)
183+
.concat(
184+
context.pipelineData.forumChannels.map((c) => ({
185+
value: 'forumChannel',
186+
metadata: {
187+
id: c.threadId,
188+
page: '',
189+
forumChannelId: c.id,
190+
threadName: c.threadName,
191+
},
192+
})),
193+
)
144194
}
145195

146196
async processStream(
@@ -260,6 +310,15 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
260310
const { new: _, ...raw } = ch
261311
return raw
262312
})
313+
314+
context.integration.settings.forumChannels = lodash.uniqBy(
315+
context.pipelineData.forumChannels.map((ch) => {
316+
const { new: _, ...raw } = ch
317+
delete raw.threadId
318+
return raw
319+
}),
320+
(ch: any) => ch.id,
321+
)
263322
}
264323

265324
parseActivities(
@@ -330,7 +389,12 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
330389
const activities: AddActivitiesSingle[] = records.reduce((acc, record) => {
331390
let parent = ''
332391

333-
const channelInfo = context.pipelineData.channelsInfo[stream.metadata.id]
392+
const isForum = stream.metadata.forumChannelId !== undefined
393+
394+
let channelInfo = context.pipelineData.channelsInfo[stream.metadata.id]
395+
if (isForum) {
396+
channelInfo = context.pipelineData.forumChannelsInfo[stream.metadata.forumChannelId]
397+
}
334398

335399
if (!channelInfo) {
336400
const log = this.logger(context)
@@ -352,6 +416,7 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
352416
value: 'thread',
353417
metadata: {
354418
id: record.thread.id,
419+
forumChannelId: stream.metadata.forumChannelId,
355420
},
356421
})
357422

@@ -371,6 +436,8 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
371436
// record.parentId means that it's a reply
372437
else if (record.message_reference && record.message_reference.message_id) {
373438
parent = record.message_reference.message_id
439+
} else if (stream.value === 'forumChannel') {
440+
parent = stream.metadata.id
374441
}
375442

376443
let avatarUrl: string | boolean = false
@@ -382,19 +449,25 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
382449
const activityObject = {
383450
tenant: context.integration.tenantId,
384451
platform: PlatformType.DISCORD,
385-
type: 'message',
452+
type: isForum && record.id === parent ? 'thread_started' : 'message',
386453
sourceId: record.id,
387454
sourceParentId: parent,
388455
timestamp: moment(record.timestamp).utc().toDate(),
456+
...(stream.value === 'forumChannel' &&
457+
record.id === parent && { title: stream.metadata.threadName }),
389458
body: record.content
390459
? DiscordIntegrationService.replaceMentions(record.content, record.mentions)
391460
: '',
392461
url: `https://discordapp.com/channels/${context.pipelineData.guildId}/${stream.metadata.id}/${record.id}`,
393462
channel: channelInfo.name,
394463
attributes: {
395-
thread: record.thread !== undefined || stream.value === 'thread',
464+
thread:
465+
record.thread !== undefined ||
466+
stream.value === 'thread' ||
467+
stream.value === 'forumChannel',
396468
reactions: record.reactions ? record.reactions : [],
397469
attachments: record.attachments ? record.attachments : [],
470+
forum: isForum,
398471
},
399472
member: {
400473
username: record.author.username,
@@ -465,6 +538,7 @@ export class DiscordIntegrationService extends IntegrationServiceBase {
465538
return { fn: getMembers, arg: { guildId } }
466539
case 'channel':
467540
case 'thread':
541+
case 'forumChannel':
468542
return { fn: getMessages, arg: { channelId: stream.metadata.id } }
469543
default:
470544
throw new Error(`Unknown stream ${stream.value}!`)

backend/src/serverless/integrations/types/discordTypes.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@ export interface DiscordGetMembersInput {
2222
}
2323

2424
export interface DiscordChannel {
25+
parentId?: string
2526
id: string
2627
name: string
2728
thread?: boolean
29+
type?: number
2830
}
2931

3032
export type DiscordChannels = DiscordChannel[]
3133

34+
export interface DiscordChannelsOut {
35+
channels: DiscordChannels
36+
forumChannels: DiscordChannels
37+
}
38+
3239
export interface DiscordAuthor {
3340
id: string
3441
username: string
@@ -90,3 +97,15 @@ export interface DiscordGetMessagesOutput extends DiscordParsedReponse {
9097
export interface DiscordGetMembersOutput extends DiscordParsedReponse {
9198
records: DiscordMembers | []
9299
}
100+
101+
export type ProcessedChannel = {
102+
id: string
103+
name: string
104+
thread?: boolean
105+
new?: boolean
106+
}
107+
108+
export interface ProcessedChannels {
109+
channels: Array<ProcessedChannel>
110+
forumChannels: Array<ProcessedChannel>
111+
}

backend/src/serverless/integrations/types/regularTypes.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@ export type Repo = {
88

99
export type Repos = Array<Repo>
1010

11-
export type Channel = {
12-
id: string
13-
name: string
14-
thread?: boolean
15-
new?: boolean
16-
}
17-
18-
export type Channels = Array<Channel>
19-
2011
export type Endpoint = string
2112

2213
export type Endpoints = Array<Endpoint>

backend/src/serverless/integrations/usecases/discord/getChannels.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import axios from 'axios'
22
import {
33
DiscordChannel,
44
DiscordChannels,
5+
DiscordChannelsOut,
56
DiscordGetChannelsInput,
67
DiscordGetMessagesInput,
78
} from '../../types/discordTypes'
@@ -31,7 +32,7 @@ async function getChannels(
3132
input: DiscordGetChannelsInput,
3233
logger: Logger,
3334
tryChannels = true,
34-
): Promise<DiscordChannels> {
35+
): Promise<DiscordChannelsOut> {
3536
try {
3637
const config = {
3738
method: 'get',
@@ -44,6 +45,14 @@ async function getChannels(
4445
const response = await axios(config)
4546
const result: DiscordChannels = response.data
4647

48+
const forumChannels = result
49+
.filter((c) => c.type === 15)
50+
.map((c) => ({
51+
name: c.name,
52+
id: c.id,
53+
thread: true,
54+
}))
55+
4756
if (tryChannels) {
4857
const out: DiscordChannels = []
4958
for (const channel of result) {
@@ -67,13 +76,21 @@ async function getChannels(
6776
}
6877
}
6978
}
70-
return out
79+
return {
80+
channels: out,
81+
forumChannels,
82+
}
7183
}
7284

73-
return result.map((c) => ({
85+
const channelsOut = result.map((c) => ({
7486
name: c.name,
7587
id: c.id,
7688
}))
89+
90+
return {
91+
channels: channelsOut,
92+
forumChannels,
93+
}
7794
} catch (err) {
7895
logger.error({ err, input }, 'Error while getting channels from Discord')
7996
throw err

backend/src/serverless/integrations/usecases/discord/getThreads.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ async function getThreads(
2121
return result.map((c) => ({
2222
name: c.name,
2323
id: c.id,
24+
parentId: c.parent_id,
2425
thread: true,
2526
}))
2627
} catch (err) {

frontend/src/i18n/en.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ const en = {
349349
message: 'sent a message',
350350
replied: 'replied to a message',
351351
replied_thread: 'replied to a thread',
352+
started_thread: 'started a new thread',
352353
joined_guild: 'joined server',
353354
left_guild: 'left server'
354355
},

frontend/src/integrations/discord/components/activity/discord-activity-content.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<template>
22
<div>
33
<blockquote
4-
v-if="activity.parent && displayThread"
4+
v-if="
5+
activity.parent && displayThread && !isParentInForum
6+
"
57
class="relative px-3 border-l-4 text-gray-500 border-gray-200 text-xs leading-5 mb-4 parsed-body"
68
v-html="$sanitize($marked(activity.parent.body))"
79
/>
@@ -38,6 +40,11 @@ export default {
3840
required: false,
3941
default: true
4042
}
43+
},
44+
computed: {
45+
isParentInForum() {
46+
return this.activity.type === 'thread_started'
47+
}
4148
}
4249
}
4350
</script>

0 commit comments

Comments
 (0)