Skip to content

Commit bce9b8a

Browse files
committed
Add a miner that only supports Twitch moments for now
GetChannel propagates errors Fix rewards being removed crashing the app
1 parent 418bced commit bce9b8a

File tree

11 files changed

+209
-17
lines changed

11 files changed

+209
-17
lines changed

cmd/live.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package cmd
22

33
import (
4+
"fmt"
45
"github.com/spf13/cobra"
6+
"log"
57
"ttv-cli/internal/pkg/config"
68
"ttv-cli/internal/pkg/twitch/gql/operation/channelfollows"
79
"ttv-cli/internal/pkg/twitch/gql/query/users"
@@ -21,14 +23,20 @@ var liveCmd = &cobra.Command{
2123
}
2224

2325
// Get all streamers from Twitch API
24-
streamers := users.GetUsers(s)
26+
streamers, err := users.GetUsers(s)
27+
if err != nil {
28+
log.Fatalf("Could not get channel information - %s\n", err)
29+
}
2530

2631
// Filter between live and offline streamers
2732
online := make([]users.User, 0)
2833
offline := make([]users.User, 0)
2934

30-
for _, user := range streamers {
31-
if user.Stream.CreatedAt != "" {
35+
for i, user := range streamers {
36+
if len(user.Id) == 0 {
37+
fmt.Printf("Could not find channel information for '%s'\n", s[i])
38+
continue
39+
} else if user.Stream.CreatedAt != "" {
3240
online = append(online, user)
3341
} else {
3442
offline = append(offline, user)

cmd/miner.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/Adeithe/go-twitch"
7+
"github.com/spf13/cobra"
8+
"log"
9+
"ttv-cli/internal/app/miner/moments"
10+
"ttv-cli/internal/pkg/config"
11+
"ttv-cli/internal/pkg/twitch/gql/query/users"
12+
)
13+
14+
var minerCmd = &cobra.Command{
15+
Use: "miner",
16+
Short: "Mines channel points, moments, and drops",
17+
Args: cobra.MinimumNArgs(1),
18+
Run: func(cmd *cobra.Command, args []string) {
19+
run(context.Background(), args)
20+
},
21+
}
22+
23+
func run(ctx context.Context, names []string) {
24+
cfg := config.CreateOrRead()
25+
26+
p := twitch.PubSub()
27+
28+
p.OnShardConnect(func(shard int) {
29+
fmt.Printf("Shard #%d connected!\n", shard)
30+
})
31+
32+
streamerByIds := make(map[string]string, 0)
33+
us, err := users.GetUsers(names)
34+
if err != nil {
35+
log.Fatalf("Could not fetch channel information for users - %s\n", err)
36+
}
37+
38+
for i, u := range us {
39+
if len(u.Id) == 0 {
40+
log.Printf("Could not find user with name '%s'\n", names[i])
41+
continue
42+
}
43+
streamerByIds[u.Id] = u.DisplayName
44+
}
45+
46+
err = moments.MineMoments(p, streamerByIds, cfg.AuthToken)
47+
if err != nil {
48+
log.Fatalf("Could not subscribe to Moments - %s\n", err)
49+
}
50+
51+
defer p.Close()
52+
53+
fmt.Printf("Started listening to %d topics on %d shards\n", p.GetNumTopics(), p.GetNumShards())
54+
55+
<-ctx.Done()
56+
}
57+
58+
func init() {
59+
rootCmd.AddCommand(minerCmd)
60+
}

internal/app/miner/moments/moments.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package moments
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"github.com/Adeithe/go-twitch/pubsub"
8+
"log"
9+
"strings"
10+
"time"
11+
moments "ttv-cli/internal/pkg/twitch/gql/operation/communitymomentcalloutclaim"
12+
pubsub2 "ttv-cli/internal/pkg/twitch/pubsub"
13+
)
14+
15+
const topic = "community-moments-channel-v1"
16+
17+
func MineMoments(c *pubsub.Client, streamerByIds map[string]string, authToken string) error {
18+
for id, s := range streamerByIds {
19+
log.Printf("Listening to topic: '%s' for streamer: '%s' (%s)\n", topic, s, id)
20+
if err := c.ListenWithAuth(authToken, topic, id); err != nil {
21+
msg := fmt.Sprintf("Failed to listen to topic: '%s' for streamer: '%s' (%s) - %v", topic, s, id, err)
22+
return errors.New(msg)
23+
}
24+
time.Sleep(time.Second)
25+
}
26+
27+
handleUpdate := func(shard int, topic string, data []byte) {
28+
fmt.Printf("Shard #%d > %s %s\n", shard, topic, strings.TrimSpace(string(data)))
29+
30+
var resp pubsub2.CommunityMomentsChannelResponse
31+
if err := json.Unmarshal(data, &resp); err != nil {
32+
log.Println(err)
33+
}
34+
35+
if len(resp.MomentId) > 0 {
36+
log.Printf("Attempting to redeem moment ID: '%s'\n", resp.MomentId)
37+
err := moments.ClaimCommunityMoment(resp.MomentId, authToken)
38+
if err != nil {
39+
log.Println(err)
40+
}
41+
}
42+
}
43+
44+
c.OnShardMessage(handleUpdate)
45+
46+
return nil
47+
}

internal/app/rewards/tui/model.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"github.com/charmbracelet/bubbles/list"
77
tea "github.com/charmbracelet/bubbletea"
8+
"log"
89
"sort"
910
"ttv-cli/internal/pkg/twitch/gql/query/channel"
1011
"ttv-cli/internal/pkg/twitch/pubsub"
@@ -19,14 +20,22 @@ type Model struct {
1920
}
2021

2122
func NewModel(streamer string, authToken string) Model {
22-
//listModel := list.New(make([]list.Item, 0), list.NewDefaultDelegate(), 0, 0)
23+
c, err := channel.GetChannel(streamer)
24+
if err != nil {
25+
log.Fatalf("Failed to get channel information for '%s' - %s", streamer, err)
26+
}
27+
if len(c.Id) == 0 {
28+
log.Fatalf("Could not find channel for '%s'\n", streamer)
29+
}
30+
2331
m := Model{
24-
twitchChannel: channel.GetChannel(streamer),
32+
twitchChannel: c,
2533
authToken: authToken,
2634
list: list.New(make([]list.Item, 0), list.NewDefaultDelegate(), 0, 0),
2735
itemsById: make(map[string]*item),
2836
rewardsUpdateChannel: make(chan pubsub.CommunityPointsChannelResponse),
2937
}
38+
3039
m.list.Title = fmt.Sprintf("%s's Rewards", m.twitchChannel.DisplayName)
3140
return m
3241
}

internal/app/rewards/tui/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
2727
// Reward has been paused or disabled, remove it from the list
2828
if msg.IsPaused || !msg.IsEnabled {
2929
for index, listItem := range m.list.Items() {
30-
if listItem.(item).RewardId == msg.Id {
30+
if listItem.(*item).RewardId == msg.Id {
3131
m.list.RemoveItem(index)
3232
}
3333
}

internal/pkg/twitch/gql/gql.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gql
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
67
"io"
78
"io/ioutil"
89
"log"
@@ -50,6 +51,10 @@ func post(request any, authToken string) ([]byte, error) {
5051
return nil, err
5152
}
5253

54+
if httpResp.StatusCode != 200 {
55+
return nil, errors.New(string(body)) // TODO: Narrow this to just the error
56+
}
57+
5358
return body, nil
5459
}
5560

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package communitymomentcalloutclaim
2+
3+
import (
4+
"log"
5+
"ttv-cli/internal/pkg/twitch/gql"
6+
)
7+
8+
type input struct {
9+
MomentID string `json:"momentID"`
10+
}
11+
12+
type variables struct {
13+
Input input `json:"input"`
14+
}
15+
16+
type persistedQuery struct {
17+
Version int `json:"version"`
18+
Sha256Hash string `json:"sha256Hash"`
19+
}
20+
21+
type extensions struct {
22+
PersistedQuery persistedQuery `json:"persistedQuery"`
23+
}
24+
25+
type request struct {
26+
OperationName string `json:"operationName"`
27+
Variables variables `json:"variables"`
28+
Extensions extensions `json:"extensions"`
29+
}
30+
31+
func makeRequest(momentId string) request {
32+
return request{
33+
OperationName: "CommunityMomentCallout_Claim",
34+
Variables: variables{
35+
Input: input{
36+
MomentID: momentId,
37+
},
38+
},
39+
Extensions: extensions{
40+
PersistedQuery: persistedQuery{
41+
Version: 1,
42+
Sha256Hash: "e2d67415aead910f7f9ceb45a77b750a1e1d9622c936d832328a0689e054db62",
43+
},
44+
},
45+
}
46+
}
47+
48+
func ClaimCommunityMoment(momentId string, authToken string) error {
49+
req := makeRequest(momentId)
50+
resp, err := gql.PostWithAuth(req, authToken)
51+
if err != nil {
52+
return err
53+
}
54+
55+
log.Println(string(resp))
56+
return nil
57+
}

internal/pkg/twitch/gql/query/channel/channel.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package channel
22

33
import (
44
"encoding/json"
5-
"log"
65
"ttv-cli/internal/pkg/twitch/gql"
76
)
87

@@ -71,19 +70,20 @@ type CommunityPointsCustomRewardGlobalCooldownSetting struct {
7170
IsEnabled bool `json:"isEnabled"`
7271
}
7372

74-
func GetChannel(name string) Channel {
73+
// GetChannel Note that this function will not throw if a channel was not found for the provided name
74+
func GetChannel(name string) (Channel, error) {
7575
request := GetChannelRequest{Query: getChannelQuery}
7676
request.Variables.Name = name
7777

7878
body, err := gql.Post(request)
7979
if err != nil {
80-
log.Fatalln(err)
80+
return Channel{}, err
8181
}
8282

8383
var response GetChannelResponse
8484
if err := json.Unmarshal(body, &response); err != nil {
85-
log.Fatalln(err)
85+
return Channel{}, err
8686
}
8787

88-
return response.Data.Channel
88+
return response.Data.Channel, nil
8989
}

internal/pkg/twitch/gql/query/users/users.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package users
22

33
import (
44
"encoding/json"
5-
"log"
65
"ttv-cli/internal/pkg/twitch/gql"
76
)
87

98
const getUsersQuery = `query Users($logins: [String!]) {
109
users(logins: $logins) {
1110
displayName
11+
id
1212
login
1313
profileURL
1414
stream {
@@ -32,6 +32,7 @@ type request struct {
3232

3333
type User struct {
3434
DisplayName string `json:"displayName"`
35+
Id string `json:"id"`
3536
Login string `json:"login"`
3637
ProfileURL string `json:"profileURL"`
3738
Stream struct {
@@ -58,19 +59,18 @@ func makeRequest(logins []string) request {
5859
}
5960
}
6061

61-
func GetUsers(logins []string) []User {
62-
// Create POST request body
62+
func GetUsers(logins []string) ([]User, error) {
6363
request := makeRequest(logins)
6464

6565
gqlResp, err := gql.Post(request)
6666
if err != nil {
67-
log.Fatalln(err)
67+
return nil, err
6868
}
6969

7070
var response Response
7171
if err := json.Unmarshal(gqlResp, &response); err != nil {
72-
log.Fatalln(err)
72+
return nil, err
7373
}
7474

75-
return response.Data.Users
75+
return response.Data.Users, nil
7676
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package pubsub
2+
3+
type CommunityMomentsChannelResponse struct {
4+
Type string `json:"type"`
5+
MomentId string `json:"moment_id"`
6+
}

0 commit comments

Comments
 (0)