@overlaysymphony/twitch 0.2.2 → 0.3.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 (174) hide show
  1. package/package.json +6 -2
  2. package/src/authentication/authentication.ts +1 -1
  3. package/src/authentication/index.ts +1 -1
  4. package/src/chat/chat.ts +69 -134
  5. package/src/chat/helpers.ts +33 -24
  6. package/src/chat/index.ts +4 -6
  7. package/src/chat/pronouns.ts +141 -0
  8. package/src/eventsub/events/automod.message.hold-2.ts +81 -0
  9. package/src/eventsub/events/automod.message.update-2.ts +94 -0
  10. package/src/eventsub/events/automod.settings.update-1.ts +65 -0
  11. package/src/eventsub/events/automod.terms.update-1.ts +59 -0
  12. package/src/eventsub/events/channel.ad_break.begin-1.ts +50 -0
  13. package/src/eventsub/events/channel.ban-1.ts +58 -0
  14. package/src/eventsub/events/channel.bits.use-1.ts +112 -0
  15. package/src/eventsub/events/channel.channel_points_automatic_reward_redemption.add-2.ts +88 -0
  16. package/src/eventsub/events/channel.channel_points_custom_reward.add-1.ts +101 -0
  17. package/src/eventsub/events/channel.channel_points_custom_reward.remove-1.ts +103 -0
  18. package/src/eventsub/events/channel.channel_points_custom_reward.update-1.ts +103 -0
  19. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.add-1.ts +65 -0
  20. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.update-1.ts +65 -0
  21. package/src/eventsub/events/channel.charity_campaign.donate-1.ts +69 -0
  22. package/src/eventsub/events/channel.charity_campaign.progress-1.ts +74 -0
  23. package/src/eventsub/events/channel.charity_campaign.start-1.ts +76 -0
  24. package/src/eventsub/events/channel.charity_campaign.stop-1.ts +76 -0
  25. package/src/eventsub/events/channel.chat.clear-1.ts +41 -0
  26. package/src/eventsub/events/channel.chat.clear_user_messages-1.ts +47 -0
  27. package/src/eventsub/events/channel.chat.message-1.ts +160 -0
  28. package/src/eventsub/events/channel.chat.message_delete-1.ts +49 -0
  29. package/src/eventsub/events/channel.chat.notification-1.ts +317 -0
  30. package/src/eventsub/events/channel.chat.user_message_hold-1.ts +75 -0
  31. package/src/eventsub/events/channel.chat.user_message_update-1.ts +82 -0
  32. package/src/eventsub/events/channel.chat_settings.update-1.ts +83 -0
  33. package/src/eventsub/events/channel.cheer-1.ts +50 -0
  34. package/src/eventsub/events/channel.follow-2.ts +49 -0
  35. package/src/eventsub/events/channel.goal.begin-1.ts +80 -0
  36. package/src/eventsub/events/channel.goal.end-1.ts +80 -0
  37. package/src/eventsub/events/channel.goal.progress-1.ts +80 -0
  38. package/src/eventsub/events/channel.guest_star_guest.update-beta.ts +85 -0
  39. package/src/eventsub/events/channel.guest_star_session.begin-beta.ts +50 -0
  40. package/src/eventsub/events/channel.guest_star_session.end-beta.ts +58 -0
  41. package/src/eventsub/events/channel.guest_star_settings.update-beta.ts +62 -0
  42. package/src/eventsub/events/channel.hype_train.begin-2.ts +90 -0
  43. package/src/eventsub/events/channel.hype_train.end-2.ts +70 -0
  44. package/src/eventsub/events/channel.hype_train.progress-2.ts +90 -0
  45. package/src/eventsub/events/channel.moderate-2.ts +258 -0
  46. package/src/eventsub/events/channel.moderator.add-1.ts +44 -0
  47. package/src/eventsub/events/channel.moderator.remove-1.ts +44 -0
  48. package/src/eventsub/events/channel.poll.begin-1.ts +73 -0
  49. package/src/eventsub/events/channel.poll.end-1.ts +75 -0
  50. package/src/eventsub/events/channel.poll.progress-1.ts +73 -0
  51. package/src/eventsub/events/channel.prediction.begin-1.ts +72 -0
  52. package/src/eventsub/events/channel.prediction.end-1.ts +76 -0
  53. package/src/eventsub/events/channel.prediction.lock-1.ts +72 -0
  54. package/src/eventsub/events/channel.prediction.progress-1.ts +72 -0
  55. package/src/eventsub/events/channel.raid-1.ts +48 -0
  56. package/src/eventsub/events/channel.shared_chat.begin-1.ts +55 -0
  57. package/src/eventsub/events/channel.shared_chat.end-1.ts +46 -0
  58. package/src/eventsub/events/channel.shared_chat.update-1.ts +55 -0
  59. package/src/eventsub/events/channel.shield_mode.begin-1.ts +51 -0
  60. package/src/eventsub/events/channel.shield_mode.end-1.ts +51 -0
  61. package/src/eventsub/events/channel.shoutout.create-1.ts +61 -0
  62. package/src/eventsub/events/channel.shoutout.receive-1.ts +51 -0
  63. package/src/eventsub/events/channel.subscribe-1.ts +48 -0
  64. package/src/eventsub/events/channel.subscription.end-1.ts +48 -0
  65. package/src/eventsub/events/channel.subscription.gift-1.ts +52 -0
  66. package/src/eventsub/events/channel.subscription.message-1.ts +66 -0
  67. package/src/eventsub/events/channel.suspicious_user.message-1.ts +85 -0
  68. package/src/eventsub/events/channel.suspicious_user.update-1.ts +55 -0
  69. package/src/eventsub/events/channel.unban-1.ts +50 -0
  70. package/src/eventsub/events/channel.unban_request.create-1.ts +53 -0
  71. package/src/eventsub/events/channel.unban_request.resolve-1.ts +64 -0
  72. package/src/eventsub/events/channel.update-2.ts +48 -0
  73. package/src/eventsub/events/channel.vip.add-1.ts +44 -0
  74. package/src/eventsub/events/channel.vip.remove-1.ts +44 -0
  75. package/src/eventsub/events/channel.warning.acknowledge-1.ts +47 -0
  76. package/src/eventsub/events/channel.warning.send-1.ts +57 -0
  77. package/src/eventsub/events/index.ts +73 -284
  78. package/src/eventsub/events/stream.offline-1.ts +38 -0
  79. package/src/eventsub/events/stream.online-1.ts +44 -0
  80. package/src/eventsub/events/user.update-1.ts +48 -0
  81. package/src/eventsub/events/user.whisper.message-1.ts +51 -0
  82. package/src/eventsub/events-helpers.ts +52 -24
  83. package/src/eventsub/eventsub.ts +37 -74
  84. package/src/eventsub/index.ts +8 -6
  85. package/src/eventsub/messages.ts +16 -4
  86. package/src/helix/channel-points/custom-rewards.ts +120 -33
  87. package/src/helix/channel-points/index.ts +1 -1
  88. package/src/helix/channels/channels.ts +39 -0
  89. package/src/helix/channels/index.ts +1 -0
  90. package/src/helix/chat/emotes.ts +49 -0
  91. package/src/helix/chat/index.ts +3 -0
  92. package/src/helix/chat/send.ts +109 -0
  93. package/src/helix/chat/shared.ts +49 -0
  94. package/src/helix/helix.ts +6 -21
  95. package/src/helix/subscriptions/index.ts +1 -1
  96. package/src/helix/subscriptions/subscriptions.ts +46 -48
  97. package/src/helix/users/index.ts +1 -1
  98. package/src/helix/users/users.ts +22 -19
  99. package/src/helpers/alerts/alerts.ts +5 -5
  100. package/src/helpers/alerts/index.ts +2 -2
  101. package/src/helpers/charity/charity.ts +2 -2
  102. package/src/helpers/charity/index.ts +1 -1
  103. package/src/helpers/goal/goal.ts +2 -2
  104. package/src/helpers/goal/index.ts +1 -1
  105. package/src/helpers/hype-train/hype-train.ts +2 -2
  106. package/src/helpers/hype-train/index.ts +1 -1
  107. package/src/helpers/poll/index.ts +1 -1
  108. package/src/helpers/poll/poll.ts +3 -3
  109. package/src/helpers/prediction/index.ts +1 -1
  110. package/src/helpers/prediction/prediction.ts +3 -3
  111. package/src/helpers/redemption/index.ts +1 -1
  112. package/src/helpers/redemption/redemption.ts +2 -2
  113. package/src/helpers/status/index.ts +1 -1
  114. package/src/helpers/status/status.ts +2 -2
  115. package/src/ui/authentication.ts +1 -1
  116. package/src/ui/popup.ts +26 -3
  117. package/src/chat/interfaces/events.ts +0 -269
  118. package/src/chat/interfaces/index.ts +0 -9
  119. package/src/chat/parser.ts +0 -254
  120. package/src/eventsub/events/channel.ad_break.begin.ts +0 -51
  121. package/src/eventsub/events/channel.ban.ts +0 -59
  122. package/src/eventsub/events/channel.channel_points_custom_reward._.ts +0 -29
  123. package/src/eventsub/events/channel.channel_points_custom_reward.add.ts +0 -75
  124. package/src/eventsub/events/channel.channel_points_custom_reward.remove.ts +0 -77
  125. package/src/eventsub/events/channel.channel_points_custom_reward.update.ts +0 -77
  126. package/src/eventsub/events/channel.channel_points_custom_reward_redemption._.ts +0 -10
  127. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.add.ts +0 -59
  128. package/src/eventsub/events/channel.channel_points_custom_reward_redemption.update.ts +0 -59
  129. package/src/eventsub/events/channel.charity_campaign.donate.ts +0 -66
  130. package/src/eventsub/events/channel.charity_campaign.progress.ts +0 -67
  131. package/src/eventsub/events/channel.charity_campaign.start.ts +0 -69
  132. package/src/eventsub/events/channel.charity_campaign.stop.ts +0 -69
  133. package/src/eventsub/events/channel.chat.clear.ts +0 -42
  134. package/src/eventsub/events/channel.chat.clear_user_messages.ts +0 -48
  135. package/src/eventsub/events/channel.chat.message_delete.ts +0 -50
  136. package/src/eventsub/events/channel.chat.notification.ts +0 -50
  137. package/src/eventsub/events/channel.cheer.ts +0 -51
  138. package/src/eventsub/events/channel.follow.ts +0 -50
  139. package/src/eventsub/events/channel.goal.begin.ts +0 -51
  140. package/src/eventsub/events/channel.goal.end.ts +0 -55
  141. package/src/eventsub/events/channel.goal.progress.ts +0 -51
  142. package/src/eventsub/events/channel.guest_star_guest.update.ts +0 -66
  143. package/src/eventsub/events/channel.guest_star_session.begin.ts +0 -46
  144. package/src/eventsub/events/channel.guest_star_session.end.ts +0 -48
  145. package/src/eventsub/events/channel.guest_star_settings.update.ts +0 -50
  146. package/src/eventsub/events/channel.hype_train._.ts +0 -12
  147. package/src/eventsub/events/channel.hype_train.begin.ts +0 -57
  148. package/src/eventsub/events/channel.hype_train.end.ts +0 -55
  149. package/src/eventsub/events/channel.hype_train.progress.ts +0 -70
  150. package/src/eventsub/events/channel.moderator.add.ts +0 -45
  151. package/src/eventsub/events/channel.moderator.remove.ts +0 -45
  152. package/src/eventsub/events/channel.poll._.ts +0 -26
  153. package/src/eventsub/events/channel.poll.begin.ts +0 -55
  154. package/src/eventsub/events/channel.poll.end.ts +0 -57
  155. package/src/eventsub/events/channel.poll.progress.ts +0 -55
  156. package/src/eventsub/events/channel.prediction._.ts +0 -25
  157. package/src/eventsub/events/channel.prediction.begin.ts +0 -51
  158. package/src/eventsub/events/channel.prediction.end.ts +0 -55
  159. package/src/eventsub/events/channel.prediction.lock.ts +0 -51
  160. package/src/eventsub/events/channel.prediction.progress.ts +0 -51
  161. package/src/eventsub/events/channel.raid.ts +0 -49
  162. package/src/eventsub/events/channel.shield_mode.begin.ts +0 -50
  163. package/src/eventsub/events/channel.shield_mode.end.ts +0 -50
  164. package/src/eventsub/events/channel.shoutout.create.ts +0 -62
  165. package/src/eventsub/events/channel.shoutout.receive.ts +0 -52
  166. package/src/eventsub/events/channel.subscribe.ts +0 -49
  167. package/src/eventsub/events/channel.subscription.end.ts +0 -49
  168. package/src/eventsub/events/channel.subscription.gift.ts +0 -53
  169. package/src/eventsub/events/channel.subscription.message.ts +0 -67
  170. package/src/eventsub/events/channel.unban.ts +0 -51
  171. package/src/eventsub/events/channel.update.ts +0 -49
  172. package/src/eventsub/events/stream.offline.ts +0 -39
  173. package/src/eventsub/events/stream.online.ts +0 -45
  174. package/src/eventsub/events/user.update.ts +0 -45
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@overlaysymphony/twitch",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Twitch module for the OverlaySymphony interactive streaming framework.",
5
5
  "homepage": "https://github.com/OverlaySymphony/overlaysymphony",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/OverlaySymphony/overlaysymphony"
