189 lines
6.2 KiB
TypeScript
189 lines
6.2 KiB
TypeScript
![]() |
import { LooseObject } from "../../../types/looseTypes"
|
||
|
import { StreamerData, StreamData, Social } from "../../../types/scraping/Streamer"
|
||
|
|
||
|
/**
|
||
|
* Class that interacts with the Twitch api
|
||
|
*/
|
||
|
export class TwitchAPI {
|
||
|
public readonly twitchUrl = 'https://gql.twitch.tv/gql'
|
||
|
public headers = {
|
||
|
"Client-Id": "kimne78kx3ncx6brgo4mv6wki5h1ko"
|
||
|
}
|
||
|
|
||
|
constructor() {}
|
||
|
|
||
|
/**
|
||
|
* Gets information about a streamer, like socials, about, and more.
|
||
|
* @see StreamerData
|
||
|
* @param streamerName The username of the streamer
|
||
|
* @returns Promise<StreamerData>
|
||
|
*/
|
||
|
public getStreamerInfo = async (streamerName: string) => {
|
||
|
const payload = [
|
||
|
{
|
||
|
"operationName": "ChannelRoot_AboutPanel",
|
||
|
"variables": {
|
||
|
"channelLogin": streamerName,
|
||
|
"skipSchedule": false
|
||
|
},
|
||
|
"extensions": {
|
||
|
"persistedQuery": {
|
||
|
"version": 1,
|
||
|
"sha256Hash": "6089531acef6c09ece01b440c41978f4c8dc60cb4fa0124c9a9d3f896709b6c6"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
"operationName":"StreamMetadata",
|
||
|
"variables":{
|
||
|
"channelLogin": streamerName
|
||
|
},
|
||
|
"extensions":{
|
||
|
"persistedQuery":{
|
||
|
"version":1,
|
||
|
"sha256Hash":"a647c2a13599e5991e175155f798ca7f1ecddde73f7f341f39009c14dbf59962"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
"operationName": "StreamTagsTrackingChannel",
|
||
|
"variables": {
|
||
|
"channel": streamerName
|
||
|
},
|
||
|
"extensions": {
|
||
|
"persistedQuery": {
|
||
|
"version": 1,
|
||
|
"sha256Hash": "6aa3851aaaf88c320d514eb173563d430b28ed70fdaaf7eeef6ed4b812f48608"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
"operationName": "VideoPreviewOverlay",
|
||
|
"variables": {
|
||
|
"login": streamerName
|
||
|
},
|
||
|
"extensions": {
|
||
|
"persistedQuery": {
|
||
|
"version": 1,
|
||
|
"sha256Hash": "9515480dee68a77e667cb19de634739d33f243572b007e98e67184b1a5d8369f"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
"operationName": "UseViewCount",
|
||
|
"variables": {
|
||
|
"channelLogin": streamerName
|
||
|
},
|
||
|
"extensions": {
|
||
|
"persistedQuery": {
|
||
|
"version": 1,
|
||
|
"sha256Hash": "00b11c9c428f79ae228f30080a06ffd8226a1f068d6f52fbc057cbde66e994c2"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
]
|
||
|
|
||
|
const res = await fetch(this.twitchUrl, {
|
||
|
method: 'POST',
|
||
|
body: JSON.stringify(payload),
|
||
|
headers: this.headers
|
||
|
})
|
||
|
|
||
|
const data = await res.json()
|
||
|
console.log(data[1].data, data[1].data.user.stream)
|
||
|
|
||
|
|
||
|
const rawStreamerData = data[0].data
|
||
|
|
||
|
|
||
|
// get socials
|
||
|
const socials: LooseObject[] = []
|
||
|
if (rawStreamerData.user.channel && rawStreamerData.user.channel.socialMedias) {
|
||
|
for (let social of rawStreamerData.user.channel.socialMedias) {
|
||
|
socials.push({
|
||
|
type: social.name,
|
||
|
name: social.title,
|
||
|
link: social.url
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if is liver
|
||
|
const rawStreamData = data[1].data.user.stream
|
||
|
let parsedStream: StreamData | null;
|
||
|
if(!rawStreamData) {
|
||
|
parsedStream = null
|
||
|
} else {
|
||
|
const tags: string[] = []
|
||
|
for (let tagData of data[2].data.user.stream.freeformTags) {
|
||
|
tags.push(tagData.name)
|
||
|
}
|
||
|
|
||
|
parsedStream = {
|
||
|
title: data[1].data.user.lastBroadcast.title,
|
||
|
topic: rawStreamData.game.name,
|
||
|
startedAt: new Date(rawStreamData.createdAt).valueOf(),
|
||
|
tags,
|
||
|
viewers: Number(data[4].data.user.stream.viewersCount),
|
||
|
preview: data[3].data.user.stream.previewImageURL
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const abbreviatedFollowers = Intl.NumberFormat('en-US', {
|
||
|
notation: "compact",
|
||
|
maximumFractionDigits: 1
|
||
|
}).format(rawStreamerData.user.followers.totalCount)
|
||
|
|
||
|
const streamerData: StreamerData = {
|
||
|
username: rawStreamerData.user.displayName,
|
||
|
about: rawStreamerData.user.description,
|
||
|
pfp: rawStreamerData.user.profileImageURL,
|
||
|
followers: rawStreamerData.user.followers.totalCount,
|
||
|
socials: socials as Social[],
|
||
|
isLive: (!!parsedStream),
|
||
|
isPartner: rawStreamerData.user.isPartner,
|
||
|
followersAbbv: abbreviatedFollowers,
|
||
|
colorHex: '#' + rawStreamerData.user.primaryColorHex,
|
||
|
id: Number(rawStreamerData.user.id),
|
||
|
stream: parsedStream
|
||
|
}
|
||
|
|
||
|
return Promise.resolve(streamerData)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the current viewers of a stream
|
||
|
* @param streamerName The username of the streamer
|
||
|
* @returns Promise<number>
|
||
|
*/
|
||
|
public getViewers = async (streamerName: string) => {
|
||
|
const payload = [
|
||
|
{
|
||
|
"operationName": "UseViewCount",
|
||
|
"variables": {
|
||
|
"channelLogin": streamerName
|
||
|
},
|
||
|
"extensions": {
|
||
|
"persistedQuery": {
|
||
|
"version": 1,
|
||
|
"sha256Hash": "00b11c9c428f79ae228f30080a06ffd8226a1f068d6f52fbc057cbde66e994c2"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
]
|
||
|
|
||
|
const res = await fetch(this.twitchUrl, {
|
||
|
method: 'POST',
|
||
|
body: JSON.stringify(payload),
|
||
|
headers: this.headers
|
||
|
})
|
||
|
|
||
|
const rawData = await res.json()
|
||
|
console.log(rawData)
|
||
|
|
||
|
if(!rawData[0].data.user.stream)
|
||
|
return Promise.reject(new Error(`Streamer ${streamerName} is not live`))
|
||
|
|
||
|
return Promise.resolve(rawData[0].data.user.stream.viewersCount)
|
||
|
}
|
||
|
}
|