main.go - 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
       ---
       main.go (8057B)
       ---
            1 package main
            2 
            3 import (
            4         "errors"
            5         "flag"
            6         "fmt"
            7         "html/template"
            8         "io"
            9         "io/ioutil"
           10         "log"
           11         "net"
           12         "net/http"
           13         "os"
           14         "path/filepath"
           15         "strings"
           16         "time"
           17 )
           18 
           19 import "twitch"
           20 
           21 type TwitchHandler func(http.ResponseWriter, *http.Request) error
           22 
           23 type Templates struct {
           24         Pages  map[string]*template.Template
           25         Themes map[string]*template.Template
           26 }
           27 
           28 var templates *Templates
           29 
           30 // config
           31 var config_addr string
           32 var config_addrtype string
           33 var config_chmod uint
           34 var config_clientid string
           35 var config_datadir string
           36 var config_templatethemedir string = "templates/themes/default/"
           37 var config_templatepagedir string = "templates/pages/"
           38 var config_staticcontentdir string = "static/"
           39 
           40 func NewTemplates() *Templates {
           41         t := &Templates{}
           42         t.Pages = make(map[string]*template.Template)
           43         t.Themes = make(map[string]*template.Template)
           44         return t
           45 }
           46 
           47 // NOTE: uses "themes" and "pages" variable: map[string]*Template.
           48 func (t *Templates) Render(w io.Writer, pagename string, themename string, data interface{}) error {
           49         if _, ok := t.Themes[themename]; !ok {
           50                 return errors.New(fmt.Sprintf("theme template \"%s\" not found", themename))
           51         }
           52         if _, ok := t.Pages[pagename]; !ok {
           53                 return errors.New(fmt.Sprintf("page template \"%s\" not found", pagename))
           54         }
           55         render, err := t.Pages[pagename].Clone()
           56         if err != nil {
           57                 return err
           58         }
           59         // NOTE: the template.Tree must be copied after Clone() too.
           60         _, err = render.AddParseTree("render", t.Themes[themename].Tree.Copy())
           61         if err != nil {
           62                 return err
           63         }
           64         err = render.ExecuteTemplate(w, "render", data)
           65         if err != nil {
           66                 return err
           67         }
           68         return nil
           69 }
           70 
           71 func (t *Templates) LoadPages(path string) error {
           72         templates, err := t.LoadTemplates(path)
           73         if err == nil {
           74                 t.Pages = templates
           75         }
           76         return err
           77 }
           78 
           79 func (t *Templates) LoadThemes(path string) error {
           80         templates, err := t.LoadTemplates(path)
           81         if err == nil {
           82                 t.Themes = templates
           83         }
           84         return err
           85 }
           86 
           87 func (t *Templates) LoadTemplates(path string) (map[string]*template.Template, error) {
           88         m := make(map[string]*template.Template)
           89         path, err := filepath.Abs(path)
           90         if err != nil {
           91                 return m, err
           92         }
           93         err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
           94                 if err != nil {
           95                         return err
           96                 }
           97                 if info.IsDir() {
           98                         return nil
           99                 }
          100                 // strip prefix.
          101                 name := strings.TrimPrefix(p, path)
          102                 // replace potentially inconsistent paths (Windows).
          103                 name = strings.Replace(name, "\\", "/", -1)
          104                 name = strings.TrimPrefix(name, "/")
          105                 // read template data from file..
          106                 data, err := ioutil.ReadFile(p)
          107                 if err != nil {
          108                         return err
          109                 }
          110                 t := template.New(name)
          111                 _, err = t.Parse(string(data))
          112                 if err != nil {
          113                         return err
          114                 }
          115                 m[name] = t
          116                 return err
          117         })
          118         return m, err
          119 }
          120 
          121 func NotFound(w http.ResponseWriter, err string) {
          122         http.Error(w, "404 "+err, http.StatusNotFound)
          123 }
          124 
          125 func BadRequest(w http.ResponseWriter, err string) {
          126         http.Error(w, "400 "+err, http.StatusBadRequest)
          127 }
          128 
          129 func MakeHandler(h TwitchHandler) func(http.ResponseWriter, *http.Request) {
          130         return func(w http.ResponseWriter, r *http.Request) {
          131                 err := r.ParseForm()
          132                 if err == nil {
          133                         err = h(w, r)
          134                 } else {
          135                         BadRequest(w, "Can't parse form: "+err.Error())
          136                         return
          137                 }
          138                 // unhandled error so far: 500
          139                 if err != nil {
          140                         http.Error(w, "500 "+err.Error(), http.StatusInternalServerError)
          141                 }
          142         }
          143 }
          144 
          145 func FeaturedHandler(w http.ResponseWriter, r *http.Request) error {
          146         featured, err := twitch.GetFeatured()
          147         if err != nil {
          148                 return err
          149         }
          150         return templates.Render(w, "featured.html", "page.html", featured)
          151 }
          152 
          153 func PlaylistHandler(w http.ResponseWriter, r *http.Request) error {
          154         channel := r.FormValue("c")
          155         if channel == "" {
          156                 BadRequest(w, "No channel name specified")
          157                 return nil
          158         }
          159         format := r.FormValue("f") // if empty "redirect".
          160         token, err := twitch.GetToken(channel)
          161         if err != nil {
          162                 return err
          163         }
          164         url := fmt.Sprintf("http://usher.justin.tv/api/channel/hls/%s.m3u8?token=%s&sig=%s", channel, token.Token, token.Sig)
          165         switch format {
          166         case "html":
          167                 return templates.Render(w, "playlist.html", "page.html", struct {
          168                         Url string
          169                 }{
          170                         Url: url,
          171                 })
          172         case "plain":
          173                 w.Write([]byte(url))
          174         default: // redirect
          175                 w.Header().Set("Location", url)
          176                 w.WriteHeader(http.StatusFound)
          177                 if r.Method == "GET" {
          178                         w.Write([]byte(url))
          179                 }
          180         }
          181         return nil
          182 }
          183 
          184 func GameHandler(w http.ResponseWriter, r *http.Request) error {
          185         gamename := r.FormValue("g")
          186         if gamename == "" {
          187                 BadRequest(w, "No game name specified")
          188                 return nil
          189         }
          190         game, err := twitch.GetGame(gamename)
          191         if err != nil {
          192                 return err
          193         }
          194         v := struct {
          195                 Name       string
          196                 TwitchGame *twitch.Game
          197         }{
          198                 Name:       gamename,
          199                 TwitchGame: game,
          200         }
          201         return templates.Render(w, "game.html", "page.html", v)
          202 }
          203 
          204 func GamesHandler(w http.ResponseWriter, r *http.Request) error {
          205         games, err := twitch.GetGames()
          206         if err != nil {
          207                 return err
          208         }
          209         return templates.Render(w, "games.html", "page.html", games)
          210 }
          211 
          212 func LinksHandler(w http.ResponseWriter, r *http.Request) error {
          213         return templates.Render(w, "links.html", "page.html", struct{}{})
          214 }
          215 
          216 func VODSHandler(w http.ResponseWriter, r *http.Request) error {
          217         var err error
          218         var users *twitch.Users
          219         var videos *twitch.Videos
          220 
          221         format := r.FormValue("f")
          222         name := r.FormValue("n")
          223 
          224         v := struct {
          225                 Search string
          226                 User   twitch.User
          227                 Videos []twitch.Video
          228         }{
          229                 Search: name,
          230         }
          231 
          232         if len(name) > 0 {
          233                 users, err = twitch.GetUseridByName(name)
          234                 if err != nil {
          235                         NotFound(w, "(login) name not found or an error occurred")
          236                         return nil
          237                 }
          238                 if len(users.Items) == 0 {
          239                         NotFound(w, "(login) name not found")
          240                         return nil
          241                 }
          242                 for _, u := range users.Items {
          243                         videos, err = twitch.GetVideosByUser(u.Id)
          244                         if err != nil {
          245                                 return err
          246                         }
          247                         break // return for first user.
          248                 }
          249                 v.User = users.Items[0]
          250                 v.Videos = videos.Items
          251         }
          252 
          253         switch format {
          254         case "atom":
          255                 w.Header().Set("Content-Type", "text/xml; charset=utf-8")
          256                 fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?>`+"\n")
          257                 return templates.Render(w, "vods.xml", "atom.xml", v)
          258         default:
          259                 return templates.Render(w, "vods.html", "page.html", v)
          260         }
          261 }
          262 
          263 func usage() {
          264         fmt.Fprintf(os.Stderr, "Usage: %s\n", os.Args[0])
          265         flag.PrintDefaults()
          266 }
          267 
          268 func main() {
          269         flag.StringVar(&twitch.Clientid, "c", "", "Client-ID token")
          270         flag.StringVar(&config_datadir, "d", "", "Chdir to data directory")
          271         flag.StringVar(&config_addr, "l", "127.0.0.1:8080", "listen address")
          272         flag.UintVar(&config_chmod, "m", 0755, "Permission for unix domain socket")
          273         flag.StringVar(&config_addrtype, "t", "tcp4", `listen type: "tcp", "tcp4", "tcp6", "unix" or "unixpacket"`)
          274         flag.Parse()
          275 
          276         if len(twitch.Clientid) == 0 {
          277                 usage()
          278                 os.Exit(1)
          279         }
          280 
          281         // Remove previous UDS if it exists.
          282         if config_addrtype == "unix" {
          283                 os.Remove(config_addr)
          284         }
          285 
          286         l, err := net.Listen(config_addrtype, config_addr)
          287         if err != nil {
          288                 log.Fatalln(err)
          289         }
          290 
          291         // Set permission on UDS.
          292         if config_addrtype == "unix" {
          293                 err = os.Chmod(config_addr, os.FileMode(config_chmod))
          294                 if err != nil {
          295                         log.Fatalln(err)
          296                 }
          297         }
          298 
          299         if config_datadir != "" {
          300                 err = os.Chdir(config_datadir)
          301                 if err != nil {
          302                         log.Fatalln(err)
          303                 }
          304         }
          305 
          306         templates = NewTemplates()
          307         // Parse templates and keep in-memory.
          308         err = templates.LoadPages(config_templatepagedir)
          309         if err != nil {
          310                 log.Fatalln(err)
          311         }
          312         err = templates.LoadThemes(config_templatethemedir)
          313         if err != nil {
          314                 log.Fatalln(err)
          315         }
          316 
          317         http.HandleFunc("/featured", MakeHandler(FeaturedHandler))
          318         http.HandleFunc("/playlist", MakeHandler(PlaylistHandler))
          319         http.HandleFunc("/games", MakeHandler(GamesHandler))
          320         http.HandleFunc("/game", MakeHandler(GameHandler))
          321         http.HandleFunc("/links", MakeHandler(LinksHandler))
          322         http.HandleFunc("/vods", MakeHandler(VODSHandler))
          323 
          324         fileserv := http.FileServer(http.Dir(config_staticcontentdir)).ServeHTTP
          325         http.HandleFunc("/", MakeHandler(func(w http.ResponseWriter, r *http.Request) error {
          326                 if r.URL.Path == "/" {
          327                         return FeaturedHandler(w, r)
          328                 } else {
          329                         fileserv(w, r)
          330                         return nil
          331                 }
          332         }))
          333 
          334         s := &http.Server{
          335                 ReadTimeout:    30 * time.Second,
          336                 WriteTimeout:   30 * time.Second,
          337                 MaxHeaderBytes: 1 << 20,
          338         }
          339         s.Serve(l)
          340 }