<commit_before>//Package api implements the endpoints and utilities necessary to present
//a consistent API to autobd-nodes
//Currently there are three endpoints:
//
//NOTE: All endpoints return a JSON encoded error string on error via logging.LogHttpErr()
//
// /<version>/manifest?dir=<dirname>
//
// /<version>/sync?grab=<file or directory name>
//
// /version
//
//The '/manifest' endpoint takes a directory as an argument passed as the url
//paramater 'dir'. It returns a JSON encoded map of strings->manifest.Manifest
//of the requested directory or a JSON encoded error string on error
//
//The '/sync' endpoint takes a directory or filename as an argument pass as the url
//parameter 'grab'. If 'grab' is a directory, it will set the HTTP header 'Content-Type-'
//to 'application/x-tar', and transport the directory packed as a tarball to the client via packing.PackDir()
//
//The '/version' endpoint simple returns a JSON version.VersionInfo struct
//
package api

import (
	"compress/gzip"
	"encoding/json"
	"fmt"
	"github.com/tywkeene/autobd/logging"
	"github.com/tywkeene/autobd/manifest"
	"github.com/tywkeene/autobd/packing"
	"github.com/tywkeene/autobd/version"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
	return w.Writer.Write(b)
}

//Check and make sure the client wants or can handle gzip, and replace the writer if it
//can, if not, simply use the normal http.ResponseWriter
func GzipHandler(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			fn(w, r)
			return
		}
		w.Header().Set("Content-Encoding", "gzip")
		gz := gzip.NewWriter(w)
		defer gz.Close()
		gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
		fn(gzr, r)
	}
}

//GetQueryValue() takes a name of a key:value pair to fetchf rom a URL encoded query,
//a http.ResponseWriter 'w', and a http.Request 'r'. In the event that an error is encountered
//the error will be returned to the client via logging facilities that use 'w' and 'r'
func GetQueryValue(name string, w http.ResponseWriter, r *http.Request) string {
	query, err := url.ParseQuery(r.URL.RawQuery)
	if err != nil {
		logging.LogHttpErr(w, r, fmt.Errorf("Query parse error"), http.StatusInternalServerError)
		return ""
	}
	value := query.Get(name)
	if len(value) == 0 || value == "" {
		logging.LogHttpErr(w, r, fmt.Errorf("Must specify %s", name), http.StatusBadRequest)
		return ""
	}
	return value
}

//ServeManifest() is the http handler for the "/manifest" API endpoint.
//It takes the requested directory passed as a url parameter "dir" i.e "/manifest?dir=/"
//
//It will then generate a manifest by calling api.GetQueryValue(), then writes it to the client as a
//map[string]*manifest.Manifest encoded in json
func ServeManifest(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	dir := GetQueryValue("dir", w, r)
	if dir == "" {
		return
	}
	dirManifest, err := manifest.GetManifest(dir)
	if err != nil {
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting manifest"), http.StatusInternalServerError)
		return
	}
	serial, _ := json.MarshalIndent(&dirManifest, "  ", "  ")
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Server", "Autobd v"+version.Server())
	io.WriteString(w, string(serial))
}

//ServeServerVer() is the http handler for the "/version" http API endpoint.
//It writes the json encoded struct version.VersionInfo to the client
func ServeServerVer(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	serialVer, _ := json.MarshalIndent(&version.VersionInfo{version.Server(), version.Commit()}, "  ", "  ")

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Server", "Autobd v"+version.Server())
	io.WriteString(w, string(serialVer))
}

//ServeSync() is the http handler for the "/sync" http API endpoint.
//It takes the requested directory or file name passed as a url parameter "grab" i.e "/sync?grab=file1"
//
//If the requested file is a directory, it will be tarballed and the "Content-Type" http-header will be
//set to "application/x-tar".
//If the file is a normal file, it will be served with http.ServeContent(), with the Content-Type http-header
//set by http.ServeContent()
func ServeSync(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	grab := GetQueryValue("grab", w, r)
	if grab == "" {
		return
	}
	fd, err := os.Open(grab)
	if err != nil {
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting file"), http.StatusInternalServerError)
		return
	}
	defer fd.Close()
	info, err := fd.Stat()
	if err != nil {
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting file"), http.StatusInternalServerError)
		return
	}
	if info.IsDir() == true {
		w.Header().Set("Content-Type", "application/x-tar")
		if err := packing.PackDir(grab, w); err != nil {
			logging.LogHttpErr(w, r, fmt.Errorf("Error packing directory"), http.StatusInternalServerError)
			return
		}
		return
	}
	http.ServeContent(w, r, grab, info.ModTime(), fd)
}

func SetupRoutes() {
	http.HandleFunc("/"+"v"+version.Major()+"/manifest", GzipHandler(ServeManifest))
	http.HandleFunc("/"+"v"+version.Major()+"/sync", GzipHandler(ServeSync))
	http.HandleFunc("/version", GzipHandler(ServeServerVer))
}
<commit_msg>Added more logging to api errors<commit_after>//Package api implements the endpoints and utilities necessary to present
//a consistent API to autobd-nodes
//Currently there are three endpoints:
//
//NOTE: All endpoints return a JSON encoded error string on error via logging.LogHttpErr()
//
// /<version>/manifest?dir=<dirname>
//
// /<version>/sync?grab=<file or directory name>
//
// /version
//
//The '/manifest' endpoint takes a directory as an argument passed as the url
//paramater 'dir'. It returns a JSON encoded map of strings->manifest.Manifest
//of the requested directory or a JSON encoded error string on error
//
//The '/sync' endpoint takes a directory or filename as an argument pass as the url
//parameter 'grab'. If 'grab' is a directory, it will set the HTTP header 'Content-Type-'
//to 'application/x-tar', and transport the directory packed as a tarball to the client via packing.PackDir()
//
//The '/version' endpoint simple returns a JSON version.VersionInfo struct
//
package api

import (
	"compress/gzip"
	"encoding/json"
	"fmt"
	"github.com/tywkeene/autobd/logging"
	"github.com/tywkeene/autobd/manifest"
	"github.com/tywkeene/autobd/packing"
	"github.com/tywkeene/autobd/version"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
)

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
	return w.Writer.Write(b)
}

//Check and make sure the client wants or can handle gzip, and replace the writer if it
//can, if not, simply use the normal http.ResponseWriter
func GzipHandler(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			fn(w, r)
			return
		}
		w.Header().Set("Content-Encoding", "gzip")
		gz := gzip.NewWriter(w)
		defer gz.Close()
		gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
		fn(gzr, r)
	}
}

//GetQueryValue() takes a name of a key:value pair to fetchf rom a URL encoded query,
//a http.ResponseWriter 'w', and a http.Request 'r'. In the event that an error is encountered
//the error will be returned to the client via logging facilities that use 'w' and 'r'
func GetQueryValue(name string, w http.ResponseWriter, r *http.Request) string {
	query, err := url.ParseQuery(r.URL.RawQuery)
	if err != nil {
		log.Println(err)
		logging.LogHttpErr(w, r, fmt.Errorf("Query parse error"), http.StatusInternalServerError)
		return ""
	}
	value := query.Get(name)
	if len(value) == 0 || value == "" {
		log.Println(err)
		logging.LogHttpErr(w, r, fmt.Errorf("Must specify %s", name), http.StatusBadRequest)
		return ""
	}
	return value
}

//ServeManifest() is the http handler for the "/manifest" API endpoint.
//It takes the requested directory passed as a url parameter "dir" i.e "/manifest?dir=/"
//
//It will then generate a manifest by calling api.GetQueryValue(), then writes it to the client as a
//map[string]*manifest.Manifest encoded in json
func ServeManifest(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	dir := GetQueryValue("dir", w, r)
	if dir == "" {
		return
	}
	dirManifest, err := manifest.GetManifest(dir)
	if err != nil {
		log.Println(err)
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting manifest"), http.StatusInternalServerError)
		return
	}
	serial, _ := json.MarshalIndent(&dirManifest, "  ", "  ")
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Server", "Autobd v"+version.Server())
	io.WriteString(w, string(serial))
}

//ServeServerVer() is the http handler for the "/version" http API endpoint.
//It writes the json encoded struct version.VersionInfo to the client
func ServeServerVer(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	serialVer, _ := json.MarshalIndent(&version.VersionInfo{version.Server(), version.Commit()}, "  ", "  ")

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Server", "Autobd v"+version.Server())
	io.WriteString(w, string(serialVer))
}

//ServeSync() is the http handler for the "/sync" http API endpoint.
//It takes the requested directory or file name passed as a url parameter "grab" i.e "/sync?grab=file1"
//
//If the requested file is a directory, it will be tarballed and the "Content-Type" http-header will be
//set to "application/x-tar".
//If the file is a normal file, it will be served with http.ServeContent(), with the Content-Type http-header
//set by http.ServeContent()
func ServeSync(w http.ResponseWriter, r *http.Request) {
	logging.LogHttp(r)
	grab := GetQueryValue("grab", w, r)
	if grab == "" {
		return
	}
	fd, err := os.Open(grab)
	if err != nil {
		log.Println(err)
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting file"), http.StatusInternalServerError)
		return
	}
	defer fd.Close()
	info, err := fd.Stat()
	if err != nil {
		log.Println(err)
		logging.LogHttpErr(w, r, fmt.Errorf("Error getting file"), http.StatusInternalServerError)
		return
	}
	if info.IsDir() == true {
		w.Header().Set("Content-Type", "application/x-tar")
		if err := packing.PackDir(grab, w); err != nil {
			log.Println(err)
			logging.LogHttpErr(w, r, fmt.Errorf("Error packing directory"), http.StatusInternalServerError)
			return
		}
		return
	}
	http.ServeContent(w, r, grab, info.ModTime(), fd)
}

func SetupRoutes() {
	http.HandleFunc("/"+"v"+version.Major()+"/manifest", GzipHandler(ServeManifest))
	http.HandleFunc("/"+"v"+version.Major()+"/sync", GzipHandler(ServeSync))
	http.HandleFunc("/version", GzipHandler(ServeServerVer))
}
<|endoftext|>


<commit_before>// Package api contains api service and web panel
// Project Gonder.
// Author Supme
// Copyright Supme 2016
// License http://opensource.org/licenses/MIT MIT License
//
//  THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
// Please see the License.txt file for more information.
package api

import (
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/NYTimes/gziphandler"
	"github.com/supme/gonder/models"
	"github.com/tdewolff/minify"
	minifyCSS "github.com/tdewolff/minify/css"
	minifyHTML "github.com/tdewolff/minify/html"
	minifyJS "github.com/tdewolff/minify/js"
	minifyJSON "github.com/tdewolff/minify/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"runtime"
	"strconv"
)

var (
	user   auth
	apilog *log.Logger
	min    *minify.M
)

func init() {
	min = minify.New()
	min.AddFunc("text/css", minifyCSS.Minify)
	min.AddFunc("text/html", minifyHTML.Minify)
	min.AddFunc("application/javascript", minifyJS.Minify)
	min.AddFunc("application/json", minifyJSON.Minify)
}

