twitch-notifier.go (2866B)
1 //usr/bin/go run $0 $@; exit $? 2 package main 3 4 import ( 5 "encoding/json" 6 "flag" 7 "fmt" 8 "io" 9 "log" 10 "net/http" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 const clientid = "b2cnkgs6ricpfzuimk03xt1vlsia4g" 17 18 var ( 19 streams = []string{ 20 "476845395", // "bashbunni", 21 // "447179030", // "rwxrob", 22 "474725923", // "togglebit", 23 "167160215", // "theprimeagen", 24 "443849438", // "ashkankiani", 25 "71942329", // "acegikmo", 26 "114257969", // "teej_dv", 27 "233334675", // "battlestategames", 28 "73570785", // "marlliciuss", 29 "39661750", // "tensterakdary", 30 "106013742", // "pestily", 31 "423054112", // "jakobychill", 32 "63880003", // "anton", 33 "81463115", // "kotton", 34 "150900538", // "pehapkari", 35 } 36 37 dunstify = flag.Bool("dunstify", false, "Use dunstify output") 38 fresh = flag.Int("fresh", 0, "Get streams started before N minutes") 39 ) 40 41 type Stream struct { 42 Id json.Number `json:"_id"` 43 StreamType string `json:"stream_type"` 44 Viewers uint `json:"viewers"` 45 CreatedAt time.Time `json:"created_at"` 46 Channel Channel 47 } 48 49 type Channel struct { 50 Status string 51 Name string 52 Game string 53 } 54 55 func main() { 56 flag.Parse() 57 58 req, err := http.NewRequest("GET", "https://api.twitch.tv/kraken/streams?channel="+strings.Join(streams, ","), nil) 59 if err != nil { 60 log.Fatal(err) 61 } 62 req.Header.Add("Accept", "application/vnd.twitchtv.v5+json") 63 req.Header.Add("Client-ID", clientid) 64 65 rsp, err := http.DefaultClient.Do(req) 66 if err != nil { 67 log.Fatal(err) 68 } 69 defer rsp.Body.Close() 70 71 bodyraw, err := io.ReadAll(rsp.Body) 72 if err != nil { 73 log.Fatal(err) 74 } 75 76 var object struct{ Streams []Stream } 77 err = json.Unmarshal(bodyraw, &object) 78 if err != nil { 79 log.Fatal(err) 80 } 81 82 now := time.Now() 83 forbiden := make(map[json.Number]bool) 84 85 if *fresh > 0 { 86 duration, _ := time.ParseDuration("-" + strconv.Itoa(*fresh) + "m") 87 target := now.Add(duration) 88 for _, stream := range object.Streams { 89 if stream.CreatedAt.Before(target) { 90 forbiden[stream.Id] = true 91 } 92 } 93 } 94 95 if *dunstify { 96 var sb strings.Builder 97 for _, stream := range object.Streams { 98 if forbiden[stream.Id] { 99 continue 100 } 101 uptime := now.Sub(stream.CreatedAt) 102 sb.WriteString(fmt.Sprintf("• <b>%s</b> (%d) - %s\n", stream.Channel.Name, stream.Viewers, fmtDuration(uptime))) 103 sb.WriteString(fmt.Sprintf(" %s\n", stream.Channel.Status)) 104 sb.WriteString(fmt.Sprintf(" <i>%s</i>\n", stream.Channel.Game)) 105 } 106 fmt.Print(sb.String()) 107 } else { 108 for _, stream := range object.Streams { 109 if forbiden[stream.Id] { 110 continue 111 } 112 fmt.Printf("%s\n", stream.Channel.Name) 113 } 114 } 115 } 116 117 func fmtDuration(d time.Duration) string { 118 d = d.Round(time.Minute) 119 h := d / time.Hour 120 d -= h * time.Hour 121 m := d / time.Minute 122 return fmt.Sprintf("%02d:%02d", h, m) 123 } 124 125 func remove(s []Stream, i int) []Stream { 126 s[i] = s[len(s)-1] 127 return s[:len(s)-1] 128 }