diff --git a/.github/workflows/stream_xinpianchang.yml b/.github/workflows/stream_xinpianchang.yml new file mode 100644 index 000000000..45e33009f --- /dev/null +++ b/.github/workflows/stream_xinpianchang.yml @@ -0,0 +1,31 @@ +name: xinpianchang + +on: + push: + paths: + - "extractors/xinpianchang/*.go" + - ".github/workflows/stream_xinpianchang.yml" + pull_request: + paths: + - "extractors/xinpianchang/*.go" + - ".github/workflows/stream_xinpianchang.yml" + schedule: + # run ci weekly + - cron: "0 0 * * 0" + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + go: ["1.16"] + os: [ubuntu-latest] + name: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Test + run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/xinpianchang diff --git a/README.md b/README.md index 1899e05b1..5f6ac2eaf 100644 --- a/README.md +++ b/README.md @@ -597,6 +597,7 @@ $ lux -j "https://www.bilibili.com/video/av20203945" | YouTube | | ✓ | | | ✓ | | [![youtube](https://github.com/iawia002/lux/actions/workflows/stream_youtube.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_youtube.yml) | | 西瓜视频(头条) | , , | ✓ | | | | | [![ixigua](https://github.com/iawia002/lux/actions/workflows/stream_ixigua.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_ixigua.yml) | | 爱奇艺 | | ✓ | | | | | [![iqiyi](https://github.com/iawia002/lux/actions/workflows/stream_iqiyi.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_iqiyi.yml) | +| 新片场 | | ✓ | | | | | [![xinpianchang](https://github.com/iawia002/lux/actions/workflows/stream_xinpianchang.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_xinpianchang.yml) | | 芒果 TV | | ✓ | | | | | [![mgtv](https://github.com/iawia002/lux/actions/workflows/stream_mgtv.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_mgtv.yml) | | 糖豆广场舞 | | ✓ | | | | | [![tangdou](https://github.com/iawia002/lux/actions/workflows/stream_tangdou.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_tangdou.yml) | | Tumblr | | ✓ | ✓ | | | | [![tumblr](https://github.com/iawia002/lux/actions/workflows/stream_tumblr.yml/badge.svg)](https://github.com/iawia002/lux/actions/workflows/stream_tumblr.yml) | diff --git a/app/register.go b/app/register.go index 301faddbf..e19df6fc6 100644 --- a/app/register.go +++ b/app/register.go @@ -32,6 +32,7 @@ import ( _ "github.com/iawia002/lux/extractors/vimeo" _ "github.com/iawia002/lux/extractors/weibo" _ "github.com/iawia002/lux/extractors/ximalaya" + _ "github.com/iawia002/lux/extractors/xinpianchang" _ "github.com/iawia002/lux/extractors/xvideos" _ "github.com/iawia002/lux/extractors/yinyuetai" _ "github.com/iawia002/lux/extractors/youku" diff --git a/extractors/xinpianchang/xinpianchang.go b/extractors/xinpianchang/xinpianchang.go new file mode 100644 index 000000000..f0df69d71 --- /dev/null +++ b/extractors/xinpianchang/xinpianchang.go @@ -0,0 +1,117 @@ +package xinpianchang + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/itchyny/gojq" + "github.com/pkg/errors" + + "github.com/iawia002/lux/extractors" + "github.com/iawia002/lux/request" +) + +func init() { + extractors.Register("xinpianchang", New()) +} + +type extractor struct{} + +type Video struct { + Title string `json:"title"` + Qualities []struct { + Quality string `json:"quality"` + Size int64 `json:"size"` + URL string `json:"url"` + Ext string `json:"ext"` + } `json:"qualities"` +} + +// New returns a xinpianchang extractor. +func New() extractors.Extractor { + return &extractor{} +} + +// Extract is the main function to extract the data. +func (e *extractor) Extract(url string, option extractors.Options) ([]*extractors.Data, error) { + headers := map[string]string{ + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:98.0) Gecko/20100101 Firefox/98.0", + } + + html, err := request.Get(url, url, headers) + if err != nil { + return nil, errors.WithStack(err) + } + + r1 := regexp.MustCompile(`vid = \"(.+?)\";`) + r2 := regexp.MustCompile(`modeServerAppKey = \"(.+?)\";`) + + vid := r1.FindSubmatch([]byte(html))[1] + appKey := r2.FindSubmatch([]byte(html))[1] + + video_url := fmt.Sprintf("https://mod-api.xinpianchang.com/mod/api/v2/media/%s?appKey=%s", string(vid), string(appKey)) + body, err := request.Get(video_url, url, headers) + if err != nil { + return nil, errors.WithStack(err) + } + + var m interface{} + err = json.Unmarshal([]byte(body), &m) + if err != nil { + return nil, errors.WithStack(err) + } + + query, err := gojq.Parse("{title: .data.title} + {qualities: [(.data.resource.progressive[] | {quality: .quality, size: .filesize, url: .url, ext: .mime})]}") + if err != nil { + return nil, errors.WithStack(err) + } + iter := query.Run(m) + video := Video{} + + for { + v, ok := iter.Next() + if !ok { + break + } + if err, ok := v.(error); ok { + return nil, errors.WithStack(err) + } + + jsonbody, err := json.Marshal(v) + if err != nil { + return nil, errors.WithStack(err) + } + + if err := json.Unmarshal(jsonbody, &video); err != nil { + return nil, errors.WithStack(err) + } + } + + streams := make(map[string]*extractors.Stream) + for _, quality := range video.Qualities { + streams[quality.Quality] = &extractors.Stream{ + Size: quality.Size, + Quality: quality.Quality, + Parts: []*extractors.Part{ + { + URL: quality.URL, + Size: quality.Size, + Ext: strings.Split(quality.Ext, "/")[1], + }, + }, + } + } + + return []*extractors.Data{ + { + Site: "新片场 xinpianchang.com", + Title: video.Title, + Type: extractors.DataTypeVideo, + Streams: streams, + URL: url, + }, + }, nil + +} diff --git a/extractors/xinpianchang/xinpianchang_test.go b/extractors/xinpianchang/xinpianchang_test.go new file mode 100644 index 000000000..36346acf4 --- /dev/null +++ b/extractors/xinpianchang/xinpianchang_test.go @@ -0,0 +1,32 @@ +package xinpianchang + +import ( + "testing" + + "github.com/iawia002/lux/extractors" + "github.com/iawia002/lux/test" +) + +func TestDownload(t *testing.T) { + tests := []struct { + name string + args test.Args + }{ + { + name: "test 1", + args: test.Args{ + URL: "https://www.xinpianchang.com/a10880684?from=ArticlePageSimilar", + Title: "超炫酷视觉系创意短片《遗留》", + Quality: "720p", + Size: 79595290, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := New().Extract(tt.args.URL, extractors.Options{}) + test.CheckError(t, err) + test.Check(t, tt.args, data[0]) + }) + } +}