func apiHandler(fn http.HandlerFunc) http.Handler {
	return gziphandler.GzipHandler(min.Middleware(fn))
}

func apiHandlerCheck(fn http.HandlerFunc) http.Handler {
	return gziphandler.GzipHandler(min.Middleware(user.Check(fn)))
}

// Run start api server
func Run() {
	l, err := os.OpenFile(models.FromRootDir("log/api.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Printf("error opening api log file: %v", err)
	}
	defer l.Close()
	apilog = log.New(io.MultiWriter(l, os.Stdout), "", log.Ldate|log.Ltime)

	api := http.NewServeMux()

	api.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		mem := new(runtime.MemStats)
		runtime.ReadMemStats(mem)
		w.Write([]byte("Welcome to San Tropez! (Conn: " + strconv.Itoa(models.Db.Stats().OpenConnections) + " Allocate: " + strconv.FormatUint(mem.Alloc, 10) + ")"))
	})

	// API
	//api.HandleFunc("/api/", user.Check(apiRequest))
	api.Handle("/api/", apiHandlerCheck(apiRequest))

	// Reports
	api.Handle("/report", apiHandlerCheck(report))
	api.Handle("/report/status", apiHandlerCheck(reportCampaignStatus))
	api.Handle("/report/jump", apiHandlerCheck(reportJumpDetailedCount))
	api.Handle("/report/unsubscribed", apiHandlerCheck(reportUnsubscribed))

	api.Handle("/preview", apiHandlerCheck(getMailPreview))
	api.Handle("/unsubscribe", apiHandlerCheck(getUnsubscribePreview))

	api.Handle("/filemanager", apiHandlerCheck(filemanager))

	// Static dirs
	//api.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(models.FromRootDir("api/http/assets/")))))
	api.Handle("/assets/", apiHandler(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			http.ServeFile(w, r, path.Join(models.FromRootDir("api/http/"),
				r.URL.Path))
		})))

	api.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(models.FromRootDir("files/")))))

	api.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/x-icon")
		ico, _ := base64.StdEncoding.DecodeString("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAByGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/8q2uP9yGSL/yra4/3IZIv/Ktrj/yra4/3IZIv9yGSL/yra4/8q2uP9yGSL/yra4/3IZIv/Ktrj/chki/3IZIv/Ktrj/chki/+je3/9yGSL/yra4/3IZIv/Ktrj/chki/8q2uP9yGSL/chki/8q2uP9yGSL/yra4/3IZIv9yGSL/yra4/+je3//Ktrj/chki/8q2uP9yGSL/yra4/3IZIv/Ktrj/yra4/3IZIv/Ktrj/yra4/3IZIv9yGSL/chki/+je3/9yGSL/yra4/3IZIv/Ktrj/chki/8q2uP9yGSL/yra4/3IZIv9yGSL/yra4/3IZIv/Ktrj/chki/3IZIv/Ktrj/chki/8q2uP9yGSL/yra4/8q2uP9yGSL/chki/8q2uP/Ktrj/chki/8q2uP/Ktrj/yra4/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/+je3//o3t//6N7f/+je3//o3t//6N7f/+je3/9yGSL/6N7f/+je3//o3t//6N7f/+je3//o3t//chki/3IZIv/o3t//yra4/8q2uP/Ktrj/yra4/8q2uP/Ktrj/chki/8q2uP/Ktrj/yra4/8q2uP/Ktrj/6N7f/3IZIv9yGSL/6N7f/8q2uP9yGSL/chki/3IZIv/Ktrj/6N7f/3IZIv/Ktrj/yra4/3IZIv9yGSL/yra4/+je3/9yGSL/chki/+je3//Ktrj/chki/8q2uP/o3t//6N7f/8q2uP9yGSL/6N7f/+je3/9yGSL/chki/8q2uP/o3t//chki/3IZIv/o3t//yra4/3IZIv/o3t//yra4/8q2uP/Ktrj/chki/8q2uP/Ktrj/chki/3IZIv/Ktrj/6N7f/3IZIv9yGSL/6N7f/+je3/9yGSL/chki/3IZIv9yGSL/chki/3IZIv/Ktrj/yra4/8q2uP/Ktrj/6N7f/+je3/9yGSL/chki/+je3//o3t//6N7f/+je3//o3t//6N7f/+je3/9yGSL/6N7f/+je3//o3t//6N7f/+je3//o3t//chki/3IZIv/Ktrj/yra4/8q2uP/Ktrj/yra4/8q2uP/Ktrj/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
		w.Write(ico)
	})

	api.HandleFunc("/{{.StatPng}}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/gif")
		blank, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
		w.Write(blank)
	})

	api.Handle("/panel", user.Check(func(w http.ResponseWriter, r *http.Request) {
		if pusher, ok := w.(http.Pusher); ok {
			// Push is supported.
			for _, p := range []string{
				//"/assets/jquery/jquery-3.1.1.min.js",
				//"/assets/w2ui/w2ui.min.js",
				//"/assets/w2ui/w2ui.min.css",
				//"/assets/locale/ru-ru.json",
				//"/assets/ckeditor/ckeditor.js",
				//"/assets/ckeditor/plugins/codemirror/js/codemirror.min.js",
				//"/assets/ckeditor/plugins/codemirror/css/codemirror.min.css",
				//"/assets/panel/layout.js",
				//"/assets/panel/group.js",
				//"/assets/panel/sender.js",
				//"/assets/panel/campaign.js",
				//"/assets/panel/recipient.js",
				//"/assets/panel/profile.js",
				//"/assets/panel/users.js",
				//"/assets/panel/editor.js",
			} {
				if err := pusher.Push(p, nil); err != nil {
					apilog.Printf("Failed to push: %v", err)
				}
			}
		} else {
			apilog.Print("Push not supported")
		}
		if f, err := ioutil.ReadFile(models.FromRootDir("api/http/index.html")); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		} else {
			w.Write(f)
			//fmt.Fprintf(w, string(f))
		}
	}))

	api.HandleFunc("/logout", user.Logout)

	api.HandleFunc("/status/ws/campaign.log", user.Check(campaignLog))
	api.HandleFunc("/status/ws/api.log", user.Check(apiLog))
	api.HandleFunc("/status/ws/utm.log", user.Check(utmLog))
	api.HandleFunc("/status/ws/main.log", user.Check(mainLog))

	apilog.Println("API listening on port " + models.Config.APIPort + "...")
	apilog.Fatal(http.ListenAndServeTLS(":"+models.Config.APIPort, models.FromRootDir("/cert/server.pem"), models.FromRootDir("/cert/server.key"), api))
}

func apiRequest(w http.ResponseWriter, r *http.Request) {
	var js []byte
	if r.FormValue("request") == "" {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	req, err := parseRequest(r.FormValue("request"))
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(fmt.Sprintf(`{"status": "error", "message": "%s"}`, err)))
		return
	}

	switch r.URL.Path {
	case "/api/users":
		js, err = users(req)
	case "/api/groups":
		js, err = groups(req)
	case "/api/campaign":
		js, err = campaign(req)
	case "/api/campaigns":
		js, err = campaigns(req)
	case "/api/profilelist":
		js, err = profilesList(req)
	case "/api/recipients":
		js, err = recipients(req)
	case "/api/sender":
		js, err = sender(req)
	case "/api/senderlist":
		js, err = senderList(req)
	case "/api/units":
		js, err = units(req)
	case "/api/profiles":
		js, err = profiles(req)
	default:
		err = errors.New("Path not defined")
	}

	if err != nil {
		js = []byte(fmt.Sprintf(`{"status": "error", "message": "%s"}`, err))
	} else if js == nil {
		js = []byte(`{"status": "success", "message": ""}`)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}
<commit_msg>optional gzip and minify<commit_after>// Package api contains api service and web panel
// Project Gonder.
// Author Supme
// Copyright Supme 2016
// License http://opensource.org/licenses/MIT MIT License
//
//  THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
// Please see the License.txt file for more information.
package api

import (
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/NYTimes/gziphandler"
	"github.com/supme/gonder/models"
	"github.com/tdewolff/minify"
	minifyCSS "github.com/tdewolff/minify/css"
	minifyHTML "github.com/tdewolff/minify/html"
	minifyJS "github.com/tdewolff/minify/js"
	minifyJSON "github.com/tdewolff/minify/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"runtime"
	"strconv"
)

var (
	user   auth
	apilog *log.Logger
	min    *minify.M
)

const (
	useGZIP = true

	useMinify     = false
	useMinifyCSS  = true
	useMinifyHTML = true
	useMinifyJS   = true
	useMinifyJSON = true
)

func init() {
	min = minify.New()
	if useMinifyCSS {
		min.AddFunc("text/css", minifyCSS.Minify)
	}
	if useMinifyHTML {
		min.AddFunc("text/html", minifyHTML.Minify)
	}
	if useMinifyJS {
		min.AddFunc("application/javascript", minifyJS.Minify)
	}
	if useMinifyJSON {
		min.AddFunc("application/json", minifyJSON.Minify)
	}
}

func apiHandler(fn http.HandlerFunc, checkAuth bool) http.Handler {
	var handler http.Handler
	if checkAuth {
		handler = user.Check(fn)
	} else {
		handler = fn
	}
	if useMinify {
		handler = min.Middleware(handler)
	}
	if useGZIP {
		handler = gziphandler.GzipHandler(handler)
	}
	return handler
}

