Files
iptv-proxy/pkg/server/xtreamHandles.go
Pierre-Emmanuel Jacquier 20f07dc0a1 Add License on files
Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com>
2020-04-19 13:35:29 +02:00

277 lines
6.8 KiB
Go

/*
* Iptv-Proxy is a project to proxyfie an m3u file and to proxyfie an Xtream iptv service (client API).
* Copyright (C) 2020 Pierre-Emmanuel Jacquier
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package server
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/jamesnetherton/m3u"
xtreamapi "github.com/pierre-emmanuelJ/iptv-proxy/pkg/xtream-proxy"
uuid "github.com/satori/go.uuid"
)
type cacheMeta struct {
string
time.Time
}
var hlsChannelsRedirectURL map[string]url.URL = map[string]url.URL{}
var hlsChannelsRedirectURLLock = sync.RWMutex{}
// XXX Use key/value storage e.g: etcd, redis...
// and remove that dirty globals
var xtreamM3uCache map[string]cacheMeta = map[string]cacheMeta{}
var xtreamM3uCacheLock = sync.RWMutex{}
func (c *Config) cacheXtreamM3u(m3uURL *url.URL) error {
xtreamM3uCacheLock.Lock()
defer xtreamM3uCacheLock.Unlock()
playlist, err := m3u.Parse(m3uURL.String())
if err != nil {
return err
}
tmp := *c
tmp.playlist = &playlist
path := filepath.Join("/tmp", uuid.NewV4().String()+".iptv-proxy")
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
if err := tmp.marshallInto(f, true); err != nil {
return err
}
xtreamM3uCache[m3uURL.String()] = cacheMeta{path, time.Now()}
return nil
}
func (c *Config) xtreamGetAuto(ctx *gin.Context) {
newQuery := ctx.Request.URL.Query()
q := c.RemoteURL.Query()
for k, v := range q {
if k == "username" || k == "password" {
continue
}
newQuery.Add(k, strings.Join(v, ","))
}
ctx.Request.URL.RawQuery = newQuery.Encode()
c.xtreamGet(ctx)
}
func (c *Config) xtreamGet(ctx *gin.Context) {
rawURL := fmt.Sprintf("%s/get.php?username=%s&password=%s", c.XtreamBaseURL, c.XtreamUser, c.XtreamPassword)
q := ctx.Request.URL.Query()
for k, v := range q {
if k == "username" || k == "password" {
continue
}
rawURL = fmt.Sprintf("%s&%s=%s", rawURL, k, strings.Join(v, ","))
}
m3uURL, err := url.Parse(rawURL)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
xtreamM3uCacheLock.RLock()
meta, ok := xtreamM3uCache[m3uURL.String()]
d := time.Now().Sub(meta.Time)
if !ok || d.Hours() >= float64(c.M3UCacheExpiration) {
log.Printf("[iptv-proxy] %v | %s | xtream cache m3u file\n", time.Now().Format("2006/01/02 - 15:04:05"), ctx.ClientIP())
xtreamM3uCacheLock.RUnlock()
if err := c.cacheXtreamM3u(m3uURL); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
} else {
xtreamM3uCacheLock.RUnlock()
}
ctx.Header("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, c.M3UFileName))
xtreamM3uCacheLock.RLock()
path := xtreamM3uCache[m3uURL.String()].string
xtreamM3uCacheLock.RUnlock()
ctx.Header("Content-Type", "application/octet-stream")
ctx.File(path)
}
func (c *Config) xtreamPlayerAPIGET(ctx *gin.Context) {
c.xtreamPlayerAPI(ctx, ctx.Request.URL.Query())
}
func (c *Config) xtreamPlayerAPIPOST(ctx *gin.Context) {
contents, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
q, err := url.ParseQuery(string(contents))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.xtreamPlayerAPI(ctx, q)
}
func (c *Config) xtreamPlayerAPI(ctx *gin.Context, q url.Values) {
var action string
if len(q["action"]) > 0 {
action = q["action"][0]
}
client, err := xtreamapi.New(c.XtreamUser, c.XtreamPassword, c.XtreamBaseURL)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
resp, httpcode, err := client.Action(c.ProxyConfig, action, q)
if err != nil {
ctx.AbortWithError(httpcode, err)
return
}
log.Printf("[iptv-proxy] %v | %s |Action\t%s\n", time.Now().Format("2006/01/02 - 15:04:05"), ctx.ClientIP(), action)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.JSON(http.StatusOK, resp)
}
func (c *Config) xtreamXMLTV(ctx *gin.Context) {
client, err := xtreamapi.New(c.XtreamUser, c.XtreamPassword, c.XtreamBaseURL)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
resp, err := client.GetXMLTV()
client.GetVideoOnDemandCategories()
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.Data(http.StatusOK, "application/xml", resp)
}
func (c *Config) xtreamStream(ctx *gin.Context) {
id := ctx.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/%s/%s/%s", c.XtreamBaseURL, c.XtreamUser, c.XtreamPassword, id))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.stream(ctx, rpURL)
}
func (c *Config) xtreamStreamLive(ctx *gin.Context) {
id := ctx.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/live/%s/%s/%s", c.XtreamBaseURL, c.XtreamUser, c.XtreamPassword, id))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.stream(ctx, rpURL)
}
func (c *Config) xtreamStreamMovie(ctx *gin.Context) {
id := ctx.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/movie/%s/%s/%s", c.XtreamBaseURL, c.XtreamUser, c.XtreamPassword, id))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.stream(ctx, rpURL)
}
func (c *Config) xtreamStreamSeries(ctx *gin.Context) {
id := ctx.Param("id")
rpURL, err := url.Parse(fmt.Sprintf("%s/series/%s/%s/%s", c.XtreamBaseURL, c.XtreamUser, c.XtreamPassword, id))
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.stream(ctx, rpURL)
}
func (c *Config) hlsrStream(ctx *gin.Context) {
hlsChannelsRedirectURLLock.RLock()
url, ok := hlsChannelsRedirectURL[ctx.Param("channel")+".m3u8"]
if !ok {
ctx.AbortWithError(http.StatusNotFound, errors.New("HSL redirect url not found"))
hlsChannelsRedirectURLLock.RUnlock()
return
}
hlsChannelsRedirectURLLock.RUnlock()
req, err := url.Parse(
fmt.Sprintf(
"%s://%s/hlsr/%s/%s/%s/%s/%s/%s",
url.Scheme,
url.Host,
ctx.Param("token"),
c.XtreamUser,
c.XtreamPassword,
ctx.Param("channel"),
ctx.Param("hash"),
ctx.Param("chunk"),
),
)
if err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
c.stream(ctx, req)
}