Skip to content

Commit 7771eba

Browse files
committed
feat: add webhook handling
- adds optional internal webhook server - exposes function to handle events externally - adds chat message events
1 parent 01ecf84 commit 7771eba

17 files changed

+572
-67
lines changed

bun.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/webhooks.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { env } from 'bun'
2+
import { Kient } from 'kient'
3+
4+
const kient = new Kient()
5+
kient.setAuthToken(env.KICK_TOKEN as string)
6+
7+
if (kient.webhookServerFetch) {
8+
Bun.serve({
9+
fetch: kient.webhookServerFetch,
10+
port: 3000,
11+
})
12+
}
13+
14+
const subscription = await kient.api.webhook.subscribe({
15+
method: 'webhook',
16+
events: [
17+
{
18+
name: 'chat.message.sent',
19+
version: 1,
20+
},
21+
],
22+
})
23+
console.log(subscription)
24+
25+
const subscriptions = await kient.api.webhook.getSubscriptions()
26+
console.log(subscriptions.map((sub) => sub.raw))
27+
28+
for (const sub of subscriptions) {
29+
sub.unsubscribe()
30+
}
31+
32+
kient.addListener('KIENT_CHAT_MESSAGE_SENT', (message) => {
33+
console.log('chat message received from', message.sender.username)
34+
})

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"@types/bun": "^1.1.6",
2929
"bun-plugin-dts": "^0.3.0",
3030
"changelogen": "^0.5.7",
31-
"hono": "^4.7.2",
3231
"typedoc": "^0.26.5",
3332
"typedoc-plugin-markdown": "^4.2.3",
3433
"typedoc-plugin-missing-exports": "^3.0.0",
@@ -40,6 +39,8 @@
4039
},
4140
"dependencies": {
4241
"defu": "^6.1.4",
42+
"destr": "^2.0.3",
43+
"hono": "^4.7.2",
4344
"nanoid": "^5.1.0",
4445
"ofetch": "^1.3.4",
4546
"pusher-js": "^8.4.0-rc2",

src/api/webhook/get-events.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Kient } from 'kient'
2+
import type { APIResponse } from '../../util/api-response'
3+
import { SubscriptionEvent, type SubscriptionEventData } from '../../structures/subscription-event'
4+
5+
export async function getEventSubscriptions(kient: Kient) {
6+
const response =
7+
await kient._apiClient.fetch<APIResponse<SubscriptionEventData[]>>('/events/subscriptions')
8+
9+
const subscriptionInstances = []
10+
for (const subscription of response.data) {
11+
const subscriptionEvent = new SubscriptionEvent(kient, subscription)
12+
subscriptionInstances.push(subscriptionEvent)
13+
}
14+
15+
return subscriptionInstances
16+
}

src/api/webhook/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { APIBase } from '../api-base'
2+
import { getEventSubscriptions } from './get-events'
3+
import { subscribeToEvent, type WebhookSubscriptionParams } from './subscribe'
4+
import { unsubscribeFromEvents } from './unsubscribe'
5+
/**
6+
* Description placeholder
7+
*
8+
* @group APIs
9+
*/
10+
export class WebhookAPI extends APIBase {
11+
/**
12+
* Returns an array of events that are currently subscribed to
13+
*/
14+
getSubscriptions() {
15+
return getEventSubscriptions(this.kient)
16+
}
17+
18+
/**
19+
* Subscribes to a specific event and returns details about subscription
20+
*/
21+
subscribe(params: WebhookSubscriptionParams) {
22+
return subscribeToEvent(this.kient, params)
23+
}
24+
25+
/**
26+
* Unsubscribes to an array of subscription IDs
27+
*/
28+
unsubscribe(ids: string[]) {
29+
return unsubscribeFromEvents(this.kient, ids)
30+
}
31+
}

src/api/webhook/subscribe.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Kient } from 'kient'
2+
import type { APIResponse } from '../../util/api-response'
3+
import {
4+
BasicSubscriptionEvent,
5+
type BasicSubscriptionEventData,
6+
} from '../../structures/basic-subscription-event'
7+
8+
export interface WebhookSubscriptionParams {
9+
method: 'webhook'
10+
events: Array<{
11+
name: string
12+
version: number
13+
}>
14+
}
15+
16+
export async function subscribeToEvent(kient: Kient, param: WebhookSubscriptionParams) {
17+
const response = await kient._apiClient.fetch<APIResponse<BasicSubscriptionEventData[]>>(
18+
'/events/subscriptions',
19+
{
20+
method: 'POST',
21+
body: param,
22+
},
23+
)
24+
25+
const basicSubscriptionInstances = []
26+
for (const subscription of response.data) {
27+
if (subscription.error) {
28+
throw new Error(subscription.error)
29+
}
30+
const basicSubscription = new BasicSubscriptionEvent(kient, subscription)
31+
basicSubscriptionInstances.push(basicSubscription)
32+
}
33+
34+
return basicSubscriptionInstances
35+
}

