feat: add twitch live detection to streamer list
This commit is contained in:
@@ -33,6 +33,17 @@ type Channel struct {
|
||||
Login string
|
||||
}
|
||||
|
||||
// StreamInfo describes whether a channel is live.
|
||||
type StreamInfo struct {
|
||||
Online bool
|
||||
}
|
||||
|
||||
// PlaybackToken carries the Twitch playback token/signature pair for live HLS access.
|
||||
type PlaybackToken struct {
|
||||
Signature string
|
||||
Value string
|
||||
}
|
||||
|
||||
// StreamerStatus describes one row's resolution state in the UI.
|
||||
type StreamerStatus string
|
||||
|
||||
@@ -47,6 +58,7 @@ type StreamerEntry struct {
|
||||
ConfigLogin string
|
||||
Login string
|
||||
ChannelID string
|
||||
Live bool
|
||||
Status StreamerStatus
|
||||
Error string
|
||||
}
|
||||
@@ -106,3 +118,44 @@ func (s *Service) ResolveStreamer(ctx context.Context, login string) (*Channel,
|
||||
}
|
||||
return &Channel{ID: data.User.ID, Login: resolvedLogin}, nil
|
||||
}
|
||||
|
||||
// StreamInfo resolves whether the given channel ID is currently live.
|
||||
func (s *Service) StreamInfo(ctx context.Context, channelID string) (*StreamInfo, error) {
|
||||
var data struct {
|
||||
User *struct {
|
||||
Stream *struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"stream"`
|
||||
} `json:"user"`
|
||||
}
|
||||
if err := s.GQL.Do(ctx, gql.WithIsStreamLiveQuery(channelID), &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.User == nil {
|
||||
return nil, fmt.Errorf("stream info missing user for channel %s", channelID)
|
||||
}
|
||||
if data.User.Stream == nil {
|
||||
return &StreamInfo{Online: false}, nil
|
||||
}
|
||||
return &StreamInfo{Online: true}, nil
|
||||
}
|
||||
|
||||
// PlaybackAccessToken fetches the Twitch playback token needed to access a live stream.
|
||||
func (s *Service) PlaybackAccessToken(ctx context.Context, login string) (*PlaybackToken, error) {
|
||||
var data struct {
|
||||
StreamPlaybackAccessToken *struct {
|
||||
Signature string `json:"signature"`
|
||||
Value string `json:"value"`
|
||||
} `json:"streamPlaybackAccessToken"`
|
||||
}
|
||||
if err := s.GQL.Do(ctx, gql.PlaybackAccessToken(login), &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.StreamPlaybackAccessToken == nil || data.StreamPlaybackAccessToken.Signature == "" || data.StreamPlaybackAccessToken.Value == "" {
|
||||
return nil, fmt.Errorf("playback access token missing signature or value for %s", login)
|
||||
}
|
||||
return &PlaybackToken{
|
||||
Signature: data.StreamPlaybackAccessToken.Signature,
|
||||
Value: data.StreamPlaybackAccessToken.Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -63,3 +63,64 @@ func TestResolveStreamerNotFound(t *testing.T) {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamInfoOffline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeGQL{data: map[string]string{
|
||||
"WithIsStreamLiveQuery": `{"user":{"stream":null}}`,
|
||||
}}
|
||||
service := &Service{GQL: client}
|
||||
info, err := service.StreamInfo(context.Background(), "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.Online {
|
||||
t.Fatalf("info = %#v, want offline", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamInfoOnline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeGQL{data: map[string]string{
|
||||
"WithIsStreamLiveQuery": `{"user":{"stream":{"id":"broadcast"}}}`,
|
||||
}}
|
||||
service := &Service{GQL: client}
|
||||
info, err := service.StreamInfo(context.Background(), "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !info.Online {
|
||||
t.Fatalf("info = %#v, want online", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaybackAccessToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeGQL{data: map[string]string{
|
||||
"PlaybackAccessToken": `{"streamPlaybackAccessToken":{"signature":"sig","value":"token"}}`,
|
||||
}}
|
||||
service := &Service{GQL: client}
|
||||
token, err := service.PlaybackAccessToken(context.Background(), "streamer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if token.Signature != "sig" || token.Value != "token" {
|
||||
t.Fatalf("token = %#v", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaybackAccessTokenMissingFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := &fakeGQL{data: map[string]string{
|
||||
"PlaybackAccessToken": `{"streamPlaybackAccessToken":{"signature":"sig"}}`,
|
||||
}}
|
||||
service := &Service{GQL: client}
|
||||
_, err := service.PlaybackAccessToken(context.Background(), "streamer")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user