@overlaysymphony/twitch 0.3.2 → 0.3.4
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 +2 -2
- package/src/authentication/authentication.ts +2 -2
- package/src/chat/chat.ts +4 -4
- package/src/chat/pronouns.ts +4 -7
- package/src/helix/chat/emotes.ts +59 -22
- package/src/helix/streams/index.ts +1 -0
- package/src/helix/streams/streams.ts +48 -0
- package/src/helix/users/users.ts +22 -0
- package/src/helpers/alerts/alerts.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overlaysymphony/twitch",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Twitch module for the OverlaySymphony interactive streaming framework.",
|
|
5
5
|
"homepage": "https://github.com/OverlaySymphony/overlaysymphony",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"./package.json": "./package.json"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@overlaysymphony/core": "0.2.
|
|
21
|
+
"@overlaysymphony/core": "0.2.3"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@vitest/coverage-v8": "^2.1.2",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TwitchUser,
|
|
1
|
+
import { type TwitchUser, getCurrentUser } from "../helix/users/index.ts"
|
|
2
2
|
|
|
3
3
|
export interface Authentication {
|
|
4
4
|
tokenType: "bearer"
|
|
@@ -61,7 +61,7 @@ export async function getAuthentication(
|
|
|
61
61
|
return undefined
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const user = await
|
|
64
|
+
const user = await getCurrentUser(authentication as Authentication)
|
|
65
65
|
if (!user) {
|
|
66
66
|
return undefined
|
|
67
67
|
}
|
package/src/chat/chat.ts
CHANGED
|
@@ -7,7 +7,7 @@ type ChatMessage = EventPayload<"channel.chat.message">["event"]
|
|
|
7
7
|
type ChatCommand = ChatMessage & {
|
|
8
8
|
message: {
|
|
9
9
|
command: string
|
|
10
|
-
parameters?: string
|
|
10
|
+
parameters?: string
|
|
11
11
|
} & ChatMessage["message"]
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -46,6 +46,8 @@ export default async function createChat(
|
|
|
46
46
|
|
|
47
47
|
const onMessage: ChatMessageSubscriber = (callback) => {
|
|
48
48
|
return eventsub.on(["channel.chat.message"], (payload) => {
|
|
49
|
+
if (payload.event.broadcaster_user_id !== authentication.user.id) return
|
|
50
|
+
|
|
49
51
|
callback(payload.event)
|
|
50
52
|
})
|
|
51
53
|
}
|
|
@@ -54,15 +56,13 @@ export default async function createChat(
|
|
|
54
56
|
const regex = /^\s*!([a-z0-9]+)(?:\s+(.+))?$/i
|
|
55
57
|
|
|
56
58
|
return onMessage((payload) => {
|
|
57
|
-
const [, command,
|
|
59
|
+
const [, command, parameters] =
|
|
58
60
|
payload.message.text.match(regex) ?? ([] as Array<string | undefined>)
|
|
59
61
|
|
|
60
62
|
if (command !== name) {
|
|
61
63
|
return
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
const parameters = text?.split(" ")
|
|
65
|
-
|
|
66
66
|
callback({
|
|
67
67
|
...payload,
|
|
68
68
|
message: {
|
package/src/chat/pronouns.ts
CHANGED
|
@@ -33,17 +33,17 @@ const pronouns: Record<string, Pronouns> = {
|
|
|
33
33
|
},
|
|
34
34
|
shethem: {
|
|
35
35
|
subject: "she",
|
|
36
|
-
object: "
|
|
36
|
+
object: "them",
|
|
37
37
|
posessive: "their",
|
|
38
38
|
},
|
|
39
39
|
hethem: {
|
|
40
40
|
subject: "he",
|
|
41
|
-
object: "
|
|
41
|
+
object: "them",
|
|
42
42
|
posessive: "their",
|
|
43
43
|
},
|
|
44
44
|
heshe: {
|
|
45
45
|
subject: "he",
|
|
46
|
-
object: "
|
|
46
|
+
object: "her",
|
|
47
47
|
posessive: "their",
|
|
48
48
|
},
|
|
49
49
|
xexem: {
|
|
@@ -123,10 +123,7 @@ export async function getUserPronouns(
|
|
|
123
123
|
export async function getPronouns(
|
|
124
124
|
login: string,
|
|
125
125
|
fallback: keyof typeof pronouns = "theythem",
|
|
126
|
-
): Promise<{
|
|
127
|
-
subject: string
|
|
128
|
-
object: string
|
|
129
|
-
}> {
|
|
126
|
+
): Promise<Pronouns> {
|
|
130
127
|
if (!(login in cache)) {
|
|
131
128
|
try {
|
|
132
129
|
const id = await getUserPronouns(login)
|
package/src/helix/chat/emotes.ts
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
import { type Authentication } from "../../authentication/index.ts"
|
|
2
2
|
import { helix } from "../helix.ts"
|
|
3
3
|
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
interface Emote {
|
|
5
|
+
/** An ID that identifies this emote. */
|
|
6
|
+
id: string
|
|
7
|
+
/** The name of the emote. This is the name that viewers type in the chat window to get the emote to appear. */
|
|
8
|
+
name: string
|
|
9
|
+
/** A generator for the image URLs for the emote. */
|
|
10
|
+
image: (config?: { format?: string; mode?: string; scale?: string }) => string
|
|
11
|
+
/** The subscriber tier at which the emote is unlocked. This field contains the tier information only if emote_type is set to subscriptions, otherwise, it's an empty string. */
|
|
12
|
+
tier: string
|
|
13
|
+
/** The type of emote. Possible values are:\n* bitstier — A custom Bits tier emote.\n* follower — A custom follower emote.\n* subscriptions — A custom subscriber emote. */
|
|
14
|
+
emote_type: string
|
|
15
|
+
/** An ID that identifies the emote set that the emote belongs to. */
|
|
16
|
+
emote_set_id: string
|
|
17
|
+
/** The ID of the broadcaster who owns the emote. */
|
|
18
|
+
owner_id: string
|
|
19
|
+
/** The formats that the emote is available in. Possible values are:\n* animated — An animated GIF is available for this emote.\n* static — A static PNG file is available for this emote. */
|
|
20
|
+
format: string[]
|
|
21
|
+
/** The sizes that the emote is available in.\n* 1.0 — A small version (28px x 28px) is available.\n* 2.0 — A medium version (56px x 56px) is available.\n* 3.0 — A large version (112px x 112px) is available. */
|
|
22
|
+
scale: string[]
|
|
23
|
+
/** The background themes that the emote is available in. Possible values are:\n* dark\n* light */
|
|
24
|
+
theme_mode: string[]
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
interface ChannelEmoteResponse {
|
|
17
|
-
data: Array<
|
|
18
|
-
Omit<ChannelEmote, "image"> & {
|
|
19
|
-
images: {
|
|
20
|
-
url_1x: string
|
|
21
|
-
url_2x: string
|
|
22
|
-
url_4x: string
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
>
|
|
28
|
+
data: Array<Omit<Emote, "image" | "owner_id">>
|
|
26
29
|
template: string
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export async function getChannelEmotes(
|
|
30
33
|
authentication: Authentication,
|
|
31
|
-
): Promise<
|
|
34
|
+
): Promise<Emote[]> {
|
|
32
35
|
const { data: emotes, template } = await helix<
|
|
33
36
|
ChannelEmoteResponse,
|
|
34
37
|
{
|
|
@@ -42,8 +45,42 @@ export async function getChannelEmotes(
|
|
|
42
45
|
},
|
|
43
46
|
})
|
|
44
47
|
|
|
45
|
-
return emotes.map((
|
|
48
|
+
return emotes.map((emote) => ({
|
|
49
|
+
owner_id: authentication.user.id,
|
|
50
|
+
...emote,
|
|
51
|
+
image: ({ format, mode, scale } = {}) =>
|
|
52
|
+
template
|
|
53
|
+
.replace("{{id}}", emote.id)
|
|
54
|
+
.replace("{{format}}", format ?? emote.format[0])
|
|
55
|
+
.replace("{{theme_mode}}", mode ?? emote.theme_mode[0])
|
|
56
|
+
.replace("{{scale}}", scale ?? emote.scale[emote.scale.length - 1]),
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getUserEmotes(
|
|
61
|
+
authentication: Authentication,
|
|
62
|
+
): Promise<Emote[]> {
|
|
63
|
+
const { data: emotes, template } = await helix<
|
|
64
|
+
ChannelEmoteResponse,
|
|
65
|
+
{
|
|
66
|
+
user_id: string
|
|
67
|
+
}
|
|
68
|
+
>(authentication, {
|
|
69
|
+
method: "GET",
|
|
70
|
+
path: "/chat/emotes/user",
|
|
71
|
+
params: {
|
|
72
|
+
user_id: authentication.user.id,
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return emotes.map((emote) => ({
|
|
77
|
+
owner_id: authentication.user.id,
|
|
46
78
|
...emote,
|
|
47
|
-
image: () =>
|
|
79
|
+
image: ({ format, mode, scale } = {}) =>
|
|
80
|
+
template
|
|
81
|
+
.replace("{{id}}", emote.id)
|
|
82
|
+
.replace("{{format}}", format ?? emote.format[0])
|
|
83
|
+
.replace("{{theme_mode}}", mode ?? emote.theme_mode[0])
|
|
84
|
+
.replace("{{scale}}", scale ?? emote.scale[emote.scale.length - 1]),
|
|
48
85
|
}))
|
|
49
86
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./streams.ts"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Authentication } from "../../authentication/index.ts"
|
|
2
|
+
import { helix } from "../helix.ts"
|
|
3
|
+
|
|
4
|
+
export interface TwitchStream {
|
|
5
|
+
id: string
|
|
6
|
+
user_id: string
|
|
7
|
+
user_login: string
|
|
8
|
+
user_name: string
|
|
9
|
+
game_id: string
|
|
10
|
+
game_name: string
|
|
11
|
+
type: string
|
|
12
|
+
title: string
|
|
13
|
+
tags: string[]
|
|
14
|
+
viewer_count: number
|
|
15
|
+
started_at: string
|
|
16
|
+
language: string
|
|
17
|
+
thumbnail_url: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface StreamsResponse {
|
|
21
|
+
data: TwitchStream[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getStream(
|
|
25
|
+
authentication: Authentication,
|
|
26
|
+
login?: string | string[],
|
|
27
|
+
id?: string | string[],
|
|
28
|
+
): Promise<TwitchStream | undefined> {
|
|
29
|
+
if (!login && !id) return undefined
|
|
30
|
+
|
|
31
|
+
if (typeof login === "string" && login.startsWith("@")) {
|
|
32
|
+
login = login.slice(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { data: streams } = await helix<
|
|
36
|
+
StreamsResponse,
|
|
37
|
+
{ user_id?: string | string[]; user_login?: string | string[] }
|
|
38
|
+
>(authentication, {
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/users",
|
|
41
|
+
params: {
|
|
42
|
+
user_id: id,
|
|
43
|
+
user_login: login,
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return streams[0]
|
|
48
|
+
}
|
package/src/helix/users/users.ts
CHANGED
|
@@ -26,6 +26,12 @@ export async function getUser(
|
|
|
26
26
|
login?: string | string[],
|
|
27
27
|
id?: string | string[],
|
|
28
28
|
): Promise<TwitchUser | undefined> {
|
|
29
|
+
if (!login && !id) return undefined
|
|
30
|
+
|
|
31
|
+
if (typeof login === "string" && login.startsWith("@")) {
|
|
32
|
+
login = login.slice(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
const { data: users } = await helix<
|
|
30
36
|
UsersResponse,
|
|
31
37
|
{ id?: string | string[]; login?: string | string[] }
|
|
@@ -45,3 +51,19 @@ export async function getUser(
|
|
|
45
51
|
created_at: new Date(created_at),
|
|
46
52
|
}
|
|
47
53
|
}
|
|
54
|
+
|
|
55
|
+
export async function getCurrentUser(
|
|
56
|
+
authentication: Authentication,
|
|
57
|
+
): Promise<TwitchUser | undefined> {
|
|
58
|
+
const { data: users } = await helix<UsersResponse>(authentication, {
|
|
59
|
+
method: "GET",
|
|
60
|
+
path: "/users",
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const [{ created_at, ...user }] = users
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...user,
|
|
67
|
+
created_at: new Date(created_at),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -47,7 +47,7 @@ export function onAlert(
|
|
|
47
47
|
|
|
48
48
|
export default function createAlertQueue(
|
|
49
49
|
eventsub: TwitchEventSub,
|
|
50
|
-
handleAlert: (alert: Alert) =>
|
|
50
|
+
handleAlert: (alert: Alert) => unknown,
|
|
51
51
|
): Queue<Alert>["dismiss"] {
|
|
52
52
|
const queue = createQueue<Alert>()
|
|
53
53
|
queue.listen(handleAlert)
|