diff --git a/src/extension/content-script/batteries/Twitch.ts b/src/extension/content-script/batteries/Twitch.ts index 6175a8bd2f..cd0634c3d2 100644 --- a/src/extension/content-script/batteries/Twitch.ts +++ b/src/extension/content-script/batteries/Twitch.ts @@ -7,12 +7,45 @@ const urlMatcher = /^https:\/\/(?:www\.)?twitch.tv\/([^/]+).+$/; const validationRegex = /^[a-z0-9_.-]+$/i; const clientIdExtractor = /clientId="([A-Z0-9]+)"/i; +/* eslint-disable @typescript-eslint/no-explicit-any */ +async function twitchApiCall( + clientId: string, + version: number, + operationName: string, + staticHash: string, + variables: { [prop: string]: string | number } +): Promise { + const data = await fetch(`https://gql.twitch.tv/gql`, { + headers: { + "Client-Id": clientId, + "User-Agent": navigator.userAgent, + }, + method: "POST", + body: JSON.stringify([ + { + operationName: operationName, + variables: variables, + extensions: { + persistedQuery: { + version: version, + sha256Hash: staticHash, + }, + }, + }, + ]), + }).then((res) => res.json()); + return data; +} + const battery = async (): Promise => { const urlParts = document.location.pathname.split("/"); - const channel = urlParts[1]; - if (!channel) return; // not a channel - if (!validationRegex.test(channel)) return; // invalid channel + let channel = urlParts[1] != "videos" ? urlParts[1] : undefined; + const video = urlParts[1] == "videos" ? urlParts[2] : undefined; + + if (!channel && !video) return; // not a channel + if (channel && !validationRegex.test(channel)) return; // invalid channel + if (video && !validationRegex.test(video)) return; // invalid video let channelData; @@ -37,37 +70,66 @@ const battery = async (): Promise => { } } + let channelID; + + // Get channel ID + if (video) { + // If video page + channelData = ( + await twitchApiCall( + clientId, + 1, + "ChannelVideoCore", + "cf1ccf6f5b94c94d662efec5223dfb260c9f8bf053239a76125a58118769e8e2", + { + videoID: video, + } + ) + )[0]; + if ( + channelData && + channelData.data && + channelData.data.video && + channelData.data.video.owner + ) { + channelID = channelData.data.video.owner.id; + channel = channelData.data.video.owner.login; + } + } else if (channel) { + // if channel page + channelData = ( + await twitchApiCall( + clientId, + 1, + "ChannelShell", + "580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe", + { + login: channel, + } + ) + )[0]; + if (channelData && channelData.data && channelData.data.userOrError) { + channelID = channelData.data.userOrError.id; + } + } + // Simulate internal API call to obtain channelData. // A public api should be used here instead, but i couldn't find an alternative // that didn't require oauth (thus breaking user experience). - channelData = await fetch(`https://gql.twitch.tv/gql`, { - headers: { - "Client-Id": clientId, - "User-Agent": navigator.userAgent, - }, - method: "POST", - body: JSON.stringify([ - { - operationName: "ChannelRoot_AboutPanel", - variables: { - channelLogin: channel, - skipSchedule: true, - }, - extensions: { - persistedQuery: { - version: 1, - sha256Hash: - "6089531acef6c09ece01b440c41978f4c8dc60cb4fa0124c9a9d3f896709b6c6", - }, - }, - }, - ]), - }).then((res) => res.json()); + channelData = await twitchApiCall( + clientId, + 1, + "ViewerFeedback_Creator", + "26c143e165e6d56fc69daddac9942d93ca48aa9ad3b6f38abf75ac45f5e59571", + { + channelID: channelID, + } + ); channelData = - !channelData || !channelData[0] || !channelData[0].data.user + !channelData || !channelData[0] || !channelData[0].data.creator ? null - : channelData[0].data.user; + : channelData[0].data.creator; if (channelData) break; else {