// Run start api server
func Run() {
	l, err := os.OpenFile(models.FromRootDir("log/api.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Printf("error opening api log file: %v", err)
	}
	defer l.Close()
	apilog = log.New(io.MultiWriter(l, os.Stdout), "", log.Ldate|log.Ltime)

	api := http.NewServeMux()

	api.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		mem := new(runtime.MemStats)
		runtime.ReadMemStats(mem)
		w.Write([]byte("Welcome to San Tropez! (Conn: " + strconv.Itoa(models.Db.Stats().OpenConnections) + " Allocate: " + strconv.FormatUint(mem.Alloc, 10) + ")"))
	})

	// API
	//api.HandleFunc("/api/", user.Check(apiRequest))
	api.Handle("/api/", apiHandler(apiRequest, true))

	// Reports
	api.Handle("/report", apiHandler(report, true))
	api.Handle("/report/status", apiHandler(reportCampaignStatus, true))
	api.Handle("/report/jump", apiHandler(reportJumpDetailedCount, true))
	api.Handle("/report/unsubscribed", apiHandler(reportUnsubscribed, true))

	api.Handle("/preview", apiHandler(getMailPreview, true))
	api.Handle("/unsubscribe", apiHandler(getUnsubscribePreview, true))

	api.Handle("/filemanager", apiHandler(filemanager, true))

	// Static dirs
	//api.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(models.FromRootDir("api/http/assets/")))))
	api.Handle("/assets/", apiHandler(http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			http.ServeFile(w, r, path.Join(models.FromRootDir("api/http/"),
				r.URL.Path))
		}), false))

	api.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(models.FromRootDir("files/")))))

	api.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/x-icon")
		ico, _ := base64.StdEncoding.DecodeString("AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAByGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/8q2uP9yGSL/yra4/3IZIv/Ktrj/yra4/3IZIv9yGSL/yra4/8q2uP9yGSL/yra4/3IZIv/Ktrj/chki/3IZIv/Ktrj/chki/+je3/9yGSL/yra4/3IZIv/Ktrj/chki/8q2uP9yGSL/chki/8q2uP9yGSL/yra4/3IZIv9yGSL/yra4/+je3//Ktrj/chki/8q2uP9yGSL/yra4/3IZIv/Ktrj/yra4/3IZIv/Ktrj/yra4/3IZIv9yGSL/chki/+je3/9yGSL/yra4/3IZIv/Ktrj/chki/8q2uP9yGSL/yra4/3IZIv9yGSL/yra4/3IZIv/Ktrj/chki/3IZIv/Ktrj/chki/8q2uP9yGSL/yra4/8q2uP9yGSL/chki/8q2uP/Ktrj/chki/8q2uP/Ktrj/yra4/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/+je3//o3t//6N7f/+je3//o3t//6N7f/+je3/9yGSL/6N7f/+je3//o3t//6N7f/+je3//o3t//chki/3IZIv/o3t//yra4/8q2uP/Ktrj/yra4/8q2uP/Ktrj/chki/8q2uP/Ktrj/yra4/8q2uP/Ktrj/6N7f/3IZIv9yGSL/6N7f/8q2uP9yGSL/chki/3IZIv/Ktrj/6N7f/3IZIv/Ktrj/yra4/3IZIv9yGSL/yra4/+je3/9yGSL/chki/+je3//Ktrj/chki/8q2uP/o3t//6N7f/8q2uP9yGSL/6N7f/+je3/9yGSL/chki/8q2uP/o3t//chki/3IZIv/o3t//yra4/3IZIv/o3t//yra4/8q2uP/Ktrj/chki/8q2uP/Ktrj/chki/3IZIv/Ktrj/6N7f/3IZIv9yGSL/6N7f/+je3/9yGSL/chki/3IZIv9yGSL/chki/3IZIv/Ktrj/yra4/8q2uP/Ktrj/6N7f/+je3/9yGSL/chki/+je3//o3t//6N7f/+je3//o3t//6N7f/+je3/9yGSL/6N7f/+je3//o3t//6N7f/+je3//o3t//chki/3IZIv/Ktrj/yra4/8q2uP/Ktrj/yra4/8q2uP/Ktrj/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/chki/3IZIv9yGSL/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
		w.Write(ico)
	})

	api.HandleFunc("/{{.StatPng}}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/gif")
		blank, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
		w.Write(blank)
	})

	api.Handle("/panel", user.Check(func(w http.ResponseWriter, r *http.Request) {
		if pusher, ok := w.(http.Pusher); ok {
			// Push is supported.
			for _, p := range []string{
				"/assets/jquery/jquery-3.1.1.min.js",
				"/assets/w2ui/w2ui.min.js",
				"/assets/w2ui/w2ui.min.css",
				"/assets/locale/ru-ru.json",
				"/assets/ckeditor/ckeditor.js",
				"/assets/ckeditor/plugins/codemirror/js/codemirror.min.js",
				"/assets/ckeditor/plugins/codemirror/css/codemirror.min.css",
				"/assets/panel/layout.js",
				"/assets/panel/group.js",
				"/assets/panel/sender.js",
				"/assets/panel/campaign.js",
				"/assets/panel/recipient.js",
				"/assets/panel/profile.js",
				"/assets/panel/users.js",
				"/assets/panel/editor.js",
			} {
				if err := pusher.Push(p, nil); err != nil {
					apilog.Printf("Failed to push: %v", err)
				}
			}
		} else {
			apilog.Print("Push not supported")
		}
		if f, err := ioutil.ReadFile(models.FromRootDir("api/http/index.html")); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		} else {
			w.Write(f)
			//fmt.Fprintf(w, string(f))
		}
	}))

	api.HandleFunc("/logout", user.Logout)

	api.HandleFunc("/status/ws/campaign.log", user.Check(campaignLog))
	api.HandleFunc("/status/ws/api.log", user.Check(apiLog))
	api.HandleFunc("/status/ws/utm.log", user.Check(utmLog))
	api.HandleFunc("/status/ws/main.log", user.Check(mainLog))

	apilog.Println("API listening on port " + models.Config.APIPort + "...")
	apilog.Fatal(http.ListenAndServeTLS(":"+models.Config.APIPort, models.FromRootDir("/cert/server.pem"), models.FromRootDir("/cert/server.key"), api))
}

func apiRequest(w http.ResponseWriter, r *http.Request) {
	var js []byte
	if r.FormValue("request") == "" {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}

	req, err := parseRequest(r.FormValue("request"))
	if err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte(fmt.Sprintf(`{"status": "error", "message": "%s"}`, err)))
		return
	}

	switch r.URL.Path {
	case "/api/users":
		js, err = users(req)
	case "/api/groups":
		js, err = groups(req)
	case "/api/campaign":
		js, err = campaign(req)
	case "/api/campaigns":
		js, err = campaigns(req)
	case "/api/profilelist":
		js, err = profilesList(req)
	case "/api/recipients":
		js, err = recipients(req)
	case "/api/sender":
		js, err = sender(req)
	case "/api/senderlist":
		js, err = senderList(req)
	case "/api/units":
		js, err = units(req)
	case "/api/profiles":
		js, err = profiles(req)
	default:
		err = errors.New("Path not defined")
	}

	if err != nil {
		js = []byte(fmt.Sprintf(`{"status": "error", "message": "%s"}`, err))
	} else if js == nil {
		js = []byte(`{"status": "success", "message": ""}`)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}
<|endoftext|>


<commit_before>// 4 january 2016
package main

import (
	"os"
	"encoding/xml"
	"path/filepath"
	"net/url"
	"strings"
	"errors"

	"github.com/andlabs/ohv/internal/ui"
)

type devhelpSub struct {
	Name	string			`xml:"name,attr"`
	Link		string			`xml:"link,attr"`
	Subs		[]*devhelpSub		`xml:"sub"`
}

type devhelpKeyword struct {
	Name	string		`xml:"name,attr"`
	Link		string		`xml:"link,attr"`
}

type devhelpBook struct {
	Title			string			`xml:"title,attr"`
	Link			string			`xml:"link,attr"`
	Chapters		[]*devhelpSub		`xml:"chapters>sub"`
	Functions		[]*devhelpKeyword	`xml:"functions>keyword"`
}

type Devhelp struct {
	name	string
	dir		string
	root		Topic
}

func (d *Devhelp) Name() string {
	return d.name
}

func (d *Devhelp) Books() []Topic {
	return []Topic{d.root}
}

func (d *Devhelp) Orphans() []Topic {
	return nil
}

func (d *Devhelp) Lookup(url *url.URL) Topic {
	// TODO
	println(url.String())
	return nil
}

func OpenDevhelp(dir string) (*Devhelp, error) {
	d := new(Devhelp)
	d.dir = dir

	files, err := filepath.Glob(filepath.Join(d.dir, "*.devhelp2"))
	if err != nil {
		return nil, err
	}
	if len(files) != 1 {
		return nil, errors.New("zero or multiple possible devhelp files in " + d.dir)
	}

	file, err := os.Open(files[0])
	if err != nil {
		return nil, err
	}
	defer file.Close()

	dd := new(devhelpBook)
	decoder := xml.NewDecoder(file)
	err = decoder.Decode(dd)
	if err != nil {
		return nil, err
	}

	d.root = newDevhelpTopic(dd.Title, dd.Link, dd.Chapters, d.dir, nil, d)
	return d, nil
}

type DevhelpTopic struct {
	name	string
	dir		string
	path		string
	anchor	string
	parent	Topic
	children	[]Topic
	source	HelpSource
}

func newDevhelpTopic(name string, link string, children []*devhelpSub, dir string, parent Topic, source HelpSource) *DevhelpTopic {
	d := new(DevhelpTopic)
	d.name = name
	// devhelp splits filename from anchor with strchr(): https://git.gnome.org/browse/devhelp/tree/src/dh-assistant-view.c?id=b65a383b5f46908ac16ae4f55c7dcce26c891713#n228
	// TODO does the code REQUIRE an anchor? some s don't have them
	parts := strings.SplitN(link, "#", 2)
	d.path = parts[0]
	if len(parts) == 2 {
		d.anchor = parts[1]
	}
	d.dir = dir
	d.parent = parent
	d.source = source
	for _, child := range children {
		ct := newDevhelpTopic(child.Name, child.Link, child.Subs, d.dir, d, d.source)
		d.children = append(d.children, ct)
	}
	return d
}

func (d *DevhelpTopic) Prepare() (*Prepared, error) {
	return &Prepared{
		Path:			filepath.Join(d.dir, d.path),
		Anchor:		d.anchor,
	}, nil
}

func (d *DevhelpTopic) Name() string {
	return d.name
}

func (d *DevhelpTopic) Parent() Topic {
	return d.parent
}

func (d *DevhelpTopic) Children() []Topic {
	return d.children
}

func (d *DevhelpTopic) Source() HelpSource {
	return d.source
}

func (d *DevhelpTopic) Less(t Topic) bool {
	panic("unreachable; DevhelpTopics are already sorted")
}

func (d *DevhelpTopic) TreeNodeText() string {
	return d.Name()
}

func (d *DevhelpTopic) TreeNodeParent() ui.TreeNode {
	return d.Parent()
}

func (d *DevhelpTopic) TreeNodeChildren() []ui.TreeNode {
	if len(d.children) == 0 {
		return nil
	}
	c := make([]ui.TreeNode, len(d.children))
	for i, n := range d.children {
		c[i] = n
	}
	return c
}
<commit_msg>Preparation for the next devhelp change.<commit_after>// 4 january 2016
package main

// TODO - ideally the keywords would be separate from the topic list, but I'm not sure how — or even if — devhelp maps them back to a topic in the contents

import (
	"os"
	"encoding/xml"
	"path/filepath"
	"net/url"
	"strings"
	"errors"

	"github.com/andlabs/ohv/internal/ui"
)

type devhelpSub struct {
	Name	string			`xml:"name,attr"`
	Link		string			`xml:"link,attr"`
	Subs		[]*devhelpSub		`xml:"sub"`
}

type devhelpKeyword struct {
	Name	string		`xml:"name,attr"`
	Link		string		`xml:"link,attr"`
}

type devhelpBook struct {
	Title			string			`xml:"title,attr"`
	Link			string			`xml:"link,attr"`
	Chapters		[]*devhelpSub		`xml:"chapters>sub"`
	Functions		[]*devhelpKeyword	`xml:"functions>keyword"`
}

type Devhelp struct {
	name	string
	dir		string
	root		Topic
}

func (d *Devhelp) Name() string {
	return d.name
}

