diff --git a/.gitignore b/.gitignore index ae2a4b2..cf430c3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ vendor/ fy dist +/.idea diff --git a/bd/sign.go b/baidu.go similarity index 51% rename from bd/sign.go rename to baidu.go index ec497ef..4f93a2e 100644 --- a/bd/sign.go +++ b/baidu.go @@ -1,14 +1,131 @@ -package bd +package fy import ( + "context" "fmt" + "net/http" + "net/url" "regexp" + "strings" "github.com/robertkrimen/otto" + "github.com/tidwall/gjson" ) -const signJS = ` +type baiduTranslator struct{} +var baidu translator = new(baiduTranslator) + +func (*baiduTranslator) desc() (string, string) { + return "baiduTranslator", "https://fanyi.baiduTranslator.com/" +} + +func BaiduTranslate(ctx context.Context, req Request) (resp *Response) { + return baidu.translate(ctx, req) +} + +func (b *baiduTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(b) + + r, _, err := sendRequest(ctx, http.MethodGet, "https://www.baidu.com", nil, nil) + if err != nil { + resp.Err = fmt.Errorf("notReadResp error: %v", err) + return + } + cookies := r.Cookies() + + r, _, err = sendRequest(ctx, http.MethodGet, "https://fanyi.baidu.com", nil, nil) + if err != nil { + resp.Err = fmt.Errorf("notReadResp error: %v", err) + return + } + fanyiCookies := r.Cookies() + + param := url.Values{"query": {req.Text}} + detectUrl := "https://fanyi.baidu.com/langdetect" + body := strings.NewReader(param.Encode()) + r, data, err := sendRequest(ctx, http.MethodPost, detectUrl, body, func(req *http.Request) error { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + return nil + }) + + jr := gjson.Parse(string(data)) + if errorCode := jr.Get("error").Int(); errorCode != 0 { + resp.Err = fmt.Errorf("langdetect json result error is %d", errorCode) + return + } + from := jr.Get("lan").String() + + r, data, err = sendRequest(ctx, http.MethodGet, "https://fanyi.baidu.com", nil, func(req *http.Request) error { + addCookies(req, cookies) + addCookies(req, fanyiCookies) + return nil + }) + if err != nil { + err = fmt.Errorf("SendRequest error: %v", err) + return + } + + token, gtk, err := b.tokenAndGtk(string(data)) + if err != nil { + resp.Err = fmt.Errorf("getTokenAndGtk error: %v", err) + return + } + sign, err := b.sign(gtk, req.Text) + if err != nil { + resp.Err = fmt.Errorf("calSign error: %v", err) + return + } + req.ToLang = b.convertLanguage(req.ToLang) + param = url.Values{ + "from": {from}, + "to": {req.ToLang}, + "query": {req.Text}, + "sign": {sign}, + "token": {token}, + } + urlStr := "https://fanyi.baidu.com/v2transapi" + body = strings.NewReader(param.Encode()) + _, data, err = sendRequest(ctx, "POST", urlStr, body, func(req *http.Request) error { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + addCookies(req, cookies) + addCookies(req, fanyiCookies) + return nil + }) + if err != nil { + resp.Err = fmt.Errorf("sendRequest error: %v", err) + return + } + + jr = gjson.Parse(string(data)) + if errorCode := jr.Get("error").Int(); errorCode != 0 { + resp.Err = fmt.Errorf("json result error is %d", errorCode) + return + } + + resp.Result = jr.Get("trans_result.data.0.dst").String() + return +} + +func (*baiduTranslator) convertLanguage(language string) string { + l := language + switch language { + case Chinese: + l = "zh" + case Korean: + l = "kor" + case Japanese: + l = "jp" + case French: + l = "fra" + case Spanish: + l = "spa" + } + + return l +} + +const baiduSignJS = ` function a(r) { if (Array.isArray(r)) { for (var o = 0, t = Array(r.length); o < r.length; o++) t[o] = r[o]; @@ -49,7 +166,7 @@ const signJS = ` result = e(query) ` -func calSign(gtk, query string) (string, error) { +func (*baiduTranslator) sign(gtk, query string) (string, error) { vm := otto.New() if err := vm.Set("gtk", gtk); err != nil { return "", fmt.Errorf("vm.Set gtk error: %v", err) @@ -57,7 +174,7 @@ func calSign(gtk, query string) (string, error) { if err := vm.Set("query", query); err != nil { return "", fmt.Errorf("vm.Set query error: %v", err) } - value, err := vm.Run(signJS) + value, err := vm.Run(baiduSignJS) if err != nil { return "", fmt.Errorf("vm.Run error: %v", err) } @@ -68,7 +185,7 @@ func calSign(gtk, query string) (string, error) { return result, nil } -func getTokenAndGtk(dataStr string) (token, gtk string, err error) { +func (*baiduTranslator) tokenAndGtk(dataStr string) (token, gtk string, err error) { tokenResult := regexp.MustCompile(`token: '(?P\S+)',`).FindStringSubmatch(dataStr) if len(tokenResult) != 2 { err = fmt.Errorf("cannot get token") diff --git a/baidu_test.go b/baidu_test.go new file mode 100644 index 0000000..5a6a50d --- /dev/null +++ b/baidu_test.go @@ -0,0 +1,32 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBaiduTranslate(t *testing.T) { + { + resp := BaiduTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(baidu) + expectedResp.Result = "测试" + assert.Equal(t, expectedResp, resp) + } + + { + resp := BaiduTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(baidu) + expectedResp.Result = "test" + assert.Equal(t, expectedResp, resp) + } +} diff --git a/bd/bd.go b/bd/bd.go deleted file mode 100644 index 79885af..0000000 --- a/bd/bd.go +++ /dev/null @@ -1,115 +0,0 @@ -package bd - -import ( - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/xwjdsh/fy" - - "github.com/tidwall/gjson" -) - -type baidu struct{} - -var langConvertMap = map[string]string{ - fy.Chinese: "zh", - fy.Korean: "kor", - fy.Japanese: "jp", - fy.French: "fra", - fy.Spanish: "spa", -} - -func init() { - fy.Register(new(baidu)) -} - -func (b *baidu) Desc() (string, string, string) { - return "bd", "baidu", "https://fanyi.baidu.com/" -} - -func (b *baidu) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(b) - - r, err := fy.NotReadResp(http.Get("https://www.baidu.com")) - if err != nil { - resp.Err = fmt.Errorf("fy.NotReadResp error: %v", err) - return - } - baiduCookies := r.Cookies() - - r, err = fy.NotReadResp(http.Get("https://fanyi.baidu.com")) - if err != nil { - resp.Err = fmt.Errorf("fy.NotReadResp error: %v", err) - return - } - baiduFanyiCookies := r.Cookies() - - param := url.Values{"query": {req.Text}} - detectUrl := "https://fanyi.baidu.com/langdetect" - body := strings.NewReader(param.Encode()) - r, data, err := fy.SendRequest("POST", detectUrl, body, func(req *http.Request) error { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - return nil - }) - - jr := gjson.Parse(string(data)) - if errorCode := jr.Get("error").Int(); errorCode != 0 { - resp.Err = fmt.Errorf("langdetect json result error is %d", errorCode) - return - } - from := jr.Get("lan").String() - - r, data, err = fy.SendRequest("GET", "https://fanyi.baidu.com", nil, func(req *http.Request) error { - fy.AddCookies(req, baiduCookies) - fy.AddCookies(req, baiduFanyiCookies) - return nil - }) - if err != nil { - err = fmt.Errorf("fy.SendRequest error: %v", err) - return - } - - token, gtk, err := getTokenAndGtk(string(data)) - if err != nil { - resp.Err = fmt.Errorf("getTokenAndGtk error: %v", err) - return - } - sign, err := calSign(gtk, req.Text) - if err != nil { - resp.Err = fmt.Errorf("calSign error: %v", err) - return - } - if tl, ok := langConvertMap[req.TargetLang]; ok { - req.TargetLang = tl - } - param = url.Values{ - "from": {from}, - "to": {req.TargetLang}, - "query": {req.Text}, - "sign": {sign}, - "token": {token}, - } - urlStr := "https://fanyi.baidu.com/v2transapi" - body = strings.NewReader(param.Encode()) - _, data, err = fy.SendRequest("POST", urlStr, body, func(req *http.Request) error { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - fy.AddCookies(req, baiduCookies) - fy.AddCookies(req, baiduFanyiCookies) - return nil - }) - if err != nil { - resp.Err = fmt.Errorf("fy.SendRequest error: %v", err) - return - } - - jr = gjson.Parse(string(data)) - if errorCode := jr.Get("error").Int(); errorCode != 0 { - resp.Err = fmt.Errorf("json result error is %d", errorCode) - return - } - - resp.Result = jr.Get("trans_result.data.0.dst").String() - return -} diff --git a/bd/bd_test.go b/bd/bd_test.go deleted file mode 100644 index 27f07ac..0000000 --- a/bd/bd_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package bd - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_baidu_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - b *baidu - args args - wantResp *fy.Response - }{ - { - name: "text = test", - b: &baidu{}, - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "baidu", - Result: "测试", - Err: nil, - }, - }, - { - name: "text = 测试", - b: &baidu{}, - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "baidu", - Result: "test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &baidu{} - if gotResp := b.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("baidu.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} diff --git a/bd/sign_test.go b/bd/sign_test.go deleted file mode 100644 index 2f8559d..0000000 --- a/bd/sign_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package bd - -import "testing" - -func Test_calSign(t *testing.T) { - type args struct { - gtk string - query string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "test", - args: args{ - gtk: "320305.131321201", - query: "test", - }, - want: "431039.159886", - }, - { - name: "测试", - args: args{ - gtk: "320305.131321201", - query: "测试", - }, - want: "706553.926920", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := calSign(tt.args.gtk, tt.args.query) - if (err != nil) != tt.wantErr { - t.Errorf("calSign() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("calSign() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_getTokenAndGtk(t *testing.T) { - type args struct { - dataStr string - } - tests := []struct { - name string - args args - wantToken string - wantGtk string - wantErr bool - }{ - { - args: args{dataStr: ""}, - wantErr: true, - }, - { - args: args{ - dataStr: ` - - - - - - window['common'] = { - token: '395c9e69e667bf7579a2a3ade6391edb', - systime: '1525177203477', - logid: 'a831f7926cd2a9bd8078cf7968894a01', - `, - }, - wantToken: "395c9e69e667bf7579a2a3ade6391edb", - wantGtk: "320305.131321201", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotToken, gotGtk, err := getTokenAndGtk(tt.args.dataStr) - if (err != nil) != tt.wantErr { - t.Errorf("getTokenAndGtk() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotToken != tt.wantToken { - t.Errorf("getTokenAndGtk() gotToken = %v, want %v", gotToken, tt.wantToken) - } - if gotGtk != tt.wantGtk { - t.Errorf("getTokenAndGtk() gotGtk = %v, want %v", gotGtk, tt.wantGtk) - } - }) - } -} diff --git a/bing.go b/bing.go new file mode 100644 index 0000000..2698189 --- /dev/null +++ b/bing.go @@ -0,0 +1,68 @@ +package fy + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/tidwall/gjson" +) + +type bingTranslator struct{} + +var bing translator = new(bingTranslator) + +func (b *bingTranslator) desc() (string, string) { + return "bing", "https://www.bing.com/translator/" +} + +func BingTranslate(ctx context.Context, req Request) *Response { + return bing.translate(ctx, req) +} + +func (b *bingTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(b) + + detectUrl := "https://www.bing.com/tdetect/" + param := url.Values{"text": {req.Text}} + body := strings.NewReader(param.Encode()) + _, data, err := sendRequest(ctx, http.MethodPost, detectUrl, body, func(req *http.Request) error { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return nil + }) + from := string(data) + + req.ToLang = b.convertLanguage(req.ToLang) + param = url.Values{ + "from": {from}, + "to": {req.ToLang}, + "text": {req.Text}, + } + + urlStr := "https://www.bing.com/ttranslate/" + body = strings.NewReader(param.Encode()) + _, data, err = sendRequest(ctx, "POST", urlStr, body, func(req *http.Request) error { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return nil + }) + if err != nil { + resp.Err = fmt.Errorf("sendRequest error: %v", err) + return + } + + jr := gjson.Parse(string(data)) + resp.Result = jr.Get("translationResponse").String() + return +} + +func (b *bingTranslator) convertLanguage(language string) string { + l := language + switch language { + case Chinese: + l = "zh-CHS" + } + + return l +} diff --git a/bing_test.go b/bing_test.go new file mode 100644 index 0000000..3371ca3 --- /dev/null +++ b/bing_test.go @@ -0,0 +1,32 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBingTranslate(t *testing.T) { + { + resp := BingTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(bing) + expectedResp.Result = "测试" + assert.Equal(t, expectedResp, resp) + } + + { + resp := BingTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(bing) + expectedResp.Result = "Test" + assert.Equal(t, expectedResp, resp) + } +} diff --git a/by/by.go b/by/by.go deleted file mode 100644 index 839eef3..0000000 --- a/by/by.go +++ /dev/null @@ -1,67 +0,0 @@ -package by - -import ( - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/tidwall/gjson" - "github.com/xwjdsh/fy" -) - -type bing struct{} - -var langConvertMap = map[string]string{ - fy.Chinese: "zh-CHS", -} - -func init() { - fy.Register(new(bing)) -} - -func (b *bing) Desc() (string, string, string) { - return "by", "bing", "https://www.bing.com/translator/" -} - -func (b *bing) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(b) - - detectUrl := "https://www.bing.com/tdetect/" - param := url.Values{"text": {req.Text}} - body := strings.NewReader(param.Encode()) - _, data, err := fy.SendRequest("POST", detectUrl, body, func(req *http.Request) error { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - return nil - }) - from := string(data) - - if tl, ok := langConvertMap[req.TargetLang]; ok { - req.TargetLang = tl - } - param = url.Values{ - "from": {from}, - "to": {req.TargetLang}, - "text": {req.Text}, - } - - urlStr := "https://www.bing.com/ttranslate/" - body = strings.NewReader(param.Encode()) - _, data, err = fy.SendRequest("POST", urlStr, body, func(req *http.Request) error { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - return nil - }) - if err != nil { - resp.Err = fmt.Errorf("fy.SendRequest error: %v", err) - return - } - - jr := gjson.Parse(string(data)) - if errorCode := jr.Get("statusCode").Int(); errorCode != 200 { - resp.Err = fmt.Errorf("json result statusCodeis %d", errorCode) - return - } - - resp.Result = jr.Get("translationResponse").String() - return -} diff --git a/by/by_test.go b/by/by_test.go deleted file mode 100644 index f963fe8..0000000 --- a/by/by_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package by - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_bing_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - b *bing - args args - wantResp *fy.Response - }{ - { - name: "text = test", - b: new(bing), - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "bing", - Result: "测试", - Err: nil, - }, - }, - { - name: "text = 测试", - b: new(bing), - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "bing", - Result: "Test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &bing{} - if gotResp := b.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("bing.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} diff --git a/cmd/fy/main.go b/cmd/fy/main.go index e613a1d..0cc55f9 100644 --- a/cmd/fy/main.go +++ b/cmd/fy/main.go @@ -2,49 +2,53 @@ package main import ( "flag" - "fmt" "io/ioutil" "os" "strings" + "time" "github.com/xwjdsh/fy" - _ "github.com/xwjdsh/fy/bd" - _ "github.com/xwjdsh/fy/by" - _ "github.com/xwjdsh/fy/gg" - _ "github.com/xwjdsh/fy/qq" - _ "github.com/xwjdsh/fy/sg" - _ "github.com/xwjdsh/fy/yd" "github.com/atotto/clipboard" "github.com/fatih/color" "golang.org/x/crypto/ssh/terminal" ) +const ( + iconBad = "✗" + + logo = ` + ____ + / __/_ __ + / /_/ / / / + / __/ /_/ / + /_/ \__, / + /____/ + +https://github.com/xwjdsh/fy +` + coffeeEmoji = "\u2615\ufe0f" +) + var ( - version = "unknown" - isDebug = flag.Bool("d", false, "Debug mode, if an error occurs in the translation, the error message is displayed") - sources = flag.Bool("s", false, "Display translators information") - filePath = flag.String("f", "", "file path") - only = flag.String("o", "", "Select only the translators, comma separated. eg 'bd,gg', it can also be set by the 'FY_ONLY' environment variable") - except = flag.String("e", "", "Select translators except these, comma separated. eg 'bd,gg', it can also be set by the 'FY_EXCEPT' environment variable") - targetLang = flag.String("t", "", "The target language of translation") + isDebug = flag.Bool("d", false, "Debug mode, if an error occurs during translation, the error message will be displayed as the translation result") + filePath = flag.String("f", "", "File path") + targetLang = flag.String("t", "", "The target language of the translation") + + translator = flag.String("translator", "", "Restrict the translators used, comma separated. eg 'baidu,google'") + timeout = flag.Duration("timeout", 5*time.Second, "The timeout for each translator") ) func main() { flag.Parse() - if *sources { - printSources() - return - } text, err := getText() if err != nil { - color.Red("%s %v", fy.IconBad, err) + color.Red("%s %v", iconBad, err) os.Exit(1) } if text == "" { - color.Green(fy.Logo) - fmt.Printf(fy.Desc, version) + color.Green(logo) return } isChinese := fy.IsChinese(text) @@ -52,35 +56,25 @@ func main() { *targetLang = getTargetLang(isChinese) } - translators, err := getTranslators() - if err != nil { - color.Red("%s %v", fy.IconBad, err) - os.Exit(1) + req := &fy.Request{ + ToLang: *targetLang, + Text: text, } - req := fy.Request{ - TargetLang: *targetLang, - Text: text, + var translators []string + if *translator != "" { + translators = strings.Split(*translator, ",") } - responseChan := make(chan *fy.Response) - wrap := func(t fy.Translator) { - responseChan <- t.Translate(req) - } - for _, t := range translators { - go wrap(t) - } - - fmt.Println() - for range translators { - resp := <-responseChan + ch := fy.AsyncTranslate(*timeout, req, translators...) + for resp := range ch { if resp.Err != nil { if !*isDebug { continue } resp.Result = resp.Err.Error() } - color.Green("\t%s [%s]\n\n", fy.CoffeeEmoji, resp.FullName) + color.Green("\t%s [%s]\n\n", coffeeEmoji, resp.Name) color.Magenta("\t\t%s\n\n", resp.Result) } } @@ -128,22 +122,3 @@ func getTargetLang(isChinese bool) string { } return target } - -func getTranslators() ([]fy.Translator, error) { - if *only == "" { - *only = os.Getenv("FY_ONLY") - } - if *except == "" { - *except = os.Getenv("FY_EXCEPT") - } - return fy.Filter(*only, *except) -} - -func printSources() { - translators, _ := fy.Filter("", "") - fmt.Println() - for _, t := range translators { - fy.PrintSource(t.Desc()) - } - fmt.Println() -} diff --git a/fy.go b/fy.go index cc03230..98d6ce2 100644 --- a/fy.go +++ b/fy.go @@ -1,10 +1,9 @@ package fy import ( - "fmt" - "log" - "strings" + "context" "sync" + "time" ) const ( @@ -18,89 +17,77 @@ const ( Spanish = "es" ) -var ( - translatorMap = map[string]Translator{} - lock sync.Mutex -) +var translators = []translator{ + baidu, bing, google, sogou, tencent, youdao, +} -// Register register a translator -func Register(t Translator) { - lock.Lock() - defer lock.Unlock() +// Request translation request +type Request struct { + // FromLang the from language of the translation + FromLang string + // ToLang the to language of the translation + ToLang string + // Text translation text + Text string +} - name, _, _ := t.Desc() - if _, ok := translatorMap[name]; ok { - log.Panicf("%s has been registered", name) +// Response translation response +type Response struct { + // Name the translator name + Name string + // Homepage the translator homepage + Homepage string + // Result the translation result + Result string + // Err the translation error + Err error +} + +func newResp(t translator) *Response { + name, homepage := t.desc() + return &Response{ + Name: name, + Homepage: homepage, } - translatorMap[name] = t } -// Filter filter translators -func Filter(only, except string) ([]Translator, error) { - lock.Lock() - defer lock.Unlock() +type translator interface { + desc() (name string, source string) + translate(context.Context, Request) *Response +} - getMap := func(str string) (map[string]bool, error) { - m := map[string]bool{} - if str == "" { - return m, nil +func AsyncTranslate(eachTranslatorTimeout time.Duration, req *Request, ts ...string) <-chan *Response { + var limitMap map[string]bool + if len(ts) > 0 { + limitMap = map[string]bool{} + for _, name := range ts { + limitMap[name] = true } - for _, s := range strings.Split(str, ",") { - if _, ok := translatorMap[s]; !ok { - return nil, fmt.Errorf("the translator [%s] does not exist", s) - } - m[s] = true - } - return m, nil - } - onlyMap, err := getMap(only) - if err != nil { - return nil, err - } - exceptMap, err := getMap(except) - if err != nil { - return nil, err } - result := []Translator{} - for k, v := range translatorMap { - if (len(onlyMap) > 0 && !onlyMap[k]) || (len(exceptMap) > 0 && exceptMap[k]) { + + wg := sync.WaitGroup{} + ch := make(chan *Response) + for _, t := range translators { + name, _ := t.desc() + if limitMap != nil && !limitMap[name] { continue } - result = append(result, v) - } - return result, nil -} -// NewResp new Response, set fullname -func NewResp(t Translator) *Response { - _, fullname, _ := t.Desc() - return &Response{ - FullName: fullname, - } -} + wg.Add(1) + go func(t translator) { + defer wg.Done() -// Request translate request -type Request struct { - // The target language of translation - TargetLang string - // Text translate text - Text string -} + ctx, cancel := context.WithTimeout(context.Background(), eachTranslatorTimeout) + defer cancel() + ch <- t.translate(ctx, *req) -// Response translate response -type Response struct { - // FullName translator fullname - FullName string - // Result translate result - Result string - // Err translate error - Err error -} + }(t) + } + + go func() { + wg.Wait() + close(ch) + }() -// Translator translator interface -type Translator interface { - // Desc get translator info - Desc() (name string, fullname string, source string) - // Translate do translation task - Translate(Request) *Response + return ch } diff --git a/gg/gg.go b/gg/gg.go deleted file mode 100644 index a52256e..0000000 --- a/gg/gg.go +++ /dev/null @@ -1,77 +0,0 @@ -package gg - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/tidwall/gjson" - "github.com/xwjdsh/fy" -) - -type google struct{} - -func init() { - fy.Register(new(google)) -} - -func (s *google) Desc() (string, string, string) { - return "gg", "google", "https://translate.google.cn/" -} - -func (s *google) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(s) - _, data, err := fy.ReadResp(http.Get("https://translate.google.cn")) - if err != nil { - resp.Err = fmt.Errorf("fy.ReadResp error: %v", err) - return - } - vq, err := getVq(string(data)) - if err != nil { - resp.Err = fmt.Errorf("getVq error: %v", err) - return - } - tk, err := calTK(vq, req.Text) - if err != nil { - resp.Err = fmt.Errorf("calTK error: %v", err) - return - } - - u, _ := url.Parse("https://translate.google.cn/translate_a/single") - param := u.Query() - param.Set("client", "t") - param.Set("sl", "auto") - param.Set("hl", "zh-CN") - param.Set("tl", req.TargetLang) - param.Set("dt", "t") - param.Set("ie", "UTF-8") - param.Set("oe", "UTF-8") - param.Set("source", "btn") - param.Set("ssel", "3") - param.Set("tsel", "3") - param.Set("kc", "0") - param.Set("tk", tk) - param.Set("q", req.Text) - u.RawQuery = param.Encode() - - _, data, err = fy.SendRequest("GET", u.String(), nil, func(r *http.Request) error { - r.Header.Set("User-Agent", "Paw/3.1.5 (Macintosh; OS X/10.13.2) GCDHTTPRequest") - return nil - }) - if err != nil { - resp.Err = fmt.Errorf("fy.SendRequest error: %v", err) - return - } - - jr := gjson.Parse(string(data)) - if !jr.Get("..0.0.0").Exists() { - resp.Err = fmt.Errorf("cannot get google translate result") - return - } - jsonResult := jr.Get("..0.0").Array() - for _, r := range jsonResult { - resp.Result += r.Get("..0.0").String() - } - - return -} diff --git a/gg/gg_test.go b/gg/gg_test.go deleted file mode 100644 index 85ba4d3..0000000 --- a/gg/gg_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package gg - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_google_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - g *google - args args - wantResp *fy.Response - }{ - { - name: "text = test", - g: &google{}, - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "google", - Result: "测试", - Err: nil, - }, - }, - { - name: "text = 测试", - g: &google{}, - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "google", - Result: "test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &google{} - if gotResp := s.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("google.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} diff --git a/gg/tk_test.go b/gg/tk_test.go deleted file mode 100644 index 57f7f51..0000000 --- a/gg/tk_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package gg - -import "testing" - -func Test_calTK(t *testing.T) { - type args struct { - vq string - query string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - args: args{ - vq: "423665.134779550", - query: "test", - }, - want: "238118.382167", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := calTK(tt.args.vq, tt.args.query) - if (err != nil) != tt.wantErr { - t.Errorf("calTK() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("calTK() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/go.mod b/go.mod index ffbe13e..f0739ea 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,15 @@ module github.com/xwjdsh/fy require ( - github.com/atotto/clipboard v0.1.0 - github.com/fatih/color v1.6.0 - github.com/mattn/go-colorable v0.0.9 - github.com/mattn/go-isatty v0.0.3 - github.com/robertkrimen/otto v0.0.0-20180305042045-6c383dd335ef - github.com/tidwall/gjson v1.1.0 - github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1 - golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94 - golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a + github.com/atotto/clipboard v0.1.2 + github.com/fatih/color v1.7.0 + github.com/mattn/go-colorable v0.1.1 + github.com/mattn/go-isatty v0.0.7 + github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d + github.com/tidwall/gjson v1.2.1 + github.com/tidwall/match v1.0.1 + github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 // indirect + golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 + golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 gopkg.in/sourcemap.v1 v1.0.5 ) diff --git a/go.sum b/go.sum index e59c8bd..5761e76 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,37 @@ github.com/atotto/clipboard v0.1.0 h1:pw3q0vqdkRc2qob0PLEZo3NFSQehzW2dgSdbMI75kEs= github.com/atotto/clipboard v0.1.0/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/robertkrimen/otto v0.0.0-20180305042045-6c383dd335ef h1:daSuzN1zlr5dQpf4lqthbjikjfpE6sQw0WgEBE+DUfA= github.com/robertkrimen/otto v0.0.0-20180305042045-6c383dd335ef/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= +github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/tidwall/gjson v1.1.0 h1:/7OBSUzFP8NhuzLlHg0vETJrRL02C++0ql5uSY3DITs= github.com/tidwall/gjson v1.1.0/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= +github.com/tidwall/gjson v1.2.1 h1:j0efZLrZUvNerEf6xqoi0NjWMK5YlLrR7Guo/dxY174= +github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1 h1:pWIN9LOlFRCJFqWIOEbHLvY0WWJddsjH2FQ6N0HKZdU= github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 h1:BP2bjP495BBPaBcS5rmqviTfrOkN5rO5ceKAMRZCRFc= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94 h1:m5xBqfQdnzv6XuV/pJizrLOwUoGzyn1J249cA0cKL4o= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a h1:DwI0ihryIiWlRUKL/ii7Snvn4LiL9TvMoVZq3qMbffg= golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= diff --git a/gg/tk.go b/google.go similarity index 55% rename from gg/tk.go rename to google.go index fc53a48..e713363 100644 --- a/gg/tk.go +++ b/google.go @@ -1,14 +1,87 @@ -package gg +package fy import ( + "context" "fmt" + "net/http" + "net/url" "regexp" "github.com/robertkrimen/otto" + "github.com/tidwall/gjson" ) +type googleTranslator struct{} + +var google translator = new(googleTranslator) + +func (*googleTranslator) desc() (string, string) { + return "google", "https://translate.google.cn/" +} + +func GoogleTranslate(ctx context.Context, req Request) *Response { + return google.translate(ctx, req) +} + +func (g *googleTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(g) + _, data, err := sendRequest(ctx, http.MethodGet, "https://translate.google.cn", nil, nil) + if err != nil { + resp.Err = fmt.Errorf("readResp error: %v", err) + return + } + vq, err := g.getVq(string(data)) + if err != nil { + resp.Err = fmt.Errorf("getVq error: %v", err) + return + } + tk, err := g.calTK(vq, req.Text) + if err != nil { + resp.Err = fmt.Errorf("calTK error: %v", err) + return + } + + u, _ := url.Parse("https://translate.google.cn/translate_a/single") + param := u.Query() + param.Set("client", "t") + param.Set("sl", "auto") + param.Set("hl", "zh-CN") + param.Set("tl", req.ToLang) + param.Set("dt", "t") + param.Set("ie", "UTF-8") + param.Set("oe", "UTF-8") + param.Set("source", "btn") + param.Set("ssel", "3") + param.Set("tsel", "3") + param.Set("kc", "0") + param.Set("tk", tk) + param.Set("q", req.Text) + u.RawQuery = param.Encode() + + _, data, err = sendRequest(ctx, "GET", u.String(), nil, func(r *http.Request) error { + r.Header.Set("User-Agent", "Paw/3.1.5 (Macintosh; OS X/10.13.2) GCDHTTPRequest") + return nil + }) + if err != nil { + resp.Err = fmt.Errorf("sendRequest error: %v", err) + return + } + + jr := gjson.Parse(string(data)) + if !jr.Get("..0.0.0").Exists() { + resp.Err = fmt.Errorf("cannot get google translate result") + return + } + jsonResult := jr.Get("..0.0").Array() + for _, r := range jsonResult { + resp.Result += r.Get("..0.0").String() + } + + return +} + const ( - tkJS = ` + googleSignJS = ` var Tq = function(a) { return function() { return a @@ -53,7 +126,7 @@ const ( ` ) -func calTK(vq, query string) (string, error) { +func (*googleTranslator) calTK(vq, query string) (string, error) { vm := otto.New() if err := vm.Set("Vq", vq); err != nil { return "", fmt.Errorf("vm.Set Vq error: %v", err) @@ -61,14 +134,14 @@ func calTK(vq, query string) (string, error) { if err := vm.Set("query", query); err != nil { return "", fmt.Errorf("vm.Set query error: %v", err) } - value, err := vm.Run(tkJS) + value, err := vm.Run(googleSignJS) if err != nil { return "", fmt.Errorf("vm.Run error: %v", err) } return value.String(), nil } -func getVq(dataStr string) (string, error) { +func (*googleTranslator) getVq(dataStr string) (string, error) { //dataStr = `LOW_CONFIDENCE_THRESHOLD=-1;MAX_ALTERNATIVES_ROUNDTRIP_RESULTS=1;TKK=eval('((function(){var a\x3d1966732470;var b\x3d1714107181;return 423123+\x27.\x27+(a+b)})())');VERSION_LABEL = 'twsfe_w_20180402_RC00';` vqResult := regexp.MustCompile(`tkk:'(?P[\s\S]+)',experiment_ids`).FindStringSubmatch(dataStr) if len(vqResult) != 2 { diff --git a/google_test.go b/google_test.go new file mode 100644 index 0000000..21465e8 --- /dev/null +++ b/google_test.go @@ -0,0 +1,33 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGoogleTranslate(t *testing.T) { + { + resp := GoogleTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(google) + expectedResp.Result = "测试" + assert.Equal(t, expectedResp, resp) + } + + { + resp := GoogleTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(google) + expectedResp.Result = "test" + assert.EqualValues(t, expectedResp, resp) + + } +} diff --git a/qq/qq_test.go b/qq/qq_test.go deleted file mode 100644 index 41bb6b0..0000000 --- a/qq/qq_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package qq - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_tencent_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - t *tencent - args args - wantResp *fy.Response - }{ - { - name: "text = test", - t: &tencent{}, - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "tencent", - Result: "试验", - Err: nil, - }, - }, - { - name: "text = 测试", - t: &tencent{}, - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "tencent", - Result: "test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - te := &tencent{} - if gotResp := te.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("tencent.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} - -func Test_getQtk(t *testing.T) { - type args struct { - dataStr string - } - tests := []struct { - name string - args args - wantQtv string - wantQtk string - wantErr bool - }{ - { - args: args{ - dataStr: ` - document.cookie = "qtv=ad15088b8bcde724"; - document.cookie = "qtk=aK4qrfL4bLogktVEfIMc785lhWKxOuLuOF243HgKs/lOcPqPhoiwsR+7ysGoTF/rqx1EABKUpNJq2OqbE1PY9T9ICiU2Qm2l0yIMqg3mworcjCX8tiaZzYjkQQqSTk7gCIz/WY0NhTJUrrOemb6nRQ=="; - `, - }, - wantQtv: "ad15088b8bcde724", - wantQtk: "aK4qrfL4bLogktVEfIMc785lhWKxOuLuOF243HgKs/lOcPqPhoiwsR+7ysGoTF/rqx1EABKUpNJq2OqbE1PY9T9ICiU2Qm2l0yIMqg3mworcjCX8tiaZzYjkQQqSTk7gCIz/WY0NhTJUrrOemb6nRQ==", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotQtv, gotQtk, err := getQtk(tt.args.dataStr) - if (err != nil) != tt.wantErr { - t.Errorf("getQtk() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotQtv != tt.wantQtv { - t.Errorf("getQtk() gotQtv = %v, want %v", gotQtv, tt.wantQtv) - } - if gotQtk != tt.wantQtk { - t.Errorf("getQtk() gotQtk = %v, want %v", gotQtk, tt.wantQtk) - } - }) - } -} diff --git a/sg/sg.go b/sg/sg.go deleted file mode 100644 index 21a0b8e..0000000 --- a/sg/sg.go +++ /dev/null @@ -1,62 +0,0 @@ -package sg - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/tidwall/gjson" - "github.com/xwjdsh/fy" -) - -type sogou struct{} - -func init() { - fy.Register(new(sogou)) -} - -var langConvertMap = map[string]string{ - fy.Chinese: "zh-CHS", -} - -func (s *sogou) Desc() (string, string, string) { - return "sg", "sogou", "https://fanyi.sogou.com" -} - -func (s *sogou) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(s) - - if tl, ok := langConvertMap[req.TargetLang]; ok { - req.TargetLang = tl - } - - sign, err := calSign("auto", req.TargetLang, req.Text) - if err != nil { - resp.Err = fmt.Errorf("calSign error: %v", err) - return - } - - param := url.Values{ - "from": {"auto"}, - "to": {req.TargetLang}, - "text": {req.Text}, - "s": {sign}, - } - urlStr := "https://fanyi.sogou.com/reventondc/translateV1" - _, data, err := fy.ReadResp(http.PostForm(urlStr, param)) - if err != nil { - resp.Err = fmt.Errorf("fy.ReadResp error: %v", err) - return - } - - jr := gjson.Parse(string(data)) - - dit := jr.Get("data.translate.dit").String() - if dit == "" { - resp.Err = fmt.Errorf("cannot get translate result") - } else { - resp.Result = dit - } - - return -} diff --git a/sg/sg_test.go b/sg/sg_test.go deleted file mode 100644 index cf98ff3..0000000 --- a/sg/sg_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package sg - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_sogou_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - s *sogou - args args - wantResp *fy.Response - }{ - { - name: "text = test", - s: &sogou{}, - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "sogou", - Result: "试验", - Err: nil, - }, - }, - { - name: "text = 测试", - s: &sogou{}, - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "sogou", - Result: "test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &sogou{} - if gotResp := s.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("sogou.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} diff --git a/sg/sign_test.go b/sg/sign_test.go deleted file mode 100644 index 8e2b1b4..0000000 --- a/sg/sign_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package sg - -import "testing" - -func Test_calSign(t *testing.T) { - type args struct { - from string - to string - text string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "test", - args: args{ - from: "auto", - to: "zh-CHS", - text: "test", - }, - want: "f4919df0708ff951580adfde0085a4cc", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := calSign(tt.args.from, tt.args.to, tt.args.text) - if (err != nil) != tt.wantErr { - t.Errorf("calSign() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("calSign() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/sg/sign.go b/sogou.go similarity index 85% rename from sg/sign.go rename to sogou.go index 6d1f31b..dac02ee 100644 --- a/sg/sign.go +++ b/sogou.go @@ -1,11 +1,76 @@ -package sg +package fy import ( + "context" "fmt" + "net/http" + "net/url" + "strings" "github.com/robertkrimen/otto" + "github.com/tidwall/gjson" ) +type sogouTranslator struct{} + +var sogou translator = new(sogouTranslator) + +func (s *sogouTranslator) desc() (string, string) { + return "sogou", "https://fanyi.sogou.com" +} + +func SogouTranslate(ctx context.Context, req Request) *Response { + return sogou.translate(ctx, req) +} + +func (s *sogouTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(s) + req.ToLang = s.convertLanguage(req.ToLang) + + sign, err := s.calSign("auto", req.ToLang, req.Text) + if err != nil { + resp.Err = fmt.Errorf("calSign error: %v", err) + return + } + + param := url.Values{ + "from": {"auto"}, + "to": {req.ToLang}, + "text": {req.Text}, + "s": {sign}, + } + urlStr := "https://fanyi.sogou.com/reventondc/translateV1" + body := strings.NewReader(param.Encode()) + _, data, err := sendRequest(ctx, http.MethodPost, urlStr, body, func(req *http.Request) error { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + return nil + }) + if err != nil { + resp.Err = fmt.Errorf("readResp error: %v", err) + return + } + + jr := gjson.Parse(string(data)) + dit := jr.Get("data.translate.dit").String() + if dit == "" { + resp.Err = fmt.Errorf("cannot get translate result") + } else { + resp.Result = dit + } + + return +} + +func (s *sogouTranslator) convertLanguage(language string) string { + l := language + switch language { + case Chinese: + l = "zh-CHS" + } + + return l +} + const signJS = ` var n122 = { "rotl": function(t, e) { @@ -200,9 +265,9 @@ function sign(t, n) { result = sign(query) ` -func calSign(from, to, text string) (string, error) { +func (*sogouTranslator) calSign(from, to, text string) (string, error) { vm := otto.New() - if err := vm.Set("query", from+to+text+"b33bf8c58706155663d1ad5dba4192dc"); err != nil { + if err := vm.Set("query", from+to+text+"72da1dc662daf182c4f7671ec884074b"); err != nil { return "", fmt.Errorf("vm.Set query error: %v", err) } value, err := vm.Run(signJS) diff --git a/sogou_test.go b/sogou_test.go new file mode 100644 index 0000000..46b7226 --- /dev/null +++ b/sogou_test.go @@ -0,0 +1,32 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSogouTranslate(t *testing.T) { + { + resp := SogouTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(sogou) + expectedResp.Result = "试验" + assert.Equal(t, expectedResp, resp) + } + + { + resp := SogouTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(sogou) + expectedResp.Result = "test" + assert.Equal(t, expectedResp, resp) + } +} diff --git a/qq/qq.go b/tencent.go similarity index 64% rename from qq/qq.go rename to tencent.go index 0eba6da..224160f 100644 --- a/qq/qq.go +++ b/tencent.go @@ -1,6 +1,7 @@ -package qq +package fy import ( + "context" "fmt" "net/http" "net/url" @@ -9,47 +10,39 @@ import ( "time" "github.com/tidwall/gjson" - - "github.com/xwjdsh/fy" ) -type tencent struct{} +type tencentTranslator struct{} -var langConvertMap = map[string]string{ - fy.Chinese: "zh", - fy.Japanese: "jp", - fy.Korean: "kr", -} +var tencent translator = new(tencentTranslator) -func init() { - fy.Register(new(tencent)) +func (t *tencentTranslator) desc() (string, string) { + return "tencent", "https://fanyi.qq.com/" } -func (t *tencent) Desc() (string, string, string) { - return "qq", "tencent", "https://fanyi.qq.com/" +func TencentTranslate(ctx context.Context, req Request) *Response { + return tencent.translate(ctx, req) } -func (t *tencent) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(t) +func (t *tencentTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(t) - _, data, err := fy.SendRequest("GET", "https://fanyi.qq.com", nil, nil) + _, data, err := sendRequest(ctx, "GET", "https://fanyi.qq.com", nil, nil) if err != nil { - err = fmt.Errorf("fy.SendRequest error: %v", err) + err = fmt.Errorf("sendRequest error: %v", err) return } - qtv, qtk, err := getQtk(string(data)) + qtv, qtk, err := t.getQtk(string(data)) if err != nil { resp.Err = fmt.Errorf("getQtk error: %v", err) return } - if tl, ok := langConvertMap[req.TargetLang]; ok { - req.TargetLang = tl - } + req.ToLang = t.convertLanguage(req.ToLang) param := url.Values{ "source": {"auto"}, - "target": {req.TargetLang}, + "target": {req.ToLang}, "sourceText": {req.Text}, "qtv": {qtv}, "qtk": {qtk}, @@ -58,7 +51,7 @@ func (t *tencent) Translate(req fy.Request) (resp *fy.Response) { urlStr := "https://fanyi.qq.com/api/translate" body := strings.NewReader(param.Encode()) - _, data, err = fy.SendRequest("POST", urlStr, body, func(req *http.Request) error { + _, data, err = sendRequest(ctx, "POST", urlStr, body, func(req *http.Request) error { req.Header.Set("Origin", "http://fanyi.qq.com") req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0") req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") @@ -66,7 +59,7 @@ func (t *tencent) Translate(req fy.Request) (resp *fy.Response) { return nil }) if err != nil { - resp.Err = fmt.Errorf("fy.SendRequest error: %v", err) + resp.Err = fmt.Errorf("sendRequest error: %v", err) return } @@ -87,7 +80,21 @@ func (t *tencent) Translate(req fy.Request) (resp *fy.Response) { return } -func getQtk(dataStr string) (qtv string, qtk string, err error) { +func (*tencentTranslator) convertLanguage(language string) string { + l := language + switch language { + case Chinese: + l = "zh" + case Japanese: + l = "jp" + case Korean: + l = "kr" + } + + return l +} + +func (*tencentTranslator) getQtk(dataStr string) (qtv string, qtk string, err error) { //document.cookie = "qtv=ad15088b8bcde724"; qtvResult := regexp.MustCompile(`"qtv=(?P\S+)";`).FindStringSubmatch(dataStr) if len(qtvResult) != 2 { diff --git a/tencent_test.go b/tencent_test.go new file mode 100644 index 0000000..f7f3653 --- /dev/null +++ b/tencent_test.go @@ -0,0 +1,33 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTencentTranslate(t *testing.T) { + { + resp := TencentTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(tencent) + expectedResp.Result = "试验 / 测试 / 烤钵 / 检验" + assert.Equal(t, expectedResp, resp) + } + + { + resp := TencentTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(tencent) + expectedResp.Result = "test" + assert.Equal(t, expectedResp, resp) + } + +} diff --git a/utils.go b/utils.go index b0e3983..b695d64 100644 --- a/utils.go +++ b/utils.go @@ -1,40 +1,15 @@ package fy import ( + "context" "fmt" "io" "io/ioutil" "net/http" "unicode" - - "github.com/fatih/color" -) - -const ( - // IconGood icon goog - IconGood = "✔" - // IconBad icon bad - IconBad = "✗" - // Logo logo - Logo = ` - ____ - / __/_ __ - / /_/ / / / - / __/ /_/ / - /_/ \__, / - /____/ - -` - // Desc version and homepage - Desc = ` - version: %s -homepage: https://github.com/xwjdsh/fy -` - // CoffeeEmoji coffee emoji - CoffeeEmoji = "\u2615\ufe0f" ) -// IsChinese whether param is Chinese +// IsChinese determines whether the param is Chinese. func IsChinese(str string) bool { for _, r := range str { if unicode.Is(unicode.Scripts["Han"], r) { @@ -44,50 +19,32 @@ func IsChinese(str string) bool { return false } -// PrintSource print source info -func PrintSource(name, fullname, source string) { - fmt.Printf("\t %s %s [%s]\t%s\n", color.GreenString(IconGood), name, fullname, source) -} - -// ReadResp read response and closed it -func ReadResp(resp *http.Response, err error) (*http.Response, []byte, error) { - if err != nil { - return nil, nil, fmt.Errorf("http request error: %v", err) - } - defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, nil, fmt.Errorf("ioutil.ReadAll error: %v", err) - } - return resp, respBody, nil -} - -// NotReadResp dont read response, just closed it -func NotReadResp(resp *http.Response, err error) (*http.Response, error) { - if err != nil { - return nil, fmt.Errorf("http response error: %v", err) - } - defer resp.Body.Close() - return resp, nil -} - -// SendRequest send request -func SendRequest(method, urlStr string, body io.Reader, f func(*http.Request) error) (*http.Response, []byte, error) { +func sendRequest(ctx context.Context, method, urlStr string, body io.Reader, f func(*http.Request) error) (*http.Response, []byte, error) { req, err := http.NewRequest(method, urlStr, body) if err != nil { return nil, nil, fmt.Errorf("http.NewRequest error: %v", err) } + req = req.WithContext(ctx) client := &http.Client{} if f != nil { if err := f(req); err != nil { return nil, nil, fmt.Errorf("f error: %v", err) } } - return ReadResp(client.Do(req)) + + resp, err := client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("http request error: %v", err) + } + defer resp.Body.Close() + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("ioutil.ReadAll error: %v", err) + } + return resp, respBody, nil } -// AddCookies add cookies to http request -func AddCookies(req *http.Request, cookies []*http.Cookie) *http.Request { +func addCookies(req *http.Request, cookies []*http.Cookie) *http.Request { for _, cookie := range cookies { req.AddCookie(cookie) } diff --git a/utils_test.go b/utils_test.go index bbafa10..744b93d 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,43 +1,12 @@ package fy -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestIsChinese(t *testing.T) { - type args struct { - str string - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "str = test", - args: args{ - str: "test", - }, - want: false, - }, - { - name: "str = 测试", - args: args{ - str: "测试", - }, - want: true, - }, - { - name: "str = test 测试", - args: args{ - str: "test 测试", - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := IsChinese(tt.args.str); got != tt.want { - t.Errorf("IsChinese() = %v, want %v", got, tt.want) - } - }) - } + assert.True(t, IsChinese("测试")) + assert.False(t, IsChinese("test")) } diff --git a/yd/yd_test.go b/yd/yd_test.go deleted file mode 100644 index 67b95fb..0000000 --- a/yd/yd_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package sg - -import ( - "reflect" - "testing" - - "github.com/xwjdsh/fy" -) - -func Test_youdao_Translate(t *testing.T) { - type args struct { - req fy.Request - } - tests := []struct { - name string - y *youdao - args args - wantResp *fy.Response - }{ - { - name: "text = test", - y: &youdao{}, - args: args{ - req: fy.Request{ - TargetLang: "zh-CN", - Text: "test", - }, - }, - wantResp: &fy.Response{ - FullName: "youdao", - Result: "测试", - Err: nil, - }, - }, - { - name: "text = 测试", - y: &youdao{}, - args: args{ - req: fy.Request{ - TargetLang: "en", - Text: "测试", - }, - }, - wantResp: &fy.Response{ - FullName: "youdao", - Result: "test", - Err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - y := &youdao{} - if gotResp := y.Translate(tt.args.req); !reflect.DeepEqual(gotResp, tt.wantResp) { - t.Errorf("youdao.Translate() = %v, want %v", gotResp, tt.wantResp) - } - }) - } -} diff --git a/yd/yd.go b/youdao.go similarity index 60% rename from yd/yd.go rename to youdao.go index f99a096..63c8988 100644 --- a/yd/yd.go +++ b/youdao.go @@ -1,6 +1,7 @@ -package sg +package fy import ( + "context" "crypto/md5" "encoding/hex" "fmt" @@ -11,29 +12,26 @@ import ( "time" "github.com/tidwall/gjson" - "github.com/xwjdsh/fy" ) -type youdao struct{} +type youdaoTranslator struct{} -var langConvertMap = map[string]string{ - fy.Chinese: "zh-CHS", -} +var youdao translator = new(youdaoTranslator) -func init() { - fy.Register(new(youdao)) +func (y *youdaoTranslator) desc() (string, string) { + return "youdao", "http://fanyi.youdao.com/" } -func (y *youdao) Desc() (string, string, string) { - return "yd", "youdao", "http://fanyi.youdao.com/" +func YoudaoTranslate(ctx context.Context, req Request) *Response { + return youdao.translate(ctx, req) } -func (y *youdao) Translate(req fy.Request) (resp *fy.Response) { - resp = fy.NewResp(y) +func (y *youdaoTranslator) translate(ctx context.Context, req Request) (resp *Response) { + resp = newResp(y) - r, err := fy.NotReadResp(http.Get("http://youdao.com")) + r, _, err := sendRequest(ctx, http.MethodGet, "http://youdao.com", nil, nil) if err != nil { - resp.Err = fmt.Errorf("fy.NotReadResp error: %v", err) + resp.Err = fmt.Errorf("notReadResp error: %v", err) return } cookies := r.Cookies() @@ -47,12 +45,10 @@ func (y *youdao) Translate(req fy.Request) (resp *fy.Response) { h.Write([]byte("fanyideskweb" + req.Text + salt + `ebSeFb%=XZ%T[KZ)c(sy!`)) sign := hex.EncodeToString(h.Sum(nil)) - if tl, ok := langConvertMap[req.TargetLang]; ok { - req.TargetLang = tl - } + req.ToLang = y.convertLanguage(req.ToLang) param := url.Values{ "from": {"AUTO"}, - "to": {req.TargetLang}, + "to": {req.ToLang}, "i": {req.Text}, "client": {"fanyideskweb"}, "salt": {salt}, @@ -62,15 +58,15 @@ func (y *youdao) Translate(req fy.Request) (resp *fy.Response) { } urlStr := "http://fanyi.youdao.com/translate_o" body := strings.NewReader(param.Encode()) - _, data, err := fy.SendRequest("POST", urlStr, body, func(req *http.Request) error { + _, data, err := sendRequest(ctx, "POST", urlStr, body, func(req *http.Request) error { req.Header.Set("Referer", "http://fanyi.youdao.com/") req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0") req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - fy.AddCookies(req, cookies) + addCookies(req, cookies) return nil }) if err != nil { - resp.Err = fmt.Errorf("fy.SendRequest error: %v", err) + resp.Err = fmt.Errorf("sendRequest error: %v", err) return } @@ -83,3 +79,13 @@ func (y *youdao) Translate(req fy.Request) (resp *fy.Response) { resp.Result = jr.Get("translateResult.0").String() return } + +func (y *youdaoTranslator) convertLanguage(language string) string { + l := language + switch language { + case Chinese: + l = "zh-CHS" + } + + return l +} diff --git a/youdao_test.go b/youdao_test.go new file mode 100644 index 0000000..2560b49 --- /dev/null +++ b/youdao_test.go @@ -0,0 +1,32 @@ +package fy + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestYoudaoTranslate(t *testing.T) { + { + resp := YoudaoTranslate(context.Background(), Request{ + ToLang: Chinese, + Text: "test", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(youdao) + expectedResp.Result = "测试" + assert.Equal(t, expectedResp, resp) + } + + { + resp := YoudaoTranslate(context.Background(), Request{ + ToLang: English, + Text: "测试", + }) + assert.Nil(t, resp.Err) + expectedResp := newResp(youdao) + expectedResp.Result = "test" + assert.Equal(t, expectedResp, resp) + } +}