@overlaysymphony/twitch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +9 -0
  2. package/package.json +40 -0
  3. package/src/authentication/authentication.ts +107 -0
  4. package/src/authentication/index.ts +1 -0
  5. package/src/chat/chat.ts +160 -0
  6. package/src/chat/index.ts +6 -0
  7. package/src/chat/interfaces/events.ts +269 -0
  8. package/src/chat/interfaces/index.ts +9 -0
  9. package/src/chat/parser.ts +265 -0
  10. package/src/eventsub/events/channel.ad_break.begin.ts +51 -0
  11. package/src/eventsub/events/channel.ban.ts +59 -0
  12. package/src/eventsub/events/channel.channel_points_custom_reward._.ts +29 -0
  13. package/src/eventsub/events/channel.channel_points_custom_reward.add.ts +75 -0
  14. package/src/eventsub/events/channel.channel_points_custom_reward.remove.ts +77 -0
  15. package/src/eventsub/events/channel.channel_points_custom_reward.update.ts +77 -0
  16. package/src/eventsub/events/channel.channel_points_custom_reward_redemption._.ts +10 -0
  17. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.add.ts +59 -0
  18. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.update.ts +59 -0
  19. package/src/eventsub/events/channel.charity_campaign.donate.ts +66 -0
  20. package/src/eventsub/events/channel.charity_campaign.progress.ts +67 -0
  21. package/src/eventsub/events/channel.charity_campaign.start.ts +69 -0
  22. package/src/eventsub/events/channel.charity_campaign.stop.ts +69 -0
  23. package/src/eventsub/events/channel.chat.clear.ts +42 -0
  24. package/src/eventsub/events/channel.chat.clear_user_messages.ts +48 -0
  25. package/src/eventsub/events/channel.chat.message_delete.ts +50 -0
  26. package/src/eventsub/events/channel.chat.notification.ts +50 -0
  27. package/src/eventsub/events/channel.cheer.ts +51 -0
  28. package/src/eventsub/events/channel.follow.ts +50 -0
  29. package/src/eventsub/events/channel.goal.begin.ts +51 -0
  30. package/src/eventsub/events/channel.goal.end.ts +55 -0
  31. package/src/eventsub/events/channel.goal.progress.ts +51 -0
  32. package/src/eventsub/events/channel.guest_star_guest.update.ts +66 -0
  33. package/src/eventsub/events/channel.guest_star_session.begin.ts +46 -0
  34. package/src/eventsub/events/channel.guest_star_session.end.ts +48 -0
  35. package/src/eventsub/events/channel.guest_star_settings.update.ts +50 -0
  36. package/src/eventsub/events/channel.hype_train._.ts +12 -0
  37. package/src/eventsub/events/channel.hype_train.begin.ts +57 -0
  38. package/src/eventsub/events/channel.hype_train.end.ts +55 -0
  39. package/src/eventsub/events/channel.hype_train.progress.ts +70 -0
  40. package/src/eventsub/events/channel.moderator.add.ts +45 -0
  41. package/src/eventsub/events/channel.moderator.remove.ts +45 -0
  42. package/src/eventsub/events/channel.poll._.ts +26 -0
  43. package/src/eventsub/events/channel.poll.begin.ts +55 -0
  44. package/src/eventsub/events/channel.poll.end.ts +57 -0
  45. package/src/eventsub/events/channel.poll.progress.ts +55 -0
  46. package/src/eventsub/events/channel.prediction._.ts +25 -0
  47. package/src/eventsub/events/channel.prediction.begin.ts +51 -0
  48. package/src/eventsub/events/channel.prediction.end.ts +55 -0
  49. package/src/eventsub/events/channel.prediction.lock.ts +51 -0
  50. package/src/eventsub/events/channel.prediction.progress.ts +51 -0
  51. package/src/eventsub/events/channel.raid.ts +49 -0
  52. package/src/eventsub/events/channel.shield_mode.begin.ts +50 -0
  53. package/src/eventsub/events/channel.shield_mode.end.ts +50 -0
  54. package/src/eventsub/events/channel.shoutout.create.ts +62 -0
  55. package/src/eventsub/events/channel.shoutout.receive.ts +52 -0
  56. package/src/eventsub/events/channel.subscribe.ts +49 -0
  57. package/src/eventsub/events/channel.subscription.end.ts +49 -0
  58. package/src/eventsub/events/channel.subscription.gift.ts +53 -0
  59. package/src/eventsub/events/channel.subscription.message.ts +67 -0
  60. package/src/eventsub/events/channel.unban.ts +51 -0
  61. package/src/eventsub/events/channel.update.ts +49 -0
  62. package/src/eventsub/events/index.ts +284 -0
  63. package/src/eventsub/events/stream.offline.ts +39 -0
  64. package/src/eventsub/events/stream.online.ts +45 -0
  65. package/src/eventsub/events/user.update.ts +45 -0
  66. package/src/eventsub/events-helpers.ts +29 -0
  67. package/src/eventsub/eventsub.ts +88 -0
  68. package/src/eventsub/index.ts +7 -0
  69. package/src/eventsub/messages.ts +34 -0
  70. package/src/helix/channel-points/custom-rewards.ts +63 -0
  71. package/src/helix/channel-points/index.ts +1 -0
  72. package/src/helix/helix.ts +89 -0
  73. package/src/helix/subscriptions/index.ts +1 -0
  74. package/src/helix/subscriptions/subscriptions.ts +122 -0
  75. package/src/helix/users/index.ts +1 -0
  76. package/src/helix/users/users.ts +42 -0
  77. package/src/helpers/alerts/alerts.ts +60 -0
  78. package/src/helpers/alerts/index.ts +2 -0
  79. package/src/helpers/charity/charity.ts +89 -0
  80. package/src/helpers/charity/index.ts +1 -0
  81. package/src/helpers/goal/goal.ts +38 -0
  82. package/src/helpers/goal/index.ts +1 -0
  83. package/src/helpers/hype-train/hype-train.ts +51 -0
  84. package/src/helpers/hype-train/index.ts +1 -0
  85. package/src/helpers/poll/index.ts +1 -0
  86. package/src/helpers/poll/poll.ts +63 -0
  87. package/src/helpers/prediction/index.ts +1 -0
  88. package/src/helpers/prediction/prediction.ts +66 -0
  89. package/src/helpers/redemption/index.ts +1 -0
  90. package/src/helpers/redemption/redemption.ts +42 -0
  91. package/src/helpers/status/index.ts +1 -0
  92. package/src/helpers/status/status.ts +61 -0
  93. package/src/setupTests.ts +0 -0
  94. package/src/ui/authentication.ts +230 -0
  95. package/src/ui/popup.ts +115 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # `@overlaysymphony/obs`
