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

Add twitch support #1017

Merged
merged 6 commits into from
Apr 6, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions src/extension/content-script/batteries/Twitch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import getOriginData from "../originData";
import { findLightningAddressInText, setLightningData } from "./helpers";

const urlMatcher = /^https:\/\/(?:www\.)?twitch.tv\/([^/]+).+$/;

/* eslint-disable @typescript-eslint/no-explicit-any */
async function twitchApiCall(
clientId: string,
version: number,
operationName: string,
staticHash: string,
variables: { [prop: string]: string | number }
): Promise<any> {
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;
}

function extractClientId() {
const clientIdExtractor = /clientId="([A-Z0-9]+)"/i;
for (const scriptEl of document.querySelectorAll("script")) {
const clientIdMatch = scriptEl.innerHTML.match(clientIdExtractor);
const clientId = clientIdMatch ? clientIdMatch[1] : "";
if (clientId) {
return clientId;
}
}
}

async function fetchChannelDescription(channelID: string, clientId: string) {
const channelData = (
await twitchApiCall(
clientId,
1,
"ViewerFeedback_Creator",
"26c143e165e6d56fc69daddac9942d93ca48aa9ad3b6f38abf75ac45f5e59571",
{
channelID,
}
)
)[0];
if (channelData?.data?.creator) {
const creatorDetails = channelData.data.creator;
const channelDescription = creatorDetails.description;
const address = findLightningAddressInText(channelDescription);

if (address) {
setLightningData([
{
method: "lnurl",
address,
...getOriginData(),
description: channelDescription,
name: creatorDetails.displayName ?? creatorDetails.login,
icon: creatorDetails.profileImageURL ?? "",
},
]);
}
}
}

async function handleChannelPage(channel: string, clientId: string) {
const channelData = (
await twitchApiCall(
clientId,
1,
"ChannelShell",
"580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe",
{
login: channel,
}
)
)[0];
if (channelData?.data?.userOrError?.id) {
fetchChannelDescription(channelData.data.userOrError.id, clientId);
}
}

async function handleVideoPage(videoID: string, clientId: string) {
const channelData = (
await twitchApiCall(
clientId,
1,
"ChannelVideoCore",
"cf1ccf6f5b94c94d662efec5223dfb260c9f8bf053239a76125a58118769e8e2",
{
videoID,
}
)
)[0];
if (channelData?.data?.video?.owner?.id) {
fetchChannelDescription(channelData.data.video.owner.id, clientId);
}
return;
}

const battery = async (): Promise<void> => {
const urlParts = document.location.pathname.split("/");
const clientId = extractClientId();

if (!clientId) return;

if (urlParts[1] === "videos") {
await handleVideoPage(urlParts[2], clientId);
} else {
await handleChannelPage(urlParts[1], clientId);
}
};

const Twitch = {
urlMatcher,
battery,
};

export default Twitch;
2 changes: 2 additions & 0 deletions src/extension/content-script/batteries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Reddit from "./Reddit";
import SoundCloud from "./SoundCloud";
import StackOverflow from "./StackOverflow";
import Substack from "./Substack";
import Twitch from "./Twitch";
import Twitter from "./Twitter";
import Vida from "./Vida";
import VimeoVideo from "./VimeoVideo";
Expand All @@ -34,6 +35,7 @@ const enhancements = [
Substack,
GeyserProject,
Vida,
Twitch,

// Monetization must likely always be the last one as this is the fallback option if no specific enhancement matched
Monetization,
Expand Down