Update ZTEG and HWTC API implementations and logging changes

This commit updates ZTEG and HWTC implementation with refreshed logic around handling channels and EPGs data. It also includes minor changes in logging information for clarity. Deleted 'config.example.yaml' file. Additionally, implemented new test cases to validate these changes.
This commit is contained in:
Senis 2024-01-21 12:23:39 +08:00
parent e4c79cd316
commit 70628dd938
No known key found for this signature in database
14 changed files with 223 additions and 65 deletions

View File

@ -2,11 +2,11 @@ package api
type Client interface {
GetChannels() ([]Channel, error)
GetEPGs(id int) ([]Epg, error)
GetEPGs(id string) ([]Epg, error)
}
type Channel struct {
ChannelID int
ChannelID string
ChannelName string
ChannelURL string
TimeShiftURL string

View File

@ -2,37 +2,29 @@ package hwtc
import (
"regexp"
"strconv"
"strings"
"github.com/mitchellh/mapstructure"
)
func bytesToChannels(resp []byte) ([]Channel, error) {
re := regexp.MustCompile(`(?s)ChannelID="\d*".*?ChannelFECPort="\d*"`)
data := re.FindAllString(string(resp), -1)
re := regexp.MustCompile(`ChannelID="\w+".*?ChannelFECPort="\d+"`)
data := re.FindAll(resp, -1)
var channelMaps []map[string]any
var channelMaps []map[string]string
re2 := regexp.MustCompile(`画中画|单音轨`)
for i := range data {
if re2.MatchString(data[i]) {
if re2.Match(data[i]) {
continue
}
d := data[i]
res := strings.Split(d, ",")
kvMap := make(map[string]any)
res := strings.Split(string(d), ",")
kvMap := make(map[string]string)
for ii := range res {
kvs := strings.SplitN(res[ii], "=", 2)
val := strings.Trim(kvs[1], "\"")
if kvs[0] == "ChannelID" {
channelID, _ := strconv.Atoi(val)
kvMap[kvs[0]] = channelID
} else {
kvMap[kvs[0]] = val
}
kvMap[kvs[0]] = val
}
channelMaps = append(channelMaps, kvMap)
}

View File

@ -2,11 +2,10 @@ package hwtc
import (
"errors"
"net/http"
)
func (c *Client) updateCookie() error {
resp, err := c.cli.R().SetQueryParams(map[string]string{
resp, err := c.cli.R().SetFormData(map[string]string{
"UserID": c.userId,
"Authenticator": c.authenticator,
}).Post("ValidAuthenticationHWCTC.jsp")
@ -14,20 +13,13 @@ func (c *Client) updateCookie() error {
return err
}
var isLogin bool
cookie := new(http.Cookie)
cookies := resp.Cookies()
for i := range cookies {
if cookies[i].Name == "JSESSIONID" && len(cookies[i].Value) > 0 {
cookie = cookies[i]
isLogin = true
c.cli.SetCookie(cookies[i])
return nil
}
}
if isLogin {
c.cli.SetCookie(cookie)
return nil
}
return errors.New("no valid cookie")
}

View File

@ -1,8 +1,8 @@
package hwtc
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
@ -14,8 +14,7 @@ import (
func New(conf *config.Config) *Client {
r := &Client{
cli: resty.New().SetRetryCount(3).SetBaseURL(fmt.Sprintf("%s/EPG/jsp", conf.Api.ApiHost)),
cli: resty.New().SetRetryCount(3).SetBaseURL(fmt.Sprintf("%s/EPG/jsp", conf.Api.ApiHost)),
userId: conf.Api.Auth["userid"],
authenticator: conf.Api.Auth["authenticator"],
}
@ -23,16 +22,16 @@ func New(conf *config.Config) *Client {
return r
}
func (c *Client) getEPGBytes(channelId int) ([]byte, error) {
func (c *Client) getEPGBytes(channelId string) ([]byte, error) {
var buf []byte
for i := 0; i < 3; i++ {
resp, err := c.cli.R().ForceContentType("text/html;charset=UTF-8").SetQueryParam("channelId", strconv.Itoa(channelId)).
resp, err := c.cli.R().ForceContentType("text/html;charset=UTF-8").SetQueryParam("channelId", channelId).
Get("stliveplay_30/en/getTvodData.jsp")
if err != nil {
return nil, err
}
if strings.Contains(resp.String(), "(\"resignon\",\"1\")") {
if strings.Contains(resp.String(), "resignon") {
time.Sleep(time.Second * 3)
if err := c.updateCookie(); err != nil {
return nil, err
@ -46,7 +45,7 @@ func (c *Client) getEPGBytes(channelId int) ([]byte, error) {
return buf, nil
}
func (c *Client) GetEPGs(id int) ([]api.Epg, error) {
func (c *Client) GetEPGs(id string) ([]api.Epg, error) {
buf, err := c.getEPGBytes(id)
if err != nil {
return nil, err
@ -72,8 +71,6 @@ func (c *Client) GetEPGs(id int) ([]api.Epg, error) {
}
func (c *Client) getChannelBytes() ([]byte, error) {
var buf []byte
for i := 0; i < 3; i++ {
resp, err := c.cli.R().
Get("getchannellistHWCTC.jsp")
@ -81,18 +78,16 @@ func (c *Client) getChannelBytes() ([]byte, error) {
return nil, err
}
if strings.Contains(resp.String(), "(\"resignon\",\"1\")") {
if strings.Contains(resp.String(), "resignon") {
time.Sleep(time.Second * 3)
if err := c.updateCookie(); err != nil {
return nil, err
}
continue
}
buf = resp.Body()
break
return resp.Body(), nil
}
return buf, nil
return nil, errors.New("retry after 3 times")
}
func (c *Client) GetChannels() ([]api.Channel, error) {

View File

@ -29,7 +29,7 @@ type Channel struct {
ChannelFCCIP string `mapstructure:"ChannelFCCIP"`
ChannelFCCPort string `mapstructure:"ChannelFCCPort"`
ChannelFECPort string `mapstructure:"ChannelFECPort"`
ChannelID int `mapstructure:"ChannelID"`
ChannelID string `mapstructure:"ChannelID"`
ChannelLocked string `mapstructure:"ChannelLocked"`
ChannelLogURL string `mapstructure:"ChannelLogURL"`
ChannelName string `mapstructure:"ChannelName"`

38
api/zteg/channel.go Normal file
View File

@ -0,0 +1,38 @@
package zteg
import (
"regexp"
"strings"
"github.com/mitchellh/mapstructure"
)
func bytesToChannels(resp []byte) ([]Channel, error) {
re := regexp.MustCompile(`ChannelID="\w+".*?ChannelFCCPort="\d+"`)
data := re.FindAll(resp, -1)
var channelMaps []map[string]string
re2 := regexp.MustCompile(`PIP`)
for i := range data {
if re2.Match(data[i]) {
continue
}
d := data[i]
res := strings.Split(string(d), ",")
kvMap := make(map[string]string)
for ii := range res {
kvs := strings.SplitN(res[ii], "=", 2)
val := strings.Trim(kvs[1], "\"")
kvMap[kvs[0]] = val
}
channelMaps = append(channelMaps, kvMap)
}
var channels []Channel
if err := mapstructure.Decode(&channelMaps, &channels); err != nil {
return nil, err
}
return channels, nil
}

31
api/zteg/channel_test.go Normal file
View File

@ -0,0 +1,31 @@
package zteg
import (
"testing"
"github.com/thank243/iptvChannel/config"
)
func TestGetChannels(t *testing.T) {
c := config.ReadConfig()
r := New(c)
resp, err := r.getChannelBytes()
if err != nil {
t.Error(err)
}
// resp, err := os.ReadFile("channel.bin")
// if err != nil {
// t.Error(err)
// }
channels, err := bytesToChannels(resp)
if err != nil {
t.Log(err)
}
for _, channel := range channels {
t.Log(channel.ChannelName)
}
}

25
api/zteg/cookie.go Normal file
View File

@ -0,0 +1,25 @@
package zteg
import (
"errors"
)
func (c *Client) updateCookie() error {
resp, err := c.cli.R().SetFormData(map[string]string{
"UserID": c.userId,
"Authenticator": c.authenticator,
}).Post("platform/auth.jsp")
if err != nil {
return err
}
cookies := resp.Cookies()
for i := range cookies {
if cookies[i].Name == "JSESSIONID" && len(cookies[i].Value) > 0 {
c.cli.SetCookie(cookies[i])
return nil
}
}
return errors.New("no valid cookie")
}

View File

@ -1,4 +1,35 @@
package zteg
import (
"github.com/go-resty/resty/v2"
)
type Client struct {
cli *resty.Client
userId string
authenticator string
}
type Channel struct {
BeginTime string `mapstructure:"BeginTime"`
ChannelPurchased string `mapstructure:"ChannelPurchased"`
LocalTimeShift string `mapstructure:"LocalTimeShift"`
UserTeamChannelID string `mapstructure:"UserTeamChannelID"`
ChannelFCCServerAddr string `mapstructure:"ChannelFCCServerAddr"`
ChannelFCCIP string `mapstructure:"ChannelFCCIP"`
ChannelFCCPort string `mapstructure:"ChannelFCCPort"`
ChannelID string `mapstructure:"ChannelID"`
ChannelLogURL string `mapstructure:"ChannelLogURL"`
ChannelName string `mapstructure:"ChannelName"`
UserChannelID string `mapstructure:"UserChannelID"`
ChannelSDP string `mapstructure:"ChannelSDP"`
ChannelType string `mapstructure:"ChannelType"`
ChannelURL string `mapstructure:"ChannelURL"`
Interval string `mapstructure:"Interval"`
Lasting string `mapstructure:"Lasting"`
PositionX string `mapstructure:"PositionX"`
PositionY string `mapstructure:"PositionY"`
TimeShift string `mapstructure:"TimeShift"`
TimeShiftURL string `mapstructure:"TimeShiftURL"`
}

View File

@ -1,18 +1,86 @@
package zteg
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/go-resty/resty/v2"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"github.com/thank243/iptvChannel/api"
"github.com/thank243/iptvChannel/config"
)
func New(conf *config.Config) *Client {
return new(Client)
r := &Client{
cli: resty.New().SetRetryCount(3).SetBaseURL(fmt.Sprintf("%s/iptvepg", conf.Api.ApiHost)),
userId: conf.Api.Auth["userid"],
authenticator: conf.Api.Auth["authenticator"],
}
return r
}
func (c *Client) GetEPGs(id int) ([]api.Epg, error) {
// GetEPGs todo
func (c *Client) GetEPGs(id string) ([]api.Epg, error) {
return []api.Epg{}, nil
}
func (c *Client) GetChannels() ([]api.Channel, error) {
return []api.Channel{}, nil
func (c *Client) getChannelBytes() ([]byte, error) {
for i := 0; i < 3; i++ {
resp, err := c.cli.R().SetFormData(map[string]string{
"MAIN_WIN_SRC": "/iptvepg/empty.jsp",
"NEED_UPDATE_STB": "1",
"BUILD_ACTION": "FRAMESET_BUILDER",
}).Post("function/frameset_builder.jsp")
if err != nil {
return nil, err
}
if strings.Contains(resp.String(), "resignon") {
time.Sleep(time.Second * 3)
if err := c.updateCookie(); err != nil {
return nil, err
}
continue
}
// convert gbk to utf-8
buf, err := io.ReadAll(transform.NewReader(bytes.NewReader(resp.Body()), simplifiedchinese.GBK.NewDecoder()))
if err != nil {
return nil, err
}
return buf, nil
}
return nil, errors.New("retry after 3 times")
}
func (c *Client) GetChannels() ([]api.Channel, error) {
buf, err := c.getChannelBytes()
if err != nil {
return nil, err
}
chs, err := bytesToChannels(buf)
if err != nil {
return nil, err
}
var channels []api.Channel
for i := range chs {
ch := chs[i]
channels = append(channels, api.Channel{
ChannelID: ch.ChannelID,
ChannelName: ch.ChannelName,
ChannelURL: ch.ChannelURL,
TimeShiftURL: ch.TimeShiftURL,
})
}
return channels, nil
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"github.com/beevik/etree"
@ -116,7 +115,7 @@ func (s *Server) getEPGs(c echo.Context) error {
ch := channels[i]
// create channel, format: <channel id="1"><display-name lang="zh">CCTV1</display-name></channel>
channelXml := tv.CreateElement("channel")
channelXml.CreateAttr("id", strconv.Itoa(ch.ChannelID))
channelXml.CreateAttr("id", ch.ChannelID)
name := channelXml.CreateElement("display-name")
name.CreateAttr("lang", "zh")
name.CreateText(ch.ChannelName)

View File

@ -1,13 +0,0 @@
LogLevel: debug # trace, debug, info, warn, error, fatal
Cron: "5 */12 * * *" # same as Cron Expressions
MaxConcurrent: 5 # not more than 16
Address: 0.0.0.0:8888
Mode: udpxy # udpxy, igmp, url
UdpxyHost: "http://YOUR_UDPXY_ADDR" # not blank if mode is udpxy
Api:
Provider: hwtc # hwtc, zteg
ApiHost: "http://YOUR_API_HOST"
Auth:
UserID: "YOUR_IPTV_ID"
Authenticator: "YOUR_PRIVATE_KEY"

View File

@ -75,7 +75,7 @@ func (c *Controller) Start() error {
config.GetVersion(), c.conf.LogLevel, c.maxConcurrent, strings.ToUpper(c.conf.Mode), c.conf.Api.Provider)
log.Info("Starting service..")
log.Info("Fetch EPGs and Channels data on initial startup")
log.Info("Fetch Channels and EPGs data on initial startup")
c.Run()
time.Sleep(time.Second)
@ -105,7 +105,7 @@ func (c *Controller) Run() {
}
if err := c.fetchEPGs(); err != nil {
return
log.Error(err)
}
}

2
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.18.2
golang.org/x/text v0.14.0
)
require (
@ -36,7 +37,6 @@ require (
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect