mirror of
https://github.com/pierre-emmanuelJ/iptv-proxy.git
synced 2026-05-19 21:25:29 +02:00
Xtream: Genarate playlist from API live categories (#104)
Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4034a960a4
commit
d48a875c3a
24
cmd/root.go
24
cmd/root.go
@@ -73,17 +73,18 @@ var rootCmd = &cobra.Command{
|
||||
Hostname: viper.GetString("hostname"),
|
||||
Port: viper.GetInt("port"),
|
||||
},
|
||||
RemoteURL: remoteHostURL,
|
||||
XtreamUser: config.CredentialString(xtreamUser),
|
||||
XtreamPassword: config.CredentialString(xtreamPassword),
|
||||
XtreamBaseURL: xtreamBaseURL,
|
||||
M3UCacheExpiration: viper.GetInt("m3u-cache-expiration"),
|
||||
User: config.CredentialString(viper.GetString("user")),
|
||||
Password: config.CredentialString(viper.GetString("password")),
|
||||
AdvertisedPort: viper.GetInt("advertised-port"),
|
||||
HTTPS: viper.GetBool("https"),
|
||||
M3UFileName: viper.GetString("m3u-file-name"),
|
||||
CustomEndpoint: viper.GetString("custom-endpoint"),
|
||||
RemoteURL: remoteHostURL,
|
||||
XtreamUser: config.CredentialString(xtreamUser),
|
||||
XtreamPassword: config.CredentialString(xtreamPassword),
|
||||
XtreamBaseURL: xtreamBaseURL,
|
||||
M3UCacheExpiration: viper.GetInt("m3u-cache-expiration"),
|
||||
User: config.CredentialString(viper.GetString("user")),
|
||||
Password: config.CredentialString(viper.GetString("password")),
|
||||
AdvertisedPort: viper.GetInt("advertised-port"),
|
||||
HTTPS: viper.GetBool("https"),
|
||||
M3UFileName: viper.GetString("m3u-file-name"),
|
||||
CustomEndpoint: viper.GetString("custom-endpoint"),
|
||||
XtreamGenerateApiGet: viper.GetBool("xtream-api-get"),
|
||||
}
|
||||
|
||||
if conf.AdvertisedPort == 0 {
|
||||
@@ -130,6 +131,7 @@ func init() {
|
||||
rootCmd.Flags().String("xtream-password", "", "Xtream-code password login")
|
||||
rootCmd.Flags().String("xtream-base-url", "", "Xtream-code base url e.g(http://expample.tv:8080)")
|
||||
rootCmd.Flags().Int("m3u-cache-expiration", 1, "M3U cache expiration in hour")
|
||||
rootCmd.Flags().BoolP("xtream-api-get", "", false, "Generate get.php from xtream API instead of get.php original endpoint")
|
||||
|
||||
if e := viper.BindPFlags(rootCmd.Flags()); e != nil {
|
||||
log.Fatal("error binding PFlags to viper")
|
||||
|
||||
@@ -43,15 +43,16 @@ type HostConfiguration struct {
|
||||
|
||||
// ProxyConfig Contain original m3u playlist and HostConfiguration
|
||||
type ProxyConfig struct {
|
||||
HostConfig *HostConfiguration
|
||||
XtreamUser CredentialString
|
||||
XtreamPassword CredentialString
|
||||
XtreamBaseURL string
|
||||
M3UCacheExpiration int
|
||||
M3UFileName string
|
||||
CustomEndpoint string
|
||||
RemoteURL *url.URL
|
||||
AdvertisedPort int
|
||||
HTTPS bool
|
||||
User, Password CredentialString
|
||||
HostConfig *HostConfiguration
|
||||
XtreamUser CredentialString
|
||||
XtreamPassword CredentialString
|
||||
XtreamBaseURL string
|
||||
XtreamGenerateApiGet bool
|
||||
M3UCacheExpiration int
|
||||
M3UFileName string
|
||||
CustomEndpoint string
|
||||
RemoteURL *url.URL
|
||||
AdvertisedPort int
|
||||
HTTPS bool
|
||||
User, Password CredentialString
|
||||
}
|
||||
|
||||
@@ -48,8 +48,13 @@ func (c *Config) routes(r *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
func (c *Config) xtreamRoutes(r *gin.RouterGroup) {
|
||||
r.GET("/get.php", c.authenticate, c.xtreamGet)
|
||||
r.POST("/get.php", c.authenticate, c.xtreamGet)
|
||||
getphp := gin.HandlerFunc(c.xtreamGet)
|
||||
if c.XtreamGenerateApiGet {
|
||||
getphp = c.xtreamApiGet
|
||||
}
|
||||
r.GET("/get.php", c.authenticate, getphp)
|
||||
r.POST("/get.php", c.authenticate, getphp)
|
||||
r.GET("/apiget", c.authenticate, c.xtreamApiGet)
|
||||
r.GET("/player_api.php", c.authenticate, c.xtreamPlayerAPIGET)
|
||||
r.POST("/player_api.php", c.appAuthenticate, c.xtreamPlayerAPIPOST)
|
||||
r.GET("/xmltv.php", c.authenticate, c.xtreamXMLTV)
|
||||
|
||||
@@ -50,17 +50,12 @@ var hlsChannelsRedirectURLLock = sync.RWMutex{}
|
||||
var xtreamM3uCache map[string]cacheMeta = map[string]cacheMeta{}
|
||||
var xtreamM3uCacheLock = sync.RWMutex{}
|
||||
|
||||
func (c *Config) cacheXtreamM3u(m3uURL *url.URL) error {
|
||||
func (c *Config) cacheXtreamM3u(playlist *m3u.Playlist, cacheName string) error {
|
||||
xtreamM3uCacheLock.Lock()
|
||||
defer xtreamM3uCacheLock.Unlock()
|
||||
|
||||
playlist, err := m3u.Parse(m3uURL.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp := *c
|
||||
tmp.playlist = &playlist
|
||||
tmp.playlist = playlist
|
||||
|
||||
path := filepath.Join(os.TempDir(), uuid.NewV4().String()+".iptv-proxy.m3u")
|
||||
f, err := os.Create(path)
|
||||
@@ -72,11 +67,63 @@ func (c *Config) cacheXtreamM3u(m3uURL *url.URL) error {
|
||||
if err := tmp.marshallInto(f, true); err != nil {
|
||||
return err
|
||||
}
|
||||
xtreamM3uCache[m3uURL.String()] = cacheMeta{path, time.Now()}
|
||||
xtreamM3uCache[cacheName] = cacheMeta{path, time.Now()}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) xtreamGenerateM3u(ctx *gin.Context, extension string) (*m3u.Playlist, error) {
|
||||
client, err := xtreamapi.New(c.XtreamUser.String(), c.XtreamPassword.String(), c.XtreamBaseURL, ctx.Request.UserAgent())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cat, err := client.GetLiveCategories()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// this is specific to xtream API,
|
||||
// prefix with "live" if there is an extension.
|
||||
var prefix string
|
||||
if extension != "" {
|
||||
prefix = "live/"
|
||||
}
|
||||
|
||||
var playlist = new(m3u.Playlist)
|
||||
playlist.Tracks = make([]m3u.Track, 0)
|
||||
|
||||
for _, category := range cat {
|
||||
live, err := client.GetLiveStreams(fmt.Sprint(category.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, stream := range live {
|
||||
track := m3u.Track{Name: stream.Name, Length: -1, URI: "", Tags: nil}
|
||||
|
||||
//TODO: Add more tag if needed.
|
||||
if stream.EPGChannelID != "" {
|
||||
track.Tags = append(track.Tags, m3u.Tag{Name: "tvg-id", Value: stream.EPGChannelID})
|
||||
}
|
||||
if stream.Name != "" {
|
||||
track.Tags = append(track.Tags, m3u.Tag{Name: "tvg-name", Value: stream.Name})
|
||||
}
|
||||
if stream.Icon != "" {
|
||||
track.Tags = append(track.Tags, m3u.Tag{Name: "tvg-logo", Value: stream.Icon})
|
||||
}
|
||||
if category.Name != "" {
|
||||
track.Tags = append(track.Tags, m3u.Tag{Name: "group-title", Value: category.Name})
|
||||
}
|
||||
|
||||
track.URI = fmt.Sprintf("%s/%s%s/%s/%s.%s", c.XtreamBaseURL, prefix, c.XtreamUser, c.XtreamPassword, fmt.Sprint(stream.ID), extension)
|
||||
playlist.Tracks = append(playlist.Tracks, track)
|
||||
}
|
||||
}
|
||||
|
||||
return playlist, nil
|
||||
}
|
||||
|
||||
func (c *Config) xtreamGetAuto(ctx *gin.Context) {
|
||||
newQuery := ctx.Request.URL.Query()
|
||||
q := c.RemoteURL.Query()
|
||||
@@ -117,7 +164,12 @@ func (c *Config) xtreamGet(ctx *gin.Context) {
|
||||
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 {
|
||||
playlist, err := m3u.Parse(m3uURL.String())
|
||||
if err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck
|
||||
return
|
||||
}
|
||||
if err := c.cacheXtreamM3u(&playlist, m3uURL.String()); err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck
|
||||
return
|
||||
}
|
||||
@@ -134,6 +186,45 @@ func (c *Config) xtreamGet(ctx *gin.Context) {
|
||||
ctx.File(path)
|
||||
}
|
||||
|
||||
func (c *Config) xtreamApiGet(ctx *gin.Context) {
|
||||
const (
|
||||
apiGet = "apiget"
|
||||
)
|
||||
|
||||
var (
|
||||
extension = ctx.Query("output")
|
||||
cacheName = apiGet + extension
|
||||
)
|
||||
|
||||
xtreamM3uCacheLock.RLock()
|
||||
meta, ok := xtreamM3uCache[cacheName]
|
||||
d := time.Since(meta.Time)
|
||||
if !ok || d.Hours() >= float64(c.M3UCacheExpiration) {
|
||||
log.Printf("[iptv-proxy] %v | %s | xtream cache API m3u file\n", time.Now().Format("2006/01/02 - 15:04:05"), ctx.ClientIP())
|
||||
xtreamM3uCacheLock.RUnlock()
|
||||
playlist, err := c.xtreamGenerateM3u(ctx, extension)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck
|
||||
return
|
||||
}
|
||||
if err := c.cacheXtreamM3u(playlist, cacheName); err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck
|
||||
return
|
||||
}
|
||||
} else {
|
||||
xtreamM3uCacheLock.RUnlock()
|
||||
}
|
||||
|
||||
ctx.Header("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, c.M3UFileName))
|
||||
xtreamM3uCacheLock.RLock()
|
||||
path := xtreamM3uCache[cacheName].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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user