Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): add Get Custom Rewards endpoint #4328

Merged
merged 2 commits into from Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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