refactor, bypass dns filtering using google dns, yts host resolution using qwant
This commit is contained in:
		
							
								
								
									
										81
									
								
								ytsclient/yts_resolver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								ytsclient/yts_resolver.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
package ytsclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	r "github.com/levigross/grequests"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var ErrNotFound = errors.New("not found")
 | 
			
		||||
 | 
			
		||||
type SearchResponse struct {
 | 
			
		||||
	Status string `json:"status"`
 | 
			
		||||
	Data   struct {
 | 
			
		||||
		Query struct {
 | 
			
		||||
			Locale string `json:"locale"`
 | 
			
		||||
			Query  string `json:"query"`
 | 
			
		||||
			Offset int    `json:"offset"`
 | 
			
		||||
		} `json:"query"`
 | 
			
		||||
		Cache struct {
 | 
			
		||||
			Key        string `json:"key"`
 | 
			
		||||
			Created    int    `json:"created"`
 | 
			
		||||
			Expiration int    `json:"expiration"`
 | 
			
		||||
			Status     string `json:"status"`
 | 
			
		||||
			Age        int    `json:"age"`
 | 
			
		||||
			Version    int    `json:"version"`
 | 
			
		||||
			System     string `json:"system"`
 | 
			
		||||
			Mode       int    `json:"mode"`
 | 
			
		||||
		} `json:"cache"`
 | 
			
		||||
		Result struct {
 | 
			
		||||
			Total int `json:"total"`
 | 
			
		||||
			Items []struct {
 | 
			
		||||
				Title    string `json:"title"`
 | 
			
		||||
				Favicon  string `json:"favicon"`
 | 
			
		||||
				URL      string `json:"url"`
 | 
			
		||||
				Source   string `json:"source"`
 | 
			
		||||
				Desc     string `json:"desc"`
 | 
			
		||||
				ID       string `json:"_id"`
 | 
			
		||||
				Score    int    `json:"score"`
 | 
			
		||||
				Position int    `json:"position"`
 | 
			
		||||
			} `json:"items"`
 | 
			
		||||
			Filters struct {
 | 
			
		||||
				Freshness struct {
 | 
			
		||||
					Label    string `json:"label"`
 | 
			
		||||
					Name     string `json:"name"`
 | 
			
		||||
					Type     string `json:"type"`
 | 
			
		||||
					Selected string `json:"selected"`
 | 
			
		||||
					Values   []struct {
 | 
			
		||||
						Value     string `json:"value"`
 | 
			
		||||
						Label     string `json:"label"`
 | 
			
		||||
						Translate bool   `json:"translate"`
 | 
			
		||||
					} `json:"values"`
 | 
			
		||||
				} `json:"freshness"`
 | 
			
		||||
			} `json:"filters"`
 | 
			
		||||
			Ads []interface{} `json:"ads"`
 | 
			
		||||
		} `json:"result"`
 | 
			
		||||
	} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func ResolveYts() (url string, err error) {
 | 
			
		||||
	res, err := r.Get("https://api.qwant.com/api/search/web?count=10&q=yts&t=web&uiv=4", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	    return "", err
 | 
			
		||||
	}
 | 
			
		||||
	sr := &SearchResponse{}
 | 
			
		||||
	if err := res.JSON(sr); err != nil{
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(sr.Data.Result.Items) == 0 {
 | 
			
		||||
		return "", ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range sr.Data.Result.Items {
 | 
			
		||||
		if !strings.HasPrefix(v.URL, "https://yts.") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		return v.URL, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", ErrNotFound
 | 
			
		||||
}
 | 
			
		||||
@@ -1,31 +1,56 @@
 | 
			
		||||
package ytsclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	r "github.com/levigross/grequests"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	r "github.com/levigross/grequests"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Client struct {
 | 
			
		||||
type client struct {
 | 
			
		||||
	url         string
 | 
			
		||||
	list        string
 | 
			
		||||
	get         string
 | 
			
		||||
	suggestions string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	defaultHost = "https://yts.am"
 | 
			
		||||
	apiPrefix   = "/api/v2"
 | 
			
		||||
	dnsServer   = "8.8.4.4"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	list        = "/list_movies.json"
 | 
			
		||||
	get         = "/movie_details.json"
 | 
			
		||||
	suggestions = "/movie_suggestions.json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewClient(url string) *Client {
 | 
			
		||||
	if url == "" {
 | 
			
		||||
		url = "https://yts.am/api/v2"
 | 
			
		||||
 | 
			
		||||
func NewClient(host ...string) Client {
 | 
			
		||||
	if len(host) == 0 || host[0] == "" {
 | 
			
		||||
		if host, err := ResolveYts(); err == nil {
 | 
			
		||||
			defaultHost = strings.TrimSuffix(host, "/")
 | 
			
		||||
		}
 | 
			
		||||
		host = []string{defaultHost}
 | 
			
		||||
	}
 | 
			
		||||
	return &Client{url, url + list, url + get, url + suggestions}
 | 
			
		||||
	d := net.Dialer{
 | 
			
		||||
		Timeout: 5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	net.DefaultResolver = &net.Resolver{
 | 
			
		||||
		PreferGo:     true,
 | 
			
		||||
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
			
		||||
			return d.DialContext(ctx, network, fmt.Sprintf("%s:53", dnsServer))
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return &client{host[0], host[0] + apiPrefix + list, host[0] + apiPrefix + get, host[0] + apiPrefix + suggestions}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *Client) List(params *ListParams) ([]Movie, error) {
 | 
			
		||||
func (y *client) List(params *ListParams) ([]Movie, error) {
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		params = &ListParams{}
 | 
			
		||||
	}
 | 
			
		||||
@@ -40,7 +65,7 @@ func (y *Client) List(params *ListParams) ([]Movie, error) {
 | 
			
		||||
	return resp.Data.Movies, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *Client) Search(search string, params *ListParams) ([]Movie, error) {
 | 
			
		||||
func (y *client) Search(search string, params *ListParams) ([]Movie, error) {
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		params = &ListParams{}
 | 
			
		||||
	}
 | 
			
		||||
@@ -56,7 +81,7 @@ func (y *Client) Search(search string, params *ListParams) ([]Movie, error) {
 | 
			
		||||
	return resp.Data.Movies, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *Client) Get(id string, params *MovieParams) (Movie, error) {
 | 
			
		||||
func (y *client) Get(id string, params *MovieParams) (Movie, error) {
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		params = &MovieParams{}
 | 
			
		||||
	}
 | 
			
		||||
@@ -73,7 +98,7 @@ func (y *Client) Get(id string, params *MovieParams) (Movie, error) {
 | 
			
		||||
	return resp.Data.Movie, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (y *Client) Suggestions(id int) ([]Movie, error) {
 | 
			
		||||
func (y *client) Suggestions(id int) ([]Movie, error) {
 | 
			
		||||
	ro := &r.RequestOptions{
 | 
			
		||||
		Params: map[string]string{"movie_id": strconv.Itoa(id)},
 | 
			
		||||
	}
 | 
			
		||||
@@ -87,3 +112,10 @@ func (y *Client) Suggestions(id int) ([]Movie, error) {
 | 
			
		||||
	}
 | 
			
		||||
	return resp.Data.Movies, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Client interface {
 | 
			
		||||
	List(params *ListParams) ([]Movie, error)
 | 
			
		||||
	Search(search string, params *ListParams) ([]Movie, error)
 | 
			
		||||
	Get(id string, params *MovieParams) (Movie, error)
 | 
			
		||||
	Suggestions(id int) ([]Movie, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ package ytsclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
@@ -24,31 +26,45 @@ var (
 | 
			
		||||
		name string
 | 
			
		||||
		test func(t *testing.T)
 | 
			
		||||
	}{{
 | 
			
		||||
		name: "no dns server",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			dnsServer = "192.168.1.1"
 | 
			
		||||
			yc := NewClient()
 | 
			
		||||
			ms, err := yc.List(nil)
 | 
			
		||||
			require.Error(t, err)
 | 
			
		||||
			t.Log(err)
 | 
			
		||||
			require.Nil(t, ms)
 | 
			
		||||
			dnsServer = "8.8.8.8"
 | 
			
		||||
		},
 | 
			
		||||
	},{
 | 
			
		||||
		name: "default url",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			assert.Equal(t, "https://yts.am/api/v2", c.url)
 | 
			
		||||
			yc := NewClient()
 | 
			
		||||
			c, ok :=  yc.(*client)
 | 
			
		||||
			require.True(t, ok)
 | 
			
		||||
			assert.Contains(t, c.url,"https://yts.")
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "list",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			c := NewClient()
 | 
			
		||||
			_, err := c.List(nil)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "search",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			c := NewClient()
 | 
			
		||||
			l, err := c.Search("South Park", nil)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			require.NotEmpty(t, l)
 | 
			
		||||
			southParkMovie = l[0]
 | 
			
		||||
			assert.Equal(t, southParkID, strconv.Itoa(southParkMovie.ID))
 | 
			
		||||
		},
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "list 50",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			c := NewClient()
 | 
			
		||||
			p := &ListParams{Limit: 50}
 | 
			
		||||
			l, err := c.List(p)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
@@ -57,7 +73,7 @@ var (
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "get",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			c := NewClient()
 | 
			
		||||
			m, err := c.Get(southParkID, nil)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.NotEmpty(t, m)
 | 
			
		||||
@@ -66,6 +82,7 @@ var (
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "magnet",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			require.NotEmpty(t, southParkMovie.Torrents)
 | 
			
		||||
			m, err := southParkMovie.Magnet(southParkMovie.Torrents[1])
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			// Don't test trackers list nor YTS extension
 | 
			
		||||
@@ -76,7 +93,7 @@ var (
 | 
			
		||||
	}, {
 | 
			
		||||
		name: "suggestions",
 | 
			
		||||
		test: func(t *testing.T) {
 | 
			
		||||
			c := NewClient("")
 | 
			
		||||
			c := NewClient()
 | 
			
		||||
			s, err := c.Suggestions(10)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.NotEmpty(t, s)
 | 
			
		||||
@@ -89,3 +106,12 @@ func TestYTSClient(t *testing.T) {
 | 
			
		||||
		t.Run(test.name, test.test)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func TestResolveYts(t *testing.T) {
 | 
			
		||||
	defaultHost = ""
 | 
			
		||||
	host, err := ResolveYts()
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Contains(t, host, "https://yts.")
 | 
			
		||||
	t.Log(host)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user