@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.
Files changed (47) hide show
  1. package/README.md +163 -0
  2. package/biome.json +44 -0
  3. package/bun.lock +78 -0
  4. package/example/01-authorize-bot/.env.example +7 -0
  5. package/example/01-authorize-bot/index.ts +60 -0
  6. package/example/02-webhook/.env.example +7 -0
  7. package/example/02-webhook/index.ts +80 -0
  8. package/example/03-ngrok/.env.example +7 -0
  9. package/example/03-ngrok/index.ts +92 -0
  10. package/example/04-categories-api/.env.example +7 -0
  11. package/example/04-categories-api/index.ts +77 -0
  12. package/example/05-users-api/.env.example +7 -0
  13. package/example/05-users-api/index.ts +60 -0
  14. package/example/06-channels-api/.env.example +7 -0
  15. package/example/06-channels-api/index.ts +60 -0
  16. package/example/07-channel-rewards-api/.env.example +7 -0
  17. package/example/07-channel-rewards-api/index.ts +60 -0
  18. package/example/08-basic-chat-bot/.env.example +7 -0
  19. package/example/08-basic-chat-bot/index.ts +102 -0
  20. package/package.json +23 -0
  21. package/qodana.yaml +31 -0
  22. package/src/KickClient.ts +172 -0
  23. package/src/Logger.ts +25 -0
  24. package/src/api/CategoriesAPI.ts +45 -0
  25. package/src/api/ChannelRewardsAPI.ts +121 -0
  26. package/src/api/ChannelsAPI.ts +63 -0
  27. package/src/api/KicksAPI.ts +37 -0
  28. package/src/api/LivestreamsAPI.ts +65 -0
  29. package/src/api/ModerationAPI.ts +60 -0
  30. package/src/api/UsersAPI.ts +72 -0
  31. package/src/auth/AuthManager.ts +64 -0
  32. package/src/auth/CallbackServer.ts +57 -0
  33. package/src/auth/OAuth.ts +55 -0
  34. package/src/auth/PKCE.ts +13 -0
  35. package/src/auth/TokenManager.ts +53 -0
  36. package/src/chat/ChatClient.ts +48 -0
  37. package/src/rest/RestClient.ts +39 -0
  38. package/src/webhooks/NgrokAdapter.ts +46 -0
  39. package/src/webhooks/WebhookRouter.ts +135 -0
  40. package/src/webhooks/WebhookServer.ts +41 -0
  41. package/tsconfig.json +29 -0
  42. package/types/api.d.ts +158 -0
  43. package/types/auth.d.ts +38 -0
  44. package/types/chat.d.ts +14 -0
  45. package/types/client.d.ts +67 -0
  46. package/types/index.d.ts +4 -0
  47. 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,7 @@
1
+ KICK_CLIENT_ID=[YOUR_CLIENT_ID_HERE]
2
+ KICK_CLIENT_SECRET=[YOUR_CLIENT_SECRET_HERE]
3
+
4
+ # These two lines below are the lines you need to fill in after getting tokens
5
+ # After running the ./index.ts file
6
+ KICK_ACCESS_TOKEN=
7
+ KICK_REFRESH_TOKEN=
@@ -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,7 @@
1
+ KICK_CLIENT_ID=[YOUR_CLIENT_ID_HERE]
2
+ KICK_CLIENT_SECRET=[YOUR_CLIENT_SECRET_HERE]
3
+
4
+ # These two lines below are the lines you need to fill in after getting tokens
5
+ # After running the ./index.ts file
6
+ KICK_ACCESS_TOKEN=
7
+ KICK_REFRESH_TOKEN=
@@ -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,7 @@
1
+ KICK_CLIENT_ID=[YOUR_CLIENT_ID_HERE]
2
+ KICK_CLIENT_SECRET=[YOUR_CLIENT_SECRET_HERE]
3
+
4
+ # These two lines below are the lines you need to fill in after getting tokens
5
+ # After running the ./index.ts file
6
+ KICK_ACCESS_TOKEN=
7
+ KICK_REFRESH_TOKEN=
@@ -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
+ }