@overlaysymphony/twitch 0.2.0 → 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.
- package/package.json +4 -5
- package/src/authentication/authentication.ts +27 -1
- package/src/chat/helpers.ts +36 -0
- package/src/chat/index.ts +1 -0
- package/src/eventsub/events/index.ts +0 -1
- package/src/eventsub/eventsub.ts +1 -2
- package/src/helpers/poll/poll.ts +16 -2
- package/src/helpers/redemption/redemption.ts +20 -1
- package/src/ui/authentication.ts +1 -2
- package/src/ui/popup.ts +3 -3
- package/src/ui/vite-env.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overlaysymphony/twitch",
|
|
3
|
-
"version": "0.2.
|
|
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",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./package.json": "./package.json"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@overlaysymphony/core": "0.
|
|
17
|
+
"@overlaysymphony/core": "0.2.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@vitest/coverage-v8": "^2.1.2",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"eslint": "^9.19.0",
|
|
23
23
|
"prettier": "^3.4.2",
|
|
24
24
|
"typescript": "^5.7.2",
|
|
25
|
+
"vite": "^6.2.1",
|
|
25
26
|
"vitest": "^2.1.2",
|
|
26
27
|
"@overlaysymphony/tooling": "0.0.0"
|
|
27
28
|
},
|
|
@@ -32,8 +33,6 @@
|
|
|
32
33
|
"lint-prettier": "prettier --check .",
|
|
33
34
|
"lint-depcheck": "depcheck .",
|
|
34
35
|
"test": "vitest",
|
|
35
|
-
"
|
|
36
|
-
"clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo dist",
|
|
37
|
-
"build": "echo TODO"
|
|
36
|
+
"clean": "rm -rf node_modules/.cache tsconfig.tsbuildinfo"
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type TwitchUser, getUsers } from "../helix/users/index.js"
|
|
2
2
|
|
|
3
3
|
export interface Authentication {
|
|
4
|
-
clientId: string
|
|
5
4
|
tokenType: "bearer"
|
|
5
|
+
clientId: string
|
|
6
6
|
accessToken: string
|
|
7
7
|
scope: string[]
|
|
8
8
|
expires: Date
|
|
@@ -69,6 +69,32 @@ export async function getAuthentication(
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
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)
|
|
94
|
+
|
|
95
|
+
return authentication
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
export async function popupAuthentication(
|
|
73
99
|
clientId: string,
|
|
74
100
|
scopes: string[],
|
|
@@ -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
|
@@ -276,7 +276,6 @@ export function buildSubscription<
|
|
|
276
276
|
Subscription extends TwitchSubscription<Type>,
|
|
277
277
|
>(type: Type, userId: string): Subscription {
|
|
278
278
|
const creator = subscriptionBuilders[type]
|
|
279
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
280
279
|
if (!creator) {
|
|
281
280
|
throw new Error(`Unknown type ${type}`)
|
|
282
281
|
}
|
package/src/eventsub/eventsub.ts
CHANGED
|
@@ -66,8 +66,7 @@ export async function createEventSub(
|
|
|
66
66
|
for (const type of types) {
|
|
67
67
|
if (!subscriptions[type]) {
|
|
68
68
|
subscriptions[type] = true
|
|
69
|
-
|
|
70
|
-
createSubscription(sessionId, authentication, type)
|
|
69
|
+
void createSubscription(sessionId, authentication, type)
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
package/src/helpers/poll/poll.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type TwitchEventSub } from "../../eventsub/index.js"
|
|
2
2
|
|
|
3
3
|
export interface Poll {
|
|
4
|
+
id: string
|
|
4
5
|
title: string
|
|
5
6
|
choices: Array<{
|
|
6
7
|
id: string
|
|
@@ -10,7 +11,7 @@ export interface Poll {
|
|
|
10
11
|
votesChannelPoints: number
|
|
11
12
|
votesNormal: number
|
|
12
13
|
}>
|
|
13
|
-
endsAt
|
|
14
|
+
endsAt?: Date
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const mapTypeToTrigger = {
|
|
@@ -24,14 +25,25 @@ export function onPoll(
|
|
|
24
25
|
handlePoll: (poll: Poll, trigger: "begin" | "progress" | "end") => void,
|
|
25
26
|
): void {
|
|
26
27
|
const poll: Poll = {
|
|
28
|
+
id: "",
|
|
27
29
|
title: "",
|
|
28
30
|
choices: [],
|
|
29
|
-
endsAt:
|
|
31
|
+
endsAt: undefined,
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
eventsub.subscribe(
|
|
33
35
|
["channel.poll.begin", "channel.poll.progress", "channel.poll.end"],
|
|
34
36
|
(payload) => {
|
|
37
|
+
// Twitch sometimes sends duplicate end events.
|
|
38
|
+
if (payload.type === "channel.poll.end") {
|
|
39
|
+
// Skip if its already ended
|
|
40
|
+
if (typeof poll.endsAt === "undefined") return
|
|
41
|
+
|
|
42
|
+
// Skip if its for the wrong poll.
|
|
43
|
+
if (payload.event.id !== poll.id) return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
poll.id = payload.event.id
|
|
35
47
|
poll.title = payload.event.title
|
|
36
48
|
poll.choices = payload.event.choices.map(
|
|
37
49
|
({
|
|
@@ -55,6 +67,8 @@ export function onPoll(
|
|
|
55
67
|
payload.type === "channel.poll.progress"
|
|
56
68
|
) {
|
|
57
69
|
poll.endsAt = payload.event.ends_at
|
|
70
|
+
} else {
|
|
71
|
+
poll.endsAt = undefined
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
handlePoll(poll, mapTypeToTrigger[payload.type])
|
|
@@ -14,16 +14,35 @@ export interface Redemption {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export function onRedemption(
|
|
18
|
+
eventsub: TwitchEventSub,
|
|
19
|
+
handleRedemption: (redemption: Redemption) => void,
|
|
20
|
+
): void
|
|
17
21
|
export function onRedemption(
|
|
18
22
|
eventsub: TwitchEventSub,
|
|
19
23
|
id: string,
|
|
20
24
|
handleRedemption: (redemption: Redemption) => void,
|
|
25
|
+
): void
|
|
26
|
+
|
|
27
|
+
export function onRedemption(
|
|
28
|
+
eventsub: TwitchEventSub,
|
|
29
|
+
...config:
|
|
30
|
+
| [string, (redemption: Redemption) => void]
|
|
31
|
+
| [(redemption: Redemption) => void]
|
|
21
32
|
): void {
|
|
33
|
+
const id = typeof config[0] === "string" ? config[0] : undefined
|
|
34
|
+
const handleRedemption =
|
|
35
|
+
typeof config[0] === "function"
|
|
36
|
+
? config[0]
|
|
37
|
+
: typeof config[1] === "function"
|
|
38
|
+
? config[1]
|
|
39
|
+
: undefined
|
|
40
|
+
|
|
22
41
|
eventsub.subscribe(
|
|
23
42
|
["channel.channel_points_custom_reward_redemption.add"],
|
|
24
43
|
(payload) => {
|
|
25
44
|
if (payload.event.reward.id === id) {
|
|
26
|
-
handleRedemption({
|
|
45
|
+
handleRedemption?.({
|
|
27
46
|
id: payload.event.id,
|
|
28
47
|
userId: payload.event.user_id,
|
|
29
48
|
userLogin: payload.event.user_login,
|
package/src/ui/authentication.ts
CHANGED
|
@@ -70,8 +70,7 @@ export class TwitchAuthentication extends HTMLElement {
|
|
|
70
70
|
this.root = this.attachShadow({ mode: "open" })
|
|
71
71
|
this.root.adoptedStyleSheets.push(stylesheet)
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
this.render()
|
|
73
|
+
void this.render()
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
get clientId(): string {
|
package/src/ui/popup.ts
CHANGED
|
@@ -17,8 +17,8 @@ if (state.step === "initial") {
|
|
|
17
17
|
|
|
18
18
|
if (state.step === "token") {
|
|
19
19
|
const authentication = await authenticateResult(state.clientId, state)
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const opener = window.opener as Window | undefined
|
|
21
|
+
opener?.postMessage({ type: "authentication", authentication }, "*")
|
|
22
22
|
window.close()
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -144,8 +144,8 @@ async function authenticateResult(
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
const authentication = await validateAuthentication({
|
|
147
|
-
clientId,
|
|
148
147
|
tokenType: result.token_type,
|
|
148
|
+
clientId,
|
|
149
149
|
accessToken: result.access_token,
|
|
150
150
|
scope: result.scope.split("+"),
|
|
151
151
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|