@overlaysymphony/twitch 0.2.3 → 0.3.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.
- package/package.json +5 -1
- package/src/authentication/authentication.ts +5 -2
- package/src/chat/chat.ts +56 -134
- package/src/chat/helpers.ts +32 -23
- package/src/chat/index.ts +2 -5
- package/src/chat/pronouns.ts +141 -0
- package/src/eventsub/events-helpers.ts +6 -6
- package/src/helix/channel-points/custom-rewards.ts +117 -30
- package/src/helix/channels/channels.ts +39 -0
- package/src/helix/channels/index.ts +1 -0
- package/src/helix/chat/emotes.ts +49 -0
- package/src/helix/chat/index.ts +3 -0
- package/src/helix/chat/send.ts +109 -0
- package/src/helix/chat/shared.ts +49 -0
- package/src/helix/helix.ts +5 -20
- package/src/helix/subscriptions/subscriptions.ts +11 -6
- package/src/helix/users/users.ts +25 -20
- package/src/chat/interfaces/events.ts +0 -269
- package/src/chat/interfaces/index.ts +0 -9
- package/src/chat/parser.ts +0 -254
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overlaysymphony/twitch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TwitchUser,
|
|
1
|
+
import { type TwitchUser, getUser } from "../helix/users/index.ts"
|
|
2
2
|
|
|
3
3
|
export interface Authentication {
|
|
4
4
|
tokenType: "bearer"
|
|
@@ -61,7 +61,10 @@ export async function getAuthentication(
|
|
|
61
61
|
return undefined
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const
|
|
64
|
+
const user = await getUser(authentication as Authentication)
|
|
65
|
+
if (!user) {
|
|
66
|
+
return undefined
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
return {
|
|
67
70
|
...authentication,
|
package/src/chat/chat.ts
CHANGED
|
@@ -1,161 +1,83 @@
|
|
|
1
|
-
import createDefer from "@overlaysymphony/core/libs/defer"
|
|
2
|
-
import createPubSub from "@overlaysymphony/core/libs/pubsub"
|
|
3
|
-
|
|
4
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
|
+
}
|
|
5
13
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type TwitchChatEventType,
|
|
9
|
-
} from "./interfaces/index.ts"
|
|
10
|
-
import parseCommand from "./parser.ts"
|
|
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
|
|
21
|
-
|
|
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:
|
|
18
|
+
callback: (event: ChatMessage) => void,
|
|
26
19
|
) => () => void
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
) => () => void
|
|
38
25
|
|
|
39
26
|
export interface TwitchChat {
|
|
40
|
-
listen: ChatListener
|
|
41
|
-
subscribe: ChatSubscriber
|
|
42
27
|
send: ChatSender
|
|
28
|
+
announce: ChatAnnouncer
|
|
43
29
|
onMessage: ChatMessageSubscriber
|
|
44
30
|
onCommand: ChatCommandSubscriber
|
|
45
31
|
}
|
|
46
32
|
|
|
47
33
|
export default async function createChat(
|
|
48
34
|
authentication: Authentication,
|
|
49
|
-
|
|
35
|
+
eventsub?: TwitchEventSub,
|
|
50
36
|
): Promise<TwitchChat> {
|
|
51
|
-
|
|
37
|
+
eventsub ??= await createEventSub(authentication)
|
|
52
38
|
|
|
53
|
-
const
|
|
54
|
-
|
|
39
|
+
const send: ChatSender = async (message) => {
|
|
40
|
+
await sendChatMessage(authentication, message)
|
|
41
|
+
}
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
43
|
+
const announce: ChatAnnouncer = async (message, color) => {
|
|
44
|
+
await sendChatAnnouncement(authentication, message, color)
|
|
45
|
+
}
|
|
59
46
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
continue
|
|
66
|
-
}
|
|
47
|
+
const onMessage: ChatMessageSubscriber = (callback) => {
|
|
48
|
+
return eventsub.on(["channel.chat.message"], (payload) => {
|
|
49
|
+
callback(payload.event)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
67
52
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
command.type === "JOIN" ||
|
|
71
|
-
command.type === "USERSTATE" ||
|
|
72
|
-
command.type === "HOSTTARGET" ||
|
|
73
|
-
command.type === "NOTICE"
|
|
74
|
-
) {
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
53
|
+
const onCommand: ChatCommandSubscriber = (name, callback) => {
|
|
54
|
+
const regex = /^\s*!([a-z0-9]+)(?:\s+(.+))?$/i
|
|
77
55
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
56
|
+
return onMessage((payload) => {
|
|
57
|
+
const [, command, text] =
|
|
58
|
+
payload.message.text.match(regex) ?? ([] as Array<string | undefined>)
|
|
82
59
|
|
|
83
|
-
if (command
|
|
84
|
-
|
|
85
|
-
socket.send(`NICK ${authentication.user.login}`)
|
|
86
|
-
continue
|
|
60
|
+
if (command !== name) {
|
|
61
|
+
return
|
|
87
62
|
}
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
console.warn("The server is about to terminate for maintenance.")
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (command.type === "GLOBALUSERSTATE") {
|
|
95
|
-
socket.send(`JOIN #${channel}`)
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
64
|
+
const parameters = text?.split(" ")
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
return promise.then(() => {
|
|
109
|
-
const listen: ChatListener = (callback) => {
|
|
110
|
-
return pubsub.subscribe((event) => {
|
|
111
|
-
callback(event)
|
|
66
|
+
callback({
|
|
67
|
+
...payload,
|
|
68
|
+
message: {
|
|
69
|
+
command: "",
|
|
70
|
+
parameters,
|
|
71
|
+
...payload.message,
|
|
72
|
+
},
|
|
112
73
|
})
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const send: ChatSender = (message) => {
|
|
126
|
-
socket.send(`PRIVMSG #${authentication.user.login} :${message}`)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const onMessage: ChatMessageSubscriber = (callback) => {
|
|
130
|
-
return pubsub.subscribe((event) => {
|
|
131
|
-
if (event.type === "PRIVMSG") {
|
|
132
|
-
callback(event)
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
|
|
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.")
|
|
142
|
-
}
|
|
143
|
-
|
|
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
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
listen,
|
|
155
|
-
subscribe,
|
|
156
|
-
send,
|
|
157
|
-
onMessage,
|
|
158
|
-
onCommand,
|
|
159
|
-
}
|
|
160
|
-
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
send,
|
|
79
|
+
announce,
|
|
80
|
+
onMessage,
|
|
81
|
+
onCommand,
|
|
82
|
+
}
|
|
161
83
|
}
|
package/src/chat/helpers.ts
CHANGED
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
interface ApplicableChatEvent {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
11
|
-
const broadcaster = event.
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export function isBroadcaster(event: ApplicableChatEvent): boolean {
|
|
10
|
+
const broadcaster = !!event.badges.find((badge) => {
|
|
11
|
+
return badge.set_id === "broadcaster"
|
|
12
|
+
})
|
|
14
13
|
|
|
15
14
|
return broadcaster
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
export function
|
|
19
|
-
const broadcaster = event.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
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 ||
|
|
25
|
+
return broadcaster || moderator
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function
|
|
29
|
-
const broadcaster = event.
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
44
|
+
return +(subscriber?.info ?? "0") || false
|
|
36
45
|
}
|
package/src/chat/index.ts
CHANGED
|
@@ -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
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface EventConfigs {}
|
|
2
|
+
|
|
3
|
+
export type EventType = keyof EventConfigs
|
|
4
|
+
export type EventPayload<Type extends EventType = EventType> =
|
|
5
|
+
EventConfigs[Type]["Payload"]
|
|
6
|
+
|
|
1
7
|
export type EventConfig<
|
|
2
8
|
Config extends {
|
|
3
9
|
Type: string
|
|
@@ -23,12 +29,6 @@ export type EventConfig<
|
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
export interface EventConfigs {}
|
|
27
|
-
|
|
28
|
-
export type EventType = keyof EventConfigs
|
|
29
|
-
export type EventPayload<Type extends EventType = EventType> =
|
|
30
|
-
EventConfigs[Type]["Payload"]
|
|
31
|
-
|
|
32
32
|
const events: {
|
|
33
33
|
[Type in EventType]?: {
|
|
34
34
|
scopes: string[]
|
|
@@ -2,51 +2,83 @@ import { type Authentication } from "../../authentication/index.ts"
|
|
|
2
2
|
import { helix } from "../helix.ts"
|
|
3
3
|
|
|
4
4
|
interface CustomReward {
|
|
5
|
-
broadcaster_id: string
|
|
6
|
-
broadcaster_login: string
|
|
7
|
-
broadcaster_name: string
|
|
8
|
-
id: string
|
|
9
|
-
title: string
|
|
10
|
-
prompt: string
|
|
11
|
-
cost: number
|
|
5
|
+
broadcaster_id: string /* The ID that uniquely identifies the broadcaster. */
|
|
6
|
+
broadcaster_login: string /* The broadcaster’s login name. */
|
|
7
|
+
broadcaster_name: string /* The broadcaster’s display name. */
|
|
8
|
+
id: string /* The ID that uniquely identifies this custom reward. */
|
|
9
|
+
title: string /* The title of the reward. */
|
|
10
|
+
prompt: string /* The prompt shown to the viewer when they redeem the reward if user input is required (see the is_user_input_required field). */
|
|
11
|
+
cost: number /* The cost of the reward in Channel Points. */
|
|
12
|
+
/** A set of custom images for the reward. This field is null if the broadcaster didn’t upload images. */
|
|
12
13
|
image: {
|
|
13
|
-
url_1x: string
|
|
14
|
-
url_2x: string
|
|
15
|
-
url_4x: string
|
|
14
|
+
url_1x: string /* The URL to a small version of the image. */
|
|
15
|
+
url_2x: string /* The URL to a medium version of the image. */
|
|
16
|
+
url_4x: string /* The URL to a large version of the image. */
|
|
16
17
|
}
|
|
18
|
+
/** A set of default images for the reward. */
|
|
17
19
|
default_image: {
|
|
18
|
-
url_1x: string
|
|
19
|
-
url_2x: string
|
|
20
|
-
url_4x: string
|
|
20
|
+
url_1x: string /* The URL to a small version of the image. */
|
|
21
|
+
url_2x: string /* The URL to a medium version of the image. */
|
|
22
|
+
url_4x: string /* The URL to a large version of the image. */
|
|
21
23
|
}
|
|
22
|
-
background_color: string
|
|
23
|
-
is_enabled: boolean
|
|
24
|
-
is_user_input_required: boolean
|
|
24
|
+
background_color: string /* The background color to use for the reward. The color is in Hex format (for example, #00E5CB). */
|
|
25
|
+
is_enabled: boolean /* A Boolean value that determines whether the reward is enabled. Is true if enabled; otherwise, false. Disabled rewards aren’t shown to the user. */
|
|
26
|
+
is_user_input_required: boolean /* A Boolean value that determines whether the user must enter information when redeeming the reward. Is true if the user is prompted. */
|
|
27
|
+
/** The settings used to determine whether to apply a maximum to the number of redemptions allowed per live stream. */
|
|
25
28
|
max_per_stream_setting: {
|
|
26
|
-
is_enabled: boolean
|
|
27
|
-
max_per_stream: number
|
|
29
|
+
is_enabled: boolean /* A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per live stream. Is true if the reward applies a limit. */
|
|
30
|
+
max_per_stream: number /* The maximum number of redemptions allowed per live stream. */
|
|
28
31
|
}
|
|
32
|
+
/** The settings used to determine whether to apply a maximum to the number of redemptions allowed per user per live stream. */
|
|
29
33
|
max_per_user_per_stream_setting: {
|
|
30
|
-
is_enabled: boolean
|
|
31
|
-
max_per_user_per_stream: number
|
|
34
|
+
is_enabled: boolean /* A Boolean value that determines whether the reward applies a limit on the number of redemptions allowed per user per live stream. Is true if the reward applies a limit. */
|
|
35
|
+
max_per_user_per_stream: number /* The maximum number of redemptions allowed per user per live stream. */
|
|
32
36
|
}
|
|
37
|
+
/** The settings used to determine whether to apply a cooldown period between redemptions and the length of the cooldown. */
|
|
33
38
|
global_cooldown_setting: {
|
|
34
|
-
is_enabled: boolean
|
|
35
|
-
global_cooldown_seconds: number
|
|
39
|
+
is_enabled: boolean /* A Boolean value that determines whether to apply a cooldown period. Is true if a cooldown period is enabled. */
|
|
40
|
+
global_cooldown_seconds: number /* The cooldown period, in seconds. */
|
|
36
41
|
}
|
|
37
|
-
is_paused: boolean
|
|
38
|
-
is_in_stock: boolean
|
|
39
|
-
should_redemptions_skip_request_queue: boolean
|
|
40
|
-
redemptions_redeemed_current_stream: number
|
|
41
|
-
cooldown_expires_at: string
|
|
42
|
+
is_paused: boolean /* A Boolean value that determines whether the reward is currently paused. Is true if the reward is paused. Viewers can’t redeem paused rewards. */
|
|
43
|
+
is_in_stock: boolean /* A Boolean value that determines whether the reward is currently in stock. Is true if the reward is in stock. Viewers can’t redeem out of stock rewards. */
|
|
44
|
+
should_redemptions_skip_request_queue: boolean /* A Boolean value that determines whether redemptions should be set to FULFILLED status immediately when a reward is redeemed. If false, status is set to UNFULFILLED and follows the normal request queue process. */
|
|
45
|
+
redemptions_redeemed_current_stream: number /* The number of redemptions redeemed during the current live stream. The number counts against the max_per_stream_setting limit. This field is null if the broadcaster’s stream isn’t live or max_per_stream_setting isn’t enabled. */
|
|
46
|
+
cooldown_expires_at: string /* The timestamp of when the cooldown period expires. Is null if the reward isn’t in a cooldown state. See the global_cooldown_setting field. */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CustomRewardResponse {
|
|
50
|
+
data: CustomReward[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Redemption {
|
|
54
|
+
broadcaster_id: string /** The ID that uniquely identifies the broadcaster. */
|
|
55
|
+
broadcaster_login: string /** The broadcaster’s login name. */
|
|
56
|
+
broadcaster_name: string /** The broadcaster’s display name. */
|
|
57
|
+
id: string /** The ID that uniquely identifies this redemption. */
|
|
58
|
+
user_login: string /** The user’s login name. */
|
|
59
|
+
user_id: string /** The ID that uniquely identifies the user that redeemed the reward. */
|
|
60
|
+
user_name: string /** The user’s display name. */
|
|
61
|
+
user_input: string /** The text the user entered at the prompt when they redeemed the reward; otherwise, an empty string if user input was not required. */
|
|
62
|
+
status: string /** The state of the redemption. Possible values are:\n - CANCELED\n - FULFILLED\n - UNFULFILLED */
|
|
63
|
+
redeemed_at: string /** The date and time of when the reward was redeemed, in RFC3339 format. */
|
|
64
|
+
/** The reward that the user redeemed. */
|
|
65
|
+
reward: {
|
|
66
|
+
id: string /** The ID that uniquely identifies the redeemed reward. */
|
|
67
|
+
title: string /** The reward’s title. */
|
|
68
|
+
prompt: string /** The prompt displayed to the viewer if user input is required. */
|
|
69
|
+
cost: number /** The reward’s cost, in Channel Points. */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface RedemptionResponse {
|
|
74
|
+
data: Redemption[]
|
|
42
75
|
}
|
|
43
76
|
|
|
44
77
|
export async function getCustomRewards(
|
|
45
78
|
authentication: Authentication,
|
|
46
79
|
): Promise<CustomReward[]> {
|
|
47
|
-
const subscriptions = await helix<
|
|
48
|
-
|
|
49
|
-
never,
|
|
80
|
+
const { data: subscriptions } = await helix<
|
|
81
|
+
CustomRewardResponse,
|
|
50
82
|
{
|
|
51
83
|
broadcaster_id: string
|
|
52
84
|
}
|
|
@@ -60,3 +92,58 @@ export async function getCustomRewards(
|
|
|
60
92
|
|
|
61
93
|
return subscriptions
|
|
62
94
|
}
|
|
95
|
+
|
|
96
|
+
export async function getRedemptions(
|
|
97
|
+
authentication: Authentication,
|
|
98
|
+
reward_id: string,
|
|
99
|
+
id: string,
|
|
100
|
+
): Promise<Redemption[]> {
|
|
101
|
+
const { data: redemptions } = await helix<
|
|
102
|
+
RedemptionResponse,
|
|
103
|
+
{
|
|
104
|
+
broadcaster_id: string
|
|
105
|
+
reward_id: string
|
|
106
|
+
id?: string
|
|
107
|
+
status?: "CANCELED" | "FULFILLED" | "UNFULFILLED"
|
|
108
|
+
}
|
|
109
|
+
>(authentication, {
|
|
110
|
+
method: "GET",
|
|
111
|
+
path: "/channel_points/custom_rewards",
|
|
112
|
+
params: {
|
|
113
|
+
broadcaster_id: authentication.user.id,
|
|
114
|
+
reward_id,
|
|
115
|
+
id,
|
|
116
|
+
status: id ? undefined : "UNFULFILLED",
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return redemptions
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function updateRedemption(
|
|
124
|
+
authentication: Authentication,
|
|
125
|
+
reward_id: string,
|
|
126
|
+
id: string,
|
|
127
|
+
status: "CANCELED" | "FULFILLED",
|
|
128
|
+
): Promise<Redemption[]> {
|
|
129
|
+
const { data: redemptions } = await helix<
|
|
130
|
+
RedemptionResponse,
|
|
131
|
+
{
|
|
132
|
+
broadcaster_id: string
|
|
133
|
+
reward_id: string
|
|
134
|
+
id: string
|
|
135
|
+
status: "CANCELED" | "FULFILLED"
|
|
136
|
+
}
|
|
137
|
+
>(authentication, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
path: "/channel_points/custom_rewards",
|
|
140
|
+
params: {
|
|
141
|
+
broadcaster_id: authentication.user.id,
|
|
142
|
+
reward_id,
|
|
143
|
+
id,
|
|
144
|
+
status,
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return redemptions
|
|
149
|
+
}
|