add support to list VODS by loginname using Twitch Helix API - twitch-go - twitch.tv web application in Go
(HTM) git clone git://git.codemadness.org/twitch-go
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit 3cbd531da39473bbbb3afad4ec609ab635a48681
(DIR) parent 045f529d96751a6e97eddf8ff83594e5a5c438b7
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sat, 6 Apr 2019 19:52:40 +0200
add support to list VODS by loginname using Twitch Helix API
and request an Atom feed.
+ improvements:
- linewrap long titles for VODS.
- get response data even on error as this contains useful debug data.
Diffstat:
M data/static/twitch.css | 3 +++
A data/templates/pages/vods.html | 40 +++++++++++++++++++++++++++++++
A data/templates/pages/vods.xml | 12 ++++++++++++
A data/templates/themes/default/atom… | 1 +
M data/templates/themes/default/page… | 1 +
M main.go | 51 +++++++++++++++++++++++++++++++
M src/twitch/twitch.go | 89 +++++++++++++++++++++++++++++--
7 files changed, 194 insertions(+), 3 deletions(-)
---
(DIR) diff --git a/data/static/twitch.css b/data/static/twitch.css
@@ -50,6 +50,9 @@ table.table tr th {
table.table tr td {
white-space: nowrap;
}
+table.table tr td.wrap {
+ white-space: normal;
+}
table.table tr th.viewers,
table.table tr th.channels {
text-align: right;
(DIR) diff --git a/data/templates/pages/vods.html b/data/templates/pages/vods.html
@@ -0,0 +1,40 @@
+{{define "title"}}VODS{{if .Search}} for {{.User.Display_name}}{{end}}{{end}}
+{{define "class"}}vods{{end}}
+
+{{define "content"}}
+
+{{if .Search}}
+
+<a href="?n={{.Search}}&format=atom">Atom feed</a>
+
+<table class="table" border="0">
+<thead>
+ <tr>
+ <th class="created_at" align="left">Created</th>
+ <th class="title" align="left">Title</th>
+ <th class="duration" align="right">Duration</th>
+ <th class="viewcount" align="right">Viewcount</th>
+ </tr>
+</thead>
+<tbody>
+{{range .Videos}}
+ <tr>
+ <td>{{.Created_at}}</td>
+ <td class="wrap"><a href="{{.Url}}">{{.Title}}</a></td>
+ <td align="right"><a href="{{.Url}}">{{.Duration}}</a></td>
+ <td align="right"><a href="{{.Url}}">{{.View_count}}</a></td>
+ </tr>
+{{end}}
+</tbody>
+</table>
+
+{{else}}
+
+<form method="get" action="">
+ <input type="search" name="n" value="" placeholder="Login name..." />
+ <input type="submit" name="list" value="List vods" />
+</form>
+
+{{end}}
+
+{{end}}
(DIR) diff --git a/data/templates/pages/vods.xml b/data/templates/pages/vods.xml
@@ -0,0 +1,12 @@
+{{define "content"}}<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+{{range .Videos}}
+ <entry>
+ <title type="html">{{.Title}}</title>
+ <link rel="alternate" type="text/html" href="{{.Url}}" />
+ <id>{{.Url}}</id>
+ <updated>{{.Created_at}}</updated>
+ <published>{{.Created_at}}</published>
+ </entry>
+{{end}}
+</feed>
+{{end}}
(DIR) diff --git a/data/templates/themes/default/atom.xml b/data/templates/themes/default/atom.xml
@@ -0,0 +1 @@
+{{template "content" .}}
(DIR) diff --git a/data/templates/themes/default/page.html b/data/templates/themes/default/page.html
@@ -11,6 +11,7 @@
<form method="get" action="/playlist">
<a href="/featured">Featured</a> |
<a href="/games">Games</a> |
+ <a href="/vods">VODS</a> |
<a href="https://git.codemadness.org/twitch-go/">Source-code</a> |
<a href="/twitch.sh">SH version</a> |
<a href="/links">Links</a>
(DIR) diff --git a/main.go b/main.go
@@ -118,6 +118,10 @@ func (t *Templates) LoadTemplates(path string) (map[string]*template.Template, e
return m, err
}
+func NotFound(w http.ResponseWriter, err string) {
+ http.Error(w, "404 "+err, http.StatusNotFound)
+}
+
func BadRequest(w http.ResponseWriter, err string) {
http.Error(w, "400 "+err, http.StatusBadRequest)
}
@@ -209,6 +213,52 @@ func LinksHandler(w http.ResponseWriter, r *http.Request) error {
return templates.Render(w, "links.html", "page.html", struct{}{})
}
+func VODSHandler(w http.ResponseWriter, r *http.Request) error {
+ var err error
+ var users *twitch.Users
+ var videos *twitch.Videos
+
+ format := r.FormValue("format")
+ name := r.FormValue("n")
+
+ v := struct {
+ Search string
+ User twitch.User
+ Videos []twitch.Video
+ }{
+ Search: name,
+ }
+
+ if len(name) > 0 {
+ users, err = twitch.GetUseridByName(name)
+ if err != nil {
+ NotFound(w, "(login) name not found or an error occurred")
+ return nil
+ }
+ if len(users.Items) == 0 {
+ NotFound(w, "(login) name not found")
+ return nil
+ }
+ for _, u := range users.Items {
+ videos, err = twitch.GetVideosByUser(u.Id)
+ if err != nil {
+ return err
+ }
+ break // return for first user.
+ }
+ v.User = users.Items[0]
+ v.Videos = videos.Items
+ }
+
+ if format == "atom" {
+ w.Header().Set("Content-Type", "text/xml; charset=utf-8")
+ fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?>`+"\n")
+ return templates.Render(w, "vods.xml", "atom.xml", v)
+ } else {
+ return templates.Render(w, "vods.html", "page.html", v)
+ }
+}
+
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s\n", os.Args[0])
flag.PrintDefaults()
@@ -268,6 +318,7 @@ func main() {
http.HandleFunc("/games", MakeHandler(GamesHandler))
http.HandleFunc("/game", MakeHandler(GameHandler))
http.HandleFunc("/links", MakeHandler(LinksHandler))
+ http.HandleFunc("/vods", MakeHandler(VODSHandler))
fileserv := http.FileServer(http.Dir(config_staticcontentdir)).ServeHTTP
http.HandleFunc("/", MakeHandler(func(w http.ResponseWriter, r *http.Request) error {
(DIR) diff --git a/src/twitch/twitch.go b/src/twitch/twitch.go
@@ -102,6 +102,23 @@ type Games struct {
}
}
+type User struct {
+ Broadcaster_type string `json:"broadcaster_type"`
+ Description string `json:"description"`
+ Display_name string `json:"display_name"`
+ Email string `json:"email"`
+ Id string `json:"id"`
+ Login string `json:"login"`
+ Offline_image_url string `json:"offline_image_url"`
+ Profile_image_url string `json:"profile_image_url"`
+ Type string `json:"type"`
+ View_count int64 `json:"view_count"`
+}
+
+type Users struct {
+ Items []User `json:"data"`
+}
+
func ReadAllUrl(url string) ([]byte, error) {
client := http.Client{
Timeout: time.Duration(30) * time.Second,
@@ -118,13 +135,13 @@ func ReadAllUrl(url string) ([]byte, error) {
if err != nil {
return nil, err
}
- if resp.StatusCode != 200 {
- return nil, errors.New(resp.Status)
- }
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
+ if resp.StatusCode != 200 {
+ return nil, errors.New(resp.Status)
+ }
return body, nil
}
@@ -162,6 +179,7 @@ func GetGames() (*Games, error) {
if err != nil {
return nil, err
}
+
var v Games
err = json.Unmarshal(body, &v)
if err != nil {
@@ -183,3 +201,68 @@ func GetGame(game string) (*Game, error) {
}
return &v, nil
}
+
+type Video struct {
+ Created_at string `json:"created_at"`
+ Description string `json:"description"`
+ Duration string `json:"duration"`
+ Id string `json:"id"`
+ Language string `json:"language"`
+ Published_at string `json:"published_at"`
+ Thumbnail_url string `json:"thumbnail_url"`
+ Title string `json:"title"`
+ Type string `json:"type"`
+ Url string `json:"url"`
+ User_id string `json:"user_id"`
+ User_name string `json:"user_name"`
+ View_count int64 `json:"view_count"`
+ Viewable string `json:"viewable"`
+}
+
+type Videos struct {
+ Items []Video `json:"data"`
+}
+
+func GetVideosByGame(gameid string) (*Videos, error) {
+ s := fmt.Sprintf("https://api.twitch.tv/helix/videos?first=100&game_id=%s", url.QueryEscape(gameid))
+ body, err := ReadAllUrl(s)
+ if err != nil {
+ return nil, err
+ }
+ var v Videos
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
+
+func GetVideosByUser(userid string) (*Videos, error) {
+ s := fmt.Sprintf("https://api.twitch.tv/helix/videos?first=100&user_id=%s",
+ url.QueryEscape(userid))
+ body, err := ReadAllUrl(s)
+ if err != nil {
+ return nil, err
+ }
+ var v Videos
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
+
+func GetUseridByName(name string) (*Users, error) {
+ s := fmt.Sprintf("https://api.twitch.tv/helix/users?login=%s",
+ url.QueryEscape(name))
+ body, err := ReadAllUrl(s)
+ if err != nil {
+ return nil, err
+ }
+ var u Users
+ err = json.Unmarshal(body, &u)
+ if err != nil {
+ return nil, err
+ }
+ return &u, nil
+}