diff --git a/pkg/xtream-proxy/xtream-proxy.go b/pkg/xtream-proxy/xtream-proxy.go index df6afab..1e88337 100644 --- a/pkg/xtream-proxy/xtream-proxy.go +++ b/pkg/xtream-proxy/xtream-proxy.go @@ -80,10 +80,10 @@ func (c *Client) login(proxyUser, proxyPassword, proxyURL string, proxyPort int, }, ServerInfo: xtream.ServerInfo{ URL: proxyURL, - Port: proxyPort, - HTTPSPort: proxyPort, + Port: xtream.FlexInt(proxyPort), + HTTPSPort: xtream.FlexInt(proxyPort), Protocol: protocol, - RTMPPort: proxyPort, + RTMPPort: xtream.FlexInt(proxyPort), Timezone: c.ServerInfo.Timezone, TimestampNow: c.ServerInfo.TimestampNow, TimeNow: c.ServerInfo.TimeNow, diff --git a/vendor/github.com/tellytv/go.xtream-codes/flex_types.go b/vendor/github.com/tellytv/go.xtream-codes/flex_types.go new file mode 100644 index 0000000..63b2812 --- /dev/null +++ b/vendor/github.com/tellytv/go.xtream-codes/flex_types.go @@ -0,0 +1,143 @@ +package xtreamcodes + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + "time" +) + +// Timestamp is a helper struct to convert unix timestamp ints and strings to time.Time. +type Timestamp struct { + time.Time + quoted bool +} + +// MarshalJSON returns the Unix timestamp as a string. +func (t Timestamp) MarshalJSON() ([]byte, error) { + if t.quoted { + return []byte(`"` + strconv.FormatInt(t.Time.Unix(), 10) + `"`), nil + } + return []byte(strconv.FormatInt(t.Time.Unix(), 10)), nil +} + +// UnmarshalJSON converts the int or string to a Unix timestamp. +func (t *Timestamp) UnmarshalJSON(b []byte) error { + // Timestamps are sometimes quoted, sometimes not, lets just always remove quotes just in case... + t.quoted = strings.Contains(string(b), `"`) + ts, err := strconv.Atoi(strings.Replace(string(b), `"`, "", -1)) + if err != nil { + return err + } + t.Time = time.Unix(int64(ts), 0) + return nil +} + +// ConvertibleBoolean is a helper type to allow JSON documents using 0/1 or "true" and "false" be converted to bool. +type ConvertibleBoolean struct { + bool + quoted bool +} + +// MarshalJSON returns a 0 or 1 depending on bool state. +func (bit ConvertibleBoolean) MarshalJSON() ([]byte, error) { + var bitSetVar int8 + if bit.bool { + bitSetVar = 1 + } + + if bit.quoted { + return json.Marshal(fmt.Sprint(bitSetVar)) + } + + return json.Marshal(bitSetVar) +} + +// UnmarshalJSON converts a 0, 1, true or false into a bool +func (bit *ConvertibleBoolean) UnmarshalJSON(data []byte) error { + bit.quoted = strings.Contains(string(data), `"`) + // Bools as ints are sometimes quoted, sometimes not, lets just always remove quotes just in case... + asString := strings.Replace(string(data), `"`, "", -1) + if asString == "1" || asString == "true" { + bit.bool = true + } else if asString == "0" || asString == "false" { + bit.bool = false + } else { + return fmt.Errorf("Boolean unmarshal error: invalid input %s", asString) + } + return nil +} + +// FlexInt is a int64 which unmarshals from JSON +// as either unquoted or quoted (with any amount +// of internal leading/trailing whitespace). +// Originally found at https://bit.ly/2NkJ0SK and +// https://play.golang.org/p/KNPxDL1yqL +type FlexInt int64 + +func (f FlexInt) MarshalJSON() ([]byte, error) { + return json.Marshal(int64(f)) +} + +func (f *FlexInt) UnmarshalJSON(data []byte) error { + var v int64 + + data = bytes.Trim(data, `" `) + + err := json.Unmarshal(data, &v) + *f = FlexInt(v) + return err +} + +type FlexFloat float64 + +func (ff *FlexFloat) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + return json.Unmarshal(b, (*float64)(ff)) + } + + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + if len(s) == 0 { + s = "0" + } + + f, err := strconv.ParseFloat(s, 64) + if err != nil { + f = 0 + } + *ff = FlexFloat(f) + return nil +} + +// JSONStringSlice is a struct containing a slice of strings. +// It is needed for cases in which we may get an array or may get +// a single string in a JSON response. +type JSONStringSlice struct { + Slice []string `json:"-"` + SingleString bool `json:"-"` +} + +// MarshalJSON returns b as the JSON encoding of b. +func (b JSONStringSlice) MarshalJSON() ([]byte, error) { + if !b.SingleString { + return json.Marshal(b.Slice) + } + return json.Marshal(b.Slice[0]) +} + +// UnmarshalJSON sets *b to a copy of data. +func (b *JSONStringSlice) UnmarshalJSON(data []byte) error { + if data[0] == '"' { + data = append([]byte(`[`), data...) + data = append(data, []byte(`]`)...) + b.SingleString = true + } + + return json.Unmarshal(data, &b.Slice) +} diff --git a/vendor/github.com/tellytv/go.xtream-codes/structs.go b/vendor/github.com/tellytv/go.xtream-codes/structs.go index eb055fe..d78e737 100644 --- a/vendor/github.com/tellytv/go.xtream-codes/structs.go +++ b/vendor/github.com/tellytv/go.xtream-codes/structs.go @@ -1,102 +1,24 @@ package xtreamcodes -import ( - "bytes" - "encoding/json" - "fmt" - "strconv" - "strings" - "time" -) - -// Timestamp is a helper struct to convert unix timestamp ints and strings to time.Time. -type Timestamp struct { - time.Time - quoted bool -} - -// MarshalJSON returns the Unix timestamp as a string. -func (t Timestamp) MarshalJSON() ([]byte, error) { - if t.quoted { - return []byte(`"` + strconv.FormatInt(t.Time.Unix(), 10) + `"`), nil - } - return []byte(strconv.FormatInt(t.Time.Unix(), 10)), nil -} - -// UnmarshalJSON converts the int or string to a Unix timestamp. -func (t *Timestamp) UnmarshalJSON(b []byte) error { - // Timestamps are sometimes quoted, sometimes not, lets just always remove quotes just in case... - t.quoted = strings.Contains(string(b), `"`) - ts, err := strconv.Atoi(strings.Replace(string(b), `"`, "", -1)) - if err != nil { - return err - } - t.Time = time.Unix(int64(ts), 0) - return nil -} - -// ConvertibleBoolean is a helper type to allow JSON documents using 0/1 or "true" and "false" be converted to bool. -type ConvertibleBoolean struct { - bool - quoted bool -} - -// MarshalJSON returns a 0 or 1 depending on bool state. -func (bit ConvertibleBoolean) MarshalJSON() ([]byte, error) { - var bitSetVar int8 - if bit.bool { - bitSetVar = 1 - } - - if bit.quoted { - return json.Marshal(fmt.Sprint(bitSetVar)) - } - - return json.Marshal(bitSetVar) -} - -// UnmarshalJSON converts a 0, 1, true or false into a bool -func (bit *ConvertibleBoolean) UnmarshalJSON(data []byte) error { - bit.quoted = strings.Contains(string(data), `"`) - // Bools as ints are sometimes quoted, sometimes not, lets just always remove quotes just in case... - asString := strings.Replace(string(data), `"`, "", -1) - if asString == "1" || asString == "true" { - bit.bool = true - } else if asString == "0" || asString == "false" { - bit.bool = false - } else { - return fmt.Errorf("Boolean unmarshal error: invalid input %s", asString) - } - return nil -} - -// jsonInt is a int64 which unmarshals from JSON -// as either unquoted or quoted (with any amount -// of internal leading/trailing whitespace). -// Originally found at https://bit.ly/2NkJ0SK and -// https://play.golang.org/p/KNPxDL1yqL -type jsonInt int64 - -func (f jsonInt) MarshalJSON() ([]byte, error) { - return json.Marshal(int64(f)) -} - -func (f *jsonInt) UnmarshalJSON(data []byte) error { - var v int64 - - data = bytes.Trim(data, `" `) - - err := json.Unmarshal(data, &v) - *f = jsonInt(v) - return err +// PanelInfo has all the general information +type PanelInfo struct { + UserInfo UserInfo `json:"user_info"` + ServerInfo ServerInfo `json:"server_info"` + Categories struct { + Series []Category `json:"series"` + Live []Category `json:"live"` + Radio []Category `json:"radio"` + Movie []Category `json:"movie"` + } `json:"categories"` + AvailableChannels map[string]Stream `json:"available_channels"` } // ServerInfo describes the state of the Xtream-Codes server. type ServerInfo struct { - HTTPSPort int `json:"https_port,string"` - Port int `json:"port,string"` + HTTPSPort FlexInt `json:"https_port,string"` + Port FlexInt `json:"port,string"` Process bool `json:"process"` - RTMPPort int `json:"rtmp_port,string"` + RTMPPort FlexInt `json:"rtmp_port,string"` Protocol string `json:"server_protocol"` TimeNow string `json:"time_now"` TimestampNow Timestamp `json:"timestamp_now,string"` @@ -106,13 +28,13 @@ type ServerInfo struct { // UserInfo is the current state of the user as it relates to the Xtream-Codes server. type UserInfo struct { - ActiveConnections int `json:"active_cons,string"` + ActiveConnections FlexInt `json:"active_cons,string"` AllowedOutputFormats []string `json:"allowed_output_formats"` Auth ConvertibleBoolean `json:"auth"` CreatedAt Timestamp `json:"created_at"` ExpDate *Timestamp `json:"exp_date"` IsTrial ConvertibleBoolean `json:"is_trial,string"` - MaxConnections int `json:"max_connections,string"` + MaxConnections FlexInt `json:"max_connections,string"` Message string `json:"message"` Password string `json:"password"` Status string `json:"status"` @@ -127,9 +49,9 @@ type AuthenticationResponse struct { // Category describes a grouping of Stream. type Category struct { - ID int `json:"category_id,string"` - Name string `json:"category_name"` - Parent int `json:"parent_id"` + ID FlexInt `json:"category_id,string"` + Name string `json:"category_name"` + Parent FlexInt `json:"parent_id"` // Set by us, not Xtream. Type string `json:"-"` @@ -138,79 +60,56 @@ type Category struct { // Stream is a streamble video source. type Stream struct { Added *Timestamp `json:"added"` - CategoryID int `json:"category_id,string"` + CategoryID FlexInt `json:"category_id,string"` + CategoryName string `json:"category_name"` ContainerExtension string `json:"container_extension"` CustomSid string `json:"custom_sid"` DirectSource string `json:"direct_source,omitempty"` EPGChannelID string `json:"epg_channel_id"` Icon string `json:"stream_icon"` - ID int `json:"stream_id"` + ID FlexInt `json:"stream_id"` Name string `json:"name"` - Number int `json:"num"` + Number FlexInt `json:"num"` Rating FlexFloat `json:"rating"` - Rating5based float64 `json:"rating_5based"` - TVArchive int `json:"tv_archive"` - TVArchiveDuration *jsonInt `json:"tv_archive_duration"` + Rating5based FlexFloat `json:"rating_5based"` + TVArchive FlexInt `json:"tv_archive"` + TVArchiveDuration *FlexInt `json:"tv_archive_duration"` Type string `json:"stream_type"` } -type FlexFloat float64 - -func (ff *FlexFloat) UnmarshalJSON(b []byte) error { - if b[0] != '"' { - return json.Unmarshal(b, (*float64)(ff)) - } - - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - if len(s) == 0 { - s = "0" - } - - f, err := strconv.ParseFloat(s, 64) - if err != nil { - f = 0 - } - *ff = FlexFloat(f) - return nil -} - // SeriesInfo contains information about a TV series. type SeriesInfo struct { BackdropPath *JSONStringSlice `json:"backdrop_path,omitempty"` Cast string `json:"cast"` - CategoryID *int `json:"category_id,string"` + CategoryID *FlexInt `json:"category_id,string"` Cover string `json:"cover"` Director string `json:"director"` EpisodeRunTime string `json:"episode_run_time"` Genre string `json:"genre"` LastModified *Timestamp `json:"last_modified,omitempty"` Name string `json:"name"` - Num int `json:"num"` + Num FlexInt `json:"num"` Plot string `json:"plot"` - Rating int `json:"rating,string"` - Rating5 float64 `json:"rating_5based"` + Rating FlexInt `json:"rating,string"` + Rating5 FlexFloat `json:"rating_5based"` ReleaseDate string `json:"releaseDate"` - SeriesID int `json:"series_id"` + SeriesID FlexInt `json:"series_id"` StreamType string `json:"stream_type"` YoutubeTrailer string `json:"youtube_trailer"` } type SeriesEpisode struct { - Added string `json:"added"` - ContainerExtension string `json:"container_extension"` - CustomSid string `json:"custom_sid"` - DirectSource string `json:"direct_source"` - EpisodeNum int `json:"episode_num"` - ID string `json:"id"` + Added string `json:"added"` + ContainerExtension string `json:"container_extension"` + CustomSid string `json:"custom_sid"` + DirectSource string `json:"direct_source"` + EpisodeNum FlexInt `json:"episode_num"` + ID string `json:"id"` Info struct { Audio FFMPEGStreamInfo `json:"audio"` - Bitrate int `json:"bitrate"` + Bitrate FlexInt `json:"bitrate"` Duration string `json:"duration"` - DurationSecs int `json:"duration_secs"` + DurationSecs FlexInt `json:"duration_secs"` MovieImage string `json:"movie_image"` Name string `json:"name"` Plot string `json:"plot"` @@ -218,8 +117,8 @@ type SeriesEpisode struct { ReleaseDate string `json:"releasedate"` Video FFMPEGStreamInfo `json:"video"` } `json:"info"` - Season int `json:"season"` - Title string `json:"title"` + Season FlexInt `json:"season"` + Title string `json:"title"` } type Series struct { @@ -233,11 +132,11 @@ type VideoOnDemandInfo struct { Info struct { Audio FFMPEGStreamInfo `json:"audio"` BackdropPath []string `json:"backdrop_path"` - Bitrate int `json:"bitrate"` + Bitrate FlexInt `json:"bitrate"` Cast string `json:"cast"` Director string `json:"director"` Duration string `json:"duration"` - DurationSecs int `json:"duration_secs"` + DurationSecs FlexInt `json:"duration_secs"` Genre string `json:"genre"` MovieImage string `json:"movie_image"` Plot string `json:"plot"` @@ -249,12 +148,12 @@ type VideoOnDemandInfo struct { } `json:"info"` MovieData struct { Added Timestamp `json:"added"` - CategoryID int `json:"category_id,string"` + CategoryID FlexInt `json:"category_id,string"` ContainerExtension string `json:"container_extension"` CustomSid string `json:"custom_sid"` DirectSource string `json:"direct_source"` Name string `json:"name"` - StreamID int `json:"stream_id"` + StreamID FlexInt `json:"stream_id"` } `json:"movie_data"` } @@ -267,9 +166,9 @@ type EPGInfo struct { ChannelID string `json:"channel_id"` Description Base64Value `json:"description"` End string `json:"end"` - EPGID int `json:"epg_id,string"` + EPGID FlexInt `json:"epg_id,string"` HasArchive ConvertibleBoolean `json:"has_archive"` - ID int `json:"id,string"` + ID FlexInt `json:"id,string"` Lang string `json:"lang"` NowPlaying ConvertibleBoolean `json:"now_playing"` Start string `json:"start"` @@ -277,30 +176,3 @@ type EPGInfo struct { StopTimestamp Timestamp `json:"stop_timestamp"` Title Base64Value `json:"title"` } - -// JSONStringSlice is a struct containing a slice of strings. -// It is needed for cases in which we may get an array or may get -// a single string in a JSON response. -type JSONStringSlice struct { - Slice []string `json:"-"` - SingleString bool `json:"-"` -} - -// MarshalJSON returns b as the JSON encoding of b. -func (b JSONStringSlice) MarshalJSON() ([]byte, error) { - if !b.SingleString { - return json.Marshal(b.Slice) - } - return json.Marshal(b.Slice[0]) -} - -// UnmarshalJSON sets *b to a copy of data. -func (b *JSONStringSlice) UnmarshalJSON(data []byte) error { - if data[0] == '"' { - data = append([]byte(`[`), data...) - data = append(data, []byte(`]`)...) - b.SingleString = true - } - - return json.Unmarshal(data, &b.Slice) -} diff --git a/vendor/github.com/tellytv/go.xtream-codes/xtream-codes.go b/vendor/github.com/tellytv/go.xtream-codes/xtream-codes.go index 9065367..5bc5ad0 100644 --- a/vendor/github.com/tellytv/go.xtream-codes/xtream-codes.go +++ b/vendor/github.com/tellytv/go.xtream-codes/xtream-codes.go @@ -7,8 +7,10 @@ import ( "encoding/json" "fmt" "io" + "log" "net/http" "net/url" + "os" "strconv" ) @@ -34,7 +36,6 @@ type XtreamClient struct { // NewClient returns an initialized XtreamClient with the given values. func NewClient(username, password, baseURL string) (*XtreamClient, error) { - _, parseURLErr := url.Parse(baseURL) if parseURLErr != nil { return nil, fmt.Errorf("error parsing url: %s", parseURLErr.Error()) @@ -72,16 +73,26 @@ func NewClient(username, password, baseURL string) (*XtreamClient, error) { // NewClientWithContext returns an initialized XtreamClient with the given values. func NewClientWithContext(ctx context.Context, username, password, baseURL string) (*XtreamClient, error) { c, err := NewClient(username, password, baseURL) + if err != nil { + return nil, err + } + c.Context = ctx - return c, err + + return c, nil } // NewClientWithUserAgent returns an initialized XtreamClient with the given values. func NewClientWithUserAgent(ctx context.Context, username, password, baseURL, userAgent string) (*XtreamClient, error) { c, err := NewClient(username, password, baseURL) + if err != nil { + return nil, err + } + c.UserAgent = userAgent c.Context = ctx - return c, err + + return c, nil } // GetStreamURL will return a stream URL string for the given streamID and wantedFormat. @@ -187,7 +198,7 @@ func (c *XtreamClient) GetStreams(streamAction, categoryID string) ([]Stream, er } for _, stream := range streams { - c.streams[stream.ID] = stream + c.streams[int(stream.ID)] = stream } return streams, nil @@ -299,6 +310,7 @@ func (c *XtreamClient) sendRequest(action string, parameters url.Values) ([]byte if action == "xmltv.php" { file = action } + url := fmt.Sprintf("%s/%s?username=%s&password=%s", c.BaseURL, file, c.Username, c.Password) if action != "" { url = fmt.Sprintf("%s&action=%s", url, action) @@ -334,5 +346,12 @@ func (c *XtreamClient) sendRequest(action string, parameters url.Values) ([]byte return nil, fmt.Errorf("cannot read response. %v", closeErr) } - return buf.Bytes(), nil + data := buf.Bytes() + + if _, ok := os.LookupEnv("XTREAM_DEBUG"); ok { + j, _ := json.MarshalIndent(data, "", " ") + log.Println(string(j)) + } + + return data, nil }