@manaobot/kick 1.0.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.
- package/README.md +163 -0
- package/biome.json +44 -0
- package/bun.lock +78 -0
- package/example/01-authorize-bot/.env.example +7 -0
- package/example/01-authorize-bot/index.ts +60 -0
- package/example/02-webhook/.env.example +7 -0
- package/example/02-webhook/index.ts +80 -0
- package/example/03-ngrok/.env.example +7 -0
- package/example/03-ngrok/index.ts +92 -0
- package/example/04-categories-api/.env.example +7 -0
- package/example/04-categories-api/index.ts +77 -0
- package/example/05-users-api/.env.example +7 -0
- package/example/05-users-api/index.ts +60 -0
- package/example/06-channels-api/.env.example +7 -0
- package/example/06-channels-api/index.ts +60 -0
- package/example/07-channel-rewards-api/.env.example +7 -0
- package/example/07-channel-rewards-api/index.ts +60 -0
- package/example/08-basic-chat-bot/.env.example +7 -0
- package/example/08-basic-chat-bot/index.ts +102 -0
- package/package.json +23 -0
- package/qodana.yaml +31 -0
- package/src/KickClient.ts +172 -0
- package/src/Logger.ts +25 -0
- package/src/api/CategoriesAPI.ts +45 -0
- package/src/api/ChannelRewardsAPI.ts +121 -0
- package/src/api/ChannelsAPI.ts +63 -0
- package/src/api/KicksAPI.ts +37 -0
- package/src/api/LivestreamsAPI.ts +65 -0
- package/src/api/ModerationAPI.ts +60 -0
- package/src/api/UsersAPI.ts +72 -0
- package/src/auth/AuthManager.ts +64 -0
- package/src/auth/CallbackServer.ts +57 -0
- package/src/auth/OAuth.ts +55 -0
- package/src/auth/PKCE.ts +13 -0
- package/src/auth/TokenManager.ts +53 -0
- package/src/chat/ChatClient.ts +48 -0
- package/src/rest/RestClient.ts +39 -0
- package/src/webhooks/NgrokAdapter.ts +46 -0
- package/src/webhooks/WebhookRouter.ts +135 -0
- package/src/webhooks/WebhookServer.ts +41 -0
- package/tsconfig.json +29 -0
- package/types/api.d.ts +158 -0
- package/types/auth.d.ts +38 -0
- package/types/chat.d.ts +14 -0
- package/types/client.d.ts +67 -0
- package/types/index.d.ts +4 -0
- package/types/webhooks.d.ts +35 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 05: Using Users API
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates how to use the Users API of the ManaoKick library.
|
|
5
|
+
* The bot will fetch and display user data once authorized.
|
|
6
|
+
* In the first run, it will print the access and refresh tokens to the console for
|
|
7
|
+
* you to save in your environment variables file (.env).
|
|
8
|
+
*
|
|
9
|
+
* Before running this code, ensure you have the following environment variables set:
|
|
10
|
+
* - KICK_CLIENT_ID
|
|
11
|
+
* - KICK_CLIENT_SECRET
|
|
12
|
+
*
|
|
13
|
+
* SCOPES: ["user:read"]
|
|
14
|
+
* Make sure to refresh the tokens and update your environment variables when scopes change.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { KickClient } from "../../src/KickClient.ts";
|
|
18
|
+
import type { KickTokenResponse } from "../../types";
|
|
19
|
+
|
|
20
|
+
// Initialize the KickClient with necessary credentials and scopes
|
|
21
|
+
const kick = new KickClient({
|
|
22
|
+
clientId: Bun.env.KICK_CLIENT_ID!,
|
|
23
|
+
clientSecret: Bun.env.KICK_CLIENT_SECRET!,
|
|
24
|
+
redirectUri: "http://localhost:3000/callback",
|
|
25
|
+
scopes: ["user:read"],
|
|
26
|
+
showLog: false,
|
|
27
|
+
auth: {
|
|
28
|
+
initialTokens: Bun.env.KICK_REFRESH_TOKEN
|
|
29
|
+
? {
|
|
30
|
+
access_token: Bun.env.KICK_ACCESS_TOKEN!,
|
|
31
|
+
refresh_token: Bun.env.KICK_REFRESH_TOKEN!,
|
|
32
|
+
}
|
|
33
|
+
: undefined,
|
|
34
|
+
onTokenUpdate: (tokens: KickTokenResponse) => {
|
|
35
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
36
|
+
console.log("\n[!] Copy these into your .env file:\n");
|
|
37
|
+
console.log(`KICK_ACCESS_TOKEN=${tokens.access_token}`);
|
|
38
|
+
console.log(`KICK_REFRESH_TOKEN=${tokens.refresh_token}`);
|
|
39
|
+
console.log(`\n====> Scopes granted: ${tokens.scope}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Bun.env.KICK_ACCESS_TOKEN = tokens.access_token;
|
|
43
|
+
Bun.env.KICK_REFRESH_TOKEN = tokens.refresh_token;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// If no refresh token is found, initiate the authorization flow
|
|
49
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
50
|
+
console.log(`Authorize the application by visiting:\n${kick.getAuthURL()}`);
|
|
51
|
+
kick.auth.createCallbackServer({ port: 3000 });
|
|
52
|
+
await kick.auth.waitForAuthorization();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Confirm successful authorization
|
|
56
|
+
console.log("\n[✔] Application authorized successfully!");
|
|
57
|
+
|
|
58
|
+
const { data } = await kick.api.users.get();
|
|
59
|
+
console.log("\n[✔] User data fetched successfully!");
|
|
60
|
+
console.log(data);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 06: Using Channels API
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates how to use the Channels API with the ManaoKick library.
|
|
5
|
+
* The bot will fetch and display current channel data once authorized.
|
|
6
|
+
* In the first run, it will print the access and refresh tokens to the console for
|
|
7
|
+
* you to save in your environment variables file (.env).
|
|
8
|
+
*
|
|
9
|
+
* Before running this code, ensure you have the following environment variables set:
|
|
10
|
+
* - KICK_CLIENT_ID
|
|
11
|
+
* - KICK_CLIENT_SECRET
|
|
12
|
+
*
|
|
13
|
+
* SCOPES: ["channel:read"]
|
|
14
|
+
* Make sure to refresh the tokens and update your environment variables when scopes change.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { KickClient } from "../../src/KickClient.ts";
|
|
18
|
+
import type { KickTokenResponse } from "../../types";
|
|
19
|
+
|
|
20
|
+
// Initialize the KickClient
|
|
21
|
+
const kick = new KickClient({
|
|
22
|
+
clientId: Bun.env.KICK_CLIENT_ID!,
|
|
23
|
+
clientSecret: Bun.env.KICK_CLIENT_SECRET!,
|
|
24
|
+
redirectUri: "http://localhost:3000/callback",
|
|
25
|
+
scopes: ["channel:read"],
|
|
26
|
+
showLog: false,
|
|
27
|
+
auth: {
|
|
28
|
+
initialTokens: Bun.env.KICK_REFRESH_TOKEN
|
|
29
|
+
? {
|
|
30
|
+
access_token: Bun.env.KICK_ACCESS_TOKEN!,
|
|
31
|
+
refresh_token: Bun.env.KICK_REFRESH_TOKEN!,
|
|
32
|
+
}
|
|
33
|
+
: undefined,
|
|
34
|
+
onTokenUpdate: (tokens: KickTokenResponse) => {
|
|
35
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
36
|
+
console.log("\n[!] Copy these into your .env file:\n");
|
|
37
|
+
console.log(`KICK_ACCESS_TOKEN=${tokens.access_token}`);
|
|
38
|
+
console.log(`KICK_REFRESH_TOKEN=${tokens.refresh_token}`);
|
|
39
|
+
console.log(`\n====> Scopes granted: ${tokens.scope}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Bun.env.KICK_ACCESS_TOKEN = tokens.access_token;
|
|
43
|
+
Bun.env.KICK_REFRESH_TOKEN = tokens.refresh_token;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Authorization flow
|
|
49
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
50
|
+
console.log(`Authorize the application by visiting:\n${kick.getAuthURL()}`);
|
|
51
|
+
kick.auth.createCallbackServer({ port: 3000 });
|
|
52
|
+
await kick.auth.waitForAuthorization();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Confirm successful authorization
|
|
56
|
+
console.log("\n[✔] Application authorized successfully!");
|
|
57
|
+
|
|
58
|
+
let { data } = await kick.api.channels.get();
|
|
59
|
+
console.log("\n[✔] Channels fetched successfully!");
|
|
60
|
+
console.log(data);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 07: Using ChannelRewards API
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates how to use the ChannelRewards API with the ManaoKick library.
|
|
5
|
+
* The bot will fetch and display current channel rewards data once authorized.
|
|
6
|
+
* In the first run, it will print the access and refresh tokens to the console for
|
|
7
|
+
* you to save in your environment variables file (.env).
|
|
8
|
+
*
|
|
9
|
+
* Before running this code, ensure you have the following environment variables set:
|
|
10
|
+
* - KICK_CLIENT_ID
|
|
11
|
+
* - KICK_CLIENT_SECRET
|
|
12
|
+
*
|
|
13
|
+
* SCOPES: ["channel:rewards:write"]
|
|
14
|
+
* Make sure to refresh the tokens and update your environment variables when scopes change.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { KickClient } from "../../src/KickClient.ts";
|
|
18
|
+
import type { KickTokenResponse } from "../../types";
|
|
19
|
+
|
|
20
|
+
// Initialize the KickClient
|
|
21
|
+
const kick = new KickClient({
|
|
22
|
+
clientId: Bun.env.KICK_CLIENT_ID!,
|
|
23
|
+
clientSecret: Bun.env.KICK_CLIENT_SECRET!,
|
|
24
|
+
redirectUri: "http://localhost:3000/callback",
|
|
25
|
+
scopes: ["channel:rewards:write"],
|
|
26
|
+
showLog: false,
|
|
27
|
+
auth: {
|
|
28
|
+
initialTokens: Bun.env.KICK_REFRESH_TOKEN
|
|
29
|
+
? {
|
|
30
|
+
access_token: Bun.env.KICK_ACCESS_TOKEN!,
|
|
31
|
+
refresh_token: Bun.env.KICK_REFRESH_TOKEN!,
|
|
32
|
+
}
|
|
33
|
+
: undefined,
|
|
34
|
+
onTokenUpdate: (tokens: KickTokenResponse) => {
|
|
35
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
36
|
+
console.log("\n[!] Copy these into your .env file:\n");
|
|
37
|
+
console.log(`KICK_ACCESS_TOKEN=${tokens.access_token}`);
|
|
38
|
+
console.log(`KICK_REFRESH_TOKEN=${tokens.refresh_token}`);
|
|
39
|
+
console.log(`\n====> Scopes granted: ${tokens.scope}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Bun.env.KICK_ACCESS_TOKEN = tokens.access_token;
|
|
43
|
+
Bun.env.KICK_REFRESH_TOKEN = tokens.refresh_token;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Authorization flow
|
|
49
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
50
|
+
console.log(`Authorize the application by visiting:\n${kick.getAuthURL()}`);
|
|
51
|
+
kick.auth.createCallbackServer({ port: 3000 });
|
|
52
|
+
await kick.auth.waitForAuthorization();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Confirm successful authorization
|
|
56
|
+
console.log("\n[✔] Application authorized successfully!");
|
|
57
|
+
|
|
58
|
+
let { data } = await kick.api.rewards.get();
|
|
59
|
+
console.log("\n[✔] Rewards fetched successfully!");
|
|
60
|
+
console.log(data);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 08: Basic Chat Bot Template
|
|
3
|
+
*
|
|
4
|
+
* This template will help you set up a basic chat bot using the ManaoKick library.
|
|
5
|
+
* The bot will listen for incoming chat messages and can be extended to respond or moderate.
|
|
6
|
+
* In the first run, it will print the access and refresh tokens to the console for
|
|
7
|
+
* you to save in your environment variables file (.env).
|
|
8
|
+
*
|
|
9
|
+
* Before running this code, ensure you have the following environment variables set:
|
|
10
|
+
* - KICK_CLIENT_ID
|
|
11
|
+
* - KICK_CLIENT_SECRET
|
|
12
|
+
*
|
|
13
|
+
* SCOPES ["chat:write", "events:subscribe", "moderation:ban", "channel:read"]
|
|
14
|
+
* Make sure to refresh the tokens and update your environment variables when scopes change.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { KickClient } from "../../src/KickClient.ts";
|
|
18
|
+
import type { ChatMessageEvent, KickTokenResponse } from "../../types";
|
|
19
|
+
|
|
20
|
+
const PREFIX = "!";
|
|
21
|
+
|
|
22
|
+
// Initialize the KickClient
|
|
23
|
+
const kick = new KickClient({
|
|
24
|
+
clientId: Bun.env.KICK_CLIENT_ID!,
|
|
25
|
+
clientSecret: Bun.env.KICK_CLIENT_SECRET!,
|
|
26
|
+
redirectUri: "http://localhost:3000/callback",
|
|
27
|
+
scopes: ["chat:write", "events:subscribe", "moderation:ban", "channel:read"],
|
|
28
|
+
showLog: false,
|
|
29
|
+
auth: {
|
|
30
|
+
initialTokens: Bun.env.KICK_REFRESH_TOKEN
|
|
31
|
+
? {
|
|
32
|
+
access_token: Bun.env.KICK_ACCESS_TOKEN!,
|
|
33
|
+
refresh_token: Bun.env.KICK_REFRESH_TOKEN!,
|
|
34
|
+
}
|
|
35
|
+
: undefined,
|
|
36
|
+
onTokenUpdate: (tokens: KickTokenResponse) => {
|
|
37
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
38
|
+
console.log("\n[!] Copy these into your .env file:\n");
|
|
39
|
+
console.log(`KICK_ACCESS_TOKEN=${tokens.access_token}`);
|
|
40
|
+
console.log(`KICK_REFRESH_TOKEN=${tokens.refresh_token}`);
|
|
41
|
+
console.log(`\n====> Scopes granted: ${tokens.scope}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
Bun.env.KICK_ACCESS_TOKEN = tokens.access_token;
|
|
45
|
+
Bun.env.KICK_REFRESH_TOKEN = tokens.refresh_token;
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Authorization flow
|
|
51
|
+
if (!Bun.env.KICK_REFRESH_TOKEN) {
|
|
52
|
+
console.log(`Authorize the application by visiting:\n${kick.getAuthURL()}`);
|
|
53
|
+
kick.auth.createCallbackServer({ port: 3000 });
|
|
54
|
+
await kick.auth.waitForAuthorization();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Confirm successful authorization
|
|
58
|
+
console.log("\n[✔] Application authorized successfully!");
|
|
59
|
+
|
|
60
|
+
// Handle incoming chat message events
|
|
61
|
+
kick.webhooks.on("chat.message.sent", async (event: ChatMessageEvent) => {
|
|
62
|
+
const message = event.content;
|
|
63
|
+
if (message[0] !== PREFIX) return; // Ignore messages without the prefix
|
|
64
|
+
|
|
65
|
+
const args = message.slice(PREFIX.length).trim().split(/ +/);
|
|
66
|
+
const command = args.shift()!.toLowerCase();
|
|
67
|
+
|
|
68
|
+
switch (command) {
|
|
69
|
+
case "ping":
|
|
70
|
+
await kick.chat.send({ content: "pong 🏓" });
|
|
71
|
+
break;
|
|
72
|
+
case "love":
|
|
73
|
+
const loveTarget = args.join(" ") || event.sender.username;
|
|
74
|
+
const lovePercentage = Math.floor(Math.random() * 101);
|
|
75
|
+
await kick.chat.send({
|
|
76
|
+
content: `${event.sender.username} ❤️ ${loveTarget}: ${lovePercentage}%!`,
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Set up ngrok to expose the webhook endpoint
|
|
85
|
+
const { url, close } = await kick.webhooks.ngrok({
|
|
86
|
+
port: 5000, // Use the same port as the webhook server
|
|
87
|
+
path: "/kick/webhook",
|
|
88
|
+
domain: "topical-goshawk-leading.ngrok-free.app", // <==== Replace with YOUR OWN ngrok domain!
|
|
89
|
+
authtoken: Bun.env.NGROK_AUTHTOKEN,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
console.log(`[✔] ngrok tunnel established at: ${url}`);
|
|
93
|
+
|
|
94
|
+
// Create a webhook server to listen for incoming events
|
|
95
|
+
kick.webhooks.createServer({ port: 5000, path: "/kick/webhook" }); // Use port that ngrok will forward to
|
|
96
|
+
|
|
97
|
+
// Subscribe to webhooks once authorized
|
|
98
|
+
kick.auth.onAuthorized(async () => {
|
|
99
|
+
await kick.webhooks.subscribe({
|
|
100
|
+
events: [{ name: "chat.message.sent" }],
|
|
101
|
+
});
|
|
102
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@manaobot/kick",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Minimal Typescript library for managing Kick.com bot",
|
|
5
|
+
"main": "src/KickClient.ts",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Tinnaphat Somsang <tinvv@outlook.co.th>",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"format": "biome format --write .",
|
|
10
|
+
"lint": "biome lint"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@biomejs/biome": "2.3.14",
|
|
14
|
+
"@ngrok/ngrok": "^1.7.0",
|
|
15
|
+
"@types/bun": "^1.3.8"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"typescript": "^5"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"consola": "^3.4.2"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/qodana.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#-------------------------------------------------------------------------------#
|
|
2
|
+
# Qodana analysis is configured by qodana.yaml file #
|
|
3
|
+
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
|
4
|
+
#-------------------------------------------------------------------------------#
|
|
5
|
+
version: "1.0"
|
|
6
|
+
|
|
7
|
+
#Specify inspection profile for code analysis
|
|
8
|
+
profile:
|
|
9
|
+
name: qodana.starter
|
|
10
|
+
|
|
11
|
+
#Enable inspections
|
|
12
|
+
#include:
|
|
13
|
+
# - name: <SomeEnabledInspectionId>
|
|
14
|
+
|
|
15
|
+
#Disable inspections
|
|
16
|
+
#exclude:
|
|
17
|
+
# - name: <SomeDisabledInspectionId>
|
|
18
|
+
# paths:
|
|
19
|
+
# - <path/where/not/run/inspection>
|
|
20
|
+
|
|
21
|
+
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
|
22
|
+
#bootstrap: sh ./prepare-qodana.sh
|
|
23
|
+
|
|
24
|
+
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
|
25
|
+
#plugins:
|
|
26
|
+
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
|
27
|
+
|
|
28
|
+
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
|
29
|
+
linter: jetbrains/qodana-js:2025.1
|
|
30
|
+
exclude:
|
|
31
|
+
- name: ES6MissingAwait
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { RestClient } from "./rest/RestClient";
|
|
2
|
+
import { createPkce } from "./auth/PKCE.ts";
|
|
3
|
+
import * as OAuth from "./auth/OAuth.ts";
|
|
4
|
+
import { ChatClient } from "./chat/ChatClient.ts";
|
|
5
|
+
import type { KickClientOptions, KickTokenResponse } from "../types";
|
|
6
|
+
import { TokenManager } from "./auth/TokenManager.ts";
|
|
7
|
+
import { WebhookRouter } from "./webhooks/WebhookRouter.ts";
|
|
8
|
+
import { AuthManager } from "./auth/AuthManager.ts";
|
|
9
|
+
import { callbackServer } from "./auth/CallbackServer.ts";
|
|
10
|
+
import { silent } from "./Logger.ts";
|
|
11
|
+
import { CategoriesAPI } from "./api/CategoriesAPI.ts";
|
|
12
|
+
import { UsersAPI } from "./api/UsersAPI.ts";
|
|
13
|
+
import { ChannelsAPI } from "./api/ChannelsAPI.ts";
|
|
14
|
+
import { ChannelRewardsAPI } from "./api/ChannelRewardsAPI.ts";
|
|
15
|
+
import { LivestreamsAPI } from "./api/LivestreamsAPI.ts";
|
|
16
|
+
import { ModerationAPI } from "./api/ModerationAPI.ts";
|
|
17
|
+
import { KicksAPI } from "./api/KicksAPI.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The primary entry point for interacting with the Kick API.
|
|
21
|
+
* @example
|
|
22
|
+
* const kick = new KickClient({
|
|
23
|
+
* clientId: Bun.env.KICK_CLIENT_ID!,
|
|
24
|
+
* clientSecret: Bun.env.KICK_CLIENT_SECRET!,
|
|
25
|
+
* redirectUri: "http://localhost:3000/callback",
|
|
26
|
+
* scopes: ["chat:write", "events:subscribe"],
|
|
27
|
+
* showLog: false,
|
|
28
|
+
* auth: {
|
|
29
|
+
* initialTokens: {
|
|
30
|
+
* access_token: Bun.env.KICK_ACCESS_TOKEN!,
|
|
31
|
+
* refresh_token: Bun.env.KICK_REFRESH_TOKEN!,
|
|
32
|
+
* },
|
|
33
|
+
* onTokenUpdate: (tokens: KickTokenResponse) => {*
|
|
34
|
+
* Bun.env.KICK_ACCESS_TOKEN = tokens.access_token;
|
|
35
|
+
* Bun.env.KICK_REFRESH_TOKEN = tokens.refresh_token;
|
|
36
|
+
* },
|
|
37
|
+
* },
|
|
38
|
+
* }); */
|
|
39
|
+
export class KickClient {
|
|
40
|
+
private readonly clientId: string;
|
|
41
|
+
private readonly clientSecret: string;
|
|
42
|
+
private readonly redirectUri: string;
|
|
43
|
+
private readonly scopes: string[];
|
|
44
|
+
private readonly state: string;
|
|
45
|
+
private pkceVerifier?: string;
|
|
46
|
+
private readonly tokenManager: TokenManager;
|
|
47
|
+
private rest = new RestClient();
|
|
48
|
+
|
|
49
|
+
public chat = new ChatClient(this.rest);
|
|
50
|
+
public webhooks: WebhookRouter;
|
|
51
|
+
public auth: AuthManager;
|
|
52
|
+
public readonly api: {
|
|
53
|
+
categories: CategoriesAPI;
|
|
54
|
+
users: UsersAPI;
|
|
55
|
+
channels: ChannelsAPI;
|
|
56
|
+
rewards: ChannelRewardsAPI;
|
|
57
|
+
livestreams: LivestreamsAPI;
|
|
58
|
+
moderation: ModerationAPI;
|
|
59
|
+
kicks: KicksAPI;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
constructor(
|
|
63
|
+
options: KickClientOptions & { webhookSecret?: string; showLog?: boolean },
|
|
64
|
+
) {
|
|
65
|
+
this.clientId = options.clientId;
|
|
66
|
+
this.clientSecret = options.clientSecret;
|
|
67
|
+
this.redirectUri = options.redirectUri;
|
|
68
|
+
this.scopes = options.scopes;
|
|
69
|
+
this.state =
|
|
70
|
+
options.state ??
|
|
71
|
+
Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString("hex");
|
|
72
|
+
|
|
73
|
+
this.webhooks = new WebhookRouter(options.clientSecret, this.rest);
|
|
74
|
+
|
|
75
|
+
this.auth = new AuthManager(this);
|
|
76
|
+
|
|
77
|
+
this.tokenManager = new TokenManager(
|
|
78
|
+
(refreshToken) =>
|
|
79
|
+
OAuth.refreshToken(refreshToken, this.clientId, this.clientSecret),
|
|
80
|
+
options.auth?.onTokenUpdate,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
this.api = {
|
|
84
|
+
categories: new CategoriesAPI(this.rest),
|
|
85
|
+
users: new UsersAPI(this.rest),
|
|
86
|
+
channels: new ChannelsAPI(this.rest),
|
|
87
|
+
rewards: new ChannelRewardsAPI(this.rest),
|
|
88
|
+
livestreams: new LivestreamsAPI(this.rest),
|
|
89
|
+
moderation: new ModerationAPI(this.rest),
|
|
90
|
+
kicks: new KicksAPI(this.rest),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (options.auth?.initialTokens) {
|
|
94
|
+
this.tokenManager.setTokens(options.auth.initialTokens);
|
|
95
|
+
this.rest.setTokenManager(this.tokenManager);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!options.showLog) {
|
|
99
|
+
silent();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generates the OAuth authorization URL and initializes PKCE verification.
|
|
105
|
+
* @returns The Kick authorization URL
|
|
106
|
+
*/
|
|
107
|
+
getAuthURL(): string {
|
|
108
|
+
const { verifier, challenge } = createPkce();
|
|
109
|
+
this.pkceVerifier = verifier;
|
|
110
|
+
|
|
111
|
+
const params = new URLSearchParams({
|
|
112
|
+
response_type: "code",
|
|
113
|
+
client_id: this.clientId,
|
|
114
|
+
redirect_uri: this.redirectUri,
|
|
115
|
+
scope: this.scopes.join(" "),
|
|
116
|
+
state: this.state,
|
|
117
|
+
code_challenge: challenge,
|
|
118
|
+
code_challenge_method: "S256",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return `https://id.kick.com/oauth/authorize?${params}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Returns the current anti-forgery state string.
|
|
126
|
+
*/
|
|
127
|
+
getState(): string {
|
|
128
|
+
return this.state;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Exchanges an authorization code for access and refresh tokens.
|
|
133
|
+
* @param code The code received from the OAuth callback
|
|
134
|
+
* @returns Kick token response
|
|
135
|
+
*/
|
|
136
|
+
async exchangeCode(code: string): Promise<KickTokenResponse> {
|
|
137
|
+
if (!this.pkceVerifier) {
|
|
138
|
+
throw new Error("PKCE verifier missing");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const token = await OAuth.exchangeCode(
|
|
142
|
+
code,
|
|
143
|
+
this.pkceVerifier,
|
|
144
|
+
this.clientId,
|
|
145
|
+
this.clientSecret,
|
|
146
|
+
this.redirectUri,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
this.tokenManager.setTokens(token);
|
|
150
|
+
this.rest.setTokenManager(this.tokenManager);
|
|
151
|
+
|
|
152
|
+
return token;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Manually refreshes the access token using a refresh token.
|
|
157
|
+
* @param refreshToken The refresh token to use
|
|
158
|
+
* @returns New Kick token response
|
|
159
|
+
*/
|
|
160
|
+
async refreshToken(refreshToken: string): Promise<KickTokenResponse> {
|
|
161
|
+
return OAuth.refreshToken(refreshToken, this.clientId, this.clientSecret);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Starts a local server to handle the OAuth redirect and token exchange.
|
|
166
|
+
* @param options Server configuration options
|
|
167
|
+
* @returns The running Bun server instance
|
|
168
|
+
*/
|
|
169
|
+
createAuthCallbackServer(options?: { port?: number; path?: string }) {
|
|
170
|
+
return callbackServer(this, options);
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/Logger.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
let createConsola: any;
|
|
2
|
+
export let logger: any;
|
|
3
|
+
|
|
4
|
+
export function silent() {
|
|
5
|
+
if (createConsola) {
|
|
6
|
+
logger = createConsola({ level: -1 });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
createConsola = (await import("consola")).createConsola;
|
|
12
|
+
|
|
13
|
+
logger = createConsola({
|
|
14
|
+
level: process.env.LOG_LEVEL
|
|
15
|
+
? parseInt(process.env.LOG_LEVEL, 10)
|
|
16
|
+
: undefined,
|
|
17
|
+
defaults: {
|
|
18
|
+
tag: "@manao/kick",
|
|
19
|
+
},
|
|
20
|
+
fancy: true,
|
|
21
|
+
});
|
|
22
|
+
} catch {
|
|
23
|
+
createConsola = null;
|
|
24
|
+
logger = console;
|
|
25
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RestClient } from "../rest/RestClient.ts";
|
|
2
|
+
import type { GetCategoriesParams } from "../../types/api";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CategoriesAPI provides access to Kick Category APIs.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const categories = await kick.api.categories.get({ limit: 10 });
|
|
9
|
+
* console.log(categories.data);
|
|
10
|
+
*/
|
|
11
|
+
export class CategoriesAPI {
|
|
12
|
+
constructor(private readonly rest: RestClient) {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Retrieve categories based on filters.
|
|
16
|
+
* * Allows filtering by cursor, limit, names, tags, or specific IDs.
|
|
17
|
+
*
|
|
18
|
+
* @param params Query parameters
|
|
19
|
+
* @returns Kick category data
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Get categories with a limit
|
|
23
|
+
* const categories = await kick.api.categories.get({ limit: 5 });
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Get specific categories by name
|
|
27
|
+
* const categories = await kick.api.categories.get({ name: ["Just Chatting", "Slots"] });
|
|
28
|
+
*/
|
|
29
|
+
async get(params: GetCategoriesParams = {}): Promise<unknown> {
|
|
30
|
+
const search = new URLSearchParams();
|
|
31
|
+
|
|
32
|
+
if (params.cursor) search.set("cursor", params.cursor.toString());
|
|
33
|
+
if (params.limit) search.set("limit", params.limit.toString());
|
|
34
|
+
if (params.name) params.name.forEach((name) => search.append("name", name));
|
|
35
|
+
if (params.tags) params.tags.forEach((tag) => search.append("tags", tag));
|
|
36
|
+
if (params.id)
|
|
37
|
+
params.id.forEach((id) => search.append("id", id.toString()));
|
|
38
|
+
|
|
39
|
+
const query = search.toString();
|
|
40
|
+
|
|
41
|
+
return this.rest.fetch(`/public/v2/categories${query ? `?${query}` : ""}`, {
|
|
42
|
+
method: "GET",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|