Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5f4dbf1
chore: install discord.js for discord event ingestion workflow
Behzad-rabiei May 12, 2025
abe033d
chore: update togethercrew db to use repo pattern
Behzad-rabiei May 12, 2025
795acb4
feat: add discord channel activity
Behzad-rabiei May 13, 2025
00d4334
chore: add discord event types to use in event ingestion workflow
Behzad-rabiei May 13, 2025
99c26ae
feat: add discord event ingestion workflow
Behzad-rabiei May 13, 2025
6e24177
chore: add sample usage to test the workflow
Behzad-rabiei May 13, 2025
2271b4a
feat: add discord channel, role, member activities
Behzad-rabiei May 13, 2025
b4032a9
chore: add interfaces to use in discord event ingestion workflow
Behzad-rabiei May 13, 2025
d49c3c2
feat: setup member and role events
Behzad-rabiei May 13, 2025
f199a18
docs: add example for discord event ingestion client
Behzad-rabiei May 13, 2025
80aa201
docs: add example for discord event ingestion client
Behzad-rabiei May 13, 2025
6001436
feat: add discord message events activities, types
Behzad-rabiei May 13, 2025
d3808b9
feat: add discord user update event types
Behzad-rabiei May 13, 2025
4e845b5
feat: add discord transformers
Behzad-rabiei May 14, 2025
b15e919
feat: setup all discord events
Behzad-rabiei May 18, 2025
092c43e
style: format the code
Behzad-rabiei May 18, 2025
e913b5d
chore: install discord-api-types for discord gateway events type usage
Behzad-rabiei May 22, 2025
6006e7e
feat: add mapper activites
Behzad-rabiei May 22, 2025
6607262
feat: add mapper activites
Behzad-rabiei May 22, 2025
9ffb62b
feat: added ma and persistence activities
Behzad-rabiei May 28, 2025
9cc6259
feat: add ignored user and channel selection activities
Behzad-rabiei May 29, 2025
a12f91d
chore: update example
Behzad-rabiei May 29, 2025
c5792a0
style: format the code
Behzad-rabiei Jun 1, 2025
0e38a3e
fix: solve the conflixts
Behzad-rabiei Jun 3, 2025
737a46f
fix: pnpm i
Behzad-rabiei Jun 3, 2025
6da2b32
style: format the code
Behzad-rabiei Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,19 @@
"packageManager": "pnpm@10.11.0",
"dependencies": {
"@aws-sdk/client-s3": "^3.758.0",
"@discordjs/rest": "^2.5.0",
"@discordjs/ws": "^2.0.2",
"@temporalio/activity": "^1.11.7",
"@temporalio/client": "^1.11.7",
"@temporalio/common": "^1.11.8",
"@temporalio/worker": "^1.11.7",
"@temporalio/workflow": "^1.11.7",
"@togethercrew.dev/db": "^3.3.0",
"@togethercrew.dev/db": "^3.10.0",
"@togethercrew.dev/tc-messagebroker": "^0.0.51",
"axios": "^1.8.3",
"bottleneck": "^2.19.5",
"discord-api-types": "^0.38.8",
"discord.js": "^14.19.3",
"dotenv": "^16.4.7",
"grammy": "^1.35.0",
"https-proxy-agent": "^7.0.6",
Expand Down
2,606 changes: 1,697 additions & 909 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions src/activities/discord/gateway/channelSelection.activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Snowflake } from 'discord.js';

import { PlatformNames, platformRepository } from '@togethercrew.dev/db';

import parentLogger from '../../../config/logger.config';

const logger = parentLogger.child({
activity: 'discord:event:channel-selection',
});

export async function isChannelSelected(
guildId: string,
channelId: Snowflake,
): Promise<boolean> {
try {
const platform = await platformRepository.findOne({
name: PlatformNames.Discord,
'metadata.id': guildId,
});

const selected: Snowflake[] | undefined =
platform?.metadata?.selectedChannels;
const isSelected = !selected?.length || selected.includes(channelId);

logger.debug(
{ guildId, channelId, selected },
isSelected ? 'channel accepted' : 'channel skipped',
);
return isSelected;
} catch (err) {
logger.error(
{ err, guildId, channelId },
'channel-filter error, defaulting to true',
);
return true;
}
}
21 changes: 21 additions & 0 deletions src/activities/discord/gateway/ignoreUser.activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Snowflake } from 'discord.js';

import parentLogger from '../../../config/logger.config';
import { GUILD_IGNORED_USERS } from '../../../shared/constants/discordGateway.constant';

const logger = parentLogger.child({
activity: 'discord:event:ignore-user',
});

function getIgnoredUsersForGuild(guildId: string): Snowflake[] {
return GUILD_IGNORED_USERS[guildId] || [];
}