func (d *Devhelp) Books() []Topic {
	return []Topic{d.root}
}

func (d *Devhelp) Orphans() []Topic {
	return nil
}

func (d *Devhelp) Lookup(url *url.URL) Topic {
	// TODO
	println(url.String())
	return nil
}

func OpenDevhelp(dir string) (*Devhelp, error) {
	d := new(Devhelp)
	d.dir = dir

	files, err := filepath.Glob(filepath.Join(d.dir, "*.devhelp2"))
	if err != nil {
		return nil, err
	}
	if len(files) != 1 {
		return nil, errors.New("zero or multiple possible devhelp files in " + d.dir)
	}

	file, err := os.Open(files[0])
	if err != nil {
		return nil, err
	}
	defer file.Close()

	dd := new(devhelpBook)
	decoder := xml.NewDecoder(file)
	err = decoder.Decode(dd)
	if err != nil {
		return nil, err
	}

	d.root = newDevhelpTopic(dd.Title, dd.Link, dd.Chapters, d.dir, nil, d)
	return d, nil
}

type DevhelpTopic struct {
	name	string
	dir		string
	path		string
	anchor	string
	parent	Topic
	children	[]Topic
	source	HelpSource
}

func newDevhelpTopic(name string, link string, children []*devhelpSub, dir string, parent Topic, source HelpSource) *DevhelpTopic {
	d := new(DevhelpTopic)
	d.name = name
	// devhelp splits filename from anchor with strchr(): https://git.gnome.org/browse/devhelp/tree/src/dh-assistant-view.c?id=b65a383b5f46908ac16ae4f55c7dcce26c891713#n228
	// TODO does the code REQUIRE an anchor? some s don't have them
	parts := strings.SplitN(link, "#", 2)
	d.path = parts[0]
	if len(parts) == 2 {
		d.anchor = parts[1]
	}
	d.dir = dir
	d.parent = parent
	d.source = source
	for _, child := range children {
		ct := newDevhelpTopic(child.Name, child.Link, child.Subs, d.dir, d, d.source)
		d.children = append(d.children, ct)
	}
	return d
}

func (d *DevhelpTopic) Prepare() (*Prepared, error) {
	return &Prepared{
		Path:			filepath.Join(d.dir, d.path),
		Anchor:		d.anchor,
	}, nil
}

func (d *DevhelpTopic) Name() string {
	return d.name
}

func (d *DevhelpTopic) Parent() Topic {
	return d.parent
}

func (d *DevhelpTopic) Children() []Topic {
	return d.children
}

func (d *DevhelpTopic) Source() HelpSource {
	return d.source
}

func (d *DevhelpTopic) Less(t Topic) bool {
	panic("unreachable; DevhelpTopics are already sorted")
}

func (d *DevhelpTopic) TreeNodeText() string {
	return d.Name()
}

func (d *DevhelpTopic) TreeNodeParent() ui.TreeNode {
	return d.Parent()
}

func (d *DevhelpTopic) TreeNodeChildren() []ui.TreeNode {
	if len(d.children) == 0 {
		return nil
	}
	c := make([]ui.TreeNode, len(d.children))
	for i, n := range d.children {
		c[i] = n
	}
	return c
}
<|endoftext|>


<commit_before>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"encoding/binary"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	opusEncoder *gopus.Encoder
	run         *exec.Cmd
	sendpcm     bool
	mu          sync.Mutex
	PCM         chan []int16
)

func init() {
	PCM = make(chan []int16, 2)
}

// SendPCM will listen on the given channel and send any
// PCM audio to Discord.  Supposedly :)
func SendPCM(v *discordgo.Voice, pcm <-chan []int16) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if sendpcm || pcm == nil {
		mu.Unlock()
		return
	}
	sendpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recvbuf, ok := <-pcm
		if !ok {
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recvbuf, frameSize, maxBytes)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		// send encoded opus data to the sendOpus channel
		v.Opus <- opus
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(s *discordgo.Session, filename string) {

	// Create a shell command "object" to run.
	run = exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	stdout, err := run.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return
	}

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return
	}

	// buffer used during loop below
	audiobuf := make([]int16, frameSize*channels)

	// Send "speaking" packet over the voice websocket
	s.Voice.Speaking(true)

	// Send not "speaking" packet over the websocket when we finish
	defer s.Voice.Speaking(false)

	// will actually only spawn one instance, a bit hacky.
	go SendPCM(s.Voice, PCM)

	for {

		// read data from ffmpeg stdout
		err = binary.Read(stdout, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			fmt.Println("Reached EOF")
			return
		}
		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return
		}

		// Send received PCM to the sendPCM channel
		PCM <- audiobuf
	}
}

// KillPlayer forces the player to stop by killing the ffmpeg cmd process
// this method may be removed later in favor of using chans or bools to
// request a stop.
func KillPlayer() {
	run.Process.Kill()
}
<commit_msg>Bit of logging comments<commit_after>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"encoding/binary"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	opusEncoder *gopus.Encoder
	run         *exec.Cmd
	sendpcm     bool
	mu          sync.Mutex
	PCM         chan []int16
)

func init() {
	PCM = make(chan []int16, 2)
}

// SendPCM will listen on the given channel and send any
// PCM audio to Discord.  Supposedly :)
func SendPCM(v *discordgo.Voice, pcm <-chan []int16) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if sendpcm || pcm == nil {
		mu.Unlock()
		return
	}
	sendpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recvbuf, ok := <-pcm
		if !ok {
			fmt.Println("PCM Channel closed.")
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recvbuf, frameSize, maxBytes)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		if v.Ready == false || v.Opus == nil {
			fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.Opus)
			return
		}
		// send encoded opus data to the sendOpus channel
		v.Opus <- opus
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(s *discordgo.Session, filename string) {

	// Create a shell command "object" to run.
	run = exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	stdout, err := run.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return
	}

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return
	}

	// buffer used during loop below
	audiobuf := make([]int16, frameSize*channels)

	// Send "speaking" packet over the voice websocket
	s.Voice.Speaking(true)

	// Send not "speaking" packet over the websocket when we finish
	defer s.Voice.Speaking(false)

	// will actually only spawn one instance, a bit hacky.
	go SendPCM(s.Voice, PCM)

	for {

		// read data from ffmpeg stdout
		err = binary.Read(stdout, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return
		}

		// Send received PCM to the sendPCM channel
		PCM <- audiobuf
	}
}

// KillPlayer forces the player to stop by killing the ffmpeg cmd process
// this method may be removed later in favor of using chans or bools to
// request a stop.
func KillPlayer() {
	run.Process.Kill()
}
<|endoftext|>


<commit_before>package core

import (
	"fmt"
	"strings"
	"time"
)

type DbType string

type Uri struct {
	DbType  DbType
	Proto   string
	Host    string
	Port    string
	DbName  string
	User    string
	Passwd  string
	Charset string
	Laddr   string
	Raddr   string
	Timeout time.Duration
	Schema  string
}

// a dialect is a driver's wrapper
type Dialect interface {
	SetLogger(logger ILogger)
	Init(*DB, *Uri, string, string) error
	URI() *Uri
	DB() *DB
	DBType() DbType
	SqlType(*Column) string
	FormatBytes(b []byte) string

	DriverName() string
	DataSourceName() string

	QuoteStr() string
	IsReserved(string) bool
	Quote(string) string
	AndStr() string
	OrStr() string
	EqStr() string
	RollBackStr() string
	AutoIncrStr() string

	SupportInsertMany() bool
	SupportEngine() bool
	SupportCharset() bool
	SupportDropIfExists() bool
	IndexOnTable() bool
	ShowCreateNull() bool

	IndexCheckSql(tableName, idxName string) (string, []interface{})
	TableCheckSql(tableName string) (string, []interface{})

	IsColumnExist(tableName string, colName string) (bool, error)

	CreateTableSql(table *Table, tableName, storeEngine, charset string) string
	DropTableSql(tableName string) string
	CreateIndexSql(tableName string, index *Index) string
	DropIndexSql(tableName string, index *Index) string

	ModifyColumnSql(tableName string, col *Column) string

	ForUpdateSql(query string) string

	//CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error
	//MustDropTable(tableName string) error

	GetColumns(tableName string) ([]string, map[string]*Column, error)
	GetTables() ([]*Table, error)
	GetIndexes(tableName string) (map[string]*Index, error)

	Filters() []Filter
	SetArguments(args map[string]string)
}

func OpenDialect(dialect Dialect) (*DB, error) {
	return Open(dialect.DriverName(), dialect.DataSourceName())
}

type Base struct {
	db             *DB
	dialect        Dialect
	driverName     string
	dataSourceName string
	logger         ILogger
	*Uri
}

func (b *Base) DB() *DB {
	return b.db
}

func (b *Base) SetLogger(logger ILogger) {
	b.logger = logger
}

func (b *Base) Init(db *DB, dialect Dialect, uri *Uri, drivername, dataSourceName string) error {
	b.db, b.dialect, b.Uri = db, dialect, uri
	b.driverName, b.dataSourceName = drivername, dataSourceName
	return nil
}

func (b *Base) URI() *Uri {
	return b.Uri
}

func (b *Base) DBType() DbType {
	return b.Uri.DbType
}

func (b *Base) FormatBytes(bs []byte) string {
	return fmt.Sprintf("0x%x", bs)
}

func (b *Base) DriverName() string {
	return b.driverName
}

func (b *Base) ShowCreateNull() bool {
	return true
}

func (b *Base) DataSourceName() string {
	return b.dataSourceName
}

func (b *Base) AndStr() string {
	return "AND"
}

func (b *Base) OrStr() string {
	return "OR"
}

func (b *Base) EqStr() string {
	return "="
}

func (db *Base) RollBackStr() string {
	return "ROLL BACK"
}

func (db *Base) SupportDropIfExists() bool {
	return true
}

func (db *Base) DropTableSql(tableName string) string {
	return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
}

func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
	db.LogSQL(query, args)
	rows, err := db.DB().Query(query, args...)
	if err != nil {
		return false, err
	}
	defer rows.Close()

	if rows.Next() {
		return true, nil
	}
	return false, nil
}

func (db *Base) IsColumnExist(tableName, colName string) (bool, error) {
	query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?"
	query = strings.Replace(query, "`", db.dialect.QuoteStr(), -1)
	return db.HasRecords(query, db.DbName, tableName, colName)
}

/*
func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error {
	sql, args := db.dialect.TableCheckSql(tableName)
	rows, err := db.DB().Query(sql, args...)
	if db.Logger != nil {
		db.Logger.Info("[sql]", sql, args)
	}
	if err != nil {
		return err
	}
	defer rows.Close()

	if rows.Next() {
		return nil
	}

	sql = db.dialect.CreateTableSql(table, tableName, storeEngine, charset)
	_, err = db.DB().Exec(sql)
	if db.Logger != nil {
		db.Logger.Info("[sql]", sql)
	}
	return err
}*/

