mirror of
https://codeberg.org/SafeTwitch/safetwitch-backend.git
synced 2025-01-20 11:22:29 -05:00
Add search endpoint
This commit is contained in:
parent
65a6c62b38
commit
7bf75fa99e
5 changed files with 559 additions and 467 deletions
|
@ -23,8 +23,10 @@ profileRouter.get('/discover', async (req, res, next) => {
|
||||||
let discoveryData;
|
let discoveryData;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
discoveryData = await twitch.getDirectory(15, cursor)
|
discoveryData = await twitch.getDirectory(15, cursor)
|
||||||
|
.catch(next)
|
||||||
} else {
|
} else {
|
||||||
discoveryData = await twitch.getDirectory(15)
|
discoveryData = await twitch.getDirectory(15)
|
||||||
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
|
@ -69,4 +71,20 @@ profileRouter.get('/badges', async (req, res, next) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
profileRouter.get('/search', async (req, res, next) => {
|
||||||
|
const query = req.query.query
|
||||||
|
if(!query)
|
||||||
|
return res.status(400).send({
|
||||||
|
status: 'error',
|
||||||
|
message: 'No query provided'
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await twitch.getSearchResult(query.toString())
|
||||||
|
.catch(next)
|
||||||
|
res.send({
|
||||||
|
status: 'ok',
|
||||||
|
data: result
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export default profileRouter
|
export default profileRouter
|
|
@ -5,7 +5,7 @@ export interface Category {
|
||||||
displayName: string
|
displayName: string
|
||||||
viewers: number
|
viewers: number
|
||||||
tags: Tag[]
|
tags: Tag[]
|
||||||
createdAt: Date
|
createdAt?: Date
|
||||||
cursor: string
|
cursor?: string
|
||||||
image: string
|
image: string
|
||||||
}
|
}
|
|
@ -17,13 +17,12 @@ export interface StreamData {
|
||||||
export interface StreamerData {
|
export interface StreamerData {
|
||||||
username: string
|
username: string
|
||||||
followers: number
|
followers: number
|
||||||
followersAbbv: string
|
|
||||||
isLive: boolean
|
isLive: boolean
|
||||||
about: string
|
about: string
|
||||||
socials?: Social[]
|
socials?: Social[]
|
||||||
pfp: string
|
pfp: string
|
||||||
stream?: StreamData | null
|
stream?: StreamData | null
|
||||||
isPartner: boolean
|
isPartner: boolean | null
|
||||||
colorHex: string
|
colorHex: string
|
||||||
id: number
|
id: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ export const errorHandler = (err: Error, req: Request, res: Response, next: Next
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(500).send({ status: 'error', message: err.message})
|
res.status(500).send({ status: 'error', message: err})
|
||||||
}
|
}
|
|
@ -16,8 +16,7 @@ export class TwitchAPI {
|
||||||
"Client-Id": "kimne78kx3ncx6brgo4mv6wki5h1ko"
|
"Client-Id": "kimne78kx3ncx6brgo4mv6wki5h1ko"
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets information about a streamer, like socials, about, and more.
|
* Gets information about a streamer, like socials, about, and more.
|
||||||
* @see StreamerData
|
* @see StreamerData
|
||||||
|
@ -40,14 +39,14 @@ export class TwitchAPI {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"operationName":"StreamMetadata",
|
"operationName": "StreamMetadata",
|
||||||
"variables":{
|
"variables": {
|
||||||
"channelLogin": streamerName
|
"channelLogin": streamerName
|
||||||
},
|
},
|
||||||
"extensions":{
|
"extensions": {
|
||||||
"persistedQuery":{
|
"persistedQuery": {
|
||||||
"version":1,
|
"version": 1,
|
||||||
"sha256Hash":"a647c2a13599e5991e175155f798ca7f1ecddde73f7f341f39009c14dbf59962"
|
"sha256Hash": "a647c2a13599e5991e175155f798ca7f1ecddde73f7f341f39009c14dbf59962"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -114,7 +113,7 @@ export class TwitchAPI {
|
||||||
// check if is liver
|
// check if is liver
|
||||||
const rawStreamData = data[1].data.user.stream
|
const rawStreamData = data[1].data.user.stream
|
||||||
let parsedStream: StreamData | null;
|
let parsedStream: StreamData | null;
|
||||||
if(!rawStreamData) {
|
if (!rawStreamData) {
|
||||||
parsedStream = null
|
parsedStream = null
|
||||||
} else {
|
} else {
|
||||||
const tags: string[] = []
|
const tags: string[] = []
|
||||||
|
@ -147,7 +146,6 @@ export class TwitchAPI {
|
||||||
socials: socials as Social[],
|
socials: socials as Social[],
|
||||||
isLive: (!!parsedStream),
|
isLive: (!!parsedStream),
|
||||||
isPartner: rawStreamerData.user.isPartner,
|
isPartner: rawStreamerData.user.isPartner,
|
||||||
followersAbbv: abbreviatedFollowers,
|
|
||||||
colorHex: '#' + rawStreamerData.user.primaryColorHex,
|
colorHex: '#' + rawStreamerData.user.primaryColorHex,
|
||||||
id: Number(rawStreamerData.user.id),
|
id: Number(rawStreamerData.user.id),
|
||||||
stream: parsedStream
|
stream: parsedStream
|
||||||
|
@ -185,7 +183,7 @@ export class TwitchAPI {
|
||||||
|
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
|
|
||||||
if(!rawData[0].data.user.stream)
|
if (!rawData[0].data.user.stream)
|
||||||
return Promise.reject(new Error(`Streamer ${streamerName} is not live`))
|
return Promise.reject(new Error(`Streamer ${streamerName} is not live`))
|
||||||
|
|
||||||
return Promise.resolve(rawData[0].data.user.stream.viewersCount)
|
return Promise.resolve(rawData[0].data.user.stream.viewersCount)
|
||||||
|
@ -215,7 +213,7 @@ export class TwitchAPI {
|
||||||
|
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
|
|
||||||
if(!rawData[0].data.user.stream)
|
if (!rawData[0].data.user.stream)
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
|
|
||||||
return Promise.resolve(true)
|
return Promise.resolve(true)
|
||||||
|
@ -228,7 +226,7 @@ export class TwitchAPI {
|
||||||
*/
|
*/
|
||||||
public getStream = async (streamerName: string) => {
|
public getStream = async (streamerName: string) => {
|
||||||
const isLive = await this.isLive(streamerName)
|
const isLive = await this.isLive(streamerName)
|
||||||
if(!isLive) return Promise.reject(new Error(`Streamer ${streamerName} is not live`))
|
if (!isLive) return Promise.reject(new Error(`Streamer ${streamerName} is not live`))
|
||||||
|
|
||||||
// Get token
|
// Get token
|
||||||
const payload = {
|
const payload = {
|
||||||
|
@ -294,7 +292,7 @@ export class TwitchAPI {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if(cursor)
|
if (cursor)
|
||||||
payload[0].variables.cursor = cursor
|
payload[0].variables.cursor = cursor
|
||||||
|
|
||||||
const res = await fetch(this.twitchUrl, {
|
const res = await fetch(this.twitchUrl, {
|
||||||
|
@ -366,7 +364,7 @@ export class TwitchAPI {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if(cursor)
|
if (cursor)
|
||||||
payload[0].variables.cursor = cursor
|
payload[0].variables.cursor = cursor
|
||||||
|
|
||||||
const res = await fetch(this.twitchUrl, {
|
const res = await fetch(this.twitchUrl, {
|
||||||
|
@ -376,15 +374,15 @@ export class TwitchAPI {
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
if(!data[0].data.game) return null;
|
if (!data[0].data.game) return null;
|
||||||
|
|
||||||
|
|
||||||
let streams = []
|
let streams = []
|
||||||
if(data[0].data.game.streams)
|
if (data[0].data.game.streams)
|
||||||
streams = data[0].data.game.streams.edges
|
streams = data[0].data.game.streams.edges
|
||||||
let formatedStreams: CategoryMinifiedStream[] = []
|
let formatedStreams: CategoryMinifiedStream[] = []
|
||||||
|
|
||||||
for(let stream of streams) {
|
for (let stream of streams) {
|
||||||
let tags = []
|
let tags = []
|
||||||
for (let tag of stream.node.freeformTags) {
|
for (let tag of stream.node.freeformTags) {
|
||||||
tags.push(tag.name)
|
tags.push(tag.name)
|
||||||
|
@ -406,7 +404,7 @@ export class TwitchAPI {
|
||||||
|
|
||||||
const rawGameData = data[1].data.game;
|
const rawGameData = data[1].data.game;
|
||||||
let tags: Tag[] = []
|
let tags: Tag[] = []
|
||||||
for(let tag of rawGameData.tags) {
|
for (let tag of rawGameData.tags) {
|
||||||
tags.push(tag.tagName)
|
tags.push(tag.tagName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +443,7 @@ export class TwitchAPI {
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
if(!data[0].data.badges) return null;
|
if (!data[0].data.badges) return null;
|
||||||
let formatedBadges: Badge[] = []
|
let formatedBadges: Badge[] = []
|
||||||
|
|
||||||
for (let badge of data[0].data.badges) {
|
for (let badge of data[0].data.badges) {
|
||||||
|
@ -488,7 +486,7 @@ export class TwitchAPI {
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
if(!data.data.user.broadcastBadges) return null;
|
if (!data.data.user.broadcastBadges) return null;
|
||||||
let formatedBadges: Badge[] = []
|
let formatedBadges: Badge[] = []
|
||||||
|
|
||||||
for (let badge of data.data.user.broadcastBadges) {
|
for (let badge of data.data.user.broadcastBadges) {
|
||||||
|
@ -509,4 +507,81 @@ export class TwitchAPI {
|
||||||
|
|
||||||
return formatedBadges
|
return formatedBadges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSearchResult = async (query: string) => {
|
||||||
|
const payload = {
|
||||||
|
"operationName": "SearchResultsPage_SearchResults",
|
||||||
|
"variables": {
|
||||||
|
"query": query,
|
||||||
|
"options": null,
|
||||||
|
"requestID": "75948144-d051-4203-8511-57f3ee9b809a"
|
||||||
|
},
|
||||||
|
"extensions": {
|
||||||
|
"persistedQuery": {
|
||||||
|
"version": 1,
|
||||||
|
"sha256Hash": "6ea6e6f66006485e41dbe3ebd69d5674c5b22896ce7b595d7fce6411a3790138"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(this.twitchUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
headers: this.headers
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
const resultsData = data.data.searchFor
|
||||||
|
|
||||||
|
const formattedStreamers: StreamerData[] = resultsData.channels.edges.map((data: any) => {
|
||||||
|
return {
|
||||||
|
username: data.item.login,
|
||||||
|
followers: data.item.followers.totalCount,
|
||||||
|
isLive: !(data.item.stream === null),
|
||||||
|
about: data.item.description,
|
||||||
|
pfp: `${process.env.URL}/proxy/img/${base64(data.item.profileImageURL)}`,
|
||||||
|
isPartner: null,
|
||||||
|
colorHex: '#fff',
|
||||||
|
id: Number(data.item.channel.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const foundCategories: Category[] = []
|
||||||
|
for (let category of resultsData.games.edges) {
|
||||||
|
let tags = category.item.tags.map((data: { tagName: string }) => {
|
||||||
|
return data.tagName
|
||||||
|
})
|
||||||
|
|
||||||
|
foundCategories.push({
|
||||||
|
name: category.item.name,
|
||||||
|
displayName: category.item.displayName,
|
||||||
|
viewers: category.item.viewersCount,
|
||||||
|
tags,
|
||||||
|
image: category.item.boxArtURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundRelatedLiveChannels: StreamData[] = await Promise.all(resultsData.relatedLiveChannels.edges.map(async (data: any) => {
|
||||||
|
return await this.getStreamerInfo(data.item.stream.broadcaster.login)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const foundChannelsWithTag: StreamData[] = resultsData.channelsWithTag.edges.map((data: any) => {
|
||||||
|
return {
|
||||||
|
username: data.item.login,
|
||||||
|
followers: data.item.followers.totalCount,
|
||||||
|
isLive: !(data.item.stream === null),
|
||||||
|
about: data.item.description,
|
||||||
|
pfp: `${process.env.URL}/proxy/img/${base64(data.item.profileImageURL)}`,
|
||||||
|
isPartner: null,
|
||||||
|
colorHex: '#fff',
|
||||||
|
id: Number(data.item.channel.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
channels: formattedStreamers,
|
||||||
|
categories: foundCategories,
|
||||||
|
relatedChannels: foundRelatedLiveChannels,
|
||||||
|
channelsWithTag: foundChannelsWithTag
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue