@overlaysymphony/twitch 0.2.0 → 0.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overlaysymphony/twitch",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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.1.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
- "dev": "echo TODO",
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
@@ -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"
@@ -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
  }
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
70
- createSubscription(sessionId, authentication, type)
69
+ void createSubscription(sessionId, authentication, type)
71
70
  }
72
71
  }
73
72
 
@@ -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: Date
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: new Date(""),
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
- if (payload.event.reward.id === id) {
26
- handleRedemption({
44
+ if (typeof id === "undefined" || payload.event.reward.id === id) {
45
+ handleRedemption?.({
27
46
  id: payload.event.id,
28
47
  userId: payload.event.user_id,
29
48
  userLogin: payload.event.user_login,
@@ -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
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
21
- window.opener.postMessage({ type: "authentication", authentication }, "*")
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" />