0
Fork 0
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:
dragongoose 2023-04-16 12:38:34 -04:00
parent 65a6c62b38
commit 7bf75fa99e
No known key found for this signature in database
GPG key ID: 50DB99B921579009
5 changed files with 559 additions and 467 deletions

View file

@ -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

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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})
} }

View file

@ -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
}
}
} }