add Twitch Client-ID support - 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 d1d8ef8c6e8114644913d3ef7e15c1a60b0946c2
 (DIR) parent e262649da11824a0a269b202169496e3eecac6de
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Tue, 20 Sep 2016 21:54:07 +0200
       
       add Twitch Client-ID support
       
       it is now required to authenticate using a requested API token to Twitch. This
       can be requested in the account settings.
       
       Add a -c flag to set the client id.
       
       Other changes:
       - merge handlers.go back into main.go again.
       - update documentation and rc.d example.
       
       Diffstat:
         M README                              |       1 +
         M data/static/twitch.sh               |       7 +++++--
         D handlers.go                         |      79 -------------------------------
         M main.go                             |      99 ++++++++++++++++++++++++++++---
         M rc_twitch                           |       2 +-
         M src/twitch/twitch.go                |       4 ++++
       
       6 files changed, 101 insertions(+), 91 deletions(-)
       ---
 (DIR) diff --git a/README b/README
       @@ -21,6 +21,7 @@ Install as OpenBSD daemon:
        
        - Make a user "_twitch", install the twitch binary to /usr/local/sbin.
        - Make a file /etc/rc.d/twitch, see rc_twitch example file.
       +- Make sure to set your Twitch Client-ID token in this file.
        - Start the daemon:
                # rcctl start twitch
        
 (DIR) diff --git a/data/static/twitch.sh b/data/static/twitch.sh
       @@ -10,14 +10,17 @@ err() {
        }
        
        [ "$1" = "" ] && err "Usage: $0 <channel>"
       +clientid="client_id_here"
        channel="$1"
        tokenurl="https://api.twitch.tv/api/channels/${channel}/access_token"
       -tokendata=$(curl -s -L "$tokenurl")
       +tokendata=$(curl -s -H "Client-ID: ${clientid}" "$tokenurl")
        token=$(printf '%s' "${tokendata}" | sed -E -e 's@.*"token":"(.*)","sig".*@\1@g' -e 's@\\"@"@g')
        sig=$(printf '%s' "${tokendata}" | sed -E 's@.*"sig":"([^"]*)".*@\1@g')
        
        if test x"$token" != x"" && test x"$sig" != x""; then
       -        curl -G -L "http://usher.justin.tv/api/channel/hls/${channel}.m3u8" \
       +        curl -G \
       +                -H "Client-ID: ${clientid}" \
       +                "http://usher.justin.tv/api/channel/hls/${channel}.m3u8" \
                        --data-urlencode "token=${token}" \
                        --data-urlencode "sig=${sig}"
        else
 (DIR) diff --git a/handlers.go b/handlers.go
       @@ -1,79 +0,0 @@
       -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")
       -        if channel == "" {
       -                BadRequest(w, "No channel name specified")
       -                return nil
       -        }
       -        format := r.FormValue("f") // if empty "redirect".
       -        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)
       -                if r.Method == "GET" {
       -                        w.Write([]byte(url))
       -                }
       -        }
       -        return nil
       -}
       -
       -func GameHandler(w http.ResponseWriter, r *http.Request) error {
       -        gamename := r.FormValue("g")
       -        if gamename == "" {
       -                BadRequest(w, "No game name specified")
       -                return nil
       -        }
       -        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
       @@ -16,6 +16,8 @@ import (
                "time"
        )
        
       +import "twitch"
       +
        type TwitchHandler func(http.ResponseWriter, *http.Request) error
        
        type Templates struct {
       @@ -25,6 +27,16 @@ type Templates struct {
        
        var templates *Templates
        
       +// config
       +var config_addr string
       +var config_addrtype string
       +var config_chmod uint
       +var config_clientid string
       +var config_datadir string
       +var config_templatethemedir string = "templates/themes/default/"
       +var config_templatepagedir string = "templates/pages/"
       +var config_staticcontentdir string = "static/"
       +
        func NewTemplates() *Templates {
                t := &Templates{}
                t.Pages = make(map[string]*template.Template)
       @@ -126,27 +138,96 @@ func MakeHandler(h TwitchHandler) func(http.ResponseWriter, *http.Request) {
                }
        }
        
       +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")
       +        if channel == "" {
       +                BadRequest(w, "No channel name specified")
       +                return nil
       +        }
       +        format := r.FormValue("f") // if empty "redirect".
       +        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)
       +                if r.Method == "GET" {
       +                        w.Write([]byte(url))
       +                }
       +        }
       +        return nil
       +}
       +
       +func GameHandler(w http.ResponseWriter, r *http.Request) error {
       +        gamename := r.FormValue("g")
       +        if gamename == "" {
       +                BadRequest(w, "No game name specified")
       +                return nil
       +        }
       +        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))
       +}
       +
       +
        func usage() {
                fmt.Fprintf(os.Stderr, "Usage: %s\n", os.Args[0])
                flag.PrintDefaults()
        }
        
        func main() {
       -        // config
       -        var config_addr string
       -        var config_addrtype string
       -        var config_chmod uint
       -        var config_datadir string
       -        var config_templatethemedir string = "templates/themes/default/"
       -        var config_templatepagedir string = "templates/pages/"
       -        var config_staticcontentdir string = "static/"
       -
       +        flag.StringVar(&twitch.Clientid, "c", "", "Client-ID token")
                flag.StringVar(&config_datadir, "d", "", "Chdir to data directory")
                flag.StringVar(&config_addr, "l", "127.0.0.1:8080", "listen address")
                flag.UintVar(&config_chmod, "m", 0755, "Permission for unix domain socket")
                flag.StringVar(&config_addrtype, "t", "tcp4", `listen type: "tcp", "tcp4", "tcp6", "unix" or "unixpacket"`)
                flag.Parse()
        
       +        if len(twitch.Clientid) == 0 {
       +                usage()
       +                os.Exit(1)
       +        }
       +
                pledgestr := "stdio rpath cpath wpath dns"
                if config_addrtype == "unix" {
                        pledgestr += " unix"
 (DIR) diff --git a/rc_twitch b/rc_twitch
       @@ -17,7 +17,7 @@ user="_twitch"
        group="_twitch"
        
        daemon="chroot -u ${user} -g ${group} $chroot ${chroot_daemon}"
       -daemon_flags="-t tcp4 -d /data -l 127.0.0.1:8081"
       +daemon_flags="-t tcp4 -d /data -l 127.0.0.1:8081 -c twitch_clientid_here"
        
        . /etc/rc.d/rc.subr
        
 (DIR) diff --git a/src/twitch/twitch.go b/src/twitch/twitch.go
       @@ -9,6 +9,9 @@ import (
                "time"
        )
        
       +// set your Twitch Client-ID.
       +var Clientid string
       +
        type Token struct {
                Mobile_restricted bool
                Sig               string
       @@ -106,6 +109,7 @@ func ReadAllUrl(url string) ([]byte, error) {
                if err != nil {
                        return nil, err
                }
       +        req.Header.Set("Client-ID", Clientid)
                resp, err := client.Do(req)
                if resp != nil {
                        defer resp.Body.Close()