forked from robotty/dank-twitch-irc
-
Notifications
You must be signed in to change notification settings - Fork 5
/
privmsg.ts
200 lines (161 loc) · 6.69 KB
/
privmsg.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import { TwitchBadgesList } from "../badges";
import { Color } from "../color";
import { TwitchEmoteList } from "../emotes";
import { TwitchFlagList } from "../flags";
import { ChannelIRCMessage } from "../irc/channel-irc-message";
import {
IRCMessage,
requireNickname,
requireParameter,
} from "../irc/irc-message";
import { tagParserFor } from "../parser/tag-values";
import { UserState } from "./userstate";
// eslint-disable-next-line no-control-regex
const actionRegex = /^\u0001ACTION (.*)\u0001$/;
export function parseActionAndMessage(trailingParameter: string): {
isAction: boolean;
message: string;
} {
const match: RegExpExecArray | null = actionRegex.exec(trailingParameter);
if (match == null) {
return {
isAction: false,
message: trailingParameter,
};
} else {
return {
isAction: true,
message: match[1],
};
}
}
interface CheerPrivmsgMessage extends PrivmsgMessage {
readonly bits: number;
readonly bitsRaw: string;
}
interface ReplyPrivmsgMessage extends PrivmsgMessage {
readonly replyParentDisplayName: string;
readonly replyParentMsgBody: string;
readonly replyParentMsgID: string;
readonly replyParentUserID: string;
readonly replyParentUserLogin: string;
}
/**
* Omits `emoteSets` and `emoteSetsRaw` from {@link UserState} (because they are not sent
* for `PRIVMSG` messages)
*/
export type PrivmsgUserState = Omit<UserState, "emoteSets" | "emoteSetsRaw">;
export class PrivmsgMessage
extends ChannelIRCMessage
implements PrivmsgUserState
{
public readonly messageText: string;
public readonly isAction: boolean;
public readonly senderUsername: string;
public readonly senderUserID: string;
public readonly badgeInfo: TwitchBadgesList;
public readonly badgeInfoRaw: string;
public readonly badges: TwitchBadgesList;
public readonly badgesRaw: string;
public readonly bits: number | undefined;
public readonly bitsRaw: string | undefined;
public readonly color: Color | undefined;
public readonly colorRaw: string;
public readonly displayName: string;
public readonly emotes: TwitchEmoteList;
public readonly emotesRaw: string;
/**
* Can be an array of Twitch AutoMod flagged words, for use in moderation and/or filtering purposes.
*
* If the `flags` tag is missing or of a unparseable format, this will be `undefined`. This is unlike most other
* attributes which when missing or malformed will fail the message parsing. However since this attribute is
* completely undocumented we cannot rely on the `flags` tag being stable, so this soft fallback is used instead.
* While it will be a major version release if this attribute changes format in dank-twitch-irc, using this is still
* at your own risk since it may suddenly contain unexpected data or turn `undefined` one day as
* Twitch changes something. In short: **Use at your own risk** and make sure your
* implementation can handle the case where this is `undefined`.
*/
public readonly flags: TwitchFlagList | undefined;
/**
* Twitch AutoMod raw flags string.
*
* If the `flags` tag is missing or of a unparseable format, this will be `undefined`. This is unlike most other
* attributes which when missing or malformed will fail the message parsing. However since this attribute is
* completely undocumented we cannot rely on the `flags` tag being stable, so this soft fallback is used instead.
* In short, ensure your implementation can handle the case where this is `undefined` or is in
* a format you don't expect.
*/
public readonly flagsRaw: string | undefined;
public readonly replyParentDisplayName: string | undefined;
public readonly replyParentMessageBody: string | undefined;
public readonly replyParentMessageID: string | undefined;
public readonly replyParentUserID: string | undefined;
public readonly replyParentUserLogin: string | undefined;
public readonly messageID: string;
public readonly isMod: boolean;
public readonly isModRaw: string;
public readonly channelID: string;
public readonly serverTimestamp: Date;
public readonly serverTimestampRaw: string;
public constructor(ircMessage: IRCMessage) {
super(ircMessage);
const { isAction, message } = parseActionAndMessage(
requireParameter(this, 1)
);
this.messageText = message;
this.isAction = isAction;
this.senderUsername = requireNickname(this);
const tagParser = tagParserFor(this.ircTags);
this.channelID = tagParser.requireString("room-id");
this.senderUserID = tagParser.requireString("user-id");
this.badgeInfo = tagParser.requireBadges("badge-info");
this.badgeInfoRaw = tagParser.requireString("badge-info");
this.badges = tagParser.requireBadges("badges");
this.badgesRaw = tagParser.requireString("badges");
this.bits = tagParser.getInt("bits");
this.bitsRaw = tagParser.getString("bits");
this.color = tagParser.getColor("color");
this.colorRaw = tagParser.requireString("color");
// trim: Twitch workaround for unsanitized data, see https://github.com/robotty/dank-twitch-irc/issues/33
this.displayName = tagParser.requireString("display-name").trim();
this.emotes = tagParser.requireEmotes("emotes", this.messageText);
this.emotesRaw = tagParser.requireString("emotes");
this.flags = tagParser.getFlags("flags", this.messageText);
this.flagsRaw = tagParser.getString("flags");
this.replyParentDisplayName = tagParser.getTrimmedString(
"reply-parent-display-name"
);
this.replyParentMessageBody = tagParser.getString("reply-parent-msg-body");
this.replyParentMessageID = tagParser.getString("reply-parent-msg-id");
this.replyParentUserID = tagParser.getString("reply-parent-user-id");
this.replyParentUserLogin = tagParser.getString("reply-parent-user-login");
this.messageID = tagParser.requireString("id");
this.isMod = tagParser.requireBoolean("mod");
this.isModRaw = tagParser.requireString("mod");
this.serverTimestamp = tagParser.requireTimestamp("tmi-sent-ts");
this.serverTimestampRaw = tagParser.requireString("tmi-sent-ts");
}
/**
* Extracts a plain object only containing the fields defined by the
* {@link PrivmsgUserState} interface.
*/
public extractUserState(): PrivmsgUserState {
return {
badgeInfo: this.badgeInfo,
badgeInfoRaw: this.badgeInfoRaw,
badges: this.badges,
badgesRaw: this.badgesRaw,
color: this.color,
colorRaw: this.colorRaw,
displayName: this.displayName,
isMod: this.isMod,
isModRaw: this.isModRaw,
};
}
public isCheer(): this is CheerPrivmsgMessage {
return this.bits != null;
}
public isReply(): this is ReplyPrivmsgMessage {
return this.replyParentMessageID != null;
}
}