Files
metatube-server/provider/airav/airav.go
T
2022-12-15 23:13:04 +08:00

234 lines
6.1 KiB
Go

package airav
import (
"encoding/json"
"fmt"
"net/url"
"path"
"strings"
"github.com/gocolly/colly/v2"
"github.com/javtube/metatube-sdk-go/common/parser"
"github.com/javtube/metatube-sdk-go/model"
"github.com/javtube/metatube-sdk-go/provider"
"github.com/javtube/metatube-sdk-go/provider/internal/scraper"
)
var (
_ provider.MovieProvider = (*AirAV)(nil)
_ provider.MovieSearcher = (*AirAV)(nil)
)
const (
Name = "AIRAV"
Priority = 1000 - 6 // unofficial provider gets lower priority.
)
const (
baseURL = "https://www.airav.wiki/"
movieURL = "https://www.airav.wiki/video/%s"
movieAPIURL = "https://www.airav.wiki/api/video/barcode/%s?lng=jp"
searchAPIURL = "https://www.airav.wiki/api/video/list?search=%s&lng=jp"
videoAPIURL = "https://www.airav.wiki/api/video/getVideoMedia?barcode=%s&vid=%s"
)
type AirAV struct {
*scraper.Scraper
}
func New() *AirAV {
return &AirAV{
Scraper: scraper.NewDefaultScraper(Name, baseURL, Priority,
scraper.WithHeaders(map[string]string{
"Origin": baseURL,
"Referer": baseURL,
})),
}
}
func (air *AirAV) NormalizeID(id string) string { return strings.ToUpper(id) }
func (air *AirAV) GetMovieInfoByID(id string) (info *model.MovieInfo, err error) {
return air.GetMovieInfoByURL(fmt.Sprintf(movieURL, id))
}
func (air *AirAV) ParseIDFromURL(rawURL string) (string, error) {
homepage, err := url.Parse(rawURL)
if err != nil {
return "", err
}
return air.NormalizeID(path.Base(homepage.Path)), nil
}
func (air *AirAV) GetMovieInfoByURL(rawURL string) (info *model.MovieInfo, err error) {
id, err := air.ParseIDFromURL(rawURL)
if err != nil {
return
}
info = &model.MovieInfo{
Provider: air.Name(),
Homepage: rawURL,
Actors: []string{},
PreviewImages: []string{},
Genres: []string{},
}
c := air.ClonedCollector()
// JSON
c.OnResponse(func(r *colly.Response) {
data := struct {
Result struct {
Actors []struct {
ID string `json:"id"`
Name string `json:"name"`
NameJP string `json:"name_jp"`
} `json:"actors"`
Barcode string `json:"barcode"`
Description string `json:"description"`
Factories []struct {
Name string `json:"name"`
} `json:"factories"`
Images []string `json:"images"`
ImgURL string `json:"img_url"`
Name string `json:"name"`
PublishDate string `json:"publish_date"`
Tags []struct {
Name string `json:"name"`
} `json:"tags"`
VID string `json:"vid"`
VideoURL struct {
URLCDN string `json:"url_cdn"`
} `json:"video_url"`
View int `json:"view"`
} `json:"result"`
Count int `json:"count"`
Status string `json:"status"`
}{}
if err = json.Unmarshal(r.Body, &data); err == nil && data.Count > 0 {
info.ID = data.Result.Barcode
info.Number = ParseNumber(data.Result.Barcode)
info.Title = data.Result.Name
info.Summary = data.Result.Description
info.ThumbURL = data.Result.ImgURL
info.CoverURL = data.Result.ImgURL
info.PreviewImages = data.Result.Images
info.ReleaseDate = parser.ParseDate(data.Result.PublishDate)
if len(data.Result.Factories) > 0 {
info.Maker = data.Result.Factories[0].Name
}
for _, tag := range data.Result.Tags {
info.Genres = append(info.Genres, tag.Name)
}
for _, actor := range data.Result.Actors {
if actor.NameJP != "" {
info.Actors = append(info.Actors, actor.NameJP)
} else if actor.Name != "" {
info.Actors = append(info.Actors, actor.Name)
}
}
if data.Result.VideoURL.URLCDN != "" {
info.PreviewVideoURL = data.Result.VideoURL.URLCDN
} else {
d := c.Clone()
d.OnResponse(func(r *colly.Response) {
videoData := struct {
Data struct {
Msg string `json:"msg"`
URL string `json:"url"`
URLCDN string `json:"url_cdn"`
URLHLS string `json:"url_hls"`
URLHLSCDN string `json:"url_hls_cdn"`
} `json:"data"`
Status string `json:"status"`
}{}
if json.Unmarshal(r.Body, &videoData) == nil {
for _, videoURL := range []string{
videoData.Data.URL, videoData.Data.URLCDN,
} {
if videoURL != "" {
info.PreviewVideoURL = videoURL
break
}
}
for _, videoURL := range []string{
videoData.Data.URLHLS, videoData.Data.URLHLSCDN,
} {
if videoURL != "" {
info.PreviewVideoHLSURL = videoURL
break
}
}
}
})
d.Visit(fmt.Sprintf(videoAPIURL, data.Result.Barcode, data.Result.VID))
}
}
})
if vErr := c.Visit(fmt.Sprintf(movieAPIURL, id)); vErr != nil {
err = vErr
}
return
}
func (air *AirAV) NormalizeKeyword(keyword string) string {
// Disable AIRAV due to its bad performance.
//
//if ss := regexp.MustCompile(`^(?i)FC2-.*?(\d+)$`).FindStringSubmatch(keyword); len(ss) == 2 {
// return fmt.Sprintf("FC2-PPV-%s", ss[1])
//}
//if number.IsSpecial(keyword) {
// return "" // Deprecate other searches if any.
//}
//return strings.ToUpper(keyword)
return ""
}
func (air *AirAV) SearchMovie(keyword string) (results []*model.MovieSearchResult, err error) {
c := air.ClonedCollector()
c.OnResponse(func(r *colly.Response) {
data := struct {
Count int `json:"count"`
Offset int `json:"offset"`
Result []struct {
Barcode string `json:"barcode"`
ImgURL string `json:"img_url"`
Name string `json:"name"`
VID string `json:"vid"`
}
Status string `json:"status"`
}{}
if json.Unmarshal(r.Body, &data) == nil {
for _, result := range data.Result {
results = append(results, &model.MovieSearchResult{
ID: result.Barcode,
Number: ParseNumber(result.Barcode),
Title: result.Name,
Provider: air.Name(),
Homepage: fmt.Sprintf(movieURL, result.Barcode),
ThumbURL: result.ImgURL,
CoverURL: result.ImgURL,
})
}
}
})
err = c.Visit(fmt.Sprintf(searchAPIURL, url.QueryEscape(keyword)))
return
}
// ParseNumber parses barcode to standard movie number.
func ParseNumber(s string) string {
s = strings.ToUpper(s)
s = strings.ReplaceAll(s, "FC2-PPV-", "FC2-") // Use `FC2` directly
return s
}
func init() {
provider.RegisterMovieFactory(Name, New)
}