func (db *Base) CreateIndexSql(tableName string, index *Index) string {
	quote := db.dialect.Quote
	var unique string
	var idxName string
	if index.Type == UniqueType {
		unique = " UNIQUE"
	}
	idxName = index.XName(tableName)
	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique,
		quote(idxName), quote(tableName),
		quote(strings.Join(index.Cols, quote(","))))
}

func (db *Base) DropIndexSql(tableName string, index *Index) string {
	quote := db.dialect.Quote
	var name string
	if index.IsRegular {
		name = index.XName(tableName)
	} else {
		name = index.Name
	}
	return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
}

func (db *Base) ModifyColumnSql(tableName string, col *Column) string {
	return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, col.StringNoPk(db.dialect))
}

func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string {
	var sql string
	sql = "CREATE TABLE IF NOT EXISTS "
	if tableName == "" {
		tableName = table.Name
	}

	sql += b.dialect.Quote(tableName)
	sql += " ("

	if len(table.ColumnsSeq()) > 0 {
		pkList := table.PrimaryKeys

		for _, colName := range table.ColumnsSeq() {
			col := table.GetColumn(colName)
			if col.IsPrimaryKey && len(pkList) == 1 {
				sql += col.String(b.dialect)
			} else {
				sql += col.StringNoPk(b.dialect)
			}
			sql = strings.TrimSpace(sql)
			if b.DriverName() == MYSQL && len(col.Comment) > 0 {
				sql += " COMMENT '" + col.Comment + "'"
			}
			sql += ", "
		}

		if len(pkList) > 1 {
			sql += "PRIMARY KEY ( "
			sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(",")))
			sql += " ), "
		}

		sql = sql[:len(sql)-2]
	}
	sql += ")"

	if b.dialect.SupportEngine() && storeEngine != "" {
		sql += " ENGINE=" + storeEngine
	}
	if b.dialect.SupportCharset() {
		if len(charset) == 0 {
			charset = b.dialect.URI().Charset
		}
		if len(charset) > 0 {
			sql += " DEFAULT CHARSET " + charset
		}
	}

	return sql
}

func (b *Base) ForUpdateSql(query string) string {
	return query + " FOR UPDATE"
}

func (b *Base) LogSQL(sql string, args []interface{}) {
	if b.logger != nil && b.logger.IsShowSQL() {
		if len(args) > 0 {
			b.logger.Infof("[SQL] %v %v", sql, args)
		} else {
			b.logger.Infof("[SQL] %v", sql)
		}
	}
}

func (b *Base) SetArguments(args map[string]string) {
}

var (
	dialects = map[string]func() Dialect{}
)

// RegisterDialect register database dialect
func RegisterDialect(dbName DbType, dialectFunc func() Dialect) {
	if dialectFunc == nil {
		panic("core: Register dialect is nil")
	}
	dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect
}

// QueryDialect query if registed database dialect
func QueryDialect(dbName DbType) Dialect {
	if d, ok := dialects[strings.ToLower(string(dbName))]; ok {
		return d()
	}
	return nil
}
<commit_msg>rename args to params (#40)<commit_after>package core

import (
	"fmt"
	"strings"
	"time"
)

type DbType string

type Uri struct {
	DbType  DbType
	Proto   string
	Host    string
	Port    string
	DbName  string
	User    string
	Passwd  string
	Charset string
	Laddr   string
	Raddr   string
	Timeout time.Duration
	Schema  string
}

// a dialect is a driver's wrapper
type Dialect interface {
	SetLogger(logger ILogger)
	Init(*DB, *Uri, string, string) error
	URI() *Uri
	DB() *DB
	DBType() DbType
	SqlType(*Column) string
	FormatBytes(b []byte) string

	DriverName() string
	DataSourceName() string

	QuoteStr() string
	IsReserved(string) bool
	Quote(string) string
	AndStr() string
	OrStr() string
	EqStr() string
	RollBackStr() string
	AutoIncrStr() string

	SupportInsertMany() bool
	SupportEngine() bool
	SupportCharset() bool
	SupportDropIfExists() bool
	IndexOnTable() bool
	ShowCreateNull() bool

	IndexCheckSql(tableName, idxName string) (string, []interface{})
	TableCheckSql(tableName string) (string, []interface{})

	IsColumnExist(tableName string, colName string) (bool, error)

	CreateTableSql(table *Table, tableName, storeEngine, charset string) string
	DropTableSql(tableName string) string
	CreateIndexSql(tableName string, index *Index) string
	DropIndexSql(tableName string, index *Index) string

	ModifyColumnSql(tableName string, col *Column) string

	ForUpdateSql(query string) string

	//CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error
	//MustDropTable(tableName string) error

	GetColumns(tableName string) ([]string, map[string]*Column, error)
	GetTables() ([]*Table, error)
	GetIndexes(tableName string) (map[string]*Index, error)

	Filters() []Filter
	SetParams(params map[string]string)
}

func OpenDialect(dialect Dialect) (*DB, error) {
	return Open(dialect.DriverName(), dialect.DataSourceName())
}

type Base struct {
	db             *DB
	dialect        Dialect
	driverName     string
	dataSourceName string
	logger         ILogger
	*Uri
}

func (b *Base) DB() *DB {
	return b.db
}

func (b *Base) SetLogger(logger ILogger) {
	b.logger = logger
}

func (b *Base) Init(db *DB, dialect Dialect, uri *Uri, drivername, dataSourceName string) error {
	b.db, b.dialect, b.Uri = db, dialect, uri
	b.driverName, b.dataSourceName = drivername, dataSourceName
	return nil
}

func (b *Base) URI() *Uri {
	return b.Uri
}

func (b *Base) DBType() DbType {
	return b.Uri.DbType
}

func (b *Base) FormatBytes(bs []byte) string {
	return fmt.Sprintf("0x%x", bs)
}

func (b *Base) DriverName() string {
	return b.driverName
}

func (b *Base) ShowCreateNull() bool {
	return true
}

func (b *Base) DataSourceName() string {
	return b.dataSourceName
}

func (b *Base) AndStr() string {
	return "AND"
}

func (b *Base) OrStr() string {
	return "OR"
}

func (b *Base) EqStr() string {
	return "="
}

func (db *Base) RollBackStr() string {
	return "ROLL BACK"
}

func (db *Base) SupportDropIfExists() bool {
	return true
}

func (db *Base) DropTableSql(tableName string) string {
	return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
}

func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
	db.LogSQL(query, args)
	rows, err := db.DB().Query(query, args...)
	if err != nil {
		return false, err
	}
	defer rows.Close()

	if rows.Next() {
		return true, nil
	}
	return false, nil
}

func (db *Base) IsColumnExist(tableName, colName string) (bool, error) {
	query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?"
	query = strings.Replace(query, "`", db.dialect.QuoteStr(), -1)
	return db.HasRecords(query, db.DbName, tableName, colName)
}

/*
func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error {
	sql, args := db.dialect.TableCheckSql(tableName)
	rows, err := db.DB().Query(sql, args...)
	if db.Logger != nil {
		db.Logger.Info("[sql]", sql, args)
	}
	if err != nil {
		return err
	}
	defer rows.Close()

	if rows.Next() {
		return nil
	}

	sql = db.dialect.CreateTableSql(table, tableName, storeEngine, charset)
	_, err = db.DB().Exec(sql)
	if db.Logger != nil {
		db.Logger.Info("[sql]", sql)
	}
	return err
}*/

func (db *Base) CreateIndexSql(tableName string, index *Index) string {
	quote := db.dialect.Quote
	var unique string
	var idxName string
	if index.Type == UniqueType {
		unique = " UNIQUE"
	}
	idxName = index.XName(tableName)
	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique,
		quote(idxName), quote(tableName),
		quote(strings.Join(index.Cols, quote(","))))
}

func (db *Base) DropIndexSql(tableName string, index *Index) string {
	quote := db.dialect.Quote
	var name string
	if index.IsRegular {
		name = index.XName(tableName)
	} else {
		name = index.Name
	}
	return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
}

func (db *Base) ModifyColumnSql(tableName string, col *Column) string {
	return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, col.StringNoPk(db.dialect))
}

func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string {
	var sql string
	sql = "CREATE TABLE IF NOT EXISTS "
	if tableName == "" {
		tableName = table.Name
	}

	sql += b.dialect.Quote(tableName)
	sql += " ("

	if len(table.ColumnsSeq()) > 0 {
		pkList := table.PrimaryKeys

		for _, colName := range table.ColumnsSeq() {
			col := table.GetColumn(colName)
			if col.IsPrimaryKey && len(pkList) == 1 {
				sql += col.String(b.dialect)
			} else {
				sql += col.StringNoPk(b.dialect)
			}
			sql = strings.TrimSpace(sql)
			if b.DriverName() == MYSQL && len(col.Comment) > 0 {
				sql += " COMMENT '" + col.Comment + "'"
			}
			sql += ", "
		}

		if len(pkList) > 1 {
			sql += "PRIMARY KEY ( "
			sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(",")))
			sql += " ), "
		}

		sql = sql[:len(sql)-2]
	}
	sql += ")"

	if b.dialect.SupportEngine() && storeEngine != "" {
		sql += " ENGINE=" + storeEngine
	}
	if b.dialect.SupportCharset() {
		if len(charset) == 0 {
			charset = b.dialect.URI().Charset
		}
		if len(charset) > 0 {
			sql += " DEFAULT CHARSET " + charset
		}
	}

	return sql
}

func (b *Base) ForUpdateSql(query string) string {
	return query + " FOR UPDATE"
}

func (b *Base) LogSQL(sql string, args []interface{}) {
	if b.logger != nil && b.logger.IsShowSQL() {
		if len(args) > 0 {
			b.logger.Infof("[SQL] %v %v", sql, args)
		} else {
			b.logger.Infof("[SQL] %v", sql)
		}
	}
}

func (b *Base) SetParams(params map[string]string) {
}

var (
	dialects = map[string]func() Dialect{}
)

// RegisterDialect register database dialect
func RegisterDialect(dbName DbType, dialectFunc func() Dialect) {
	if dialectFunc == nil {
		panic("core: Register dialect is nil")
	}
	dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect
}

