Skip to content

Commit 443bff5

Browse files
authored
Feature/49 event sub moderator (#84)
* Add `ModeratorEvent` table to DB * Add `ModeratorEventHandler` event sub to bot * Bug fix in `StreamEventHandler`
1 parent 096c41c commit 443bff5

File tree

8 files changed

+187
-9
lines changed

8 files changed

+187
-9
lines changed

bot/chat-bot.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { EventSubWsListener } from '@twurple/eventsub-ws';
1717
import {
1818
EventSubChannelBanEvent,
1919
EventSubChannelCheerEvent,
20+
EventSubChannelModeratorEvent,
2021
EventSubChannelRedemptionAddEvent,
2122
EventSubChannelUnbanEvent,
2223
EventSubStreamOfflineEvent,
@@ -35,6 +36,7 @@ import {
3536
BanEventHandler,
3637
ChannelPointEventHandler,
3738
CheerEventHandler,
39+
ModeratorEventHandler,
3840
StreamEventHandler,
3941
} from './event-sub-handlers';
4042
import InjectionTypes from '../dependency-management/types';
@@ -51,6 +53,7 @@ export default class ChatBot {
5153
@inject(BanEventHandler) private banEventHandler: BanEventHandler,
5254
@inject(ChannelPointEventHandler) private channelPointEventHandler: ChannelPointEventHandler,
5355
@inject(CheerEventHandler) private cheerEventHandler: CheerEventHandler,
56+
@inject(ModeratorEventHandler) private moderatorEventHandler: ModeratorEventHandler,
5457
@inject(StreamEventHandler) private streamEventHandler: StreamEventHandler,
5558
@inject(InjectionTypes.Logger) private logger: winston.Logger,
5659
) {
@@ -126,6 +129,20 @@ export default class ChatBot {
126129
},
127130
);
128131

132+
this.eventSubWsListener.onChannelModeratorAdd(
133+
environment.twitchBot.broadcaster.id,
134+
(event: EventSubChannelModeratorEvent): void => {
135+
this.moderatorEventHandler.addModerator(event);
136+
},
137+
);
138+
139+
this.eventSubWsListener.onChannelModeratorRemove(
140+
environment.twitchBot.broadcaster.id,
141+
(event: EventSubChannelModeratorEvent): void => {
142+
this.moderatorEventHandler.removeModerator(event);
143+
},
144+
);
145+
129146
this.eventSubWsListener.onStreamOnline(
130147
environment.twitchBot.broadcaster.id,
131148
(event: EventSubStreamOnlineEvent): void => {

bot/event-sub-handlers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import BanEventHandler from './ban-event.handler';
22
import ChannelPointEventHandler from './channel-point-event.handler';
33
import CheerEventHandler from './cheer-event.handler';
4+
import ModeratorEventHandler from './moderator-event.handler';
45
import StreamEventHandler from './stream-event.handler';
56

67
export {
78
BanEventHandler,
89
ChannelPointEventHandler,
910
CheerEventHandler,
11+
ModeratorEventHandler,
1012
StreamEventHandler,
1113
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { EventSubChannelModeratorEvent } from '@twurple/eventsub-base';
2+
import { inject, injectable } from 'inversify';
3+
import winston from 'winston';
4+
import InjectionTypes from '../../dependency-management/types';
5+
import { ModeratorEvent } from '../../database';
6+
7+
@injectable()
8+
export default class ModeratorEventHandler {
9+
constructor(
10+
@inject(InjectionTypes.Logger) private logger: winston.Logger,
11+
) {
12+
}
13+
14+
/**
15+
* Records the event of a user being added as a moderator to the channel
16+
* @param event Moderator Event
17+
*/
18+
async addModerator(event: EventSubChannelModeratorEvent): Promise<void> {
19+
return ModeratorEvent
20+
.addUserAsMod(event)
21+
.catch((reason: any) => {
22+
this.logger.error(`Unable to store Moderator Add event in DB >> ${reason}`);
23+
})
24+
.then(() => {
25+
this.logger.info(`User ${event.userDisplayName}: added as moderator for ${event.broadcasterDisplayName} on ${(new Date()).toDateString()}`);
26+
});
27+
}
28+
29+
/**
30+
* Records the event of a user being remove as a moderator to the channel
31+
* @param event Moderator Event
32+
*/
33+
async removeModerator(event: EventSubChannelModeratorEvent): Promise<void> {
34+
return ModeratorEvent
35+
.removeUserAsMod(event)
36+
.catch((reason: any) => {
37+
this.logger.error(`Unable to store Moderator Remove event in DB >> ${reason}`);
38+
})
39+
.then(() => {
40+
this.logger.info(`User ${event.userDisplayName}: removed as moderator for ${event.broadcasterDisplayName} on ${(new Date()).toDateString()}`);
41+
});
42+
}
43+
}

bot/event-sub-handlers/stream-event.handler.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ describe('Stream Event Handler Tests', () => {
3434
.get<winston.Logger>(InjectionTypes.Logger);
3535
});
3636

37-
// TODO: Write Tests
3837
describe('Start Stream Event', () => {
3938
it('should not run timeout, invoke unlurk all users, and save record in db', async () => {
4039
// Arrange
@@ -81,9 +80,9 @@ describe('Stream Event Handler Tests', () => {
8180
expect(expectedLogger.info)
8281
.toHaveBeenCalledTimes(1);
8382
expect(expectedLogger.info)
84-
.toHaveBeenCalledWith(expect.stringContaining(event.id));
83+
.toHaveBeenCalledWith(expect.stringContaining(`${event.id}`));
8584
expect(expectedLogger.info)
86-
.toHaveBeenCalledWith(expect.stringContaining(record.streamId));
85+
.toHaveBeenCalledWith(expect.stringContaining(`${record.streamId}`));
8786
expect(expectedLogger.error)
8887
.not.toHaveBeenCalled();
8988
});

database/database.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CheerEvent,
1010
DeathCounts,
1111
LurkingUsers,
12+
ModeratorEvent,
1213
Raiders,
1314
StreamEventRecord,
1415
SubscriptionGiftUsers,
@@ -40,15 +41,16 @@ const pgConfig: SequelizeOptions = {
4041
},
4142
logging: false,
4243
models: [
43-
Raiders,
44-
LurkingUsers,
45-
DeathCounts,
46-
Subscribers,
47-
SubscriptionGiftUsers,
4844
BanEvent,
4945
ChannelPointRedeem,
5046
CheerEvent,
47+
DeathCounts,
48+
LurkingUsers,
49+
ModeratorEvent,
50+
Raiders,
5151
StreamEventRecord,
52+
SubscriptionGiftUsers,
53+
Subscribers,
5254
],
5355
};
5456

database/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ChannelPointRedeem from './models/channelPointRedeem.dbo';
33
import CheerEvent from './models/cheerEvent.dbo';
44
import DeathCounts from './models/deathCountRecord.dbo';
55
import LurkingUsers from './models/lurkingUser.dbo';
6+
import ModeratorEvent from './models/moderatorEvent.dbo';
67
import Raiders from './models/raiders.dbo';
78
import StreamEventRecord from './models/stream-event-record.dbo';
89
import Subscribers from './models/subscribers.dbo';
@@ -15,6 +16,7 @@ export {
1516
CheerEvent,
1617
DeathCounts,
1718
LurkingUsers,
19+
ModeratorEvent,
1820
Raiders,
1921
StreamEventRecord,
2022
SubscriptionType,

database/models/moderatorEvent.dbo.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { EventSubChannelModeratorEvent } from '@twurple/eventsub-base';
2+
import { Column, DataType, Model, Table } from 'sequelize-typescript';
3+
4+
@Table({
5+
// consider placing this in a schema to
6+
// cover all Event Sub data
7+
// schema: 'EventSubData',
8+
tableName: 'ModeratorEvent',
9+
paranoid: true,
10+
})
11+
export default class ModeratorEvent extends Model {
12+
@Column({
13+
type: DataType.DATE,
14+
})
15+
addDate: Date;
16+
17+
@Column({
18+
type: DataType.DATE,
19+
allowNull: true,
20+
})
21+
removeDate: Date;
22+
23+
/**
24+
* The ID of the broadcaster.
25+
*/
26+
@Column({
27+
type: DataType.STRING(20),
28+
})
29+
broadcasterId: string;
30+
31+
/**
32+
* The name of the broadcaster.
33+
*/
34+
@Column({
35+
type: DataType.STRING(40),
36+
})
37+
broadcasterName: string;
38+
39+
/**
40+
* The display name of the broadcaster.
41+
*/
42+
@Column({
43+
type: DataType.STRING(40),
44+
})
45+
broadcasterDisplayName: string;
46+
47+
/**
48+
* The ID of the user target of the moderator event.
49+
*/
50+
@Column({
51+
type: DataType.STRING(20),
52+
})
53+
userId: string;
54+
55+
/**
56+
* The name of the user target of the moderator event.
57+
*/
58+
@Column({
59+
type: DataType.STRING(40),
60+
})
61+
userName: string;
62+
63+
/**
64+
* The display name of the user target of the moderator event.
65+
*/
66+
@Column({
67+
type: DataType.STRING(40),
68+
})
69+
userDisplayName: string;
70+
71+
/**
72+
* Maps add moderator event to DBO and saves a new event in the database
73+
* @param event Moderator Event
74+
* @returns Moderator Event Record
75+
*/
76+
static async addUserAsMod(event: EventSubChannelModeratorEvent): Promise<ModeratorEvent> {
77+
const record: Partial<ModeratorEvent> = {
78+
addDate: new Date(),
79+
removeDate: null,
80+
broadcasterId: event.broadcasterId,
81+
broadcasterName: event.broadcasterName,
82+
broadcasterDisplayName: event.broadcasterDisplayName,
83+
userId: event.userId,
84+
userName: event.userName,
85+
userDisplayName: event.userDisplayName,
86+
};
87+
88+
return this
89+
.create(record);
90+
}
91+
92+
/**
93+
* Updates moderator event in DB with a remove date
94+
* @param event Moderator Event
95+
* @returns Moderator Event Record
96+
*/
97+
static async removeUserAsMod(event: EventSubChannelModeratorEvent): Promise<[number, ModeratorEvent[]]> {
98+
return this
99+
.update(
100+
{ removeDate: new Date() },
101+
{
102+
where: {
103+
removeDate: null,
104+
broadcasterId: event.broadcasterId,
105+
userId: event.userId,
106+
},
107+
returning: true,
108+
},
109+
);
110+
}
111+
}

dependency-management/inversify.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
BanEventHandler,
5454
ChannelPointEventHandler,
5555
CheerEventHandler,
56+
ModeratorEventHandler,
5657
StreamEventHandler,
5758
} from '../bot/event-sub-handlers';
5859
import Broadcaster from '../bot/utilities/broadcaster';
@@ -110,9 +111,10 @@ SAContainer.bind<ICommandHandler>(InjectionTypes.CommandHandlers).to(UpTimeComma
110111
SAContainer.bind<ICommandHandler>(InjectionTypes.CommandHandlers).to(WishListCommand);
111112

112113
// Event Sub Handlers
114+
SAContainer.bind(BanEventHandler).toSelf();
113115
SAContainer.bind(ChannelPointEventHandler).toSelf();
114116
SAContainer.bind(CheerEventHandler).toSelf();
115-
SAContainer.bind(BanEventHandler).toSelf();
117+
SAContainer.bind(ModeratorEventHandler).toSelf();
116118
SAContainer.bind(StreamEventHandler).toSelf();
117119

118120
// Bind dependencies to container

0 commit comments

Comments
 (0)