@overlaysymphony/twitch 0.1.1 → 0.2.1

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 (78) hide show
  1. package/README.md +1 -1
  2. package/package.json +15 -17
  3. package/src/authentication/authentication.ts +110 -54
  4. package/src/chat/chat.ts +9 -8
  5. package/src/chat/helpers.ts +36 -0
  6. package/src/chat/index.ts +1 -0
  7. package/src/chat/interfaces/events.ts +29 -29
  8. package/src/chat/interfaces/index.ts +1 -1
  9. package/src/chat/parser.ts +9 -20
  10. package/src/eventsub/events/channel.ad_break.begin.ts +1 -1
  11. package/src/eventsub/events/channel.ban.ts +1 -1
  12. package/src/eventsub/events/channel.channel_points_custom_reward.add.ts +2 -2
  13. package/src/eventsub/events/channel.channel_points_custom_reward.remove.ts +2 -2
  14. package/src/eventsub/events/channel.channel_points_custom_reward.update.ts +2 -2
  15. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.add.ts +2 -2
  16. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.update.ts +2 -2
  17. package/src/eventsub/events/channel.charity_campaign.donate.ts +1 -1
  18. package/src/eventsub/events/channel.charity_campaign.progress.ts +1 -1
  19. package/src/eventsub/events/channel.charity_campaign.start.ts +1 -1
  20. package/src/eventsub/events/channel.charity_campaign.stop.ts +1 -1
  21. package/src/eventsub/events/channel.chat.clear.ts +1 -1
  22. package/src/eventsub/events/channel.chat.clear_user_messages.ts +1 -1
  23. package/src/eventsub/events/channel.chat.message_delete.ts +1 -1
  24. package/src/eventsub/events/channel.chat.notification.ts +1 -1
  25. package/src/eventsub/events/channel.cheer.ts +1 -1
  26. package/src/eventsub/events/channel.follow.ts +1 -1
  27. package/src/eventsub/events/channel.goal.begin.ts +1 -1
  28. package/src/eventsub/events/channel.goal.end.ts +1 -1
  29. package/src/eventsub/events/channel.goal.progress.ts +1 -1
  30. package/src/eventsub/events/channel.guest_star_guest.update.ts +1 -1
  31. package/src/eventsub/events/channel.guest_star_session.begin.ts +1 -1
  32. package/src/eventsub/events/channel.guest_star_session.end.ts +1 -1
  33. package/src/eventsub/events/channel.guest_star_settings.update.ts +1 -1
  34. package/src/eventsub/events/channel.hype_train.begin.ts +2 -2
  35. package/src/eventsub/events/channel.hype_train.end.ts +2 -2
  36. package/src/eventsub/events/channel.hype_train.progress.ts +2 -2
  37. package/src/eventsub/events/channel.moderator.add.ts +1 -1
  38. package/src/eventsub/events/channel.moderator.remove.ts +1 -1
  39. package/src/eventsub/events/channel.poll.begin.ts +2 -2
  40. package/src/eventsub/events/channel.poll.end.ts +2 -2
  41. package/src/eventsub/events/channel.poll.progress.ts +2 -2
  42. package/src/eventsub/events/channel.prediction.begin.ts +2 -2
  43. package/src/eventsub/events/channel.prediction.end.ts +2 -2
  44. package/src/eventsub/events/channel.prediction.lock.ts +2 -2
  45. package/src/eventsub/events/channel.prediction.progress.ts +2 -2
  46. package/src/eventsub/events/channel.raid.ts +1 -1
  47. package/src/eventsub/events/channel.shield_mode.begin.ts +1 -1
  48. package/src/eventsub/events/channel.shield_mode.end.ts +1 -1
  49. package/src/eventsub/events/channel.shoutout.create.ts +1 -1
  50. package/src/eventsub/events/channel.shoutout.receive.ts +1 -1
  51. package/src/eventsub/events/channel.subscribe.ts +1 -1
  52. package/src/eventsub/events/channel.subscription.end.ts +1 -1
  53. package/src/eventsub/events/channel.subscription.gift.ts +1 -1
  54. package/src/eventsub/events/channel.subscription.message.ts +1 -1
  55. package/src/eventsub/events/channel.unban.ts +1 -1
  56. package/src/eventsub/events/channel.update.ts +1 -1
  57. package/src/eventsub/events/index.ts +51 -51
  58. package/src/eventsub/events/stream.offline.ts +1 -1
  59. package/src/eventsub/events/stream.online.ts +1 -1
  60. package/src/eventsub/events/user.update.ts +1 -1
  61. package/src/eventsub/events-helpers.ts +2 -2
  62. package/src/eventsub/eventsub.ts +9 -10
  63. package/src/eventsub/messages.ts +1 -1
  64. package/src/helix/channel-points/custom-rewards.ts +2 -3
  65. package/src/helix/helix.ts +1 -1
  66. package/src/helix/subscriptions/subscriptions.ts +9 -19
  67. package/src/helix/users/users.ts +1 -1
  68. package/src/helpers/alerts/alerts.ts +3 -3
  69. package/src/helpers/charity/charity.ts +1 -1
  70. package/src/helpers/goal/goal.ts +1 -1
  71. package/src/helpers/hype-train/hype-train.ts +1 -1
  72. package/src/helpers/poll/poll.ts +17 -3
  73. package/src/helpers/prediction/prediction.ts +1 -1
  74. package/src/helpers/redemption/redemption.ts +21 -2
  75. package/src/helpers/status/status.ts +1 -1
  76. package/src/ui/authentication.ts +29 -86
  77. package/src/ui/popup.ts +46 -7
  78. package/src/ui/vite-env.d.ts +1 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `@overlaysymphony/obs`