9
+ },
6
10
  "type": "module",
7
11
  "exports": {
8
12
  "./authentication": "./src/authentication/index.ts",
@@ -14,7 +18,7 @@
14
18
  "./package.json": "./package.json"
15
19
  },
16
20
  "dependencies": {
17
- "@overlaysymphony/core": "0.2.0"
21
+ "@overlaysymphony/core": "0.2.1"
18
22
  },
19
23
  "devDependencies": {
20
24
  "@vitest/coverage-v8": "^2.1.2",
@@ -1,4 +1,4 @@
1
- import { type TwitchUser, getUsers } from "../helix/users/index.js"
1
+ import { type TwitchUser, getUsers } from "../helix/users/index.ts"
2
2
 
3
3
  export interface Authentication {
4
4
  tokenType: "bearer"
@@ -1 +1 @@
1
- export * from "./authentication.js"
1
+ export * from "./authentication.ts"
package/src/chat/chat.ts CHANGED
@@ -1,161 +1,96 @@
1
- import createDefer from "@overlaysymphony/core/libs/defer"
2
- import createPubSub from "@overlaysymphony/core/libs/pubsub"
3
-
4
- import { type Authentication } from "../authentication/index.js"
5
-
6
- import {
7
- type TwitchChatEvent,
8
- type TwitchChatEventType,
9
- } from "./interfaces/index.js"
10
- import parseCommand from "./parser.js"
11
-
12
- type ChatListener = (callback: (event: TwitchChatEvent) => void) => () => void
13
-
14
- type ChatSubscriber = <
15
- EventType extends TwitchChatEventType,
16
- Event extends TwitchChatEvent<EventType>,
17
- >(
18
- types: EventType[],
19
- callback: (event: Event) => void,
20
- ) => () => void
1
+ import { type Authentication } from "../authentication/index.ts"
2
+ import { type EventPayload } from "../eventsub/events-helpers.ts"
3
+ import createEventSub, { type TwitchEventSub } from "../eventsub/index.ts"
4
+ import { sendChatAnnouncement, sendChatMessage } from "../helix/chat/index.ts"
5
+
6
+ type ChatMessage = EventPayload<"channel.chat.message">["event"]
7
+ type ChatCommand = ChatMessage & {
8
+ message: {
9
+ command: string
10
+ parameters?: string[]
11
+ } & ChatMessage["message"]
12
+ }
21
13
 
22
- type ChatSender = (message: string) => void
14
+ type ChatSender = (message: string) => Promise<void>
15
+ type ChatAnnouncer = (message: string, color?: string) => Promise<void>
23
16
 
24
17
  type ChatMessageSubscriber = (
25
- callback: (event: TwitchChatEvent<"PRIVMSG">) => void,
18
+ callback: (event: ChatMessage) => void,
26
19
  ) => () => void
27
20
 
28
- interface ChatCommandSubscriber {
29
- (
30
- callback: (event: TwitchChatEvent<"PRIVMSG-COMMAND">) => void,
31
- name?: never,
32
- ): () => void
33
- (
34
- name: string,
35
- callback: (event: TwitchChatEvent<"PRIVMSG-COMMAND">) => void,
36
- ): () => void
37
- }
21
+ type ChatCommandSubscriber = (
22
+ name: string,
23
+ callback: (event: ChatCommand) => void,
24
+ ___?: never,
25
+ ) => () => void
26
+ // type ChatCommandSubscriber = (
27
+ // name: string,
28
+ // pattern: string,
29
+ // callback: (event: ChatCommand) => void,
30
+ // ) => () => void
38
31
 
39
32
  export interface TwitchChat {
40
- listen: ChatListener
41
- subscribe: ChatSubscriber
42
33
  send: ChatSender
34
+ announce: ChatAnnouncer
43
35
  onMessage: ChatMessageSubscriber
44
36
  onCommand: ChatCommandSubscriber
45
37
  }
46
38
 
47
- export async function createChat(
39
+ export default async function createChat(
48
40
  authentication: Authentication,
49
- channel: string = authentication.user.login,
41
+ eventsub?: TwitchEventSub,
50
42
  ): Promise<TwitchChat> {
51
- const { promise, resolve } = createDefer()
43
+ eventsub ??= await createEventSub(authentication)
52
44
 
53
- const pubsub = createPubSub<TwitchChatEvent>()
54
- const socket = new WebSocket("wss://irc-ws.chat.twitch.tv:443")
45
+ const send: ChatSender = async (message) => {
46
+ await sendChatMessage(authentication, message)
47
+ }
55
48
 
56
- socket.addEventListener("open", (connection) => {
57
- socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands")
58
- })
49
+ const announce: ChatAnnouncer = async (message, color) => {
50
+ await sendChatAnnouncement(authentication, message, color)
51
+ }
59
52
 
60
- socket.addEventListener("message", ({ data }: { data: string }) => {
61
- const messages = data.split("\r\n").filter(Boolean)
62
- for (const input of messages) {
63
- const command = parseCommand(input)
64
- if (!command) {
65
- continue
66
- }
53
+ const onMessage: ChatMessageSubscriber = (callback) => {
54
+ return eventsub.on(["channel.chat.message"], (payload) => {
55
+ callback(payload.event)
56
+ })
57
+ }
67
58
 
68
- if (
69
- command.type === "001" ||
70
- command.type === "JOIN" ||
71
- command.type === "USERSTATE" ||
72
- command.type === "HOSTTARGET" ||
73
- command.type === "NOTICE"
74
- ) {
75
- continue
76
- }
77
-
78
- if (command.type === "PING") {
79
- socket.send(`PONG :${command.message}`)
80
- continue
81
- }
82
-
83
- if (command.type === "CAP") {
84
- socket.send(`PASS oauth:${authentication.accessToken}`)
85
- socket.send(`NICK ${authentication.user.login}`)
86
- continue
87
- }
88
-
89
- if (command.type === "RECONNECT") {
90
- console.warn("The server is about to terminate for maintenance.")
91
- continue
92
- }
59
+ const onCommand: ChatCommandSubscriber = (name, ...args) => {
60
+ const pattern = typeof args[0] === "string" ? args[0] : undefined
93
61
 
94
- if (command.type === "GLOBALUSERSTATE") {
95
- socket.send(`JOIN #${channel}`)
96
- continue
97
- }
98
-
99
- if (command.type === "ROOMSTATE") {
100
- resolve()
101
- continue
102
- }
103
-
104
- pubsub.dispatch(command)
105
- }
106
- })
107
-
108
- return promise.then(() => {
109
- const listen: ChatListener = (callback) => {
110
- return pubsub.subscribe((event) => {
111
- callback(event)
112
- })
113
- }
114
-
115
- const subscribe: ChatSubscriber = (types, callback) => {
116
- return pubsub.subscribe((event) => {
117
- // @ts-expect-error: generic events are complicated
118
- if (types.includes(event.type)) {
119
- // @ts-expect-error: generic events are complicated
120
- callback(event)
121
- }
122
- })
62
+ const callback = typeof args[0] === "function" ? args[0] : args[1]
63
+ if (!callback) {
64
+ throw new Error("onCommand: Missing callback.")
123
65
  }
124
66
 
125
- const send: ChatSender = (message) => {
126
- socket.send(`PRIVMSG #${authentication.user.login} :${message}`)
127
- }
67
+ const regex = new RegExp(`^\\s*!([a-z0-9])(?:\\s+(.+))$`, "i")
128
68
 
129
- const onMessage: ChatMessageSubscriber = (callback) => {
130
- return pubsub.subscribe((event) => {
131
- if (event.type === "PRIVMSG") {
132
- callback(event)
133
- }
134
- })
135
- }
69
+ return onMessage((payload) => {
70
+ const [command, text] =
71
+ payload.message.text.match(regex) ?? ([] as Array<string | undefined>)
136
72
 
137
- const onCommand: ChatCommandSubscriber = (...args) => {
138
- const name = typeof args[0] === "string" ? args[0] : undefined
139
- const callback = typeof args[0] === "function" ? args[0] : args[1]
140
- if (!callback) {
141
- throw new Error("onCommand: Missing callback.")
73
+ if (command !== name) {
74
+ return
142
75
  }
143
76
 
144
- return pubsub.subscribe((event) => {
145
- if (event.type === "PRIVMSG-COMMAND") {
146
- if (typeof name === "undefined" || event.command === name) {
147
- callback(event)
148
- }
149
- }
150
- })
151
- }
77
+ const parameters = pattern ? undefined : text?.split(" ")
152
78
 
153
- return {
154
- listen,
155
- subscribe,
156
- send,
157
- onMessage,
158
- onCommand,
159
- }
160
- })
79
+ callback({
80
+ ...payload,
81
+ message: {
82
+ command: "",
83
+ parameters,
84
+ ...payload.message,
85
+ },
86
+ })
87
+ })
88
+ }
89
+
90
+ return {
91
+ send,
92
+ announce,
93
+ onMessage,
94
+ onCommand,
95
+ }
161
96
  }
@@ -1,36 +1,45 @@
1
1
  interface ApplicableChatEvent {
2
- tags?: {
3
- mod?: boolean
4
- badges?: {
5
- broadcaster?: boolean
6
- }
7
- }
2
+ badges: Array<{
3
+ set_id: string
4
+ id: string
5
+ info: string
6
+ }>
8
7
  }
9
8
 
10
- export function isBroadcaster(event: ApplicableChatEvent): boolean | null {
11
- const broadcaster = event.tags?.badges?.broadcaster
9
+ export function isBroadcaster(event: ApplicableChatEvent): boolean {
10
+ const broadcaster = !!event.badges.find((badge) => {
11
+ return badge.set_id === "broadcaster"
12
+ })
12
13
 
13
- if (typeof broadcaster === "undefined") return null
14
-
15
- return !!broadcaster
14
+ return broadcaster
16
15
  }
17
16
 
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
17
+ export function isModerator(event: ApplicableChatEvent): boolean {
18
+ const broadcaster = !!event.badges.find((badge) => {
19
+ return badge.set_id === "broadcaster"
20
+ })
21
+ const moderator = !!event.badges.find((badge) => {
22
+ return badge.set_id === "moderator"
23
+ })
24
24
 
25
- return broadcaster || mod
25
+ return broadcaster || moderator
26
26
  }
27
27
 
28
- export function isModOnly(event: ApplicableChatEvent): boolean | null {
29
- const broadcaster = event.tags?.badges?.broadcaster
30
- const mod = event.tags?.mod
28
+ export function isModeratorOnly(event: ApplicableChatEvent): boolean {
29
+ const broadcaster = !!event.badges.find((badge) => {
30
+ return badge.set_id === "broadcaster"
31
+ })
32
+ const moderator = !!event.badges.find((badge) => {
33
+ return badge.set_id === "moderator"
34
+ })
35
+
36
+ return !broadcaster && moderator
37
+ }
31
38
 
32
- if (typeof broadcaster === "undefined" || typeof mod === "undefined")
33
- return null
39
+ export function isSubscriber(event: ApplicableChatEvent): false | number {
40
+ const subscriber = event.badges.find((badge) => {
41
+ return badge.set_id === "subscriber"
42
+ })
34
43
 
35
- return mod && !broadcaster
44
+ return +(subscriber?.info ?? "0") || false
36
45
  }
package/src/chat/index.ts CHANGED
@@ -1,7 +1,5 @@
1
- export type {
2
- TwitchChatEventType,
3
- TwitchChatEvent,
4
- } from "./interfaces/index.js"
1
+ export { default } from "./chat.ts"
2
+ export * from "./chat.ts"
5
3
 
6
- export * from "./chat.js"
7
- export * from "./helpers.js"
4
+ export * from "./pronouns.ts"
5
+ export * from "./helpers.ts"
@@ -0,0 +1,141 @@
1
+ type AllPronounsResponse = Array<{
2
+ name: string
3
+ display: string
4
+ }>
5
+
6
+ type UserPronounsResponse = Array<{
7
+ id: string
8
+ login: string
9
+ pronoun_id: string
10
+ }>
11
+
12
+ type Pronouns = {
13
+ subject: string
14
+ object: string
15
+ posessive: string
16
+ }
17
+
18
+ const pronouns: Record<string, Pronouns> = {
19
+ hehim: {
20
+ subject: "he",
21
+ object: "him",
22
+ posessive: "his",
23
+ },
24
+ sheher: {
25
+ subject: "she",
26
+ object: "her",
27
+ posessive: "her",
28
+ },
29
+ theythem: {
30
+ subject: "they",
31
+ object: "them",
32
+ posessive: "their",
33
+ },
34
+ shethem: {
35
+ subject: "she",
36
+ object: "they",
37
+ posessive: "their",
38
+ },
39
+ hethem: {
40
+ subject: "he",
41
+ object: "they",
42
+ posessive: "their",
43
+ },
44
+ heshe: {
45
+ subject: "he",
46
+ object: "she",
47
+ posessive: "their",
48
+ },
49
+ xexem: {
50
+ subject: "xe",
51
+ object: "xem",
52
+ posessive: "xeir",
53
+ },
54
+ faefaer: {
55
+ subject: "fae",
56
+ object: "faer",
57
+ posessive: "faer",
58
+ },
59
+ vever: {
60
+ subject: "ve",
61
+ object: "ver",
62
+ posessive: "ver",
63
+ },
64
+ aeaer: {
65
+ subject: "ae",
66
+ object: "aer",
67
+ posessive: "aer",
68
+ },
69
+ ziehir: {
70
+ subject: "zie",
71
+ object: "hir",
72
+ posessive: "hir",
73
+ },
74
+ perper: {
75
+ subject: "per",
76
+ object: "per",
77
+ posessive: "per",
78
+ },
79
+ eem: {
80
+ subject: "e",
81
+ object: "em",
82
+ posessive: "eir",
83
+ },
84
+ itits: {
85
+ subject: "it",
86
+ object: "its",
87
+ posessive: "its",
88
+ },
89
+ }
90
+
91
+ const cache: Partial<Record<string, keyof typeof pronouns>> = {}
92
+
93
+ export async function getAllPronouns(): Promise<AllPronounsResponse> {
94
+ const response = await fetch("https://pronouns.alejo.io/api/pronouns")
95
+
96
+ if (!response.ok) {
97
+ throw new Error(await response.text())
98
+ }
99
+
100
+ const data = (await response.json()) as AllPronounsResponse
101
+
102
+ return data
103
+ }
104
+
105
+ export async function getUserPronouns(
106
+ login: string,
107
+ ): Promise<string | undefined> {
108
+ const response = await fetch(`https://pronouns.alejo.io/api/users/${login}`)
109
+
110
+ if (!response.ok) {
111
+ return undefined
112
+ }
113
+
114
+ try {
115
+ const data = (await response.json()) as UserPronounsResponse
116
+
117
+ return data[0].pronoun_id
118
+ } catch {
119
+ return undefined
120
+ }
121
+ }
122
+
123
+ export async function getPronouns(
124
+ login: string,
125
+ fallback: keyof typeof pronouns = "theythem",
126
+ ): Promise<{
127
+ subject: string
128
+ object: string
129
+ }> {
130
+ if (!(login in cache)) {
131
+ try {
132
+ const id = await getUserPronouns(login)
133
+ cache[login] = id
134
+ } catch {
135
+ cache[login] = undefined
136
+ }
137
+ }
138
+
139
+ const id = cache[login] ?? fallback
140
+ return pronouns[id]
141
+ }
@@ -0,0 +1,81 @@
1
+ import { type EventConfig, registerEvent } from "../events-helpers.ts"
2
+
3
+ declare module "../events-helpers.ts" {
4
+ interface EventConfigs {
5
+ "automod.message.hold": AutomodMessageHold
6
+ }
7
+ }
8
+
9
+ /** Automod Message Hold v2: When the user's message is caught by automod for review. Only public blocked terms trigger notifications, not private ones. */
10
+ type AutomodMessageHold = EventConfig<{
11
+ Type: "automod.message.hold"
12
+ Version: "2"
13
+ /** The conditions to listen for when the user's message is caught by automod for review. Only public blocked terms trigger notifications, not private ones. */
14
+ Condition: {
15
+ /** User ID of the broadcaster (channel). */
16
+ broadcaster_user_id: string
17
+ /** User ID of the moderator. */
18
+ moderator_user_id: string
19
+ }
20
+ /** The event fired when the user's message is caught by automod for review. Only public blocked terms trigger notifications, not private ones. */
21
+ Event: {
22
+ /** The ID of the broadcaster specified in the request. */
23
+ broadcaster_user_id: string
24
+ /** The login of the broadcaster specified in the request. */
25
+ broadcaster_user_login: string
26
+ /** The user name of the broadcaster specified in the request. */
27
+ broadcaster_user_name: string
28
+ /** The message sender's user ID. */
29
+ user_id: string
30
+ /** The message sender's login name. */
31
+ user_login: string
32
+ /** The message sender's display name. */
33
+ user_name: string
34
+ /** The ID of the message that was flagged by automod. */
35
+ message_id: string
36
+ /** The body of the message. */
37
+ message: {
38
+ /** The contents of the message caught by automod. */
39
+ text: string
40
+ /** Metadata surrounding the potential inappropriate fragments of the message. */
41
+ fragments: Array<{
42
+ /** Message text in a fragment. */
43
+ text: string
44
+ /** Metadata pertaining to the emote. */
45
+ emote?: {
46
+ /** An ID that uniquely identifies this emote. */
47
+ id: string
48
+ /** An ID that identifies the emote set that the emote belongs to. */
49
+ emote_set_id: string
50
+ }
51
+ /** Metadata pertaining to the cheermote. */
52
+ cheermote?: {
53
+ /** The name portion of the Cheermote string that you use in chat to cheer Bits. The full Cheermote string is the concatenation of {prefix} + {number of Bits}. */
54
+ prefix: string
55
+ /** The amount of Bits cheered. */
56
+ bits: number
57
+ /** The tier level of the cheermote. */
58
+ tier: number
59
+ }
60
+ }>
61
+ }
62
+ /** The category of the message. */
63
+ category: string
64
+ /** The level of severity. Measured between 1 to 4. */
65
+ level: number
66
+ /** The timestamp of when automod saved the message. */
67
+ held_at: string
68
+ }
69
+ }>
70
+
71
+ registerEvent("automod.message.hold", {
72
+ scopes: ["moderator:manage:automod"],
73
+ subscriber: (userId) => ({
74
+ type: "automod.message.hold",
75
+ version: "2",
76
+ condition: {
77
+ broadcaster_user_id: userId,
78
+ moderator_user_id: userId,
79
+ },
80
+ }),
81
+ })
@@ -0,0 +1,94 @@
1
+ import { type EventConfig, registerEvent } from "../events-helpers.ts"
2
+
3
+ declare module "../events-helpers.ts" {
4
+ interface EventConfigs {
5
+ "automod.message.update": AutomodMessageUpdate
6
+ }
7
+ }
8
+
9
+ /** Automod Message Update v2: When a message in the automod queue had its status changed. Only public blocked terms trigger notifications, not private ones. */
10
+ type AutomodMessageUpdate = EventConfig<{
11
+ Type: "automod.message.update"
12
+ Version: "2"
13
+ /** The conditions to listen for when a message in the automod queue had its status changed. Only public blocked terms trigger notifications, not private ones. */
14
+ Condition: {
15
+ /** User ID of the broadcaster (channel). Maximum:1. */
16
+ broadcaster_user_id: string
17
+ /** User ID of the moderator. */
18
+ moderator_user_id: string
19
+ }
20
+ /** The event fired when a message in the automod queue had its status changed. Only public blocked terms trigger notifications, not private ones. */
21
+ Event: {
22
+ /** The ID of the broadcaster specified in the request. */
23
+ broadcaster_user_id: string
24
+ /** The login of the broadcaster specified in the request. */
25
+ broadcaster_user_login: string
26
+ /** The user name of the broadcaster specified in the request. */
27
+ broadcaster_user_name: string
28
+ /** The message sender's user ID. */
29
+ user_id: string
30
+ /** The message sender's login name. */
31
+ user_login: string
32
+ /** The message sender's display name. */
33
+ user_name: string
34
+ /** The ID of the moderator. */
35
+ moderator_user_id: string
36
+ /** TThe moderator's user name. */
37
+ moderator_user_name: string
38
+ /** The login of the moderator. */
39
+ moderator_user_login: string
40
+ /** The ID of the message that was flagged by automod. */
41
+ message_id: string
42
+ /** The body of the message. */
43
+ message: {
44
+ /** The contents of the message caught by automod. */
45
+ text: string
46
+ /** Metadata surrounding the potential inappropriate fragments of the message. */
47
+ fragments: Array<{
48
+ /** Message text in a fragment. */
49
+ text: string
50
+ /** Metadata pertaining to the emote. */
51
+ emote?: {
52
+ /** An ID that uniquely identifies this emote. */
53
+ id: string
54
+ /** An ID that identifies the emote set that the emote belongs to. */
55
+ emote_set_id: string
56
+ }
57
+ /** Metadata pertaining to the cheermote. */
58
+ cheermote?: {
59
+ /** The name portion of the Cheermote string that you use in chat to cheer Bits. The full Cheermote string is the concatenation of {prefix} + {number of Bits}. */
60
+ prefix: string
61
+ /** The amount of Bits cheered. */
62
+ bits: number
63
+ /** The tier level of the cheermote. */
64
+ tier: number
65
+ }
66
+ }>
67
+ }
68
+ /** The category of the message. */
69
+ category: string
70
+ /** The level of severity. Measured between 1 to 4. */
71
+ level: number
72
+ /**
73
+ * The message's status. Possible values are:
74
+ * - Approved
75
+ * - Denied
76
+ * - Expired
77
+ */
78
+ status: string
79
+ /** The timestamp of when automod saved the message. */
80
+ held_at: string
81
+ }
82
+ }>
83
+
84
+ registerEvent("automod.message.update", {
85
+ scopes: ["moderator:manage:automod"],
86
+ subscriber: (userId) => ({
87
+ type: "automod.message.update",
88
+ version: "2",
89
+ condition: {
90
+ broadcaster_user_id: userId,
91
+ moderator_user_id: userId,
92
+ },
93
+ }),
94
+ })