2
+
3
+ This module enables [Twitch](https://dev.twitch.tv/docs/) integrations for the [OverlaySymphony interactive streaming framework](https://github.com/OverlaySymphony/overlaysymphony), including authentication, chat, eventsub, helix, and several helpers.
4
+
5
+ # OverlaySymphony
6
+
7
+ An interactive streaming framework for orchestrating overlays, bots, and other services.
8
+
9
+ > Note: This project is still very much under development. I use it actively on my own stream, so all releases should be fully functional. I will do my best to follow semver, though I cannot make any guarantees about release timelines or stability at this time.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@overlaysymphony/twitch",
3
+ "version": "0.1.0",
4
+ "description": "Twitch module for the OverlaySymphony interactive streaming framework.",
5
+ "homepage": "https://github.com/OverlaySymphony/overlaysymphony",
6
+ "type": "module",
7
+ "exports": {
8
+ "./authentication": "./src/authentication/index.js",
9
+ "./chat": "./src/chat/index.js",
10
+ "./eventsub": "./src/eventsub/index.js",
11
+ "./helix/*": "./src/helix/*/index.js",
12
+ "./helpers/*": "./src/helpers/*/index.js",
13
+ "./ui/*": "./src/ui/*.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "scripts": {
17
+ "lint": "pnpm run \"/^lint-.*/\"",
18
+ "lint-typecheck": "tsc --noEmit",
19
+ "lint-eslint": "eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
20
+ "lint-prettier": "prettier --check .",
21
+ "lint-depcheck": "depcheck .",
22
+ "test": "vitest",
23
+ "dev": "echo TODO",
24
+ "clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo dist",
25
+ "build": "echo TODO"
26
+ },
27
+ "dependencies": {
28
+ "@overlaysymphony/core": "workspace:*"
29
+ },
30
+ "devDependencies": {
31
+ "@overlaysymphony/eslint-config": "workspace:*",
32
+ "@overlaysymphony/tooling": "workspace:*",
33
+ "@vitest/coverage-v8": "^2.1.2",
34
+ "depcheck": "catalog:",
35
+ "eslint": "catalog:",
36
+ "prettier": "catalog:",
37
+ "typescript": "catalog:",
38
+ "vitest": "^2.1.2"
39
+ }
40
+ }
@@ -0,0 +1,107 @@
1
+ import querystring from "@overlaysymphony/core/libs/querystring"
2
+
3
+ import { TwitchUser, getUsers } from "../helix/users/index.js"
4
+
5
+ export interface Authentication {
6
+ clientId: string
7
+ tokenType: "bearer"
8
+ accessToken: string
9
+ scope: string[]
10
+ expires: Date
11
+ user: TwitchUser
12
+ }
13
+
14
+ export type BareAuthentication = Omit<Authentication, "user">
15
+
16
+ interface TAValidation {
17
+ client_id: string
18
+ login: string
19
+ scope: string[]
20
+ user_id: string
21
+ expires_in: number
22
+ }
23
+
24
+ interface TAResult {
25
+ token_type: "bearer"
26
+ access_token: string
27
+ scope: string
28
+ }
29
+
30
+ const localStorageKey = "overlaysymphony:service:twitch"
31
+
32
+ export async function getAuthentication(): Promise<Authentication> {
33
+ const raw = localStorage.getItem(localStorageKey)
34
+ if (!raw) {
35
+ throw new Error("Twitch authentication missing!")
36
+ }
37
+
38
+ const cached = JSON.parse(raw) as BareAuthentication
39
+ cached.expires = new Date(cached.expires)
40
+
41
+ const [user] = await getUsers(cached as Authentication)
42
+
43
+ const authentication: Authentication = {
44
+ ...cached,
45
+ user,
46
+ }
47
+
48
+ return authentication
49
+ }
50
+
51
+ export function initiateAuthentication(
52
+ clientId: string,
53
+ redirect: string,
54
+ scope: string[],
55
+ ): string {
56
+ return `https://id.twitch.tv/oauth2/authorize?${querystring.stringify({
57
+ response_type: "token",
58
+ client_id: clientId,
59
+ redirect_uri: redirect,
60
+ scope: scope.join("+"),
61
+ state: clientId,
62
+ })}`
63
+ }
64
+
65
+ export async function validateAuthentication(
66
+ authentication: Omit<BareAuthentication, "expires">,
67
+ ): Promise<BareAuthentication> {
68
+ const response = await fetch("https://id.twitch.tv/oauth2/validate", {
69
+ method: "GET",
70
+ headers: {
71
+ Authorization: `Bearer ${authentication.accessToken}`,
72
+ },
73
+ })
74
+
75
+ if (!response.ok) {
76
+ throw new Error(await response.text())
77
+ }
78
+
79
+ const validated = (await response.json()) as TAValidation
80
+
81
+ if (validated.client_id !== authentication.clientId) {
82
+ throw new Error("ClientId mismatch.")
83
+ }
84
+
85
+ return {
86
+ ...authentication,
87
+ expires: new Date(Date.now() + validated.expires_in * 1000),
88
+ }
89
+ }
90
+
91
+ export async function authenticateResult(
92
+ clientId: string,
93
+ result: TAResult,
94
+ ): Promise<BareAuthentication> {
95
+ if (!result.token_type || !result.access_token || !result.scope) {
96
+ throw new Error("Invalid result.")
97
+ }
98
+
99
+ const authentication = await validateAuthentication({
100
+ clientId,
101
+ tokenType: result.token_type,
102
+ accessToken: result.access_token,
103
+ scope: result.scope.split("+"),
104
+ })
105
+
106
+ return authentication
107
+ }
@@ -0,0 +1 @@
1
+ export * from "./authentication.js"
@@ -0,0 +1,160 @@
1
+ import createDefer from "@overlaysymphony/core/libs/defer"
2
+ import createPubSub from "@overlaysymphony/core/libs/pubsub"
3
+
4
+ import { Authentication } from "../authentication/index.js"
5
+
6
+ import { TwitchChatEvent, TwitchChatEventType } from "./interfaces/index.js"
7
+ import parseCommand from "./parser.js"
8
+
9
+ type ChatListener = (callback: (event: TwitchChatEvent) => void) => () => void
10
+
11
+ type ChatSubscriber = <
12
+ EventType extends TwitchChatEventType,
13
+ Event extends TwitchChatEvent<EventType>,
14
+ >(
15
+ types: EventType[],
16
+ callback: (event: Event) => void,
17
+ ) => () => void
18
+
19
+ type ChatSender = (message: string) => void
20
+
21
+ type ChatMessageSubscriber = (
22
+ callback: (event: TwitchChatEvent<"PRIVMSG">) => void,
23
+ ) => () => void
24
+
25
+ type ChatCommandSubscriber = {
26
+ (
27
+ callback: (event: TwitchChatEvent<"PRIVMSG-COMMAND">) => void,
28
+ name?: never,
29
+ ): () => void
30
+ (
31
+ name: string,
32
+ callback: (event: TwitchChatEvent<"PRIVMSG-COMMAND">) => void,
33
+ ): () => void
34
+ }
35
+
36
+ export interface TwitchChat {
37
+ listen: ChatListener
38
+ subscribe: ChatSubscriber
39
+ send: ChatSender
40
+ onMessage: ChatMessageSubscriber
41
+ onCommand: ChatCommandSubscriber
42
+ }
43
+
44
+ export async function createChat(
45
+ authentication: Authentication,
46
+ channel: string = authentication.user.login,
47
+ ): Promise<TwitchChat> {
48
+ const { promise, resolve } = createDefer()
49
+
50
+ const pubsub = createPubSub<TwitchChatEvent>()
51
+ const socket = new WebSocket("wss://irc-ws.chat.twitch.tv:443")
52
+
53
+ socket.addEventListener("open", (connection) => {
54
+ socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands")
55
+ })
56
+
57
+ socket.addEventListener("message", ({ data }: { data: string }) => {
58
+ const messages = data.split("\r\n").filter(Boolean)
59
+ for (const input of messages) {
60
+ const command = parseCommand(input)
61
+ if (!command) {
62
+ continue
63
+ }
64
+
65
+ if (
66
+ command.type === "001" ||
67
+ command.type === "JOIN" ||
68
+ command.type === "USERSTATE" ||
69
+ command.type === "HOSTTARGET" ||
70
+ command.type === "NOTICE" ||
71
+ false
72
+ ) {
73
+ continue
74
+ }
75
+
76
+ if (command.type === "PING") {
77
+ socket.send(`PONG :${command.message}`)
78
+ continue
79
+ }
80
+
81
+ if (command.type === "CAP") {
82
+ socket.send(`PASS oauth:${authentication.accessToken}`)
83
+ socket.send(`NICK ${authentication.user.login}`)
84
+ continue
85
+ }
86
+
87
+ if (command.type === "RECONNECT") {
88
+ console.warn("The server is about to terminate for maintenance.")
89
+ continue
90
+ }
91
+
92
+ if (command.type === "GLOBALUSERSTATE") {
93
+ socket.send(`JOIN #${channel}`)
94
+ continue
95
+ }
96
+
97
+ if (command.type === "ROOMSTATE") {
98
+ resolve()
99
+ continue
100
+ }
101
+
102
+ pubsub.dispatch(command)
103
+ }
104
+ })
105
+
106
+ return promise.then(() => {
107
+ const listen: ChatListener = (callback) => {
108
+ return pubsub.subscribe((event) => {
109
+ // @ts-ignore
110
+ callback(event)
111
+ })
112
+ }
113
+
114
+ const subscribe: ChatSubscriber = (types, callback) => {
115
+ return pubsub.subscribe((event) => {
116
+ // @ts-ignore
117
+ if (types.includes(event.type)) {
118
+ // @ts-ignore
119
+ callback(event)
120
+ }
121
+ })
122
+ }
123
+
124
+ const send: ChatSender = (message) => {
125
+ socket.send(`PRIVMSG #${authentication.user.login} :${message}`)
126
+ }
127
+
128
+ const onMessage: ChatMessageSubscriber = (callback) => {
129
+ return pubsub.subscribe((event) => {
130
+ if (event.type === "PRIVMSG") {
131
+ callback(event)
132
+ }
133
+ })
134
+ }
135
+
136
+ const onCommand: ChatCommandSubscriber = (...args) => {
137
+ const name = typeof args[0] === "string" ? args[0] : undefined
138
+ const callback = typeof args[0] === "function" ? args[0] : args[1]
139
+ if (!callback) {
140
+ throw new Error("onCommand: Missing callback.")
141
+ }
142
+
143
+ return pubsub.subscribe((event) => {
144
+ if (event.type === "PRIVMSG-COMMAND") {
145
+ if (typeof name === "undefined" || event.command === name) {
146
+ callback(event)
147
+ }
148
+ }
149
+ })
150
+ }
151
+
152
+ return {
153
+ listen,
154
+ subscribe,
155
+ send,
156
+ onMessage,
157
+ onCommand,
158
+ }
159
+ })
160
+ }
@@ -0,0 +1,6 @@
1
+ export type {
2
+ TwitchChatEventType,
3
+ TwitchChatEvent,
4
+ } from "./interfaces/index.js"
5
+
6
+ export * from "./chat.js"
@@ -0,0 +1,269 @@
1
+ export interface ChatEventSource {
2
+ host: string
3
+ user?: string
4
+ }
5
+
6
+ type AllOrNothing<T> = T | Partial<Record<keyof T, undefined>>
7
+
8
+ type TagUserType = "staff" | "global_mod" | "admin" | undefined
9
+ type TagBadges = Record<string, number>
10
+ type TagBadgeInfo = Record<string, number>
11
+ type TagEmotes = Record<
12
+ string,
13
+ Array<[startPosition: string, endPosition: string]>
14
+ >
15
+
16
+ export type PingEvent = {
17
+ type: "PING"
18
+ message: string
19
+ source?: ChatEventSource
20
+ }
21
+
22
+ export type WelcomeEvent = {
23
+ type: "001" // Logged in (successfully authenticated)
24
+ channel: string
25
+ message: string
26
+ source?: ChatEventSource
27
+ }
28
+
29
+ export type CapabilitiesEvent = {
30
+ type: "CAP"
31
+ enabled: boolean
32
+ nickname: string
33
+ capabilities: string[]
34
+ source?: ChatEventSource
35
+ }
36
+
37
+ export type JoinEvent = {
38
+ type: "JOIN"
39
+ channel: string
40
+ source?: ChatEventSource
41
+ }
42
+
43
+ export type PartEvent = {
44
+ type: "PART"
45
+ channel: string
46
+ source?: ChatEventSource
47
+ }
48
+
49
+ export type GlobalUserStateEvent = {
50
+ type: "GLOBALUSERSTATE"
51
+ tags: {
52
+ userId: string
53
+ userType: TagUserType
54
+ displayName?: string
55
+ color?: string
56
+ badges?: TagBadges
57
+ badgeInfo?: TagBadgeInfo
58
+ emoteSets?: string[]
59
+ }
60
+ source?: ChatEventSource
61
+ }
62
+
63
+ export type ReconnectEvent = {
64
+ type: "RECONNECT"
65
+ source?: ChatEventSource
66
+ }
67
+
68
+ export type ClearChatEvent = {
69
+ type: "CLEARCHAT"
70
+ channel: string
71
+ tags: {
72
+ roomId: string
73
+ targetUserId: string
74
+ banDuration: number
75
+ }
76
+ source?: ChatEventSource
77
+ }
78
+
79
+ export type HostTargetEvent = {
80
+ type: "HOSTTARGET"
81
+ channel: string
82
+ source?: ChatEventSource
83
+ }
84
+
85
+ export type NoticeEvent = {
86
+ type: "NOTICE"
87
+ channel: string
88
+ tags: {
89
+ msgId: string
90
+ targetUserId: string
91
+ }
92
+ source?: ChatEventSource
93
+ }
94
+
95
+ export type RoomStateEvent = {
96
+ type: "ROOMSTATE"
97
+ channel: string
98
+ tags: {
99
+ roomId: string
100
+ slow: number
101
+ emoteOnly: boolean
102
+ followersOnly: boolean
103
+ subsOnly: boolean
104
+ }
105
+ source?: ChatEventSource
106
+ }
107
+
108
+ export type UserStateEvent = {
109
+ type: "USERSTATE"
110
+ channel: string
111
+ tags: {
112
+ id: string
113
+ userId: string
114
+ userType: TagUserType
115
+ displayName?: string
116
+ color?: string
117
+ badges?: TagBadges
118
+ badgeInfo?: TagBadgeInfo
119
+ emoteSets?: string[]
120
+
121
+ mod: boolean
122
+ subscriber: boolean
123
+ vip: boolean
124
+ turbo: boolean
125
+ }
126
+ source?: ChatEventSource
127
+ }
128
+
129
+ export type ClearMessageEvent = {
130
+ type: "CLEARMSG"
131
+ channel: string
132
+ tags: {
133
+ login: string
134
+ roomId: string
135
+ targetMsgId?: string
136
+ }
137
+ source?: ChatEventSource
138
+ }
139
+
140
+ export type UserNoticeEvent = {
141
+ type: "USERNOTICE"
142
+ channel: string
143
+ tags: {
144
+ id: string
145
+ userId: string
146
+ userType: TagUserType
147
+ displayName?: string
148
+ login: string
149
+ color?: string
150
+ badges?: TagBadges
151
+ badgeInfo?: TagBadgeInfo
152
+ emotes?: TagEmotes
153
+ roomId: string
154
+ systemMsg: string
155
+ msgId:
156
+ | "sub"
157
+ | "resub"
158
+ | "subgift"
159
+ | "submysterygift"
160
+ | "giftpaidupgrade"
161
+ | "rewardgift"
162
+ | "anongiftpaidupgrade"
163
+ | "raid"
164
+ | "unraid"
165
+ | "ritual"
166
+ | "bitsbadgetier"
167
+
168
+ mod: boolean
169
+ subscriber: boolean
170
+ vip: boolean
171
+ turbo: boolean
172
+ }
173
+ source?: ChatEventSource
174
+ }
175
+
176
+ export type ChatMessageEvent = {
177
+ type: "PRIVMSG"
178
+ channel: string
179
+ message: string
180
+ tags: {
181
+ id: string
182
+ userId: string
183
+ userType: TagUserType
184
+ displayName?: string
185
+ color?: string
186
+ badges?: TagBadges
187
+ badgeInfo?: TagBadgeInfo
188
+ emotes?: TagEmotes
189
+ roomId: string
190
+
191
+ firstMsg: boolean
192
+ mod: boolean
193
+ subscriber: boolean
194
+ vip: boolean
195
+ turbo: boolean
196
+ } & AllOrNothing<{
197
+ replyThreadParentMsgId: string
198
+ replyThreadParentUserLogin: string
199
+ replyParentMsgId: string
200
+ replyParentUserId: string
201
+ replyParentUserLogin: string
202
+ replyParentDisplayName: string
203
+ replyParentMsgBody: string
204
+ }> &
205
+ AllOrNothing<{
206
+ pinnedChatPaidIsSystemMessage: boolean
207
+ pinnedChatPaidLevel: string
208
+ pinnedChatPaidAmount: number
209
+ pinnedChatPaidExponent: number
210
+ pinnedChatPaidCurrency: string
211
+ }>
212
+ source?: ChatEventSource
213
+ }
214
+
215
+ export type WhisperMessageEvent = {
216
+ type: "WHISPER"
217
+ channel: string
218
+ message: string
219
+ tags: {
220
+ messageId: string
221
+ threadId: string
222
+ userId: string
223
+ userType: TagUserType
224
+ displayName?: string
225
+ color?: string
226
+ badges?: TagBadges
227
+ emotes?: TagEmotes
228
+ turbo: boolean
229
+ }
230
+ source?: ChatEventSource
231
+ }
232
+
233
+ export type ChatCommandEvent = {
234
+ type: "PRIVMSG-COMMAND"
235
+ channel: string
236
+ command: string
237
+ parameters?: string
238
+ tags: ChatMessageEvent["tags"]
239
+ source?: ChatEventSource
240
+ }
241
+
242
+ export type WhisperCommandEvent = {
243
+ type: "WHISPER-COMMAND"
244
+ channel: string
245
+ command: string
246
+ parameters?: string
247
+ tags: WhisperMessageEvent["tags"]
248
+ source?: ChatEventSource
249
+ }
250
+
251
+ export type ChatEvent =
252
+ | PingEvent
253
+ | WelcomeEvent
254
+ | CapabilitiesEvent
255
+ | JoinEvent
256
+ | PartEvent
257
+ | GlobalUserStateEvent
258
+ | ReconnectEvent
259
+ | ClearChatEvent
260
+ | HostTargetEvent
261
+ | NoticeEvent
262
+ | RoomStateEvent
263
+ | UserStateEvent
264
+ | ClearMessageEvent
265
+ | UserNoticeEvent
266
+ | ChatMessageEvent
267
+ | WhisperMessageEvent
268
+ | ChatCommandEvent
269
+ | WhisperCommandEvent
@@ -0,0 +1,9 @@
1
+ import { ChatEvent } from "./events.js"
2
+
3
+ export type { ChatEvent, ChatEventSource } from "./events.js"
4
+
5
+ export type TwitchChatEventType = ChatEvent["type"]
6
+
7
+ export type TwitchChatEvent<
8
+ Type extends TwitchChatEventType = TwitchChatEventType,
9
+ > = Extract<ChatEvent, { type: Type }>