// QueryDialect query if registed database dialect
func QueryDialect(dbName DbType) Dialect {
	if d, ok := dialects[strings.ToLower(string(dbName))]; ok {
		return d()
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	id3 "github.com/mikkyang/id3-go"
	kingpin "gopkg.in/alecthomas/kingpin.v1"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	Header   = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
	iTunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd"
)

type Rss struct {
	XMLName xml.Name `xml:"rss"`
	Channel Channel  `xml:"channel"`
	Version string   `xml:"version,attr"`
	NS      string   `xml:"xmlns:itunes,attr"`
}

type Channel struct {
	PubDate     string  `xml:"pubDate,omitempty"`
	Title       string  `xml:"title,omitempty"`
	Link        string  `xml:"link,omitempty"`
	Description string  `xml:"description"`
	Language    string  `xml:"language,omitempty"`
	Images      []Image `xml:"image,omitempty"`
	Items       []Item  `xml:"item"`
}

type Item struct {
	Title       string    `xml:"title"`
	Description string    `xml:"description"`
	Enclosure   Enclosure `xml:"enclosure"`
	Guid        string    `xml:"guid"`
	Subtitle    string    `xml:"itunes:subtitle,omitempty"`
	Categories  []Text    `xml:"itunes:category,omitempty"`
	PubDate     string    `xml:"pubDate,omitempty"`
}

type Text struct {
	Value string `xml:"text,attr"`
}

type Image struct {
	Link  string `xml:"link"`
	Title string `xml:"title"`
	Url   string `xml:"url"`
}

type Enclosure struct {
	Url    string `xml:"url,attr"`
	Length int64  `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

func fileUrl(relativePath string, baseUrl string) string {
	Url, _ := url.Parse(baseUrl)
	Url.Path += relativePath
	return Url.String()
}

func formatYear(year string) string {
	if len(year) > 0 {
		t, err := time.Parse("20060102", year)
		if err != nil {
			t, err = time.Parse("20060102", year[0:len(year)-1])
			if err != nil {
				t, err = time.Parse("2006", year)
				if err != nil {
					t, err = time.Parse("20060201", year)
					if err != nil {
						return ""
					}
				}
			}
		}
		return t.Format(time.RFC1123Z)
	}
	return year
}

func addMeta(path string, f os.FileInfo, item *Item) {
	fd, err := id3.Open(path)
	if err != nil {
		item.Title = f.Name()
	} else {
		title := fd.Title()
		author := fd.Artist()
		if len(title) > 0 {
			item.Title = title
		} else {
			item.Title = author
			if len(author) > 0 {
				item.Title += " - "
			}
			item.Title += f.Name()
		}
		item.Subtitle = author
		tcon := fd.Frame("TCON")
		if tcon != nil {
			item.Categories = append(item.Categories, Text{Value: tcon.String()})
		}
		item.PubDate = formatYear(fd.Year())
	}
}

func visitFiles(workDir string, channel *Channel, publicUrl string, recursive bool, fileType string) filepath.WalkFunc {
	return func(path string, f os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if f.IsDir() && path != workDir && !recursive {
			return filepath.SkipDir
		}

		if !!f.IsDir() {
			return nil
		}

		matched, _ := filepath.Match("*."+fileType, f.Name())
		if matched {
			url := fileUrl(path[len(workDir)-1:], publicUrl)
			item := Item{Enclosure: Enclosure{Length: f.Size(), Type: "audio/mpeg",
				Url: url}, Guid: url}
			addMeta(path, f, &item)
			channel.Items = append(channel.Items, item)
		}

		return nil

	}
}

type rssHandler struct {
	header string
	body   []byte
}

func (rss *rssHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(rss.header))
	w.Write(rss.body)
}

func writeStartupMsg(workdir string, url string, feed string) {
	fmt.Printf(
		"\x1b[33;1m%v\x1b[0m \x1b[36;1m%v\x1b[0m \x1b[33;1mon:\x1b[0m \x1b[36;1m%v\x1b[0m\n",
		"Starting up dircast, serving", workdir, url)
	fmt.Printf("Download RSS feed from: %v\n", feed)
	fmt.Println("Hit CTRL-C to stop the server")
}

func onShutdown(message string) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		fmt.Printf("\x1b[31;1m%v\x1b[0m\n", message)
		os.Exit(1)
	}()
}

func server(output []byte, workdir string, baseUrl *url.URL) {

	path := baseUrl.Path
	if !strings.HasSuffix(path, "/") {
		path += "/"
	}
	rssFile := path + "rss.xml"

	rss := &rssHandler{header: Header, body: output}
	http.Handle(rssFile, rss)

	fs := http.FileServer(http.Dir(workdir))
	http.Handle(path, http.StripPrefix(path, fs))

	feed := baseUrl.Scheme + "://" + baseUrl.Host + rssFile

	writeStartupMsg(workdir, baseUrl.String(), feed)
	onShutdown("dircast stopped.")
	http.ListenAndServe(baseUrl.Host, nil)

}

var (
	baseUrl     = kingpin.Flag("server", "hostname (and path) to the root e.g. http://myserver.com/rss").Short('s').Default("http://localhost:8000/").URL()
	bind        = kingpin.Flag("bind", "Start HTTP server, bind to the server").Short('b').Bool()
	recursive   = kingpin.Flag("recursive", "how to handle the directory scan").Short('r').Bool()
	language    = kingpin.Flag("language", "the language of the RSS document, a ISO 639 value").Short('l').String()
	title       = kingpin.Flag("title", "RSS channel title").Short('t').Default("RSS FEED").String()
	description = kingpin.Flag("description", "RSS channel description").Short('d').String()
	imageUrl    = kingpin.Flag("image", "Image URL for the RSS channel image").Short('i').URL()
	fileType    = kingpin.Flag("file", "File type to include in the RSS document").Short('f').Default("mp3").String()
	path        = kingpin.Arg("directory", "directory to read files relative from").Required().ExistingDir()
)

func main() {

	kingpin.Version("0.0.1")
	kingpin.Parse()

	channel := &Channel{
		PubDate:     time.Now().Format(time.RFC1123Z),
		Title:       *title,
		Link:        (*baseUrl).String(),
		Description: *description,
		Language:    *language}

	if !strings.HasSuffix((*baseUrl).Path, "/") {
		(*baseUrl).Path = (*baseUrl).Path + "/"
	}

	err := filepath.Walk(*path, visitFiles(*path, channel, (*baseUrl).String(), *recursive, *fileType))
	if *imageUrl != nil {
		channel.Images = append(channel.Images, Image{Title: channel.Title, Link: channel.Link, Url: (*imageUrl).String()})
	}

	if err != nil {
		fmt.Printf("%s: %v\n", os.Args[0], err)
	} else {
		output, err := xml.MarshalIndent(
			&Rss{Channel: *channel, Version: "2.0", NS: iTunesNs}, "", "  ")
		if err != nil {
			fmt.Printf("error: %v\n", err)
		} else {
			if *bind {
				server(output, *path, *baseUrl)
			} else {
				os.Stdout.WriteString(Header)
				os.Stdout.Write(output)
			}
		}
	}

}
<commit_msg>output error if http.ListenAndServe fails<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	id3 "github.com/mikkyang/id3-go"
	kingpin "gopkg.in/alecthomas/kingpin.v1"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	Header   = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
	iTunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd"
)

type Rss struct {
	XMLName xml.Name `xml:"rss"`
	Channel Channel  `xml:"channel"`
	Version string   `xml:"version,attr"`
	NS      string   `xml:"xmlns:itunes,attr"`
}

type Channel struct {
	PubDate     string  `xml:"pubDate,omitempty"`
	Title       string  `xml:"title,omitempty"`
	Link        string  `xml:"link,omitempty"`
	Description string  `xml:"description"`
	Language    string  `xml:"language,omitempty"`
	Images      []Image `xml:"image,omitempty"`
	Items       []Item  `xml:"item"`
}

type Item struct {
	Title       string    `xml:"title"`
	Description string    `xml:"description"`
	Enclosure   Enclosure `xml:"enclosure"`
	Guid        string    `xml:"guid"`
	Subtitle    string    `xml:"itunes:subtitle,omitempty"`
	Categories  []Text    `xml:"itunes:category,omitempty"`
	PubDate     string    `xml:"pubDate,omitempty"`
}

type Text struct {
	Value string `xml:"text,attr"`
}

type Image struct {
	Link  string `xml:"link"`
	Title string `xml:"title"`
	Url   string `xml:"url"`
}

type Enclosure struct {
	Url    string `xml:"url,attr"`
	Length int64  `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

func fileUrl(relativePath string, baseUrl string) string {
	Url, _ := url.Parse(baseUrl)
	Url.Path += relativePath
	return Url.String()
}

func formatYear(year string) string {
	if len(year) > 0 {
		t, err := time.Parse("20060102", year)
		if err != nil {
			t, err = time.Parse("20060102", year[0:len(year)-1])
			if err != nil {
				t, err = time.Parse("2006", year)
				if err != nil {
					t, err = time.Parse("20060201", year)
					if err != nil {
						return ""
					}
				}
			}
		}
		return t.Format(time.RFC1123Z)
	}
	return year
}

func addMeta(path string, f os.FileInfo, item *Item) {
	fd, err := id3.Open(path)
	if err != nil {
		item.Title = f.Name()
	} else {
		title := fd.Title()
		author := fd.Artist()
		if len(title) > 0 {
			item.Title = title
		} else {
			item.Title = author
			if len(author) > 0 {
				item.Title += " - "
			}
			item.Title += f.Name()
		}
		item.Subtitle = author
		tcon := fd.Frame("TCON")
		if tcon != nil {
			item.Categories = append(item.Categories, Text{Value: tcon.String()})
		}
		item.PubDate = formatYear(fd.Year())
	}
}

func visitFiles(workDir string, channel *Channel, publicUrl string, recursive bool, fileType string) filepath.WalkFunc {
	return func(path string, f os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if f.IsDir() && path != workDir && !recursive {
			return filepath.SkipDir
		}

		if !!f.IsDir() {
			return nil
		}

		matched, _ := filepath.Match("*."+fileType, f.Name())
		if matched {
			url := fileUrl(path[len(workDir)-1:], publicUrl)
			item := Item{Enclosure: Enclosure{Length: f.Size(), Type: "audio/mpeg",
				Url: url}, Guid: url}
			addMeta(path, f, &item)
			channel.Items = append(channel.Items, item)
		}

		return nil

	}
}

type rssHandler struct {
	header string
	body   []byte
}

func (rss *rssHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(rss.header))
	w.Write(rss.body)
}

func writeStartupMsg(workdir string, url string, feed string) {
	fmt.Printf(
		"\x1b[33;1m%v\x1b[0m \x1b[36;1m%v\x1b[0m \x1b[33;1mon:\x1b[0m \x1b[36;1m%v\x1b[0m\n",
		"Starting up dircast, serving", workdir, url)
	fmt.Printf("Download RSS feed from: %v\n", feed)
	fmt.Println("Hit CTRL-C to stop the server")
}

func onShutdown(message string) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		fmt.Printf("\x1b[31;1m%v\x1b[0m\n", message)
		os.Exit(1)
	}()
}

func server(output []byte, workdir string, baseUrl *url.URL) error {

	path := baseUrl.Path
	if !strings.HasSuffix(path, "/") {
		path += "/"
	}
	rssFile := path + "rss.xml"

	rss := &rssHandler{header: Header, body: output}
	http.Handle(rssFile, rss)

	fs := http.FileServer(http.Dir(workdir))
	http.Handle(path, http.StripPrefix(path, fs))

	feed := baseUrl.Scheme + "://" + baseUrl.Host + rssFile

	writeStartupMsg(workdir, baseUrl.String(), feed)
	onShutdown("dircast stopped.")

	return http.ListenAndServe(baseUrl.Host, nil)

}