src/api/webhook/unsubscribe.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { Kient } from 'kient'
2+
3+
export async function unsubscribeFromEvents(kient: Kient, ids: string[]) {
4+
const params = new URLSearchParams()
5+
for (const id of ids) {
6+
params.append('id', id.toString())
7+
}
8+
9+
await kient._apiClient.fetch(`/events/subscriptions?${params}`, {
10+
method: 'DELETE',
11+
})
12+
}

src/events.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { WebhookEvents } from './webhook.handler'
2+
13
const KientEvents = {
24
WebSocketConnected: 'KIENT_WEBSOCKET_CONNECTED',
35
WebSocketDisconnected: 'KIENT_WEBSOCKET_DISCONNECTED',
@@ -10,6 +12,7 @@ type CoreEvents = {
1012

1113
export const Events = {
1214
Core: KientEvents,
15+
...WebhookEvents,
1316
}
1417

15-
export type KientEventEmitters = CoreEvents
18+
export type KientEventEmitters = CoreEvents & WebhookEvents

src/kient.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,73 @@ import defu from 'defu'
22
import { EventEmitter } from 'tseep'
33
import { APIClient, type APIClientOptions } from './api.client'
44
import type { KientEventEmitters } from './events'
5-
import { WSClient, type WSClientOptions } from './ws.client'
65
import { CategoryAPI } from './api/category'
76
import { UserAPI } from './api/user'
87
import { ChannelAPI } from './api/channel'
98
import { ChatAPI } from './api/chat'
109
import { MiscAPI } from './api/misc'
10+
import { WebhookAPI } from './api/webhook'
11+
import { WebhookServer } from './webhook.server'
12+
import { WebhookHandler } from './webhook.handler'
13+
import type { WebhookEvent } from './structures/base-event'
1114

1215
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T
1316

1417
export interface KientOptions {
15-
connectToWebsocket: boolean
1618
apiClient: APIClientOptions
17-
wsClient: WSClientOptions
19+
webhookServer: {
20+
enable: boolean
21+
}
1822
}
1923

2024
const defaultKientOptions: KientOptions = {
21-
connectToWebsocket: true,
2225
apiClient: {
2326
ofetch: {
2427
baseURL: 'https://api.kick.com/public/v1',
2528
},
2629
},
27-
wsClient: {
28-
pusher: {
29-
appKey: '32cbd69e4b950bf97679',
30-
cluster: 'us2',
31-
},
30+
webhookServer: {
31+
enable: true,
3232
},
3333
}
3434

3535
export class Kient extends EventEmitter<KientEventEmitters> {
3636
private readonly kientOptions: KientOptions
37-
_wsClient?: WSClient
37+
private _webhookServer?: WebhookServer
38+
_webhookHandler: WebhookHandler
3839
_apiClient: APIClient
40+
_kickPublicKey?: string
3941

4042
constructor(options?: DeepPartial<KientOptions>) {
4143
super()
4244
this.kientOptions = defu(options as KientOptions, defaultKientOptions)
4345

44-
this.kientOptions.connectToWebsocket ? this.connectWebsocket() : null
46+
if (this.kientOptions.webhookServer.enable) {
47+
this.createWebhookServer()
48+
}
4549

4650
this._apiClient = new APIClient(this, this.kientOptions.apiClient)
51+
this._webhookHandler = new WebhookHandler(this)
4752
}
4853

49-
connectWebsocket() {
50-
this._wsClient = new WSClient(this, this.kientOptions.wsClient)
54+
createWebhookServer() {
55+
this._webhookServer = new WebhookServer(this)
5156
}
5257

53-
setAuthToken(token: string) {
58+
async setAuthToken(token: string) {
5459
this._apiClient.setHeaders({
5560
Authorization: `Bearer ${token}`,
5661
})
62+
63+
this._kickPublicKey = await this.api.misc.getPublicKey()
64+
}
65+
66+
get webhookServerFetch() {
67+
return this._webhookServer?.fetch
68+
}
69+
70+
handleWebhookEvent(event: WebhookEvent) {
71+
this._webhookHandler.handleEvent(event)
5772
}
5873

5974
api = {
@@ -62,5 +77,6 @@ export class Kient extends EventEmitter<KientEventEmitters> {
6277
user: new UserAPI(this),
6378
channel: new ChannelAPI(this),
6479
chat: new ChatAPI(this),
80+
webhook: new WebhookAPI(this),
6581
}
6682
}

src/structures/base-event.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Kient } from '../kient'
2+
import { Base } from './base'
3+
4+
export interface WebhookEvent {
5+
messageId: string
6+
timestamp: string
7+
type: string
8+
version: string
9+
body: string
10+
}
11+
12+
export class EventBase extends Base<WebhookEvent> {
13+
/**
14+
* The information relating the received event
15+
*/
16+
webhookEvent: {
17+
/**
18+
* The event's message id
19+
*/
20+
messageId: string
21+
22+
/**
23+
* The event's timestamp
24+
*/
25+
timestamp: Date
26+
}
27+
28+
/** @internal */
29+
constructor(kient: Kient, data: WebhookEvent) {
30+
super(kient, data)
31+
32+
this.webhookEvent = {
33+
messageId: data.messageId,
34+
timestamp: new Date(data.timestamp),
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)