Skip to content

Commit

Permalink
feat(api): add Get Custom Rewards endpoint (#4328)
Browse files Browse the repository at this point in the history
* feat(api): add Get Custom Rewards endpoint

Fixes #4323

* fix
  • Loading branch information
sogehige committed Nov 16, 2020
1 parent 2bb5581 commit 2b557e0
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 13 deletions.
2 changes: 1 addition & 1 deletion locales/en.json
Expand Up @@ -121,7 +121,7 @@
"noEvents": "No events found in database.",
"whatsthis": "what's this?",
"myRewardIsNotListed": "My reward is not listed!",
"redeemAndClickRefreshToSeeReward": "If you are missing your created reward in a list, please redeem it and refresh by clicking on refresh icon.",
"redeemAndClickRefreshToSeeReward": "If you are missing your created reward in a list, refresh by clicking on refresh icon.",
"badges": {
"enabled": "Enabled",
"disabled": "Disabled"
Expand Down
40 changes: 40 additions & 0 deletions src/bot/api.ts
Expand Up @@ -53,6 +53,7 @@ type APITimeouts = {
type SubscribersEndpoint = { data: { broadcaster_id: string; broadcaster_name: string; is_gift: boolean; tier: string; plan_name: string; user_id: string; user_name: string; }[], pagination: { cursor: string } };
type FollowsEndpoint = { total: number; data: { from_id: string; from_name: string; to_id: string; toname: string; followed_at: string; }[], pagination: { cursor: string } };
export type StreamEndpoint = { data: { id: string; user_id: string, user_name: string, game_id: string, type: 'live' | '', title: string , viewer_count: number, started_at: string, language: string; thumbnail_url: string; tag_ids: string[] }[], pagination: { cursor: string } };
type CustomRewardEndpoint = { data{ broadcaster_name: string; broadcaster_id: string; id: string; image: string | null; background_color: string; is_enabled: boolean; cost: number; title: string; prompt: string; is_user_input_required: false; max_per_stream_setting: { is_enabled: boolean; max_per_stream: number; }; max_per_user_per_stream_setting: { is_enabled: boolean; max_per_user_per_stream: number }; global_cooldown_setting: { is_enabled: boolean; global_cooldown_seconds: number }; is_paused: boolean; is_in_stock: boolean; default_image: { url_1x: string; url_2x: string; url_4x: string; }; should_redemptions_skip_request_queue: boolean; redemptions_redeemed_current_stream: null | number; cooldown_expires_at: null | string; }[] };

export const currentStreamTags: {
is_auto: boolean;
Expand Down Expand Up @@ -295,6 +296,10 @@ class API extends Core {
async interval (fnc: string, interval: number) {
setInterval(async () => {
debug('api.interval', chalk.yellow(fnc + '() ') + 'check');
if (oauth.loadedTokens < 2) {
debug('api.interval', chalk.yellow(fnc + '() ') + 'tokens not loaded yet.');
return;
}
if (typeof this.api_timeouts[fnc] === 'undefined') {
this.api_timeouts[fnc] = { opts: {}, isRunning: false, inProgress: false, lastRunAt: 0 };
}
Expand Down Expand Up @@ -1968,6 +1973,41 @@ class API extends Core {
}
}

async getCustomRewards() {
const url = 'https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id=' + oauth.channelId;
const token = oauth.broadcasterAccessToken;
try {
if (token === '') {
throw Error('No broadcaster access token');
}

const request = await axios.get<CustomRewardEndpoint>(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
'Client-ID': oauth.broadcasterClientId,
},
timeout: 20000,
});

// save remaining api calls
this.calls.broadcaster.remaining = request.headers['ratelimit-remaining'];
this.calls.broadcaster.refresh = request.headers['ratelimit-reset'];
this.calls.broadcaster.limit = request.headers['ratelimit-limit'];

ioServer?.emit('api.stats', { method: 'GET', data: request.data, timestamp: Date.now(), call: 'getCustomRewards', api: 'helix', endpoint: url, code: request.status, remaining: this.calls.broadcaster });
return request.data.data;
} catch (e) {
if (e.isAxiosError) {
error(`API: ${e.config.method.toUpperCase()} ${e.config.url} - ${e.response?.status ?? 0}\n${JSON.stringify(e.response?.data ?? '--nodata--', null, 4)}\n\n${e.stack}`);
ioServer?.emit('api.stats', { method: e.config.method.toUpperCase(), timestamp: Date.now(), call: 'getCustomRewards', api: 'helix', endpoint: e.config.url, code: e.response?.status ?? 'n/a', data: e.response?.data, remaining: this.calls.bot });
} else {
error(e.stack);
ioServer?.emit('api.stats', { method: e.config.method.toUpperCase(), timestamp: Date.now(), call: 'getCustomRewards', api: 'helix', endpoint: e.config.url, code: e.response?.status ?? 'n/a', data: e.stack, remaining: this.calls.bot });
}
}
}

async getTopClips (opts: any) {
let url = 'https://api.twitch.tv/helix/clips?broadcaster_id=' + oauth.channelId;
const token = oauth.botAccessToken;
Expand Down
7 changes: 2 additions & 5 deletions src/bot/events.ts
Expand Up @@ -30,7 +30,6 @@ import { isDbConnected } from './helpers/database';
import { addUIError } from './panel';
import { translate } from './translate';
import { dayjs } from './helpers/dayjs';
import { redeemedRewards } from './pubsub';

class Events extends Core {
public timeouts: { [x: string]: NodeJS.Timeout } = {};
Expand Down Expand Up @@ -582,12 +581,10 @@ class Events extends Core {
}

public sockets() {
adminEndpoint(this.nsp, 'events::setRedeemedRewards', async (reward) => {
redeemedRewards.add(reward);
});
adminEndpoint(this.nsp, 'events::getRedeemedRewards', async (cb) => {
try {
cb(null, [...redeemedRewards]);
const rewards = await api.getCustomRewards();
cb(null, rewards ? [...rewards.map(o => o.title)] : []);
} catch (e) {
cb(e.stack, []);
}
Expand Down
1 change: 0 additions & 1 deletion src/bot/helpers/socket.ts
Expand Up @@ -87,7 +87,6 @@ function adminEndpoint (nsp: string, on: 'test.user', callback: (opts: { value:
function adminEndpoint (nsp: string, on: 'text::remove' | 'text::save', callback: (item: Readonly<Required<TextInterface>>, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'events::remove' | 'events::save', callback: (item: Readonly<Required<EventInterface>>, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'events::getRedeemedRewards', callback: (cb: (error: Error | string | null, response: string[]) => void) => void): void;
function adminEndpoint (nsp: string, on: 'events::setRedeemedRewards', callback: (value:string) => void): void;
function adminEndpoint (nsp: string, on: 'carousel::save', callback: (items: Readonly<Required<EventInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'carousel::insert', callback: (data: string, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'goals::remove' | 'goals::save', callback: (item: Readonly<Required<GoalGroupInterface>>, cb: (error: Error | string | null, ...response: any) => void) => void): void;
Expand Down
12 changes: 10 additions & 2 deletions src/bot/oauth.ts
Expand Up @@ -3,7 +3,7 @@ import axios from 'axios';
import Core from './_interface';
import * as constants from './constants';
import { areDecoratorsLoaded, persistent, settings, ui } from './decorators';
import { onChange } from './decorators/on';
import { onChange, onLoad } from './decorators/on';
import { error, info, warning } from './helpers/log';
import api from './api';
import tmi from './tmi';
Expand All @@ -21,12 +21,14 @@ class OAuth extends Core {
public bot = '';
public channelId = '';
public botId = '';
public broadcasterId = '';
@persistent()
public botClientId = '';
public broadcasterId = '';
@persistent()
public broadcasterClientId = '';

loadedTokens = 0;

@settings('general')
public generalChannel = '';

Expand Down Expand Up @@ -123,6 +125,12 @@ class OAuth extends Core {
}, 10000);
}

@onLoad('broadcasterAccessToken')
@onLoad('botAccessToken')
setBotAccessTokenLoaded() {
this.loadedTokens++;
}

@onChange('generalOwner')
@onChange('broadcasterUsername')
clearCache() {
Expand Down
3 changes: 0 additions & 3 deletions src/bot/pubsub.ts
Expand Up @@ -18,8 +18,6 @@ let connectionHash = '';

let ERR_BADAUTH = false;

export const redeemedRewards = new Set<string>();

setInterval(() => {
try {
if (oauth.broadcasterAccessToken.length > 0 && oauth.broadcasterClientId.length > 0 && oauth.broadcasterId.length > 0) {
Expand Down Expand Up @@ -70,7 +68,6 @@ const connect = () => {
if (message.type === 'MESSAGE') {
const dataMessage = JSON.parse(message.data.message);
if (dataMessage.type === 'reward-redeemed') {
redeemedRewards.add(dataMessage.data.redemption.reward.title);
// trigger reward-redeemed event
if (dataMessage.data.redemption.user_input) {
redeem(`${dataMessage.data.redemption.user.login}#${dataMessage.data.redemption.user.id} redeemed ${dataMessage.data.redemption.reward.title}: ${dataMessage.data.redemption.user_input}`);
Expand Down
1 change: 0 additions & 1 deletion src/panel/components/rewardDropdown.vue
Expand Up @@ -37,7 +37,6 @@ export default defineComponent({
} as { redeemRewards: number })
const redeemRewardsWithForcedSelected = (selected: string) => {
socket.emit('events::setRedeemedRewards', selected);
return Array.from(new Set([selected, ...redeemRewards.value]));
}
const refreshRedeemedRewards = async () => {
Expand Down

0 comments on commit 2b557e0

Please sign in to comment.