var (
	baseUrl     = kingpin.Flag("server", "hostname (and path) to the root e.g. http://myserver.com/rss").Short('s').Default("http://localhost:8000/").URL()
	bind        = kingpin.Flag("bind", "Start HTTP server, bind to the server").Short('b').Bool()
	recursive   = kingpin.Flag("recursive", "how to handle the directory scan").Short('r').Bool()
	language    = kingpin.Flag("language", "the language of the RSS document, a ISO 639 value").Short('l').String()
	title       = kingpin.Flag("title", "RSS channel title").Short('t').Default("RSS FEED").String()
	description = kingpin.Flag("description", "RSS channel description").Short('d').String()
	imageUrl    = kingpin.Flag("image", "Image URL for the RSS channel image").Short('i').URL()
	fileType    = kingpin.Flag("file", "File type to include in the RSS document").Short('f').Default("mp3").String()
	path        = kingpin.Arg("directory", "directory to read files relative from").Required().ExistingDir()
)

func main() {

	kingpin.Version("0.0.1")
	kingpin.Parse()

	channel := &Channel{
		PubDate:     time.Now().Format(time.RFC1123Z),
		Title:       *title,
		Link:        (*baseUrl).String(),
		Description: *description,
		Language:    *language}

	if !strings.HasSuffix((*baseUrl).Path, "/") {
		(*baseUrl).Path = (*baseUrl).Path + "/"
	}

	err := filepath.Walk(*path, visitFiles(*path, channel, (*baseUrl).String(), *recursive, *fileType))
	if *imageUrl != nil {
		channel.Images = append(channel.Images, Image{Title: channel.Title, Link: channel.Link, Url: (*imageUrl).String()})
	}

	if err != nil {
		fmt.Printf("%s: %v\n", os.Args[0], err)
	} else {
		output, err := xml.MarshalIndent(
			&Rss{Channel: *channel, Version: "2.0", NS: iTunesNs}, "", "  ")
		if err != nil {
			fmt.Printf("error: %v\n", err)
		} else {
			if *bind {
				err = server(output, *path, *baseUrl)
				if err != nil {
					fmt.Printf("error: %v\n", err)
				}
			} else {
				os.Stdout.WriteString(Header)
				os.Stdout.Write(output)
			}
		}
	}

}
<|endoftext|>


<commit_before>package main

import (
	"container/ring"
	"fmt"
	"strings"

	"github.com/bwmarrin/discordgo"
	"go.uber.org/zap"
)

// Bind represents a link between a Discord channel and a Redis queue
type Bind struct {
	DiscordChannel string `json:"channel_id"`
	InputQueue     string `json:"input_queue"`
	OutputQueue    string `json:"output_queue"`
}

// Message represents a message moving through this app
type Message struct {
	User string
	Text string
	// when in the `MessagesToDiscord` queue, this represents a Discord channel
	// and when in the `MessagesToGame` queue, it represents a queue name.
	Destination string
}

// DiscordManager holds program state and shares access to resources
type DiscordManager struct {
	app               *App
	DiscordClient     *discordgo.Session
	MessagesToDiscord chan Message // queue of messages waiting to be sent to Discord
	MessagesToGame    chan Message // queue of messages waiting to be sent to the game server
	MessageHistory    *ring.Ring   // ring-list of the last n messages processed to block duplicates
}

// NewDiscordManager sets up a Discord client and prepares it for starting
func NewDiscordManager(app *App) *DiscordManager {
	var err error

	dm := DiscordManager{
		app: app,
	}

	dm.app.config = app.config

	dm.DiscordClient, err = discordgo.New("Bot " + dm.app.config.DiscordToken)
	if err != nil {
		logger.Fatal("failed to create discord client", zap.Error(err))
	}

	dm.MessagesToDiscord = make(chan Message)
	dm.MessagesToGame = make(chan Message)
	dm.MessageHistory = ring.New(32)

	dm.Connect()

	return &dm
}

// Close cleans up and shuts down the Discord client
func (dm *DiscordManager) Close() error {
	return dm.DiscordClient.Close()
}

// Connect prepares the Discord client library and connects to the API.
func (dm *DiscordManager) Connect() {
	// Once the connection is ready, the client is prepared and the daemon multiplexer is started
	dm.DiscordClient.AddHandler(func(s *discordgo.Session, event *discordgo.Ready) {
		dm.Prepare()
		dm.Daemon()
	})

	err := dm.DiscordClient.Open()
	if err != nil {
		logger.Fatal("discord client connection error", zap.Error(err))
	}
}

// Prepare adds a handler for messages from Discord and a handler for messages from the game.
func (dm *DiscordManager) Prepare() {
	dm.AddDiscordHandler()
	dm.AddGameHandler()
}

// Daemon is a simple mutiplex select across four processes:
// - messages coming from Discord and waiting to get sent to the game
// - messages coming from the game and waiting to get sent to Discord
// - messages being consumed and sent to the game
// - messages being consumed and sent to Discord
// This is a blocking function and exits on fatal error.
func (dm *DiscordManager) Daemon() {
	for {
		select {
		case message := <-dm.MessagesToDiscord:
			logger.Debug("send to discord",
				zap.String("username", message.User),
				zap.String("message", message.Text),
				zap.String("Destination", message.Destination))

			dm.MessageHistory.Value = message.Text
			dm.MessageHistory.Next()
			Text := fmt.Sprintf("%s: %s", message.User, message.Text)

			sr, err := dm.DiscordClient.ChannelMessageSend(message.Destination, Text)
			if err != nil {
				logger.Warn("ChannelMessageSend error",
					zap.Error(err),
					zap.Any("sr", sr))
			}

		case message := <-dm.MessagesToGame:
			logger.Info("send to game",
				zap.String("username", message.User),
				zap.String("message", message.Text),
				zap.String("Destination", message.Destination))

			dm.MessageHistory.Value = message.Text
			dm.MessageHistory.Next()
			raw := fmt.Sprintf("%s:%s", message.User, message.Text)

			err := dm.app.sc.SendMessage(message.Destination, raw)
			if err != nil {
				logger.Warn("rm.SendMessage failed", zap.Error(err))
			}
		}
	}
}

// AddDiscordHandler uses the Discord client gateway event system to bind the Discord MessageCreate
// event to a function that consumes a message and places it on the message queue for being sent to
// the game.
func (dm DiscordManager) AddDiscordHandler() {
	dm.DiscordClient.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
		Destination := dm.GetOutgoingKeyFromChannelID(m.Message.ChannelID)

		if Destination != "" {
			duplicate := false
			dm.MessageHistory.Do(func(i interface{}) {
				if i == m.Message.Content {
					duplicate = true
				}
			})

			if !duplicate {
				dm.MessagesToGame <- Message{m.Message.Author.Username, m.Message.Content, Destination}
			}
		}
	})
}

// AddGameHandler uses the samp-go library to bind to a Redis list that contains chat messages sent
// from the game and places them on a message queue to be sent to the Discord channel.
func (dm DiscordManager) AddGameHandler() {
	for i := range dm.app.config.DiscordBinds {
		dm.app.sc.BindMessage(dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].InputQueue), func(message string) {
			split := strings.SplitN(message, ":", 2)

			if len(split) != 2 {
				logger.Warn("received from game: message malformed, no colon delimiter",
					zap.String("message", message))
				return
			}

			duplicate := false
			dm.MessageHistory.Do(func(i interface{}) {
				if i == split[1] {
					duplicate = true
				}
			})

			if !duplicate {
				dm.MessagesToDiscord <- Message{split[0], split[1], dm.app.config.DiscordBinds[i].DiscordChannel}
			}
		})
	}
}

// GetOutgoingKeyFromChannelID takes a Discord channel ID and returns a Redis queue if it is
// associated with one, otherwise returns an empty string.
func (dm DiscordManager) GetOutgoingKeyFromChannelID(channel string) string {
	for i := range dm.app.config.DiscordBinds {
		if channel == dm.app.config.DiscordBinds[i].DiscordChannel {
			return dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].OutputQueue)
		}
	}
	return ""
}

// GetChannelFromInQueue takes a Redis queue name and returns a Discord channel ID if it is
// associated with one, otherwise returns an empty string.
func (dm DiscordManager) GetChannelFromInQueue(queue string) string {
	for i := range dm.app.config.DiscordBinds {
		if queue == dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].InputQueue) {
			return dm.app.config.DiscordBinds[i].DiscordChannel
		}
	}
	return ""
}

// GetFullRedisKey returns a full redis key for naming queues in the form: myserver.rediscord.queue
func (dm DiscordManager) GetFullRedisKey(name string) string {
	return dm.app.config.Domain + ".rediscord." + name
}
<commit_msg>fixed bot messaging itself<commit_after>package main

import (
	"container/ring"
	"fmt"
	"strings"

	"github.com/bwmarrin/discordgo"
	"go.uber.org/zap"
)

// Bind represents a link between a Discord channel and a Redis queue
type Bind struct {
	DiscordChannel string `json:"channel_id"`
	InputQueue     string `json:"input_queue"`
	OutputQueue    string `json:"output_queue"`
}

// Message represents a message moving through this app
type Message struct {
	User string
	Text string
	// when in the `MessagesToDiscord` queue, this represents a Discord channel
	// and when in the `MessagesToGame` queue, it represents a queue name.
	Destination string
}

// DiscordManager holds program state and shares access to resources
type DiscordManager struct {
	app               *App
	DiscordClient     *discordgo.Session
	MessagesToDiscord chan Message // queue of messages waiting to be sent to Discord
	MessagesToGame    chan Message // queue of messages waiting to be sent to the game server
	MessageHistory    *ring.Ring   // ring-list of the last n messages processed to block duplicates
}

// NewDiscordManager sets up a Discord client and prepares it for starting
func NewDiscordManager(app *App) *DiscordManager {
	var err error

	dm := DiscordManager{
		app: app,
	}

	dm.app.config = app.config

	dm.DiscordClient, err = discordgo.New("Bot " + dm.app.config.DiscordToken)
	if err != nil {
		logger.Fatal("failed to create discord client", zap.Error(err))
	}

	dm.MessagesToDiscord = make(chan Message)
	dm.MessagesToGame = make(chan Message)
	dm.MessageHistory = ring.New(32)

	dm.Connect()

	return &dm
}

// Close cleans up and shuts down the Discord client
func (dm *DiscordManager) Close() error {
	return dm.DiscordClient.Close()
}

// Connect prepares the Discord client library and connects to the API.
func (dm *DiscordManager) Connect() {
	// Once the connection is ready, the client is prepared and the daemon multiplexer is started
	dm.DiscordClient.AddHandler(func(s *discordgo.Session, event *discordgo.Ready) {
		dm.Prepare()
		dm.Daemon()
	})

	err := dm.DiscordClient.Open()
	if err != nil {
		logger.Fatal("discord client connection error", zap.Error(err))
	}
}

// Prepare adds a handler for messages from Discord and a handler for messages from the game.
func (dm *DiscordManager) Prepare() {
	dm.AddDiscordHandler()
	dm.AddGameHandler()
}

