diff --git a/server/src/action-utils.js b/server/src/action-utils.js index 5ec79937..f3e6cef4 100644 --- a/server/src/action-utils.js +++ b/server/src/action-utils.js @@ -1,5 +1,8 @@ const Airtable = require("airtable"); const Cache = require("./cache"); +const { StaticAuthProvider } = require("@twurple/auth"); +const { ApiClient } = require("@twurple/api"); +const { get, auth } = require("./cache"); const airtable = new Airtable({ apiKey: process.env.AIRTABLE_KEY }); const slmngg = airtable.base(process.env.AIRTABLE_APP); @@ -109,6 +112,45 @@ async function getValidHeroes() { return heroes.filter(h => h.game === "Overwatch"); } +async function getTwitchChannel(client, requestedScopes, { success, error }) { + const broadcast = await get(client?.broadcast?.[0]); + if (!broadcast) return error("No broadcast associated"); + if (!broadcast.channel) return error("No channel associated with broadcast"); + + const channel = await auth.getChannel(broadcast?.channel?.[0]); + if (!channel.twitch_refresh_token) return error("No twitch auth token associated with channel"); + if (!channel.channel_id || !channel.name || !channel.twitch_scopes) return error("Invalid channel data"); + let scopes = channel.twitch_scopes.split(" "); + if (!requestedScopes.every(scope => scopes.includes(scope))) return error("Token doesn't have the required scopes"); + return { + broadcast, + channel, + scopes + }; +} + +async function getMatchData(broadcast, requireAll, { success, error }) { + const match = await get(broadcast?.live_match?.[0]); + if (!match) return error("No match associated"); + + const team1 = await get(match?.teams?.[0]); + const team2 = await get(match?.teams?.[1]); + if (requireAll && (!team1 || !team2)) return error("Did not find two teams!"); + + return { + match, + team1, + team2 + }; +} + +async function getTwitchAPIClient(channel) { + const accessToken = await auth.getTwitchAccessToken(channel); + const authProvider = new StaticAuthProvider(process.env.TWITCH_CLIENT_ID, accessToken); + return new ApiClient({authProvider}); +} + + module.exports = { - getSelfClient, dirtyID, deAirtable, updateRecord, getValidHeroes, createRecord + getSelfClient, dirtyID, deAirtable, updateRecord, getValidHeroes, createRecord, getTwitchChannel, getMatchData, getTwitchAPIClient }; diff --git a/server/src/actions/manage-prediction.js b/server/src/actions/manage-prediction.js index b2566077..f8634fd2 100644 --- a/server/src/actions/manage-prediction.js +++ b/server/src/actions/manage-prediction.js @@ -1,5 +1,7 @@ -const { ApiClient } = require("@twurple/api"); -const { StaticAuthProvider, refreshUserToken } = require("@twurple/auth"); +const { getTwitchChannel, + getTwitchAPIClient, + getMatchData +} = require("../action-utils"); const automaticPredictionTitleStartCharacter = "⬥"; @@ -49,30 +51,12 @@ module.exports = { if (!(["create", "lock", "resolve", "cancel"].includes(predictionAction))) return error("Invalid action"); console.log(predictionAction); - const broadcast = await get(client?.broadcast?.[0]); - if (!broadcast) return error("No broadcast associated"); - if (!broadcast.channel) return error("No channel associated with broadcast"); - - const channel = await auth.getChannel(broadcast?.channel?.[0]); - if (!channel.twitch_refresh_token) return error("No twitch auth token associated with channel"); - if (!channel.channel_id || !channel.name || !channel.twitch_scopes) return error("Invalid channel data"); - let scopes = channel.twitch_scopes.split(" "); - if (!["channel:manage:predictions", "channel:read:predictions"].every(scope => scopes.includes(scope))) return error("Token doesn't have the required scopes"); - - console.log(channel); - const accessToken = await auth.getTwitchAccessToken(channel); - - const authProvider = new StaticAuthProvider(process.env.TWITCH_CLIENT_ID, accessToken); - const api = new ApiClient({authProvider}); - // TODO: move cancel action to here - const match = await get(broadcast?.live_match?.[0]); - if (!match) return error("No match associated"); - - const team1 = await get(match?.teams?.[0]); - const team2 = await get(match?.teams?.[1]); - if (!team1 || !team2) return error("Did not find two teams!"); + const { broadcast, channel } = getTwitchChannel(client, ["channel:manage:predictions", "channel:read:predictions"], { success, error }); + // console.log(channel); + const api = await getTwitchAPIClient(channel); + const { match, team1, team2 } = await getMatchData(broadcast, true, { success, error }); const maps = await Promise.all((match.maps || []).map(async m => { let map = await get(m); diff --git a/server/src/actions/set-title.js b/server/src/actions/set-title.js index 56323736..710ec2a3 100644 --- a/server/src/actions/set-title.js +++ b/server/src/actions/set-title.js @@ -1,5 +1,7 @@ -const { ApiClient } = require("@twurple/api"); -const { StaticAuthProvider } = require("@twurple/auth"); +const { getTwitchChannel, + getMatchData, + getTwitchAPIClient +} = require("../action-utils"); module.exports = { key: "set-title", auth: ["client"], @@ -15,32 +17,13 @@ module.exports = { */ // eslint-disable-next-line no-empty-pattern async handler(success, error, { }, { client }, { get, auth }) { - const broadcast = await get(client?.broadcast?.[0]); - if (!broadcast) return error("No broadcast associated"); - if (!broadcast.channel) return error("No channel associated with broadcast"); + const { broadcast, channel } = getTwitchChannel(client, ["channel:manage:broadcast"], { success, error }); const event = await get(broadcast.event?.[0]); if (!event) return error("No event associated with broadcast"); - - const channel = await auth.getChannel(broadcast?.channel?.[0]); - if (!channel.twitch_refresh_token) return error("No twitch auth token associated with channel"); - if (!channel.channel_id || !channel.name || !channel.twitch_scopes) return error("Invalid channel data"); - let scopes = channel.twitch_scopes.split(" "); - if (!["channel:manage:broadcast"].every(scope => scopes.includes(scope))) return error("Token doesn't have the required scopes"); - - const accessToken = await auth.getTwitchAccessToken(channel); - - const authProvider = new StaticAuthProvider(process.env.TWITCH_CLIENT_ID, accessToken); - const api = new ApiClient({authProvider}); - - - const match = await get(broadcast?.live_match?.[0]); - if (!match) return error("No match associated"); - - const team1 = await get(match?.teams?.[0]); - const team2 = await get(match?.teams?.[1]); - if (!team1 || !team2) return error("Did not find two teams!"); + const api = await getTwitchAPIClient(channel, auth); + const { match, team1, team2 } = await getMatchData(broadcast, true, { success, error }); const formatOptions = { "event": event.name, diff --git a/server/src/actions/start-commercial.js b/server/src/actions/start-commercial.js index 7e7a9778..a4663d12 100644 --- a/server/src/actions/start-commercial.js +++ b/server/src/actions/start-commercial.js @@ -1,5 +1,6 @@ -const { ApiClient } = require("@twurple/api"); -const { StaticAuthProvider } = require("@twurple/auth"); +const { getTwitchChannel, + getTwitchAPIClient +} = require("../action-utils"); module.exports = { key: "start-commercial", auth: ["client"], @@ -15,24 +16,10 @@ module.exports = { * @returns {Promise} */ // eslint-disable-next-line no-empty-pattern - async handler(success, error, { commercialDuration }, { client, user }, { get, auth }) { + async handler(success, error, { commercialDuration }, { client }, { get, auth }) { if (!user.airtable?.website_settings?.includes("Full broadcast permissions")) return error("You don't have permission to start a commercial", 403); - - const broadcast = await get(client?.broadcast?.[0]); - if (!broadcast) return error("No broadcast associated"); - if (!broadcast.channel) return error("No channel associated with broadcast"); - - const channel = await auth.getChannel(broadcast?.channel?.[0]); - if (!channel.twitch_refresh_token) return error("No twitch auth token associated with channel"); - if (!channel.channel_id || !channel.name || !channel.twitch_scopes) return error("Invalid channel data"); - let scopes = channel.twitch_scopes.split(" "); - if (!["channel:edit:commercial"].every(scope => scopes.includes(scope))) return error("Token doesn't have the required scopes"); - - const accessToken = await auth.getTwitchAccessToken(channel); - - const authProvider = new StaticAuthProvider(process.env.TWITCH_CLIENT_ID, accessToken); - const api = new ApiClient({authProvider}); - + const { channel } = getTwitchChannel(client, ["channel:edit:commercial"], { success, error }); + const api = getTwitchAPIClient(channel); try { await api.channels.startChannelCommercial(channel.channel_id, commercialDuration); } catch (e) {