package yifysubs import ( "archive/zip" "bytes" "errors" "fmt" "io" "io/ioutil" "net/http" "path/filepath" "sort" "strings" "github.com/jpillora/scraper/scraper" "github.com/mitchellh/mapstructure" ) // Errors var ( ErrNoSubtitleFound = errors.New("yify: no subtitles found") ) // Client represent a Client used to make Search type Client struct { scraper *scraper.Endpoint Endpoint string } // Subtitle represents a Subtitle type Subtitle struct { Rating int Lang string Uploader string URL string Title string reader io.ReadCloser } // New return a new Searcher func New(endpoint string) *Client { e := &scraper.Endpoint{ Name: "yifysubtitles", Method: "GET", List: "table.other-subs > tbody > tr", Headers: map[string]string{ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2988.133 Safari/537.36", }, Result: map[string]scraper.Extractors{ "rating": scraper.Extractors{scraper.MustExtractor("td.rating-cell"), scraper.MustExtractor("span")}, "lang": scraper.Extractors{scraper.MustExtractor("td.flag-cell"), scraper.MustExtractor("span.sub-lang")}, "title": scraper.Extractors{scraper.MustExtractor("td:nth-child(3)"), scraper.MustExtractor("a"), scraper.MustExtractor("/subtitle (.*)/")}, "uploader": scraper.Extractors{scraper.MustExtractor("td.uploader-cell"), scraper.MustExtractor("a")}, "url": scraper.Extractors{scraper.MustExtractor("td.download-cell"), scraper.MustExtractor("a"), scraper.MustExtractor("@href")}, }, Debug: false, } return &Client{ scraper: e, Endpoint: endpoint, } } // Search will search Subtitles func (c *Client) Search(imdbID string) ([]*Subtitle, error) { c.scraper.URL = c.Endpoint + "/movie-imdb/{{imdbId}}" vars := map[string]string{ "imdbId": imdbID, } return c.parseSubtitle(vars) } // SearchByLang will search Subtitles with given language // The result will be ordered, with the highest rated subtitle first func (c *Client) SearchByLang(imdbID, lang string) ([]*Subtitle, error) { subtitles, err := c.Search(imdbID) if err != nil { return nil, err } return FilterByLang(subtitles, lang), nil } // FilterByLang will filter the subtitles by language // The result will be ordered, with the highest rated subtitle first func FilterByLang(subtitles []*Subtitle, language string) []*Subtitle { filterredSubtitles := []*Subtitle{} for _, s := range subtitles { if s.Lang == language { filterredSubtitles = append(filterredSubtitles, s) } } sort.Slice(filterredSubtitles, func(i, j int) bool { return filterredSubtitles[i].Rating > filterredSubtitles[j].Rating }) return filterredSubtitles } // parseSubtitle takes a map of parameters, it will do the request and return // the parsed Subtitles func (c *Client) parseSubtitle(vars map[string]string) ([]*Subtitle, error) { // Parse the page res, err := c.scraper.Execute(vars) if err != nil { return nil, err } subtitles := []*Subtitle{} // Map the res to our structure decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ WeaklyTypedInput: true, Result: &subtitles, }) if err != nil { return nil, err } if err = decoder.Decode(res); err != nil { return nil, err } if len(subtitles) == 0 { return nil, ErrNoSubtitleFound } // Add the endpoint in front of the URLs for _, s := range subtitles { s.URL = c.Endpoint + s.URL } return subtitles, nil } // DownloadZipURL returns the zip file URL of the subtitle func (s Subtitle) DownloadZipURL() string { return fmt.Sprintf("%s.zip", strings.Replace(s.URL, "/subtitles/", "/subtitle/", -1)) } func getReaderFromURL(url string) (io.ReadCloser, error) { // Download the zip file res, err := http.Get(url) if err != nil { return nil, err } defer res.Body.Close() // Read all the body body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf("Failed to read response body with error %s", err) } // Create a new zip Reader from a newly created bytes reader of the body // already read r, err := zip.NewReader(bytes.NewReader(body), res.ContentLength) if err != nil { return nil, err } for _, f := range r.File { if filepath.Ext(f.Name) != ".srt" { continue } return f.Open() } return nil, fmt.Errorf("Empty zip subtitle") } // Read implement the reader interface func (s *Subtitle) Read(p []byte) (n int, err error) { if s.reader == nil { // Download the zip and get the file reader r, err := getReaderFromURL(s.DownloadZipURL()) if err != nil { return 0, err } s.reader = r } return s.reader.Read(p) } // Close implement the closer interface func (s Subtitle) Close() error { if s.reader != nil { return s.reader.Close() } return nil }