Skip to content

Commit

Permalink
add Get Channel Stream Schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
d-fischer committed Jun 21, 2021
1 parent c95cd01 commit 4b6e9d9
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 3 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Expand Up @@ -25,7 +25,7 @@ const memberNames = [
'^click_action$',
'^(image_)?url_\\dx$',
'^emoticon_sets?$',
'^is_(anonymous|gift|user_input_required|sub_only|mature|enabled|paused|in_stock|previewable|playlist|(verified|known)_bot|live|auto|permanent)$',
'^is_(anonymous|gift|user_input_required|sub_only|mature|enabled|paused|in_stock|previewable|playlist|(verified|known)_bot|live|auto|permanent|recurring)$',
'^minimum_allowed_role$',
'^(chatter|view(er)?)_count$',
'^min_bits$',
Expand Down Expand Up @@ -82,6 +82,8 @@ const memberNames = [
'^resolver_login$',
'^cumulative_total$',
'^in_development$',
'^canceled_until$',
'^utc_offset$',
// HTTP
'^Accept$'
];
Expand Down
2 changes: 1 addition & 1 deletion packages/twitch/src/API/Helix/HelixPaginatedResult.ts
Expand Up @@ -84,7 +84,7 @@ export interface HelixPaginatedResultWithTotal<T> {
): HelixPaginatedResultWithTotal<ConstructedType<O>> {
return {
data: response.data.map(data => new type(data, client)),
cursor: response.pagination!.cursor,
cursor: response.pagination!.cursor!,
total: response.total
};
}
4 changes: 3 additions & 1 deletion packages/twitch/src/API/Helix/HelixResponse.ts
Expand Up @@ -3,12 +3,14 @@ export interface HelixResponse<T> {
data: T[];
}

/** @private */
export interface HelixPaginatedResponse<T> extends HelixResponse<T> {
pagination?: {
cursor: string;
cursor?: string;
};
}

/** @private */
export interface HelixPaginatedResponseWithTotal<T> extends HelixPaginatedResponse<T> {
total: number;
}
@@ -0,0 +1,49 @@
import type { TwitchApiCallOptions } from 'twitch-api-call';
import type { UserIdResolvable } from 'twitch-common';
import { extractUserId } from 'twitch-common';
import type { ApiClient } from '../../../ApiClient';
import { HelixPaginatedRequest } from '../HelixPaginatedRequest';
import type { HelixPaginatedResponse } from '../HelixResponse';
import type { HelixScheduleResponse } from './HelixSchedule';
import type { HelixScheduleFilter } from './HelixScheduleApi';
import type { HelixScheduleSegmentData } from './HelixScheduleSegment';
import { HelixScheduleSegment } from './HelixScheduleSegment';

/**
* A paginator specifically for schedule segments.
*/
export class HelixPaginatedScheduleSegmentRequest extends HelixPaginatedRequest<
HelixScheduleSegmentData,
HelixScheduleSegment
> {
/** @private */
constructor(broadcaster: UserIdResolvable, client: ApiClient, filter?: HelixScheduleFilter) {
super(
{
url: 'schedule',
query: {
broadcaster_id: extractUserId(broadcaster),
start_time: filter?.startDate,
utc_offset: filter?.utcOffset?.toString()
}
},
client,
data => new HelixScheduleSegment(data, client),
25
);
}

// sadly, this hack is necessary to work around the weird data model of schedules
// while still keeping the pagination code as generic as possible
/** @private */
protected async _fetchData(
additionalOptions: Partial<TwitchApiCallOptions> = {}
): Promise<HelixPaginatedResponse<HelixScheduleSegmentData>> {
const origData = ((await super._fetchData(additionalOptions)) as unknown) as HelixScheduleResponse;

return {
data: origData.data.segments,
pagination: origData.pagination
};
}
}
95 changes: 95 additions & 0 deletions packages/twitch/src/API/Helix/Schedule/HelixSchedule.ts
@@ -0,0 +1,95 @@
import { Enumerable } from '@d-fischer/shared-utils';
import type { ApiClient } from '../../../ApiClient';
import type { HelixUser } from '../User/HelixUser';
import type { HelixScheduleSegmentData } from './HelixScheduleSegment';
import { HelixScheduleSegment } from './HelixScheduleSegment';

/** @private */
export interface HelixScheduleVacationData {
start_time: string;
end_time: string;
}

/** @private */
export interface HelixScheduleData {
segments: HelixScheduleSegmentData[];
broadcaster_id: string;
broadcaster_name: string;
broadcaster_login: string;
vacation: HelixScheduleVacationData | null;
}

/** @private */
export interface HelixScheduleResponse {
data: HelixScheduleData;
pagination: {
cursor?: string;
};
}

/**
* A schedule of a channel.
*/
export class HelixSchedule {
@Enumerable(false) private readonly _data: HelixScheduleData;
@Enumerable(false) private readonly _client: ApiClient;

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

/**
* The segments of the schedule.
*/
get segments(): HelixScheduleSegment[] {
return this._data.segments.map(data => new HelixScheduleSegment(data, this._client));
}

/**
* The ID of the broadcaster.
*/
get broadcasterId(): string {
return this._data.broadcaster_id;
}

/**
* The name of the broadcaster.
*/
get broadcasterName(): string {
return this._data.broadcaster_login;
}

/**
* The display name of the broadcaster.
*/
get broadcasterDisplayName(): string {
return this._data.broadcaster_name;
}

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

/**
* The date when the current vacation started, or null if the schedule is not in vacation mode.
*/
get vacationStartDate(): Date | null {
const timestamp = this._data.vacation?.start_time;

return timestamp ? new Date(timestamp) : null;
}

/**
* The date when the current vacation ends, or null if the schedule is not in vacation mode.
*/
get vacationEndDate(): Date | null {
const timestamp = this._data.vacation?.end_time;

return timestamp ? new Date(timestamp) : null;
}
}
125 changes: 125 additions & 0 deletions packages/twitch/src/API/Helix/Schedule/HelixScheduleApi.ts
@@ -0,0 +1,125 @@
import { TwitchApiCallType } from 'twitch-api-call';
import type { UserIdResolvable } from 'twitch-common';
import { extractUserId } from 'twitch-common';
import { BaseApi } from '../../BaseApi';
import type { HelixForwardPagination } from '../HelixPagination';
import { makePaginationQuery } from '../HelixPagination';
import { HelixPaginatedScheduleSegmentRequest } from './HelixPaginatedScheduleSegmentRequest';
import type { HelixScheduleResponse } from './HelixSchedule';
import { HelixSchedule } from './HelixSchedule';
import { HelixScheduleSegment } from './HelixScheduleSegment';

/**
* Filters for the schedule request.
*/
export interface HelixScheduleFilter {
/**
* The earliest date to find schedule segments for.
*/
startDate?: string;

/**
* The offset from UTC you request for, to ensure everything goes to the correct day.
*/
utcOffset?: number;
}

/**
* @inheritDoc
*/
export interface HelixPaginatedScheduleFilter extends HelixScheduleFilter, HelixForwardPagination {}

/**
* The result of a schedule request.
*/
export interface HelixPaginatedScheduleResult {
/**
* The actual schedule object.
*/
data: HelixSchedule;

/**
* The pagination cursor.
*/
cursor?: string;
}

/**
* The Helix API methods that deal with schedules.
*/
export class HelixScheduleApi extends BaseApi {
/**
* Retrieves the schedule for a given broadcaster.
*
* @param broadcaster The broadcaster to get the schedule of.
* @param filter
*
* @expandParams
*/
async getSchedule(
broadcaster: UserIdResolvable,
filter?: HelixPaginatedScheduleFilter
): Promise<HelixPaginatedScheduleResult> {
const result = await this._client.callApi<HelixScheduleResponse>({
type: TwitchApiCallType.Helix,
url: 'schedule',
query: {
broadcaster_id: extractUserId(broadcaster),
start_time: filter?.startDate,
utc_offset: filter?.utcOffset?.toString(),
...makePaginationQuery(filter)
}
});

return {
data: new HelixSchedule(result.data, this._client),
cursor: result.pagination.cursor
};
}

/**
* Creates a paginator for schedule segments for a given broadcaster.
*
* @param broadcaster The broadcaster to get the schedule segments of.
* @param filter
*
* @expandParams
*/
getScheduleSegmentsPaginated(
broadcaster: UserIdResolvable,
filter?: HelixScheduleFilter
): HelixPaginatedScheduleSegmentRequest {
return new HelixPaginatedScheduleSegmentRequest(broadcaster, this._client, filter);
}

/**
* Retrieves a set of schedule segments by IDs.
*
* @param broadcaster The broadcaster to get schedule segments of.
* @param ids The IDs of the schedule segments.
*/
async getScheduleSegmentsByIds(broadcaster: UserIdResolvable, ids: string[]): Promise<HelixScheduleSegment[]> {
const result = await this._client.callApi<HelixScheduleResponse>({
type: TwitchApiCallType.Helix,
url: 'schedule',
query: {
broadcaster_id: extractUserId(broadcaster),
id: ids
}
});

return result.data.segments.map(data => new HelixScheduleSegment(data, this._client));
}

/**
* Retrieves a single schedule segment by ID.
*
* @param broadcaster The broadcaster to get a schedule segment of.
* @param id The ID of the schedule segment.
*/
async getScheduleSegmentById(broadcaster: UserIdResolvable, id: string): Promise<HelixScheduleSegment | null> {
const segments = await this.getScheduleSegmentsByIds(broadcaster, [id]);

return segments.length ? segments[0] : null;
}
}

0 comments on commit 4b6e9d9

Please sign in to comment.