// Daemon is a simple mutiplex select across four processes:
// - messages coming from Discord and waiting to get sent to the game
// - messages coming from the game and waiting to get sent to Discord
// - messages being consumed and sent to the game
// - messages being consumed and sent to Discord
// This is a blocking function and exits on fatal error.
func (dm *DiscordManager) Daemon() {
	for {
		select {
		case message := <-dm.MessagesToDiscord:
			logger.Debug("send to discord",
				zap.String("username", message.User),
				zap.String("message", message.Text),
				zap.String("Destination", message.Destination))

			dm.MessageHistory.Value = message.Text
			dm.MessageHistory.Next()
			Text := fmt.Sprintf("%s: %s", message.User, message.Text)

			sr, err := dm.DiscordClient.ChannelMessageSend(message.Destination, Text)
			if err != nil {
				logger.Warn("ChannelMessageSend error",
					zap.Error(err),
					zap.Any("sr", sr))
			}

		case message := <-dm.MessagesToGame:
			logger.Info("send to game",
				zap.String("username", message.User),
				zap.String("message", message.Text),
				zap.String("Destination", message.Destination))

			dm.MessageHistory.Value = message.Text
			dm.MessageHistory.Next()
			raw := fmt.Sprintf("%s:%s", message.User, message.Text)

			err := dm.app.sc.SendMessage(message.Destination, raw)
			if err != nil {
				logger.Warn("rm.SendMessage failed", zap.Error(err))
			}
		}
	}
}

// AddDiscordHandler uses the Discord client gateway event system to bind the Discord MessageCreate
// event to a function that consumes a message and places it on the message queue for being sent to
// the game.
func (dm DiscordManager) AddDiscordHandler() {
	dm.DiscordClient.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
		if m.Author.Bot {
			return
		}

		Destination := dm.GetOutgoingKeyFromChannelID(m.Message.ChannelID)

		if Destination != "" {
			duplicate := false
			dm.MessageHistory.Do(func(i interface{}) {
				if i == m.Message.Content {
					duplicate = true
				}
			})

			if !duplicate {
				dm.MessagesToGame <- Message{m.Message.Author.Username, m.Message.Content, Destination}
			}
		}
	})
}

// AddGameHandler uses the samp-go library to bind to a Redis list that contains chat messages sent
// from the game and places them on a message queue to be sent to the Discord channel.
func (dm DiscordManager) AddGameHandler() {
	for i := range dm.app.config.DiscordBinds {
		dm.app.sc.BindMessage(dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].InputQueue), func(message string) {
			split := strings.SplitN(message, ":", 2)

			if len(split) != 2 {
				logger.Warn("received from game: message malformed, no colon delimiter",
					zap.String("message", message))
				return
			}

			duplicate := false
			dm.MessageHistory.Do(func(i interface{}) {
				if i == split[1] {
					duplicate = true
				}
			})

			if !duplicate {
				dm.MessagesToDiscord <- Message{split[0], split[1], dm.app.config.DiscordBinds[i].DiscordChannel}
			}
		})
	}
}

// GetOutgoingKeyFromChannelID takes a Discord channel ID and returns a Redis queue if it is
// associated with one, otherwise returns an empty string.
func (dm DiscordManager) GetOutgoingKeyFromChannelID(channel string) string {
	for i := range dm.app.config.DiscordBinds {
		if channel == dm.app.config.DiscordBinds[i].DiscordChannel {
			return dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].OutputQueue)
		}
	}
	return ""
}

// GetChannelFromInQueue takes a Redis queue name and returns a Discord channel ID if it is
// associated with one, otherwise returns an empty string.
func (dm DiscordManager) GetChannelFromInQueue(queue string) string {
	for i := range dm.app.config.DiscordBinds {
		if queue == dm.GetFullRedisKey(dm.app.config.DiscordBinds[i].InputQueue) {
			return dm.app.config.DiscordBinds[i].DiscordChannel
		}
	}
	return ""
}

// GetFullRedisKey returns a full redis key for naming queues in the form: myserver.rediscord.queue
func (dm DiscordManager) GetFullRedisKey(name string) string {
	return dm.app.config.Domain + ".rediscord." + name
}
<|endoftext|>


<commit_before>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains high level helper functions and easy entry points for the
// entire discordgo package.  These functions are beling developed and are very
// experimental at this point.  They will most likley change so please use the
// low level functions if that's a problem.

// Package discordgo provides Discord binding for Go
package discordgo

import (
	"errors"
	"fmt"
	"net/http"
	"time"
)

// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
const VERSION = "0.17.0"

// ErrMFA will be risen by New when the user has 2FA.
var ErrMFA = errors.New("account has 2FA enabled")

// New creates a new Discord session and will automate some startup
// tasks if given enough information to do so.  Currently you can pass zero
// arguments and it will return an empty Discord session.
// There are 3 ways to call New:
//     With a single auth token - All requests will use the token blindly,
//         no verification of the token will be done and requests may fail.
//         IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT `
//         eg: `"Bot <token>"`
//     With an email and password - Discord will sign in with the provided
//         credentials.
//     With an email, password and auth token - Discord will verify the auth
//         token, if it is invalid it will sign in with the provided
//         credentials. This is the Discord recommended way to sign in.
//
// NOTE: While email/pass authentication is supported by DiscordGo it is
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token
// and then use that authentication token for all future connections.
// Also, doing any form of automation with a user (non Bot) account may result
// in that account being permanently banned from Discord.
func New(args ...interface{}) (s *Session, err error) {

	// Create an empty Session interface.
	s = &Session{
		State:                  NewState(),
		Ratelimiter:            NewRatelimiter(),
		StateEnabled:           true,
		Compress:               true,
		ShouldReconnectOnError: true,
		ShardID:                0,
		ShardCount:             1,
		MaxRestRetries:         3,
		Client:                 &http.Client{Timeout: (20 * time.Second)},
		sequence:               new(int64),
		LastHeartbeatAck:       time.Now().UTC(),
	}

	// If no arguments are passed return the empty Session interface.
	if args == nil {
		return
	}

	// Variables used below when parsing func arguments
	var auth, pass string

	// Parse passed arguments
	for _, arg := range args {

		switch v := arg.(type) {

		case []string:
			if len(v) > 3 {
				err = fmt.Errorf("too many string parameters provided")
				return
			}

			// First string is either token or username
			if len(v) > 0 {
				auth = v[0]
			}

			// If second string exists, it must be a password.
			if len(v) > 1 {
				pass = v[1]
			}

			// If third string exists, it must be an auth token.
			if len(v) > 2 {
				s.Token = v[2]
			}

		case string:
			// First string must be either auth token or username.
			// Second string must be a password.
			// Only 2 input strings are supported.

			if auth == "" {
				auth = v
			} else if pass == "" {
				pass = v
			} else if s.Token == "" {
				s.Token = v
			} else {
				err = fmt.Errorf("too many string parameters provided")
				return
			}

			//		case Config:
			// TODO: Parse configuration struct

		default:
			err = fmt.Errorf("unsupported parameter type provided")
			return
		}
	}

	// If only one string was provided, assume it is an auth token.
	// Otherwise get auth token from Discord, if a token was specified
	// Discord will verify it for free, or log the user in if it is
	// invalid.
	if pass == "" {
		s.Token = auth
	} else {
		err = s.Login(auth, pass)
		if err != nil || s.Token == "" {
			if s.MFA {
				err = ErrMFA
			} else {
				err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
			}
			return
		}
	}

	// The Session is now able to have RestAPI methods called on it.
	// It is recommended that you now call Open() so that events will trigger.

	return
}
<commit_msg>Change version to v0.18.0-alpha<commit_after>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains high level helper functions and easy entry points for the
// entire discordgo package.  These functions are beling developed and are very
// experimental at this point.  They will most likley change so please use the
// low level functions if that's a problem.

// Package discordgo provides Discord binding for Go
package discordgo

import (
	"errors"
	"fmt"
	"net/http"
	"time"
)

// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
const VERSION = "0.18.0-alpha"

// ErrMFA will be risen by New when the user has 2FA.
var ErrMFA = errors.New("account has 2FA enabled")

// New creates a new Discord session and will automate some startup
// tasks if given enough information to do so.  Currently you can pass zero
// arguments and it will return an empty Discord session.
// There are 3 ways to call New:
//     With a single auth token - All requests will use the token blindly,
//         no verification of the token will be done and requests may fail.
//         IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT `
//         eg: `"Bot <token>"`
//     With an email and password - Discord will sign in with the provided
//         credentials.
//     With an email, password and auth token - Discord will verify the auth
//         token, if it is invalid it will sign in with the provided
//         credentials. This is the Discord recommended way to sign in.
//
// NOTE: While email/pass authentication is supported by DiscordGo it is
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token
// and then use that authentication token for all future connections.
// Also, doing any form of automation with a user (non Bot) account may result
// in that account being permanently banned from Discord.
func New(args ...interface{}) (s *Session, err error) {

	// Create an empty Session interface.
	s = &Session{
		State:                  NewState(),
		Ratelimiter:            NewRatelimiter(),
		StateEnabled:           true,
		Compress:               true,
		ShouldReconnectOnError: true,
		ShardID:                0,
		ShardCount:             1,
		MaxRestRetries:         3,
		Client:                 &http.Client{Timeout: (20 * time.Second)},
		sequence:               new(int64),
		LastHeartbeatAck:       time.Now().UTC(),
	}

	// If no arguments are passed return the empty Session interface.
	if args == nil {
		return
	}

	// Variables used below when parsing func arguments
	var auth, pass string

	// Parse passed arguments
	for _, arg := range args {

		switch v := arg.(type) {

		case []string:
			if len(v) > 3 {
				err = fmt.Errorf("too many string parameters provided")
				return
			}

			// First string is either token or username
			if len(v) > 0 {
				auth = v[0]
			}

			// If second string exists, it must be a password.
			if len(v) > 1 {
				pass = v[1]
			}

			// If third string exists, it must be an auth token.
			if len(v) > 2 {
				s.Token = v[2]
			}

		case string:
			// First string must be either auth token or username.
			// Second string must be a password.
			// Only 2 input strings are supported.

			if auth == "" {
				auth = v
			} else if pass == "" {
				pass = v
			} else if s.Token == "" {
				s.Token = v
			} else {
				err = fmt.Errorf("too many string parameters provided")
				return
			}

			//		case Config:
			// TODO: Parse configuration struct

		default:
			err = fmt.Errorf("unsupported parameter type provided")
			return
		}
	}

	// If only one string was provided, assume it is an auth token.
	// Otherwise get auth token from Discord, if a token was specified
	// Discord will verify it for free, or log the user in if it is
	// invalid.
	if pass == "" {
		s.Token = auth
	} else {
		err = s.Login(auth, pass)
		if err != nil || s.Token == "" {
			if s.MFA {
				err = ErrMFA
			} else {
				err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
			}
			return
		}
	}

	// The Session is now able to have RestAPI methods called on it.
	// It is recommended that you now call Open() so that events will trigger.

	return
}
<|endoftext|>