Skip to content

Commit

Permalink
Add creator goals to the EventSub and the API package (#315)
Browse files Browse the repository at this point in the history
* Add creator goals to the Helix API

* Add creator goals to EventSub

* Add creator goals property names to eslint

* Fix the creator goal type for EventSub

* Add consistency in the docs

Co-authored-by: Daniel Fischer <daniel@d-fischer.dev>
  • Loading branch information
CaveMobster and d-fischer committed Dec 18, 2021
1 parent 2b610c6 commit fe443b4
Show file tree
Hide file tree
Showing 16 changed files with 707 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .eslintrc.js
Expand Up @@ -25,7 +25,7 @@ const memberNames = [
'^click_action$',
'^(image_)?url_\\dx$',
'^emoticon_sets?$',
'^is_(?:anonymous|broadcast|gift|user_input_required|sub_only|mature|enabled|paused|in_stock|previewable|playlist|(?:verified|known)_bot|live|auto|permanent|recurring|vacation_enabled|canceled)$',
'^is_(?:anonymous|broadcast|gift|user_input_required|sub_only|mature|enabled|paused|in_stock|previewable|playlist|(?:verified|known)_bot|live|auto|permanent|recurring|vacation_enabled|canceled|achieved)$',
'^minimum_allowed_role$',
'^(chatter|view(er)?)_count$',
'^min_bits$',
Expand Down Expand Up @@ -91,6 +91,7 @@ const memberNames = [
'^viewer_summary$',
'^aspect_(?:width|height|ratio_[xy])',
'^(?:scale|zoom)_pixels',
'^(?:current|target)_amount$',
// HTTP
'^Accept$'
];
Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/ApiClient.ts
Expand Up @@ -16,6 +16,7 @@ import { HelixClipApi } from './api/helix/clip/HelixClipApi';
import { HelixEventSubApi } from './api/helix/eventSub/HelixEventSubApi';
import { HelixExtensionsApi } from './api/helix/extensions/HelixExtensionsApi';
import { HelixGameApi } from './api/helix/game/HelixGameApi';
import { HelixGoalApi } from './api/helix/goals/HelixGoalApi';
import { HelixApiGroup } from './api/helix/HelixApiGroup';
import { HelixRateLimiter } from './api/helix/HelixRateLimiter';
import { HelixHypeTrainApi } from './api/helix/hypeTrain/HelixHypeTrainApi';
Expand Down Expand Up @@ -231,6 +232,14 @@ export class ApiClient {
return new HelixHypeTrainApi(this);
}

/**
* The Helix goal API methods.
*/
@CachedGetter()
get goals(): HelixGoalApi {
return new HelixGoalApi(this);
}

/**
* The Helix moderation API methods.
*/
Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/api/helix/HelixApiGroup.ts
Expand Up @@ -10,6 +10,7 @@ import { HelixClipApi } from './clip/HelixClipApi';
import { HelixEventSubApi } from './eventSub/HelixEventSubApi';
import { HelixExtensionsApi } from './extensions/HelixExtensionsApi';
import { HelixGameApi } from './game/HelixGameApi';
import { HelixGoalApi } from './goals/HelixGoalApi';
import { HelixHypeTrainApi } from './hypeTrain/HelixHypeTrainApi';
import { HelixModerationApi } from './moderation/HelixModerationApi';
import { HelixPollApi } from './poll/HelixPollApi';
Expand Down Expand Up @@ -97,6 +98,14 @@ export class HelixApiGroup extends BaseApi {
return new HelixGameApi(this._client);
}

/**
* The Helix goal API methods.
*/
@CachedGetter()
get goals(): HelixGoalApi {
return new HelixGoalApi(this._client);
}

/**
* The Helix Hype Train API methods.
*/
Expand Down
54 changes: 54 additions & 0 deletions packages/api/src/api/helix/eventSub/HelixEventSubApi.ts
Expand Up @@ -805,6 +805,60 @@ export class HelixEventSubApi extends BaseApi {
);
}

/**
* Subscribe to events that represent the beginning of a creator goal event in a channel.
*
* @param broadcaster The broadcaster you want to listen to goal begin events for.
* @param transport The transport options.
*/
async subscribeToChannelGoalBeginEvents(
broadcaster: UserIdResolvable,
transport: HelixEventSubTransportOptions
): Promise<HelixEventSubSubscription> {
return await this.createSubscription(
'channel.goal.begin',
'1',
{ broadcaster_user_id: extractUserId(broadcaster) },
transport
);
}

/**
* Subscribe to events that represent progress towards a creator goal.
*
* @param broadcaster The broadcaster for which you want to listen to goal progress events for.
* @param transport The transport options.
*/
async subscribeToChannelGoalProgressEvents(
broadcaster: UserIdResolvable,
transport: HelixEventSubTransportOptions
): Promise<HelixEventSubSubscription> {
return await this.createSubscription(
'channel.goal.progress',
'1',
{ broadcaster_user_id: extractUserId(broadcaster) },
transport
);
}

/**
* Subscribe to events that represent the end of a creator goal event.
*
* @param broadcaster The broadcaster for which you want to listen to goal end events for.
* @param transport The transport options.
*/
async subscribeToChannelGoalEndEvents(
broadcaster: UserIdResolvable,
transport: HelixEventSubTransportOptions
): Promise<HelixEventSubSubscription> {
return await this.createSubscription(
'channel.goal.end',
'1',
{ broadcaster_user_id: extractUserId(broadcaster) },
transport
);
}

/**
* Subscribe to events that represent the beginning of a Hype Train event in a channel.
*
Expand Down
103 changes: 103 additions & 0 deletions packages/api/src/api/helix/goals/HelixGoal.ts
@@ -0,0 +1,103 @@
import { Enumerable } from '@d-fischer/shared-utils';
import { DataObject, rawDataSymbol, rtfm } from '@twurple/common';
import type { ApiClient } from '../../../ApiClient';
import type { HelixUser } from '../user/HelixUser';

export type HelixGoalType = 'follower' | 'subscription';

/** @private */
export interface HelixGoalData {
id: string;
broadcaster_id: string;
broadcaster_name: string;
broadcaster_login: string;
type: HelixGoalType;
description: string;
current_amount: number;
target_amount: number;
created_at: Date;
}

/**
* A creator goal.
*/
@rtfm<HelixGoal>('api', 'HelixGoal', 'id')
export class HelixGoal extends DataObject<HelixGoalData> {
@Enumerable(false) private readonly _client: ApiClient;

/** @private */
constructor(data: HelixGoalData, client: ApiClient) {
super(data);
this._client = client;
}

/**
* The ID of the goal.
*/
get id(): string {
return this[rawDataSymbol].id;
}

/**
* The ID of the broadcaster the goal belongs to.
*/
get broadcasterId(): string {
return this[rawDataSymbol].broadcaster_id;
}

/**
* The display name of the broadcaster the goal belongs to.
*/
get broadcasterDisplayName(): string {
return this[rawDataSymbol].broadcaster_name;
}

/**
* The name of the broadcaster the goal belongs to.
*/
get broadcasterName(): string {
return this[rawDataSymbol].broadcaster_login;
}

/**
* Retrieves more information about the broadcaster.
*/
async getBroadcaster(): Promise<HelixUser> {
return (await this._client.users.getUserById(this[rawDataSymbol].broadcaster_id))!;
}

/**
* The type of the goal. Can be either "follower" or "subscription".
*/
get type(): HelixGoalType {
return this[rawDataSymbol].type;
}

/**
* The description of the goal.
*/
get description(): string {
return this[rawDataSymbol].description;
}

/**
* The current value of the goal.
*/
get currentAmount(): number {
return this[rawDataSymbol].current_amount;
}

/**
* The target value of the goal.
*/
get targetAmount(): number {
return this[rawDataSymbol].target_amount;
}

/**
* The date and time when the goal was created.
*/
get creationDate(): Date {
return this[rawDataSymbol].created_at;
}
}
31 changes: 31 additions & 0 deletions packages/api/src/api/helix/goals/HelixGoalApi.ts
@@ -0,0 +1,31 @@
import type { UserIdResolvable } from '@twurple/common';
import { extractUserId, rtfm } from '@twurple/common';
import { BaseApi } from '../../BaseApi';
import type { HelixResponse } from '../HelixResponse';
import type { HelixGoalData } from './HelixGoal';
import { HelixGoal } from './HelixGoal';

/**
* The Helix API methods that deal with creator goals.
*
* Can be accessed using `client.helix.goals` on an {@ApiClient} instance.
*
* ## Example
* ```ts
* const api = new ApiClient(new StaticAuthProvider(clientId, accessToken));
* const { data: goals } = await api.helix.goals.getGoals('61369223');
*/
@rtfm('api', 'HelixGoalApi')
export class HelixGoalApi extends BaseApi {
async getGoals(broadcaster: UserIdResolvable): Promise<HelixGoal[]> {
const result = await this._client.callApi<HelixResponse<HelixGoalData>>({
type: 'helix',
url: 'goals',
query: {
broadcaster_id: extractUserId(broadcaster)
}
});

return result.data.map(data => new HelixGoal(data, this._client));
}
}
4 changes: 4 additions & 0 deletions packages/api/src/index.ts
Expand Up @@ -90,6 +90,10 @@ export type { HelixExtensionTransactionData } from './api/helix/extensions/Helix
export { HelixGameApi } from './api/helix/game/HelixGameApi';
export { HelixGame } from './api/helix/game/HelixGame';

export { HelixGoalApi } from './api/helix/goals/HelixGoalApi';
export { HelixGoal } from './api/helix/goals/HelixGoal';
export type { HelixGoalData, HelixGoalType } from './api/helix/goals/HelixGoal';

export { HelixHypeTrainApi } from './api/helix/hypeTrain/HelixHypeTrainApi';
export { HelixHypeTrainContribution } from './api/helix/hypeTrain/HelixHypeTrainContribution';
export type { HelixHypeTrainContributionType } from './api/helix/hypeTrain/HelixHypeTrainContribution';
Expand Down
66 changes: 66 additions & 0 deletions packages/eventsub/src/EventSubBase.ts
Expand Up @@ -16,6 +16,9 @@ import type { Request, RequestHandler } from 'httpanda';
import type { EventSubChannelBanEvent } from './events/EventSubChannelBanEvent';
import type { EventSubChannelCheerEvent } from './events/EventSubChannelCheerEvent';
import type { EventSubChannelFollowEvent } from './events/EventSubChannelFollowEvent';
import type { EventSubChannelGoalBeginEvent } from './events/EventSubChannelGoalBeginEvent';
import type { EventSubChannelGoalEndEvent } from './events/EventSubChannelGoalEndEvent';
import type { EventSubChannelGoalProgressEvent } from './events/EventSubChannelGoalProgressEvent';
import type { EventSubChannelHypeTrainBeginEvent } from './events/EventSubChannelHypeTrainBeginEvent';
import type { EventSubChannelHypeTrainEndEvent } from './events/EventSubChannelHypeTrainEndEvent';
import type { EventSubChannelHypeTrainProgressEvent } from './events/EventSubChannelHypeTrainProgressEvent';
Expand Down Expand Up @@ -45,6 +48,9 @@ import type { EventSubUserUpdateEvent } from './events/EventSubUserUpdateEvent';
import { EventSubChannelBanSubscription } from './subscriptions/EventSubChannelBanSubscription';
import { EventSubChannelCheerSubscription } from './subscriptions/EventSubChannelCheerSubscription';
import { EventSubChannelFollowSubscription } from './subscriptions/EventSubChannelFollowSubscription';
import { EventSubChannelGoalBeginSubscription } from './subscriptions/EventSubChannelGoalBeginSubscription';
import { EventSubChannelGoalEndSubscription } from './subscriptions/EventSubChannelGoalEndSubscription';
import { EventSubChannelGoalProgressSubscription } from './subscriptions/EventSubChannelGoalProgressSubscription';
import { EventSubChannelHypeTrainBeginSubscription } from './subscriptions/EventSubChannelHypeTrainBeginSubscription';
import { EventSubChannelHypeTrainEndSubscription } from './subscriptions/EventSubChannelHypeTrainEndSubscription';
import { EventSubChannelHypeTrainProgressSubscription } from './subscriptions/EventSubChannelHypeTrainProgressSubscription';
Expand Down Expand Up @@ -839,6 +845,66 @@ To silence this warning without enabling this check (and thus to keep it off eve
return await this._genericSubscribe(EventSubChannelPredictionEndSubscription, handler, this, broadcasterId);
}

/**
* Subscribes to events that represent a Goal beginning.
*
* @param user The user for which to get notifications about Goals in their channel.
* @param handler The function that will be called for any new notifications.
*/
async subscribeToChannelGoalBeginEvents(
user: UserIdResolvable,
handler: (data: EventSubChannelGoalBeginEvent) => void
): Promise<EventSubSubscription> {
const userId = extractUserId(user);

if (!numberRegex.test(userId)) {
this._logger.warn(
'EventSubListener#subscribeToChannelGoalBeginEvents: The given user is a non-numeric string. You might be sending a user name instead of a user ID.'
);
}
return await this._genericSubscribe(EventSubChannelGoalBeginSubscription, handler, this, userId);
}

/**
* Subscribes to events that represent progress in a Goal in a channel.
*
* @param user The user for which to get notifications about Goals in their channel.
* @param handler The function that will be called for any new notifications.
*/
async subscribeToChannelGoalProgressEvents(
user: UserIdResolvable,
handler: (data: EventSubChannelGoalProgressEvent) => void
): Promise<EventSubSubscription> {
const userId = extractUserId(user);

if (!numberRegex.test(userId)) {
this._logger.warn(
'EventSubListener#subscribeToChannelGoalProgressEvents: The given user is a non-numeric string. You might be sending a user name instead of a user ID.'
);
}
return await this._genericSubscribe(EventSubChannelGoalProgressSubscription, handler, this, userId);
}

/**
* Subscribes to events that represent the end of a Goal in a channel.
*
* @param user The user for which to get notifications about Goals in their channel.
* @param handler The function that will be called for any new notifications.
*/
async subscribeToChannelGoalEndEvents(
user: UserIdResolvable,
handler: (data: EventSubChannelGoalEndEvent) => void
): Promise<EventSubSubscription> {
const userId = extractUserId(user);

if (!numberRegex.test(userId)) {
this._logger.warn(
'EventSubListener#subscribeToChannelGoalEndEvents: The given user is a non-numeric string. You might be sending a user name instead of a user ID.'
);
}
return await this._genericSubscribe(EventSubChannelGoalEndSubscription, handler, this, userId);
}

/**
* Subscribes to events that represent a Hype Train beginning.
*
Expand Down

0 comments on commit fe443b4

Please sign in to comment.