1
+ # `@overlaysymphony/twitch`
2
2
 
3
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
4
 
package/package.json CHANGED
@@ -1,40 +1,38 @@
1
1
  {
2
2
  "name": "@overlaysymphony/twitch",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Twitch module for the OverlaySymphony interactive streaming framework.",
5
5
  "homepage": "https://github.com/OverlaySymphony/overlaysymphony",
6
6
  "type": "module",
7
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",
8
+ "./authentication": "./src/authentication/index.ts",
9
+ "./chat": "./src/chat/index.ts",
10
+ "./eventsub": "./src/eventsub/index.ts",
11
+ "./helix/*": "./src/helix/*/index.ts",
12
+ "./helpers/*": "./src/helpers/*/index.ts",
13
+ "./ui/*": "./src/ui/*.ts",
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "dependencies": {
17
- "@overlaysymphony/core": "0.1.0"
17
+ "@overlaysymphony/core": "0.2.0"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@vitest/coverage-v8": "^2.1.2",
21
21
  "depcheck": "^1.4.7",
22
- "eslint": "^8.57.0",
23
- "prettier": "^3.3.3",
24
- "typescript": "~5.5.0",
22
+ "eslint": "^9.19.0",
23
+ "prettier": "^3.4.2",
24
+ "typescript": "^5.7.2",
25
+ "vite": "^6.2.1",
25
26
  "vitest": "^2.1.2",
26
- "@overlaysymphony/tooling": "0.0.0",
27
- "@overlaysymphony/eslint-config": "0.0.0"
27
+ "@overlaysymphony/tooling": "0.0.0"
28
28
  },
29
29
  "scripts": {
30
30
  "lint": "pnpm run \"/^lint-.*/\"",
31
31
  "lint-typecheck": "tsc --noEmit",
32
- "lint-eslint": "eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
32
+ "lint-eslint": "eslint .",
33
33
  "lint-prettier": "prettier --check .",
34
34
  "lint-depcheck": "depcheck .",
35
35
  "test": "vitest",
36
- "dev": "echo TODO",
37
- "clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo dist",
38
- "build": "echo TODO"
36
+ "clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo"
39
37
  }
40
38
  }
@@ -1,10 +1,8 @@
1
- import querystring from "@overlaysymphony/core/libs/querystring"
2
-
3
- import { TwitchUser, getUsers } from "../helix/users/index.js"
1
+ import { type TwitchUser, getUsers } from "../helix/users/index.js"
4
2
 
