mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 09:33:09 +01:00
Phase 5.5 abgeschlossen
- Durchsuchen Menu - Top Live Streams - Top Kategorie - Anzeige Zuschaueranzahl getTopGames(), technisch bedingt mit einer gewissen unschärfe zu den tatsächliche Zuschauer der Kategorie
This commit is contained in:
@@ -16,6 +16,8 @@ export const IPC = {
|
||||
|
||||
twitchGetFollowed: 'twitch:get-followed',
|
||||
twitchGetVideos: 'twitch:get-videos',
|
||||
twitchGetTopGames: 'twitch:get-top-games',
|
||||
twitchGetTopStreams: 'twitch:get-top-streams',
|
||||
historyGetProgress: 'history:get-progress',
|
||||
|
||||
playbackStartLive: 'playback:start-live',
|
||||
@@ -45,6 +47,14 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
ipcMain.handle(IPC.twitchGetFollowed, () => helix.getFollowedWithLiveStatus())
|
||||
ipcMain.handle(
|
||||
IPC.twitchGetTopGames,
|
||||
(_e, cursor?: string) => helix.getTopGames(40, cursor)
|
||||
)
|
||||
ipcMain.handle(
|
||||
IPC.twitchGetTopStreams,
|
||||
(_e, gameId?: string) => helix.getTopStreams(gameId ? { gameId, limit: 40 } : { limit: 100 })
|
||||
)
|
||||
ipcMain.handle(
|
||||
IPC.twitchGetVideos,
|
||||
(_e, broadcasterId: string) => helix.getVideos(broadcasterId)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type {
|
||||
FollowedChannelInfo,
|
||||
GameInfo,
|
||||
HelixFollowedChannel,
|
||||
HelixGame,
|
||||
HelixPaginatedResponse,
|
||||
HelixStream,
|
||||
HelixUser,
|
||||
@@ -127,6 +129,76 @@ export class HelixClient {
|
||||
}))
|
||||
}
|
||||
|
||||
async getTopGames(
|
||||
limit = 40,
|
||||
cursor?: string
|
||||
): Promise<{ games: GameInfo[]; cursor?: string }> {
|
||||
const params: Record<string, string> = { first: String(limit) }
|
||||
if (cursor) params.after = cursor
|
||||
|
||||
const data = await this.get<HelixPaginatedResponse<HelixGame>>('/games/top', params)
|
||||
const games = data.data.map((g) => ({
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
boxArtUrl: g.box_art_url.replace('{width}', '285').replace('{height}', '380')
|
||||
}))
|
||||
|
||||
// Zuschauerzahlen: pro Kategorie die Top-100-Streams abrufen (parallel)
|
||||
const headers = await this.authHeaders()
|
||||
const viewerCounts = await Promise.all(
|
||||
games.map(async (g) => {
|
||||
try {
|
||||
const url = new URL(`${BASE}/streams`)
|
||||
url.searchParams.set('game_id', g.id)
|
||||
url.searchParams.set('first', '100')
|
||||
const res = await fetch(url.toString(), { headers })
|
||||
if (!res.ok) return undefined
|
||||
const d = (await res.json()) as { data: HelixStream[] }
|
||||
return d.data.reduce((sum, s) => sum + s.viewer_count, 0)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
games: games.map((g, i) => ({ ...g, viewerCount: viewerCounts[i] })),
|
||||
cursor: data.pagination?.cursor
|
||||
}
|
||||
}
|
||||
|
||||
async getTopStreams(opts?: { gameId?: string; limit?: number }): Promise<FollowedChannelInfo[]> {
|
||||
const params: Record<string, string> = { first: String(opts?.limit ?? 20) }
|
||||
if (opts?.gameId) params.game_id = opts.gameId
|
||||
|
||||
const url = new URL(`${BASE}/streams`)
|
||||
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v)
|
||||
const res = await fetch(url.toString(), { headers: await this.authHeaders() })
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => '')
|
||||
throw new Error(`Helix /streams → ${res.status}: ${body}`)
|
||||
}
|
||||
const data = (await res.json()) as { data: HelixStream[] }
|
||||
const streams = data.data
|
||||
|
||||
const userIds = streams.map((s) => s.user_id)
|
||||
const avatarMap = await this.getProfileImages(userIds)
|
||||
|
||||
return streams.map((s) => ({
|
||||
broadcasterId: s.user_id,
|
||||
broadcasterLogin: s.user_login,
|
||||
broadcasterName: s.user_name,
|
||||
profileImageUrl: avatarMap.get(s.user_id) ?? '',
|
||||
isLive: true,
|
||||
streamTitle: s.title,
|
||||
gameId: s.game_id,
|
||||
gameName: s.game_name,
|
||||
viewerCount: s.viewer_count,
|
||||
thumbnailUrl: s.thumbnail_url.replace('{width}', '440').replace('{height}', '248'),
|
||||
startedAt: s.started_at
|
||||
}))
|
||||
}
|
||||
|
||||
async getFollowedWithLiveStatus(): Promise<FollowedChannelInfo[]> {
|
||||
const userId = await this.getOwnUserId()
|
||||
const followed = await this.getAllFollowed(userId)
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface HelixStream {
|
||||
user_id: string
|
||||
user_login: string
|
||||
user_name: string
|
||||
game_id: string
|
||||
game_name: string
|
||||
title: string
|
||||
viewer_count: number
|
||||
@@ -39,6 +40,22 @@ export interface HelixVideo {
|
||||
type: 'archive' | 'highlight' | 'upload'
|
||||
}
|
||||
|
||||
export interface HelixGame {
|
||||
id: string
|
||||
name: string
|
||||
box_art_url: string
|
||||
}
|
||||
|
||||
/** Simplified game info für den Renderer. */
|
||||
export interface GameInfo {
|
||||
id: string
|
||||
name: string
|
||||
/** Boxart-URL bereits auf 285×380 aufgelöst */
|
||||
boxArtUrl: string
|
||||
/** Summe der Zuschauer aus den Top-100-Streams der Kategorie */
|
||||
viewerCount?: number
|
||||
}
|
||||
|
||||
/** Simplified VOD info für den Renderer. */
|
||||
export interface VodInfo {
|
||||
id: string
|
||||
@@ -58,6 +75,7 @@ export interface FollowedChannelInfo {
|
||||
profileImageUrl: string
|
||||
isLive: boolean
|
||||
streamTitle?: string
|
||||
gameId?: string
|
||||
gameName?: string
|
||||
viewerCount?: number
|
||||
/** 440×248 thumbnail URL (only set when live) */
|
||||
|
||||
Reference in New Issue
Block a user