export async function isUserIgnored(
guildId: string,
userId: Snowflake,
): Promise<boolean> {
const ignored = getIgnoredUsersForGuild(guildId).includes(userId);
if (ignored) logger.debug({ guildId, userId }, 'user ignored');
return ignored;
}
4 changes: 4 additions & 0 deletions src/activities/discord/gateway/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './channelSelection.activity';
export * from './ignoreUser.activity';
export * from './mapping.activity';
export * from './persistence.activity';
174 changes: 174 additions & 0 deletions src/activities/discord/gateway/mapping.activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
GatewayChannelCreateDispatchData,
GatewayChannelDeleteDispatchData,
GatewayChannelUpdateDispatchData,
GatewayGuildMemberAddDispatchData,
GatewayGuildMemberRemoveDispatchData,
GatewayGuildMemberUpdateDispatchData,
GatewayGuildRoleCreateDispatchData,
GatewayGuildRoleDeleteDispatchData,
GatewayGuildRoleUpdateDispatchData,
GatewayMessageCreateDispatchData,
GatewayMessageDeleteBulkDispatchData,
GatewayMessageDeleteDispatchData,
GatewayMessageReactionAddDispatchData,
GatewayMessageReactionRemoveAllDispatchData,
GatewayMessageReactionRemoveDispatchData,
GatewayMessageReactionRemoveEmojiDispatchData,
GatewayMessageUpdateDispatchData,
} from 'discord-api-types/v10';

import { IRawInfo } from '@togethercrew.dev/db';

import parentLogger from '../../../config/logger.config';
import {
ChannelMappers,
GuildMemberMappers,
MessageMappers,
RoleMappers,
} from '../../../workflows/discord/gateway/mappers';

const logger = parentLogger.child({ activity: 'discord:event:map' });

export async function mapChannelCreate(
payload: GatewayChannelCreateDispatchData,
) {
return ChannelMappers.add(payload);
}

export async function mapChannelUpdate(
payload: GatewayChannelUpdateDispatchData,
) {
return ChannelMappers.update(payload);
}

export async function mapChannelDelete(
payload: GatewayChannelDeleteDispatchData,
) {
return ChannelMappers.remove(payload);
}

export async function mapGuildMemberCreate(
payload: GatewayGuildMemberAddDispatchData,
) {
return GuildMemberMappers.add(payload);
}

export async function mapGuildMemberUpdate(
payload: GatewayGuildMemberUpdateDispatchData,
) {
return GuildMemberMappers.update(payload);
}

export async function mapGuildMemberDelete(
payload: GatewayGuildMemberRemoveDispatchData,
) {
return GuildMemberMappers.remove(payload);
}

export async function mapRoleCreate(
payload: GatewayGuildRoleCreateDispatchData,
) {
return RoleMappers.add(payload);
}

export async function mapRoleUpdate(
payload: GatewayGuildRoleUpdateDispatchData,
) {
return RoleMappers.update(payload);
}

export async function mapRoleDelete(
payload: GatewayGuildRoleDeleteDispatchData,
) {
return RoleMappers.remove(payload);
}

// Message mapping activities
export async function mapMessageCreate(
payload: GatewayMessageCreateDispatchData,
) {
logger.debug({ messageId: payload.id }, 'Mapping message create event');
return MessageMappers.create(payload);
}

export async function mapMessageUpdate(
payload: GatewayMessageUpdateDispatchData,
) {
logger.debug({ messageId: payload.id }, 'Mapping message update event');
return MessageMappers.update(payload);
}

export async function mapMessageDelete(
payload: GatewayMessageDeleteDispatchData,
) {
logger.debug({ messageId: payload.id }, 'Mapping message delete event');
// Return the message ID for deletion - no mapping needed
return payload.id;
}

export async function mapMessageDeleteBulk(
payload: GatewayMessageDeleteBulkDispatchData,
) {
logger.debug(
{ messageIds: payload.ids },
'Mapping bulk message delete event',
);
// Return the message IDs for bulk deletion - no mapping needed
return payload.ids;
}

export async function mapMessageReactionAdd(
payload: GatewayMessageReactionAddDispatchData,
existingRawInfo?: IRawInfo,
) {
logger.debug(
{
messageId: payload.message_id,
emoji: payload.emoji.name || payload.emoji.id,
userId: payload.user_id,
},
'Mapping reaction add event',
);
return MessageMappers.reactionAdd(payload, existingRawInfo);
}

export async function mapMessageReactionRemove(
payload: GatewayMessageReactionRemoveDispatchData,
existingRawInfo: IRawInfo,
) {
logger.debug(
{
messageId: payload.message_id,
emoji: payload.emoji.name || payload.emoji.id,
userId: payload.user_id,
},
'Mapping reaction remove event',
);
return MessageMappers.reactionRemove(payload, existingRawInfo);
}

export async function mapMessageReactionRemoveAll(
payload: GatewayMessageReactionRemoveAllDispatchData,
existingRawInfo: IRawInfo,
) {
logger.debug(
{ messageId: payload.message_id },
'Mapping reaction remove all event',
);
return MessageMappers.reactionRemoveAll(payload, existingRawInfo);
}

export async function mapMessageReactionRemoveEmoji(
payload: GatewayMessageReactionRemoveEmojiDispatchData,
existingRawInfo: IRawInfo,
) {
logger.debug(
{
messageId: payload.message_id,
emoji: payload.emoji.name || payload.emoji.id,
},
'Mapping reaction remove emoji event',
);
return MessageMappers.reactionRemoveEmoji(payload, existingRawInfo);
}
Loading