Skip to content

Commit

Permalink
add receiving discord notifications when tracked user lists a new item
Browse files Browse the repository at this point in the history
  • Loading branch information
radekm2000 committed May 30, 2024
1 parent 50795a2 commit 5b57812
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 18 deletions.
2 changes: 2 additions & 0 deletions server/ecommerce/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { AdminNotificationsModule } from './admin-notifications/admin-notificati
import { FeedbacksModule } from './feedbacks/feedbacks.module';
import { DiscordBotModule } from './discord-bot/discord-bot.module';
import { DiscordNotificationsModule } from './discord-notifications/discord-notifications.module';
import { ItemNotifierModule } from './discord-bot/src/commands/notifiers/item-notifier.module';

@Module({
imports: [
Expand All @@ -39,6 +40,7 @@ import { DiscordNotificationsModule } from './discord-notifications/discord-noti
FeedbacksModule,
DiscordBotModule,
DiscordNotificationsModule,
ItemNotifierModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
4 changes: 4 additions & 0 deletions server/ecommerce/src/discord-bot/discord-bot.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Product } from 'src/utils/entities/product.entity';
import { ProductNotificationService } from 'src/product-notification/product-notification.service';
import { ProductNotification } from 'src/utils/entities/product-notification.entity';
import { FollowersService } from 'src/followers/followers.service';
import { ItemNotifierService } from './src/commands/notifiers/item-notifier.service';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';

@Module({
controllers: [DiscordBotController],
Expand All @@ -22,6 +24,8 @@ import { FollowersService } from 'src/followers/followers.service';
ProductsService,
ProductNotificationService,
FollowersService,
ItemNotifierService,
DiscordNotificationsService,
],
imports: [
TypeOrmModule.forFeature([
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { ItemNotifierService } from './item-notifier.service';
import { UsersService } from 'src/users/users.service';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Follow } from 'src/utils/entities/followers.entity';
import { User } from 'discord.js';
import { Profile } from 'src/utils/entities/profile.entity';
import { Avatar } from 'src/utils/entities/avatar.entity';
import { Product } from 'src/utils/entities/product.entity';
import { ProductNotification } from 'src/utils/entities/product-notification.entity';

@Module({
imports: [
TypeOrmModule.forFeature([
Follow,
User,
Profile,
Avatar,
Product,
ProductNotification,
]),
],
providers: [ItemNotifierService, UsersService, DiscordNotificationsService],
exports: [ItemNotifierService],
})
export class ItemNotifierModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { ItemNotifier } from './item-notifier';
import { UsersService } from 'src/users/users.service';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';
import { Product } from 'src/utils/entities/product.entity';

@Injectable()
export class ItemNotifierService {
private readonly itemNotifier: ItemNotifier;

constructor(
private usersService: UsersService,
private discordNotificationsService: DiscordNotificationsService,
) {
this.itemNotifier = new ItemNotifier({
bot: this.discordNotificationsService.discordNotificationsBot,
usersService: this.usersService,
});
}

public notifyUsers = async (userId: number, product: Product) => {
return await this.itemNotifier.notifyUsers(userId, product);
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Product } from 'src/utils/entities/product.entity';
import { DiscordBot } from '../../discordbot';
import { UsersService } from 'src/users/users.service';
import { Follow } from 'src/utils/entities/followers.entity';
import { Profile } from 'src/utils/entities/profile.entity';
Expand All @@ -8,8 +7,9 @@ import { Avatar } from 'src/utils/entities/avatar.entity';
import { Message } from 'src/utils/entities/message.entity';
import { Review } from 'src/utils/entities/review.entity';
import { EmbedBuilder } from 'discord.js';
import { DiscordBotService } from 'src/discord-bot/discord-bot.service';
import { Injectable } from '@nestjs/common';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';
import { DiscordNotificationsBot } from 'src/discord-notifications/discord-notifications-bot';

type User = {
followings: Follow[];
Expand All @@ -30,23 +30,25 @@ type User = {
};

type Config = {
discordBotService: DiscordBotService;
bot: DiscordNotificationsBot;
usersService: UsersService;
};

@Injectable()
export class ItemNotifier {
private readonly discordBotService: DiscordBotService;
private readonly usersService: UsersService;
private readonly bot: DiscordNotificationsBot;

constructor(config: Config) {
this.discordBotService = config.discordBotService;
this.bot = config.bot;
this.usersService = config.usersService;
}

public notifyUsers = async (userId: number, product: Product) => {
const user = await this.usersService.getUserInfo(userId);
const usersToNotify = await this.getUsersToNotify(user);
console.log('ludzie do notifaj');
console.log(usersToNotify);

await this.notify(usersToNotify, product);
};
Expand All @@ -59,11 +61,10 @@ export class ItemNotifier {
};

private notify = async (usersToNotify: User[], product: Product) => {
const productImage = product.images[0].imageUrl;
const productAuthor = product.user.username;
await Promise.all(
usersToNotify.map((user) =>
this.discordBotService.discordBot.sendDM(user.discordId, {
this.bot.sendDM(user.discordId, {
embeds: [
new EmbedBuilder()
.setTitle(`**${productAuthor}** has listed a new item`)
Expand All @@ -73,8 +74,7 @@ export class ItemNotifier {
{ name: 'Category', value: product.category, inline: true },
])
.setAuthor({ name: productAuthor })
.setColor('#58b9ff')
.setImage(productImage),
.setColor('#58b9ff'),
],
}),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Logger } from '@nestjs/common';
import {
AutocompleteInteraction,
ChatInputCommandInteraction,
Client,
Events,
MessageCreateOptions,
REST,
Routes,
} from 'discord.js';
import { SlashCommand } from 'src/discord-bot/src/commands/slash-command';
import { InteractionError } from 'src/discord-bot/src/errors/interactionError';

type Config = {
bot: Client;
botToken: string;
commands: SlashCommand[];
botApplicationId: string;
};

export class DiscordNotificationsBot {
private readonly logger: Logger;
private readonly bot: Client;
private readonly botToken: string;
private readonly botApplicationId: string;
private readonly commands = new Map<string, SlashCommand>();

constructor(config: Config) {
this.logger = new Logger(DiscordNotificationsBot.name);
this.bot = config.bot;
this.botToken = config.botToken;
this.botApplicationId = config.botApplicationId;
config.commands.forEach((c) => this.commands.set(c.config.name, c));
}

public start = async () => {
await this.bot.login(this.botToken);
this.bot.on('ready', this.onReady);
};

private onReady = async () => {
await this.registerCommands();
this.listenToInteractions();
this.logger.log('DiscordNotificationsBot has been started');
};

private registerCommands = async () => {
const rest = new REST().setToken(this.botToken);
const url = Routes.applicationCommands(this.botApplicationId);
await rest.put(url, {
body: Array.from(this.commands.values()).map((c) => c.config.toJSON()),
});
};

private handleCommand = async (interaction: ChatInputCommandInteraction) => {
const command = this.commands.get(interaction.commandName);
if (!command) return;

try {
await command.execute(interaction);
} catch (error) {
const isInteractionError = error instanceof InteractionError;
const msg = isInteractionError
? error.message
: 'There was an error while executing this command!';

if (!isInteractionError) {
this.logger.error(
`Error when executing ${interaction.commandName} command`,
);
}
if (interaction.replied || interaction.deferred) {
this.logger.error(
`Error when executing ${interaction.commandName} command`,
);
await interaction.followUp({
content: msg,
ephemeral: true,
});
} else {
await interaction.reply({
content: msg,
ephemeral: true,
});
}
}
};

private listenToInteractions = () => {
this.bot.on(Events.InteractionCreate, async (interaction) => {
if (interaction.isChatInputCommand()) {
await this.handleCommand(interaction);
} else if (interaction.isAutocomplete()) {
await this.handleAutocomplete(interaction);
}
});
};

private handleAutocomplete = async (interaction: AutocompleteInteraction) => {
const command = this.commands.get(interaction.commandName);
if (!command) return;

try {
await command.autocomplete(interaction);
} catch (error) {
this.logger.error(
{ error },
`Error when executing the ${interaction.commandName} autocomplete`,
);
}
};

public sendDM = async (
userDiscordId: string,
message: MessageCreateOptions,
) => {
try {
await this.bot.users.send(userDiscordId, message);
} catch (error) {}
};
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import { Injectable } from '@nestjs/common';
import { DiscordBotService } from 'src/discord-bot/discord-bot.service';
import { ProductsService } from 'src/products/products.service';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Product } from 'src/utils/entities/product.entity';
import { DiscordNotificationsBot } from './discord-notifications-bot';
import { Client, IntentsBitField } from 'discord.js';

@Injectable()
export class DiscordNotificationsService {
public notify = async (userId: number, product: Product) => {
return;
};
export class DiscordNotificationsService implements OnModuleInit {
public discordNotificationsBot: DiscordNotificationsBot;
private readonly bot: Client;
private readonly botToken: string;
private readonly botApplicationId: string;

constructor() {
this.botToken = process.env.DISCORD_BOT_TOKEN;
this.botApplicationId = process.env.DISCORD_CLIENT_ID;
this.bot = new Client({
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
],
allowedMentions: {
parse: ['everyone', 'roles', 'users'],
},
});

this.discordNotificationsBot = new DiscordNotificationsBot({
commands: [],
bot: this.bot,
botToken: this.botToken,
botApplicationId: this.botApplicationId,
});
}

async onModuleInit() {
await this.discordNotificationsBot.start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { Avatar } from 'src/utils/entities/avatar.entity';
import { ProductsService } from 'src/products/products.service';
import { Product } from 'src/utils/entities/product.entity';
import { Image } from 'src/utils/entities/image.entity';
import { ItemNotifier } from 'src/discord-bot/src/commands/notifiers/item-notifier';
import { ItemNotifierService } from 'src/discord-bot/src/commands/notifiers/item-notifier.service';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';

@Module({
imports: [
Expand All @@ -25,7 +28,13 @@ import { Image } from 'src/utils/entities/image.entity';
]),
],
controllers: [ProductNotificationController],
providers: [ProductNotificationService, UsersService, ProductsService],
providers: [
ProductNotificationService,
UsersService,
ProductsService,
ItemNotifierService,
DiscordNotificationsService,
],
exports: [ProductNotificationService],
})
export class ProductNotificationModule {}
4 changes: 4 additions & 0 deletions server/ecommerce/src/products/products.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { ProductNotification } from 'src/utils/entities/product-notification.ent
import { StripeService } from 'src/stripe/stripe.service';
import { NodemailerService } from 'src/nodemailer/nodemailer.service';
import { ItemNotifier } from 'src/discord-bot/src/commands/notifiers/item-notifier';
import { DiscordNotificationsService } from 'src/discord-notifications/discord-notifications.service';
import { ItemNotifierService } from 'src/discord-bot/src/commands/notifiers/item-notifier.service';

@Module({
controllers: [ProductsController],
Expand All @@ -24,6 +26,8 @@ import { ItemNotifier } from 'src/discord-bot/src/commands/notifiers/item-notifi
ProductNotificationService,
StripeService,
NodemailerService,
ItemNotifierService,
DiscordNotificationsService,
],
imports: [
TypeOrmModule.forFeature([
Expand Down
4 changes: 3 additions & 1 deletion server/ecommerce/src/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { randomUUID } from 'crypto';
import { UsersService } from 'src/users/users.service';
import 'dotenv/config';
import { ItemNotifier } from 'src/discord-bot/src/commands/notifiers/item-notifier';
import { ItemNotifierService } from 'src/discord-bot/src/commands/notifiers/item-notifier.service';

const s3 = new S3Client({
region: process.env.BUCKET_REGION,
Expand All @@ -37,6 +38,7 @@ export class ProductsService {
@InjectRepository(Image)
private readonly imageRepository: Repository<Image>,
private usersService: UsersService,
private itemNotifierService: ItemNotifierService,
) {
this.logger = new Logger(ProductsService.name);
}
Expand Down Expand Up @@ -324,7 +326,7 @@ export class ProductsService {
newProduct,
);
const command = new PutObjectCommand(paramsToS3);

await this.itemNotifierService.notifyUsers(userId, newProduct);
try {
await s3.send(command);
} catch (error) {
Expand Down

0 comments on commit 5b57812

Please sign in to comment.