5
3
  export interface Authentication {
6
- clientId: string
7
4
  tokenType: "bearer"
5
+ clientId: string
8
6
  accessToken: string
9
7
  scope: string[]
10
8
  expires: Date
@@ -13,53 +11,123 @@ export interface Authentication {
13
11
 
14
12
  export type BareAuthentication = Omit<Authentication, "user">
15
13
 
16
- interface TAValidation {
17
- client_id: string
18
- login: string
19
- scope: string[]
20
- user_id: string
21
- expires_in: number
14
+ const localStorageKey = "overlaysymphony:service:twitch"
15
+
16
+ export function getCachedAuthentication(
17
+ scopes?: string[],
18
+ ): BareAuthentication | undefined {
19
+ const cache = localStorage.getItem(localStorageKey)
20
+ if (!cache) {
21
+ return undefined
22
+ }
23
+
24
+ const authentication = JSON.parse(cache) as BareAuthentication
25
+ authentication.expires = new Date(authentication.expires)
26
+
27
+ if (scopes) {
28
+ for (const scope of scopes) {
29
+ if (!authentication.scope.includes(scope)) {
30
+ localStorage.removeItem(localStorageKey)
31
+ return undefined
32
+ }
33
+ }
34
+ }
35
+
36
+ return authentication
22
37
  }
23
38
 
24
- interface TAResult {
25
- token_type: "bearer"
26
- access_token: string
27
- scope: string
39
+ export function setCachedAuthentication(
40
+ authentication: BareAuthentication,
41
+ ): void {
42
+ localStorage.setItem(localStorageKey, JSON.stringify(authentication))
28
43
  }
29
44
 
30
- const localStorageKey = "overlaysymphony:service:twitch"
45
+ export function clearCachedAuthentication(): void {
46
+ localStorage.removeItem(localStorageKey)
47
+ }
31
48
 
32
- export async function getAuthentication(): Promise<Authentication> {
33
- const raw = localStorage.getItem(localStorageKey)
34
- if (!raw) {
35
- throw new Error("Twitch authentication missing!")
49
+ export async function getAuthentication(
50
+ scopes?: string[],
51
+ ): Promise<Authentication | undefined> {
52
+ let authentication = getCachedAuthentication(scopes)
53
+ if (!authentication) {
54
+ return undefined
36
55
  }
37
56
 
38
- const cached = JSON.parse(raw) as BareAuthentication
39
- cached.expires = new Date(cached.expires)
57
+ try {
58
+ authentication = await validateAuthentication(authentication)
59
+ } catch (error) {
60
+ clearCachedAuthentication()
61
+ return undefined
62
+ }
40
63
 
41
- const [user] = await getUsers(cached as Authentication)
64
+ const [user] = await getUsers(authentication as Authentication)
42
65
 
43
- const authentication: Authentication = {
44
- ...cached,
66
+ return {
67
+ ...authentication,
45
68
  user,
46
69
  }
70
+ }
71
+
72
+ export async function setAuthentication(
73
+ clientId: string,
74
+ accessToken: string,
75
+ scope: string[] = [],
76
+ ): Promise<BareAuthentication | undefined> {
77
+ let authentication: BareAuthentication = {
78
+ tokenType: "bearer",
79
+ clientId,
80
+ accessToken,
81
+ scope,
82
+
83
+ // this is ignored and replaced by validateAuthentication
84
+ expires: new Date(),
85
+ }
86
+
87
+ try {
88
+ authentication = await validateAuthentication(authentication)
89
+ } catch (error) {
90
+ return undefined
91
+ }
92
+
93
+ setCachedAuthentication(authentication)
47
94
 
48
95
  return authentication
49
96
  }
50
97
 
51
- export function initiateAuthentication(
98
+ export async function popupAuthentication(
52
99
  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
- })}`
100
+ scopes: string[],
101
+ popupUrl: string,
102
+ ): Promise<void> {
103
+ const url = new URL(
104
+ `${popupUrl}?scopes=${scopes.join("+")}&clientId=${clientId}`,
105
+ )
106
+
107
+ await new Promise<void>((resolve) => {
108
+ const listener = (event: MessageEvent) => {
109
+ if (event.origin !== url.origin) return
110
+
111
+ // const source = (event.source as Window | null)?.name
112
+ // if (source !== "OverlaySymphonyTwitchAuthenticationPopup") return
113
+
114
+ const { type, authentication } = event.data
115
+ if (type !== "authentication") return
116
+
117
+ window.removeEventListener("message", listener)
118
+
119
+ setCachedAuthentication(authentication as BareAuthentication)
120
+ resolve()
121
+ }
122
+
123
+ window.addEventListener("message", listener)
124
+
125
+ window.open(
126
+ url,
127
+ "OverlaySymphonyTwitchAuthenticationPopup",
128
+ "width=520,height=840",
129
+ )
130
+ })
63
131
  }
64
132
 
65
133
  export async function validateAuthentication(
@@ -76,7 +144,13 @@ export async function validateAuthentication(
76
144
  throw new Error(await response.text())
77
145
  }
78
146
 
79
- const validated = (await response.json()) as TAValidation
147
+ const validated = (await response.json()) as {
148
+ client_id: string
149
+ login: string
150
+ scope: string[]
151
+ user_id: string
152
+ expires_in: number
153
+ }
80
154
 
81
155
  if (validated.client_id !== authentication.clientId) {
82
156
  throw new Error("ClientId mismatch.")
@@ -87,21 +161,3 @@ export async function validateAuthentication(
87
161
  expires: new Date(Date.now() + validated.expires_in * 1000),
88
162
  }
89
163
  }
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
- }
package/src/chat/chat.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import createDefer from "@overlaysymphony/core/libs/defer"
2
2
  import createPubSub from "@overlaysymphony/core/libs/pubsub"
3
3
 
4
- import { Authentication } from "../authentication/index.js"
4
+ import { type Authentication } from "../authentication/index.js"
5
5
 
6
- import { TwitchChatEvent, TwitchChatEventType } from "./interfaces/index.js"
6
+ import {
7
+ type TwitchChatEvent,
8
+ type TwitchChatEventType,
9
+ } from "./interfaces/index.js"
7
10
  import parseCommand from "./parser.js"
8
11
 
9
12
  type ChatListener = (callback: (event: TwitchChatEvent) => void) => () => void
@@ -22,7 +25,7 @@ type ChatMessageSubscriber = (
22
25
  callback: (event: TwitchChatEvent<"PRIVMSG">) => void,
23
26
  ) => () => void
24
27
 
25
- type ChatCommandSubscriber = {
28
+ interface ChatCommandSubscriber {
26
29
  (
27
30
  callback: (event: TwitchChatEvent<"PRIVMSG-COMMAND">) => void,
28
31
  name?: never,
@@ -67,8 +70,7 @@ export async function createChat(
67
70
  command.type === "JOIN" ||
68
71
  command.type === "USERSTATE" ||
69
72
  command.type === "HOSTTARGET" ||
70
- command.type === "NOTICE" ||
71
- false
73
+ command.type === "NOTICE"
72
74
  ) {
73
75
  continue
74
76
  }
@@ -106,16 +108,15 @@ export async function createChat(
106
108
  return promise.then(() => {
107
109
  const listen: ChatListener = (callback) => {
108
110
  return pubsub.subscribe((event) => {
109
- // @ts-ignore
110
111
  callback(event)
111
112
  })
112
113
  }
113
114
 
114
115
  const subscribe: ChatSubscriber = (types, callback) => {
115
116
  return pubsub.subscribe((event) => {
116
- // @ts-ignore
117
+ // @ts-expect-error: generic events are complicated
117
118
  if (types.includes(event.type)) {
118
- // @ts-ignore
119
+ // @ts-expect-error: generic events are complicated
119
120
  callback(event)
120
121
  }
121
122
  })
@@ -0,0 +1,36 @@
1
+ interface ApplicableChatEvent {
2
+ tags?: {
3
+ mod?: boolean
4
+ badges?: {
5
+ broadcaster?: boolean
6
+ }
7
+ }
8
+ }
9
+
10
+ export function isBroadcaster(event: ApplicableChatEvent): boolean | null {
11
+ const broadcaster = event.tags?.badges?.broadcaster
12
+
13
+ if (typeof broadcaster === "undefined") return null
14
+
15
+ return !!broadcaster
16
+ }
17
+
18
+ export function isMod(event: ApplicableChatEvent): boolean | null {
19
+ const broadcaster = event.tags?.badges?.broadcaster
20
+ const mod = event.tags?.mod
21
+
22
+ if (typeof broadcaster === "undefined" || typeof mod === "undefined")
23
+ return null
24
+
25
+ return broadcaster || mod
26
+ }
27
+
28
+ export function isModOnly(event: ApplicableChatEvent): boolean | null {
29
+ const broadcaster = event.tags?.badges?.broadcaster
30
+ const mod = event.tags?.mod
31
+
32
+ if (typeof broadcaster === "undefined" || typeof mod === "undefined")
33
+ return null
34
+
35
+ return mod && !broadcaster
36
+ }
package/src/chat/index.ts CHANGED
@@ -4,3 +4,4 @@ export type {
4
4
  } from "./interfaces/index.js"
5
5
 
6
6
  export * from "./chat.js"
7
+ export * from "./helpers.js"
@@ -13,20 +13,20 @@ type TagEmotes = Record<
13
13
  Array<[startPosition: string, endPosition: string]>
14
14
  >
15
15
 
16
- export type PingEvent = {
16
+ export interface PingEvent {
17
17
  type: "PING"
18
18
  message: string
19
19
  source?: ChatEventSource
20
20
  }
21
21
 
22
- export type WelcomeEvent = {
22
+ export interface WelcomeEvent {
23
23
  type: "001" // Logged in (successfully authenticated)
24
24
  channel: string
25
25
  message: string
26
26
  source?: ChatEventSource
27
27
  }
28
28
 
29
- export type CapabilitiesEvent = {
29
+ export interface CapabilitiesEvent {
30
30
  type: "CAP"
31
31
  enabled: boolean
32
32
  nickname: string
@@ -34,21 +34,21 @@ export type CapabilitiesEvent = {
34
34
  source?: ChatEventSource
35
35
  }
36
36
 
37
- export type JoinEvent = {
37
+ export interface JoinEvent {
38
38
  type: "JOIN"
39
39
  channel: string
40
40
  source?: ChatEventSource
41
41
  }
42
42
 
43
- export type PartEvent = {
43
+ export interface PartEvent {
44
44
  type: "PART"
45
45
  channel: string
46
46
  source?: ChatEventSource
47
47
  }
48
48
 
49
- export type GlobalUserStateEvent = {
49
+ export interface GlobalUserStateEvent {
50
50
  type: "GLOBALUSERSTATE"
51
- tags: {
51
+ tags?: {
52
52
  userId: string
53
53
  userType: TagUserType
54
54
  displayName?: string
@@ -60,15 +60,15 @@ export type GlobalUserStateEvent = {
60
60
  source?: ChatEventSource
61
61
  }
62
62
 
63
- export type ReconnectEvent = {
63
+ export interface ReconnectEvent {
64
64
  type: "RECONNECT"
65
65
  source?: ChatEventSource
66
66
  }
67
67
 
68
- export type ClearChatEvent = {
68
+ export interface ClearChatEvent {
69
69
  type: "CLEARCHAT"
70
70
  channel: string
71
- tags: {
71
+ tags?: {
72
72
  roomId: string
73
73
  targetUserId: string
74
74
  banDuration: number
@@ -76,26 +76,26 @@ export type ClearChatEvent = {
76
76
  source?: ChatEventSource
77
77
  }
78
78
 
79
- export type HostTargetEvent = {
79
+ export interface HostTargetEvent {
80
80
  type: "HOSTTARGET"
81
81
  channel: string
82
82
  source?: ChatEventSource
83
83
  }
84
84
 
85
- export type NoticeEvent = {
85
+ export interface NoticeEvent {
86
86
  type: "NOTICE"
87
87
  channel: string
88
- tags: {
88
+ tags?: {
89
89
  msgId: string
90
90
  targetUserId: string
91
91
  }
92
92
  source?: ChatEventSource
93
93
  }
94
94
 
95
- export type RoomStateEvent = {
95
+ export interface RoomStateEvent {
96
96
  type: "ROOMSTATE"
97
97
  channel: string
98
- tags: {
98
+ tags?: {
99
99
  roomId: string
100
100
  slow: number
101
101
  emoteOnly: boolean
@@ -105,10 +105,10 @@ export type RoomStateEvent = {
105
105
  source?: ChatEventSource
106
106
  }
107
107
 
108
- export type UserStateEvent = {
108
+ export interface UserStateEvent {
109
109
  type: "USERSTATE"
110
110
  channel: string
111
- tags: {
111
+ tags?: {
112
112
  id: string
113
113
  userId: string
114
114
  userType: TagUserType
@@ -126,10 +126,10 @@ export type UserStateEvent = {
126
126
  source?: ChatEventSource
127
127
  }
128
128
 
129
- export type ClearMessageEvent = {
129
+ export interface ClearMessageEvent {
130
130
  type: "CLEARMSG"
131
131
  channel: string
132
- tags: {
132
+ tags?: {
133
133
  login: string
134
134
  roomId: string
135
135
  targetMsgId?: string
@@ -137,10 +137,10 @@ export type ClearMessageEvent = {
137
137
  source?: ChatEventSource
138
138
  }
139
139
 
140
- export type UserNoticeEvent = {
140
+ export interface UserNoticeEvent {
141
141
  type: "USERNOTICE"
142
142
  channel: string
143
- tags: {
143
+ tags?: {
144
144
  id: string
145
145
  userId: string
146
146
  userType: TagUserType
@@ -173,11 +173,11 @@ export type UserNoticeEvent = {
173
173
  source?: ChatEventSource
174
174
  }
175
175
 
176
- export type ChatMessageEvent = {
176
+ export interface ChatMessageEvent {
177
177
  type: "PRIVMSG"
178
178
  channel: string
179
179
  message: string
180
- tags: {
180
+ tags?: {
181
181
  id: string
182
182
  userId: string
183
183
  userType: TagUserType
@@ -212,11 +212,11 @@ export type ChatMessageEvent = {
212
212
  source?: ChatEventSource
213
213
  }
214
214
 
215
- export type WhisperMessageEvent = {
215
+ export interface WhisperMessageEvent {
216
216
  type: "WHISPER"
217
217
  channel: string
218
218
  message: string
219
- tags: {
219
+ tags?: {
220
220
  messageId: string
221
221
  threadId: string
222
222
  userId: string
@@ -230,21 +230,21 @@ export type WhisperMessageEvent = {
230
230
  source?: ChatEventSource
231
231
  }
232
232
 
233
- export type ChatCommandEvent = {
233
+ export interface ChatCommandEvent {
234
234
  type: "PRIVMSG-COMMAND"
235
235
  channel: string
236
236
  command: string
237
237
  parameters?: string
238
- tags: ChatMessageEvent["tags"]
238
+ tags?: ChatMessageEvent["tags"]
239
239
  source?: ChatEventSource
240
240
  }
241
241
 
242
- export type WhisperCommandEvent = {
242
+ export interface WhisperCommandEvent {
243
243
  type: "WHISPER-COMMAND"
244
244
  channel: string
245
245
  command: string
246
246
  parameters?: string
247
- tags: WhisperMessageEvent["tags"]
247
+ tags?: WhisperMessageEvent["tags"]
248
248
  source?: ChatEventSource
249
249
  }
250
250
 
@@ -1,4 +1,4 @@
1
- import { ChatEvent } from "./events.js"
1
+ import { type ChatEvent } from "./events.js"
2
2
 
3
3
  export type { ChatEvent, ChatEventSource } from "./events.js"
4
4