Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/49 event sub moderator #84

Merged
merged 3 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions bot/chat-bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { EventSubWsListener } from '@twurple/eventsub-ws';
import {
EventSubChannelBanEvent,
EventSubChannelCheerEvent,
EventSubChannelModeratorEvent,
EventSubChannelRedemptionAddEvent,
EventSubChannelUnbanEvent,
EventSubStreamOfflineEvent,
Expand All @@ -35,6 +36,7 @@ import {
BanEventHandler,
ChannelPointEventHandler,
CheerEventHandler,
ModeratorEventHandler,
StreamEventHandler,
} from './event-sub-handlers';
import InjectionTypes from '../dependency-management/types';
Expand All @@ -51,6 +53,7 @@ export default class ChatBot {
@inject(BanEventHandler) private banEventHandler: BanEventHandler,
@inject(ChannelPointEventHandler) private channelPointEventHandler: ChannelPointEventHandler,
@inject(CheerEventHandler) private cheerEventHandler: CheerEventHandler,
@inject(ModeratorEventHandler) private moderatorEventHandler: ModeratorEventHandler,
@inject(StreamEventHandler) private streamEventHandler: StreamEventHandler,
@inject(InjectionTypes.Logger) private logger: winston.Logger,
) {
Expand Down Expand Up @@ -126,6 +129,20 @@ export default class ChatBot {
},
);

this.eventSubWsListener.onChannelModeratorAdd(
environment.twitchBot.broadcaster.id,
(event: EventSubChannelModeratorEvent): void => {
this.moderatorEventHandler.addModerator(event);
},
);

this.eventSubWsListener.onChannelModeratorRemove(
environment.twitchBot.broadcaster.id,
(event: EventSubChannelModeratorEvent): void => {
this.moderatorEventHandler.removeModerator(event);
},
);

this.eventSubWsListener.onStreamOnline(
environment.twitchBot.broadcaster.id,
(event: EventSubStreamOnlineEvent): void => {
Expand Down
2 changes: 2 additions & 0 deletions bot/event-sub-handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import BanEventHandler from './ban-event.handler';
import ChannelPointEventHandler from './channel-point-event.handler';
import CheerEventHandler from './cheer-event.handler';
import ModeratorEventHandler from './moderator-event.handler';
import StreamEventHandler from './stream-event.handler';

export {
BanEventHandler,
ChannelPointEventHandler,
CheerEventHandler,
ModeratorEventHandler,
StreamEventHandler,
};
43 changes: 43 additions & 0 deletions bot/event-sub-handlers/moderator-event.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { EventSubChannelModeratorEvent } from '@twurple/eventsub-base';
import { inject, injectable } from 'inversify';
import winston from 'winston';
import InjectionTypes from '../../dependency-management/types';
import { ModeratorEvent } from '../../database';

@injectable()
export default class ModeratorEventHandler {
constructor(
@inject(InjectionTypes.Logger) private logger: winston.Logger,
) {
}

/**
* Records the event of a user being added as a moderator to the channel
* @param event Moderator Event
*/
async addModerator(event: EventSubChannelModeratorEvent): Promise<void> {
return ModeratorEvent
.addUserAsMod(event)
.catch((reason: any) => {
this.logger.error(`Unable to store Moderator Add event in DB >> ${reason}`);
})
.then(() => {
this.logger.info(`User ${event.userDisplayName}: added as moderator for ${event.broadcasterDisplayName} on ${(new Date()).toDateString()}`);
});
}

/**
* Records the event of a user being remove as a moderator to the channel
* @param event Moderator Event
*/
async removeModerator(event: EventSubChannelModeratorEvent): Promise<void> {
return ModeratorEvent
.removeUserAsMod(event)
.catch((reason: any) => {
this.logger.error(`Unable to store Moderator Remove event in DB >> ${reason}`);
})
.then(() => {
this.logger.info(`User ${event.userDisplayName}: removed as moderator for ${event.broadcasterDisplayName} on ${(new Date()).toDateString()}`);
});
}
}
5 changes: 2 additions & 3 deletions bot/event-sub-handlers/stream-event.handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ describe('Stream Event Handler Tests', () => {
.get<winston.Logger>(InjectionTypes.Logger);
});

// TODO: Write Tests
describe('Start Stream Event', () => {
it('should not run timeout, invoke unlurk all users, and save record in db', async () => {
// Arrange
Expand Down Expand Up @@ -81,9 +80,9 @@ describe('Stream Event Handler Tests', () => {
expect(expectedLogger.info)
.toHaveBeenCalledTimes(1);
expect(expectedLogger.info)
.toHaveBeenCalledWith(expect.stringContaining(event.id));
.toHaveBeenCalledWith(expect.stringContaining(`${event.id}`));
expect(expectedLogger.info)
.toHaveBeenCalledWith(expect.stringContaining(record.streamId));
.toHaveBeenCalledWith(expect.stringContaining(`${record.streamId}`));
expect(expectedLogger.error)
.not.toHaveBeenCalled();
});
Expand Down
12 changes: 7 additions & 5 deletions database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CheerEvent,
DeathCounts,
LurkingUsers,
ModeratorEvent,
Raiders,
StreamEventRecord,
SubscriptionGiftUsers,
Expand Down Expand Up @@ -40,15 +41,16 @@ const pgConfig: SequelizeOptions = {
},
logging: false,
models: [
Raiders,
LurkingUsers,
DeathCounts,
Subscribers,
SubscriptionGiftUsers,
BanEvent,
ChannelPointRedeem,
CheerEvent,
DeathCounts,
LurkingUsers,
ModeratorEvent,
Raiders,
StreamEventRecord,
SubscriptionGiftUsers,
Subscribers,
],
};

Expand Down
2 changes: 2 additions & 0 deletions database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ChannelPointRedeem from './models/channelPointRedeem.dbo';
import CheerEvent from './models/cheerEvent.dbo';
import DeathCounts from './models/deathCountRecord.dbo';
import LurkingUsers from './models/lurkingUser.dbo';
import ModeratorEvent from './models/moderatorEvent.dbo';
import Raiders from './models/raiders.dbo';
import StreamEventRecord from './models/stream-event-record.dbo';
import Subscribers from './models/subscribers.dbo';
Expand All @@ -15,6 +16,7 @@ export {
CheerEvent,
DeathCounts,
LurkingUsers,
ModeratorEvent,
Raiders,
StreamEventRecord,
SubscriptionType,
Expand Down
111 changes: 111 additions & 0 deletions database/models/moderatorEvent.dbo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { EventSubChannelModeratorEvent } from '@twurple/eventsub-base';
import { Column, DataType, Model, Table } from 'sequelize-typescript';

@Table({
// consider placing this in a schema to
// cover all Event Sub data
// schema: 'EventSubData',
tableName: 'ModeratorEvent',
paranoid: true,
})
export default class ModeratorEvent extends Model {
@Column({
type: DataType.DATE,
})
addDate: Date;

@Column({
type: DataType.DATE,
allowNull: true,
})
removeDate: Date;

/**
* The ID of the broadcaster.
*/
@Column({
type: DataType.STRING(20),
})
broadcasterId: string;

/**
* The name of the broadcaster.
*/
@Column({
type: DataType.STRING(40),
})
broadcasterName: string;

/**
* The display name of the broadcaster.
*/
@Column({
type: DataType.STRING(40),
})
broadcasterDisplayName: string;

/**
* The ID of the user target of the moderator event.
*/
@Column({
type: DataType.STRING(20),
})
userId: string;

/**
* The name of the user target of the moderator event.
*/
@Column({
type: DataType.STRING(40),
})
userName: string;

/**
* The display name of the user target of the moderator event.
*/
@Column({
type: DataType.STRING(40),
})
userDisplayName: string;

/**
* Maps add moderator event to DBO and saves a new event in the database
* @param event Moderator Event
* @returns Moderator Event Record
*/
static async addUserAsMod(event: EventSubChannelModeratorEvent): Promise<ModeratorEvent> {
const record: Partial<ModeratorEvent> = {
addDate: new Date(),
removeDate: null,
broadcasterId: event.broadcasterId,
broadcasterName: event.broadcasterName,
broadcasterDisplayName: event.broadcasterDisplayName,
userId: event.userId,
userName: event.userName,
userDisplayName: event.userDisplayName,
};

return this
.create(record);
}

/**
* Updates moderator event in DB with a remove date
* @param event Moderator Event
* @returns Moderator Event Record
*/
static async removeUserAsMod(event: EventSubChannelModeratorEvent): Promise<[number, ModeratorEvent[]]> {
return this
.update(
{ removeDate: new Date() },
{
where: {
removeDate: null,
broadcasterId: event.broadcasterId,
userId: event.userId,
},
returning: true,
},
);
}
}
4 changes: 3 additions & 1 deletion dependency-management/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
BanEventHandler,
ChannelPointEventHandler,
CheerEventHandler,
ModeratorEventHandler,
StreamEventHandler,
} from '../bot/event-sub-handlers';
import Broadcaster from '../bot/utilities/broadcaster';
Expand Down Expand Up @@ -110,9 +111,10 @@ SAContainer.bind<ICommandHandler>(InjectionTypes.CommandHandlers).to(UpTimeComma
SAContainer.bind<ICommandHandler>(InjectionTypes.CommandHandlers).to(WishListCommand);

// Event Sub Handlers
SAContainer.bind(BanEventHandler).toSelf();
SAContainer.bind(ChannelPointEventHandler).toSelf();
SAContainer.bind(CheerEventHandler).toSelf();
SAContainer.bind(BanEventHandler).toSelf();
SAContainer.bind(ModeratorEventHandler).toSelf();
SAContainer.bind(StreamEventHandler).toSelf();

// Bind dependencies to container
Expand Down
Loading