cleanup - 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 e11fa18a7c6d467bbb58e57eea61bf300f008e26
(DIR) parent 8d76f4f7ae7abefc7ae35e0644f9c86b299020ba
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sun, 10 May 2015 10:26:14 +0200
cleanup
- separate twitch into src module.
- remove regex route system, its overkill, use handlefunc.
- separate handlers into handlers.go
Diffstat:
R static/twitch.css -> data/static/t… | 0
R static/twitch.sh -> data/static/tw… | 0
A data/templates/pages/featured.html | 35 +++++++++++++++++++++++++++++++
A data/templates/pages/game.html | 34 +++++++++++++++++++++++++++++++
A data/templates/pages/games.html | 23 +++++++++++++++++++++++
R templates/pages/links.html -> data… | 0
A data/templates/pages/playlist.html | 8 ++++++++
R templates/themes/default/page.html… | 0
A handlers.go | 69 ++++++++++++++++++++++++++++++
M main.go | 414 ++++++-------------------------
A src/twitch/twitch.go | 167 +++++++++++++++++++++++++++++++
D templates/pages/featured.html | 35 -------------------------------
D templates/pages/game.html | 34 -------------------------------
D templates/pages/games.html | 23 -----------------------
D templates/pages/playlist.html | 7 -------
15 files changed, 406 insertions(+), 443 deletions(-)
---
(DIR) diff --git a/static/twitch.css b/data/static/twitch.css
(DIR) diff --git a/static/twitch.sh b/data/static/twitch.sh
(DIR) diff --git a/data/templates/pages/featured.html b/data/templates/pages/featured.html
@@ -0,0 +1,35 @@
+{{define "title"}}Featured streams{{end}}
+{{define "class"}}featured{{end}}
+
+{{define "content"}}
+
+<table class="table" border="0">
+<thead>
+ <tr>
+ <th class="game"><b>Game</b></th>
+ <th class="name"><b>Name</b></th>
+ <th class="title"><b>Title</b></th>
+ <th class="playlist"><b>Playlist</b></th>
+ <th class="viewers" align="right"><b>Viewers</b></th>
+ </tr>
+</thead>
+<tbody>
+
+{{range .Featured }}
+<tr>
+ <td class="game"><a href="/game?g={{.Stream.Game}}">{{.Stream.Game}}</a></td>
+ <td class="name"><a href="{{.Stream.Channel.Url}}">{{.Stream.Channel.Display_name}}</a></td>
+ <td class="title"><a href="{{.Stream.Channel.Url}}" title="{{.Stream.Channel.Status}}">{{.Stream.Channel.Status}}</a></td>
+ <td class="playlist">
+ <a href="/playlist?c={{.Stream.Channel.Name}}" title="redirect to playlist file">m3u8</a> |
+ <a href="/playlist?c={{.Stream.Channel.Name}}&f=html" title="page with playlist link">page</a> |
+ <a href="/playlist?c={{.Stream.Channel.Name}}&f=plain" title="get link to url in plain-text">plain</a>
+ </td>
+ <td align="right">{{.Stream.Viewers}}</td>
+</tr>
+{{end}}
+
+</tbody>
+</table>
+
+{{end}}
(DIR) diff --git a/data/templates/pages/game.html b/data/templates/pages/game.html
@@ -0,0 +1,34 @@
+{{define "title"}}Game: {{.Name}}{{end}}
+{{define "class"}}game{{end}}
+
+{{define "content"}}
+
+<table class="table" border="0">
+<thead>
+ <tr>
+ <th class="name"><b>Name</b></th>
+ <th class="title"><b>Title</b></th>
+ <th class="playlist"><b>Playlist</b></th>
+ <th class="viewers" align="right"><b>Viewers</b></th>
+ </tr>
+</thead>
+<tbody>
+{{with .TwitchGame}}
+ {{range .Streams}}
+ <tr>
+ <td class="name"><a href="{{.Channel.Url}}">{{.Channel.Display_name}}</a></td>
+ <td class="title"><a href="{{.Channel.Url}}" title="{{.Channel.Status}}">{{.Channel.Status}}</a></td>
+ <td class="playlist">
+ <a href="/playlist?c={{.Channel.Name}}" title="redirect to playlist file">m3u8</a> |
+ <a href="/playlist?c={{.Channel.Name}}&f=html" title="page with playlist link">page</a> |
+ <a href="/playlist?c={{.Channel.Name}}&f=plain" title="get link to url in plain-text">plain</a>
+ </td>
+ <td align="right">{{.Viewers}}</td>
+ </tr>
+
+ {{end}}
+{{end}}
+</tbody>
+</table>
+
+{{end}}
(DIR) diff --git a/data/templates/pages/games.html b/data/templates/pages/games.html
@@ -0,0 +1,23 @@
+{{define "title"}}Games{{end}}
+{{define "class"}}games{{end}}
+
+{{define "content"}}
+<table class="table" border="0">
+<thead>
+<tr>
+ <th class="game"><b>Game</b></th>
+ <th class="viewers" align="right"><b>Viewers</b></th>
+ <th class="channels" align="right"><b>Channels</b></th>
+</tr>
+</thead>
+<tbody>
+{{range .Top}}
+<tr>
+ <td class="game"><a href="/game?g={{.Game.Name}}">{{.Game.Name}}</a></td>
+ <td class="viewers" align="right">{{.Viewers}}</td>
+ <td class="channels" align="right">{{.Channels}}</td>
+</tr>
+{{end}}
+</tbody>
+</table>
+{{end}}
(DIR) diff --git a/templates/pages/links.html b/data/templates/pages/links.html
(DIR) diff --git a/data/templates/pages/playlist.html b/data/templates/pages/playlist.html
@@ -0,0 +1,8 @@
+{{define "title"}}Get playlist{{end}}
+{{define "class"}}playlist{{end}}
+
+{{define "content"}}
+ <p>Copy paste the following link in <a href="http://www.videolan.org/">VLC</a> /
+ <a href="http://mpv.io/installation/">mpv</a> as a network stream:</p>
+ <a href="{{.Url}}">{{.Url}}</a>
+{{end}}
(DIR) diff --git a/templates/themes/default/page.html b/data/templates/themes/default/page.html
(DIR) diff --git a/handlers.go b/handlers.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+)
+
+import "twitch"
+
+func FeaturedHandler(w http.ResponseWriter, r *http.Request) error {
+ featured, err := twitch.GetFeatured()
+ if err != nil {
+ return err
+ }
+ return templates.Render(w, "featured.html", "page.html", featured)
+}
+
+func PlaylistHandler(w http.ResponseWriter, r *http.Request) error {
+ channel := r.FormValue("c")
+ format := r.FormValue("f")
+ token, err := twitch.GetToken(channel)
+ if err != nil {
+ return err
+ }
+ url := fmt.Sprintf("http://usher.justin.tv/api/channel/hls/%s.m3u8?token=%s&sig=%s", channel, token.Token, token.Sig)
+ switch format {
+ case "html":
+ return templates.Render(w, "playlist.html", "page.html", struct {
+ Url string
+ }{
+ Url: url,
+ })
+ case "plain":
+ w.Write([]byte(url))
+ default: // redirect
+ w.Header().Set("Location", url)
+ w.WriteHeader(http.StatusFound)
+ w.Write([]byte(url))
+ }
+ return nil
+}
+
+func GameHandler(w http.ResponseWriter, r *http.Request) error {
+ gamename := r.FormValue("g")
+ game, err := twitch.GetGame(gamename)
+ if err != nil {
+ return err
+ }
+ v := struct {
+ Name string
+ TwitchGame *twitch.Game
+ }{
+ Name: gamename,
+ TwitchGame: game,
+ }
+ return templates.Render(w, "game.html", "page.html", v)
+}
+
+func GamesHandler(w http.ResponseWriter, r *http.Request) error {
+ games, err := twitch.GetGames()
+ if err != nil {
+ return err
+ }
+ return templates.Render(w, "games.html", "page.html", games)
+}
+
+func LinksHandler(w http.ResponseWriter, r *http.Request) error {
+ return templates.Render(w, "links.html", "page.html", make(map[string]string))
+}
(DIR) diff --git a/main.go b/main.go
@@ -1,7 +1,6 @@
package main
import (
- "encoding/json"
"errors"
"flag"
"fmt"
@@ -10,200 +9,54 @@ import (
"io/ioutil"
"net"
"net/http"
- "net/url"
"os"
"os/signal"
"path/filepath"
- "regexp"
"strings"
"syscall"
"time"
)
-type AppConfig struct {
+// config
+type Config struct {
Addr string
AddrType string
Password string // password to reload templates etc, see /admin route.
TemplateThemeDir string
TemplatePageDir string
+ StaticContentDir string
Pidfile string
}
-type TwitchToken struct {
- Mobile_restricted bool
- Sig string
- Token string
+type Templates struct {
+ Pages map[string]*template.Template
+ Themes map[string]*template.Template
}
-type TwitchFeatured struct {
- Featured []struct {
- Image string
- Priority int
- Scheduled bool
- Sponsored bool
- Stream struct {
- Id int
- Average_fps float64
- Created_at string
- Channel struct {
- Broadcaster_language string
- Delay int
- Followers int
- Display_name string
- Language string
- Name string
- Logo string
- Mature bool
- Partner bool
- Status string
- Updated_at string
- Url string
- Views int
- }
- Game string
- Video_height int
- Viewers int
- }
- Text string
- Title string
- }
-}
-
-type TwitchGame struct {
- Streams []struct {
- Id int
- Average_fps float64
- Channel struct {
- Broadcaster_language string
- Delay int
- Followers int
- Display_name string
- Language string
- Name string
- Logo string
- Mature bool
- Partner bool
- Status string
- Updated_at string
- Url string
- Views int
- }
- Created_at string
- Game string
- Video_height int
- Viewers int
- }
-}
-
-type TwitchGames struct {
- Top []struct {
- Channels int
- Game struct {
- Box struct {
- Large string
- Medium string
- Small string
- Template string
- }
- Giantbomb_id int
- Logo struct {
- Large string
- Medium string
- Small string
- Template string
- }
- Name string
- }
- Viewers int
- }
-}
-
-type Route struct {
- Regexstr string
- Re *regexp.Regexp
- Fn HandlerCallback
- Method string // POST, GET, HEAD etc.
-}
-
-type RouteMatch struct {
- Route *Route
- Names map[string]string
-}
-
-type RouteHandler struct {
- Routes []Route
-}
-
-type HandlerCallback func(http.ResponseWriter, *http.Request, *RouteMatch) error
-
-var pages map[string]*template.Template
-var themes map[string]*template.Template
-var appconfig AppConfig
-
-func (r *RouteHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
- match := FindRouteMatch(req, r.Routes)
- if match != nil {
- err := match.Route.Fn(res, req, match)
- if err != nil {
- res.WriteHeader(http.StatusInternalServerError)
- res.Write([]byte(err.Error()))
- return
- }
- } else {
- http.FileServer(http.Dir("static")).ServeHTTP(res, req)
- }
-}
-
-func NewRoute(method, restr string, fn HandlerCallback) Route {
- return Route{
- Method: method,
- Regexstr: restr,
- Re: regexp.MustCompile(restr),
- Fn: fn,
- }
-}
+var appconfig Config
+var templates *Templates
-func FindRouteMatch(req *http.Request, rs []Route) *RouteMatch {
- for _, r := range rs {
- // check HTTP method, empty in route always matches.
- if len(r.Method) > 0 && req.Method != r.Method {
- continue
- }
- // check path (regex).
- matches := r.Re.FindStringSubmatch(req.URL.Path)
- matcheslen := len(matches)
- if matcheslen == 0 {
- continue
- }
- names := r.Re.SubexpNames()
- nameslen := len(names)
- // make map of named group matches in regex match.
- tomap := map[string]string{}
- for i := 1; i < matcheslen && i < nameslen; i++ {
- tomap[names[i]] = matches[i]
- }
- return &RouteMatch{
- Route: &r,
- Names: tomap,
- }
- }
- return nil
+func NewTemplates() *Templates {
+ t := &Templates{}
+ t.Pages = make(map[string]*template.Template)
+ t.Themes = make(map[string]*template.Template)
+ return t
}
-// NOTE: uses global "themes" and "pages" variable: map[string]*Template.
-func RenderTemplate(w io.Writer, pagename string, themename string, data interface{}) error {
- if _, ok := themes[themename]; !ok {
+// NOTE: uses "themes" and "pages" variable: map[string]*Template.
+func (t *Templates) Render(w io.Writer, pagename string, themename string, data interface{}) error {
+ if _, ok := t.Themes[themename]; !ok {
return errors.New(fmt.Sprintf("theme template \"%s\" not found", themename))
}
- if _, ok := pages[pagename]; !ok {
+ if _, ok := t.Pages[pagename]; !ok {
return errors.New(fmt.Sprintf("page template \"%s\" not found", pagename))
}
- render, err := pages[pagename].Clone()
+ render, err := t.Pages[pagename].Clone()
if err != nil {
return err
}
// NOTE: the template.Tree must be copied after Clone() too.
- _, err = render.AddParseTree("render", themes[themename].Tree.Copy())
+ _, err = render.AddParseTree("render", t.Themes[themename].Tree.Copy())
if err != nil {
return err
}
@@ -214,7 +67,23 @@ func RenderTemplate(w io.Writer, pagename string, themename string, data interfa
return nil
}
-func LoadPages(path string) (map[string]*template.Template, error) {
+func (t *Templates) LoadPages(path string) error {
+ templates, err := t.LoadTemplates(path)
+ if err == nil {
+ t.Pages = templates
+ }
+ return err
+}
+
+func (t *Templates) LoadThemes(path string) error {
+ templates, err := t.LoadTemplates(path)
+ if err == nil {
+ t.Themes = templates
+ }
+ return err
+}
+
+func (t *Templates) LoadTemplates(path string) (map[string]*template.Template, error) {
m := make(map[string]*template.Template)
path, err := filepath.Abs(path)
if err != nil {
@@ -245,163 +114,19 @@ func LoadPages(path string) (map[string]*template.Template, error) {
return m, err
}
-func ReadAllUrl(url string) ([]byte, error) {
- resp, err := http.Get(url)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- return body, nil
-}
-
-func GetToken(channel string) (*TwitchToken, error) {
- url := fmt.Sprintf("https://api.twitch.tv/api/channels/%s/access_token", channel)
- body, err := ReadAllUrl(url)
- if err != nil {
- return nil, err
- }
- var v TwitchToken
- err = json.Unmarshal(body, &v)
- if err != nil {
- return nil, err
- }
- return &v, nil
-}
-
-func GetFeatured() (*TwitchFeatured, error) {
- url := "https://api.twitch.tv/kraken/streams/featured?limit=100"
- body, err := ReadAllUrl(url)
- if err != nil {
- return nil, err
- }
- var v TwitchFeatured
- err = json.Unmarshal(body, &v)
- if err != nil {
- return nil, err
- }
- return &v, nil
-}
-
-func GetGames() (*TwitchGames, error) {
- url := "https://api.twitch.tv/kraken/games/top?limit=100"
- body, err := ReadAllUrl(url)
- if err != nil {
- return nil, err
- }
- var v TwitchGames
- err = json.Unmarshal(body, &v)
- if err != nil {
- return nil, err
- }
- return &v, nil
-}
-
-func GetGame(game string) (*TwitchGame, error) {
- s := fmt.Sprintf("https://api.twitch.tv/kraken/streams?game=%s", url.QueryEscape(game))
- body, err := ReadAllUrl(s)
- if err != nil {
- return nil, err
- }
- var v TwitchGame
- err = json.Unmarshal(body, &v)
- if err != nil {
- return nil, err
- }
- return &v, nil
-}
-
-func FeaturedHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- featured, err := GetFeatured()
- if err != nil {
- return err
- }
- return RenderTemplate(w, "featured.html", "page.html", featured)
-}
-
-func PlaylistHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- channel := m.Names["channel"]
- format := m.Names["format"]
- if channel == "" {
- return errors.New("no channel specified")
- }
- token, err := GetToken(channel)
- if err != nil {
- return err
- }
- url := fmt.Sprintf("http://usher.justin.tv/api/channel/hls/%s.m3u8?token=%s&sig=%s", channel, token.Token, token.Sig)
- switch format {
- case "html":
- return RenderTemplate(w, "playlist.html", "page.html", struct {
- Url string
- }{
- Url: url,
- })
- break
- case "plain":
- w.Write([]byte(url))
- break
- default: // redirect
- w.WriteHeader(http.StatusFound)
- w.Header().Set("Location", url)
- w.Write([]byte(url))
- }
- return nil
-}
-
-func GameHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- if m.Names["game"] == "" {
- return errors.New("no channel specified")
- }
- game, err := GetGame(m.Names["game"])
- if err != nil {
- return err
- }
- v := struct {
- Name string
- TwitchGame TwitchGame
- }{
- Name: m.Names["game"],
- TwitchGame: *game,
- }
- return RenderTemplate(w, "game.html", "page.html", v)
-}
-
-func GamesHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- games, err := GetGames()
- if err != nil {
- return err
- }
- return RenderTemplate(w, "games.html", "page.html", games)
-}
-
-func LinksHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- return RenderTemplate(w, "links.html", "page.html", make(map[string]string))
-}
+type TwitchHandler func(http.ResponseWriter, *http.Request) error
-// Reload templates, write status to client.
-// on error the old templates are kept in memory.
-func ReloadTemplateHandler(w http.ResponseWriter, r *http.Request, m *RouteMatch) error {
- if appconfig.Password == "" || m.Names["password"] != appconfig.Password {
- w.WriteHeader(http.StatusUnauthorized)
- w.Write([]byte("401: unauthorized"))
- return nil
- }
- newpages, err := LoadPages(appconfig.TemplatePageDir)
- if err != nil {
- return err
- }
- newthemes, err := LoadPages(appconfig.TemplateThemeDir)
- if err != nil {
- return err
+func MakeHandler(h TwitchHandler) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err == nil {
+ err = h(w, r)
+ }
+ if err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte("500 " + err.Error()))
+ }
}
- pages = newpages
- themes = newthemes
- w.Write([]byte("OK"))
- return nil
}
func usage() {
@@ -410,9 +135,10 @@ func usage() {
}
func main() {
- appconfig = AppConfig{
- TemplateThemeDir: "templates/themes/default",
- TemplatePageDir: "templates/pages/",
+ appconfig = Config{
+ TemplateThemeDir: "data/templates/themes/default",
+ TemplatePageDir: "data/templates/pages/",
+ StaticContentDir: "data/static/",
}
appconfig.Pidfile = *flag.String("f", "", "PID file")
appconfig.Addr = *flag.String("l", "127.0.0.1:8080", "listen address")
@@ -425,22 +151,6 @@ func main() {
panic(err)
}
- routes := []Route{
- NewRoute("", `^/$`, FeaturedHandler),
- NewRoute("", `^/featured[/]?$`, FeaturedHandler),
- NewRoute("", `^/games[/]?$`, GamesHandler),
- NewRoute("", `^/game[/]?$`, GamesHandler),
- NewRoute("", `^/game/(?P<game>[a-zA-Z0-9_ :+'"\-]*)[/]?$`, GameHandler),
- NewRoute("", `/playlist/(?P<channel>[^/]*)/(?P<format>.*)[/]?$`, PlaylistHandler),
- NewRoute("", `/playlist/(?P<channel>[^/]*)[/]?$`, PlaylistHandler),
- NewRoute("", `^/links[/]?$`, LinksHandler),
- // special admin handlers: should require a password.
- NewRoute("", `^/admin/reloadtemplates/(?P<password>.*)$`, ReloadTemplateHandler),
- }
- r := &RouteHandler{
- Routes: routes,
- }
-
// Write PID to pid file.
if appconfig.Pidfile != "" {
pid := os.Getpid()
@@ -473,18 +183,34 @@ func main() {
}
}()
+ templates = NewTemplates()
// Parse templates and keep in-memory.
- pages, err = LoadPages(appconfig.TemplatePageDir)
+ err = templates.LoadPages(appconfig.TemplatePageDir)
if err != nil {
panic(err)
}
- themes, err = LoadPages(appconfig.TemplateThemeDir)
+ err = templates.LoadThemes(appconfig.TemplateThemeDir)
if err != nil {
panic(err)
}
+ http.HandleFunc("/featured", MakeHandler(FeaturedHandler))
+ http.HandleFunc("/playlist", MakeHandler(PlaylistHandler))
+ http.HandleFunc("/games", MakeHandler(GamesHandler))
+ http.HandleFunc("/game", MakeHandler(GameHandler))
+ http.HandleFunc("/links", MakeHandler(LinksHandler))
+
+ fileserv := http.FileServer(http.Dir(appconfig.StaticContentDir)).ServeHTTP
+ http.HandleFunc("/", MakeHandler(func(w http.ResponseWriter, r *http.Request) error {
+ if r.URL.Path == "/" {
+ return FeaturedHandler(w, r)
+ } else {
+ fileserv(w, r)
+ return nil
+ }
+ }))
+
s := &http.Server{
- Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
(DIR) diff --git a/src/twitch/twitch.go b/src/twitch/twitch.go
@@ -0,0 +1,167 @@
+package twitch
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+)
+
+type Token struct {
+ Mobile_restricted bool
+ Sig string
+ Token string
+}
+
+type Featured struct {
+ Featured []struct {
+ Image string
+ Priority int
+ Scheduled bool
+ Sponsored bool
+ Stream struct {
+ Id int
+ Average_fps float64
+ Created_at string
+ Channel struct {
+ Broadcaster_language string
+ Delay int
+ Followers int
+ Display_name string
+ Language string
+ Name string
+ Logo string
+ Mature bool
+ Partner bool
+ Status string
+ Updated_at string
+ Url string
+ Views int
+ }
+ Game string
+ Video_height int
+ Viewers int
+ }
+ Text string
+ Title string
+ }
+}
+
+type Game struct {
+ Streams []struct {
+ Id int
+ Average_fps float64
+ Channel struct {
+ Broadcaster_language string
+ Delay int
+ Followers int
+ Display_name string
+ Language string
+ Name string
+ Logo string
+ Mature bool
+ Partner bool
+ Status string
+ Updated_at string
+ Url string
+ Views int
+ }
+ Created_at string
+ Game string
+ Video_height int
+ Viewers int
+ }
+}
+
+type Games struct {
+ Top []struct {
+ Channels int
+ Game struct {
+ Box struct {
+ Large string
+ Medium string
+ Small string
+ Template string
+ }
+ Giantbomb_id int
+ Logo struct {
+ Large string
+ Medium string
+ Small string
+ Template string
+ }
+ Name string
+ }
+ Viewers int
+ }
+}
+
+func ReadAllUrl(url string) ([]byte, error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ return body, nil
+}
+
+func GetToken(channel string) (*Token, error) {
+ url := fmt.Sprintf("https://api.twitch.tv/api/channels/%s/access_token", channel)
+ body, err := ReadAllUrl(url)
+ if err != nil {
+ return nil, err
+ }
+ var v Token
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
+
+func GetFeatured() (*Featured, error) {
+ url := "https://api.twitch.tv/kraken/streams/featured?limit=100"
+ body, err := ReadAllUrl(url)
+ if err != nil {
+ return nil, err
+ }
+ var v Featured
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
+
+func GetGames() (*Games, error) {
+ url := "https://api.twitch.tv/kraken/games/top?limit=100"
+ body, err := ReadAllUrl(url)
+ if err != nil {
+ return nil, err
+ }
+ var v Games
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
+
+func GetGame(game string) (*Game, error) {
+ s := fmt.Sprintf("https://api.twitch.tv/kraken/streams?game=%s", url.QueryEscape(game))
+ body, err := ReadAllUrl(s)
+ if err != nil {
+ return nil, err
+ }
+ var v Game
+ err = json.Unmarshal(body, &v)
+ if err != nil {
+ return nil, err
+ }
+ return &v, nil
+}
(DIR) diff --git a/templates/pages/featured.html b/templates/pages/featured.html
@@ -1,35 +0,0 @@
-{{define "title"}}Featured streams{{end}}
-{{define "class"}}featured{{end}}
-
-{{define "content"}}
-
-<table class="table" border="0">
-<thead>
- <tr>
- <th class="game"><b>Game</b></th>
- <th class="name"><b>Name</b></th>
- <th class="title"><b>Title</b></th>
- <th class="playlist"><b>Playlist</b></th>
- <th class="viewers" align="right"><b>Viewers</b></th>
- </tr>
-</thead>
-<tbody>
-
-{{range .Featured }}
-<tr>
- <td class="game"><a href="/game/{{.Stream.Game}}">{{.Stream.Game}}</a></td>
- <td class="name"><a href="{{.Stream.Channel.Url}}">{{.Stream.Channel.Display_name}}</a></td>
- <td class="title"><a href="{{.Stream.Channel.Url}}" title="{{.Stream.Channel.Status}}">{{.Stream.Channel.Status}}</a></td>
- <td class="playlist">
- <a href="/playlist/{{.Stream.Channel.Name}}" title="redirect to playlist file">m3u8</a> |
- <a href="/playlist/{{.Stream.Channel.Name}}/html" title="page with playlist link">page</a> |
- <a href="/playlist/{{.Stream.Channel.Name}}/plain" title="get link to url in plain-text">plain</a>
- </td>
- <td align="right">{{.Stream.Viewers}}</td>
-</tr>
-{{end}}
-
-</tbody>
-</table>
-
-{{end}}
(DIR) diff --git a/templates/pages/game.html b/templates/pages/game.html
@@ -1,34 +0,0 @@
-{{define "title"}}Game: {{.Name}}{{end}}
-{{define "class"}}game{{end}}
-
-{{define "content"}}
-
-<table class="table" border="0">
-<thead>
- <tr>
- <th class="name"><b>Name</b></th>
- <th class="title"><b>Title</b></th>
- <th class="playlist"><b>Playlist</b></th>
- <th class="viewers" align="right"><b>Viewers</b></th>
- </tr>
-</thead>
-<tbody>
-{{with .TwitchGame}}
- {{range .Streams}}
- <tr>
- <td class="name"><a href="{{.Channel.Url}}">{{.Channel.Display_name}}</a></td>
- <td class="title"><a href="{{.Channel.Url}}" title="{{.Channel.Status}}">{{.Channel.Status}}</a></td>
- <td class="playlist">
- <a href="/playlist/{{.Channel.Name}}" title="redirect to playlist file">m3u8</a> |
- <a href="/playlist/{{.Channel.Name}}/html" title="page with playlist link">page</a> |
- <a href="/playlist/{{.Channel.Name}}/plain" title="get link to url in plain-text">plain</a>
- </td>
- <td align="right">{{.Viewers}}</td>
- </tr>
-
- {{end}}
-{{end}}
-</tbody>
-</table>
-
-{{end}}
(DIR) diff --git a/templates/pages/games.html b/templates/pages/games.html
@@ -1,23 +0,0 @@
-{{define "title"}}Games{{end}}
-{{define "class"}}games{{end}}
-
-{{define "content"}}
-<table class="table" border="0">
-<thead>
-<tr>
- <th class="game"><b>Game</b></th>
- <th class="viewers" align="right"><b>Viewers</b></th>
- <th class="channels" align="right"><b>Channels</b></th>
-</tr>
-</thead>
-<tbody>
-{{range .Top}}
-<tr>
- <td class="game"><a href="/game/{{.Game.Name}}">{{.Game.Name}}</a></td>
- <td class="viewers" align="right">{{.Viewers}}</td>
- <td class="channels" align="right">{{.Channels}}</td>
-</tr>
-{{end}}
-</tbody>
-</table>
-{{end}}
(DIR) diff --git a/templates/pages/playlist.html b/templates/pages/playlist.html
@@ -1,7 +0,0 @@
-{{define "title"}}Get playlist{{end}}
-{{define "class"}}playlist{{end}}
-
-{{define "content"}}
- <p>Copy paste the following link in <a href="http://www.videolan.org/">VLC</a> / <a href="http://mpv.io/installation/">mpv</a> as a network stream:</p>
- <a href="{{.Url}}">{{.Url}}</a>
-{{end}}