google_web_translate 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9de16ca14cfa21f2f4e2feba9fb67f1e662d37bc
4
- data.tar.gz: a80a769dbb0f387f9c5c12c3baa7dc31befd0cf6
3
+ metadata.gz: 4fa88abae6fc3035a666db53549211dd084bbaaf
4
+ data.tar.gz: 4942b2dd7b155f4507d8fd1b2c902c7e66074e98
5
5
  SHA512:
6
- metadata.gz: 576ad67066a1a4ba0fd953743a06a9fc713a229e6db732b5805a77b132b4984a6d7c8f4966d2f35b64f6cca9915888ee10efb3eb542c3896bfc6d3fc7d8af23c
7
- data.tar.gz: 55a74de3fddd74d6e179cea599ad82ee76e19f0799de07c66f22c25c93593a06515172456a3d6dc0705af963d1c2c54921bb996aab70128b9165cc10593e95cd
6
+ metadata.gz: 857468dc26b8538e2be64454b1b1db9dc65ada47a53d63766c351e813ff9a5010e1759333aa0a80b194faae9adf26b895ef69aaf63c524398467016f2a04e394
7
+ data.tar.gz: e37080f63adfbf5e0464b2e9f8340f7b22160e25ba36b736dd6e16404fb13e92359f80f4b9e6a3485ea1c9a1b4176dd7dc55f0360949637aae210f6a552b9287
data/.gitignore CHANGED
@@ -9,6 +9,7 @@
9
9
  /generated.js
10
10
  /spec.out
11
11
  /Gemfile.lock
12
+ /data/server_data.txt
12
13
 
13
14
  # rspec failure tracking
14
15
  .rspec_status
data/Gemfile CHANGED
@@ -17,5 +17,8 @@ end
17
17
  optional_gem 'therubyracer', platform: :ruby
18
18
  optional_gem 'therubyrhino', platform: :jruby
19
19
 
20
+ # optional c extensions
21
+ optional_gem 'concurrent-ruby-ext', platform: :ruby
22
+
20
23
  # Specify your gem's dependencies in google-web-translate.gemspec
21
24
  gemspec
data/data/urls.txt ADDED
@@ -0,0 +1,122 @@
1
+ google.ae
2
+ google.am
3
+ google.as
4
+ google.at
5
+ google.at
6
+ google.az
7
+ google.ba
8
+ google.be
9
+ google.bg
10
+ google.ca
11
+ google.cd
12
+ google.ch
13
+ google.ci
14
+ google.cl
15
+ google.cn
16
+ google.co.cr
17
+ google.co.id
18
+ google.co.il
19
+ google.co.in
20
+ google.co.jp
21
+ google.co.ke
22
+ google.co.kr
23
+ google.com
24
+ google.co.ma
25
+ google.com.ai
26
+ google.com.ar
27
+ google.com.au
28
+ google.com.bd
29
+ google.com.bh
30
+ google.com.bn
31
+ google.com.bo
32
+ google.com.br
33
+ google.com.co
34
+ google.com.cu
35
+ google.com.do
36
+ google.com.ec
37
+ google.com.eg
38
+ google.com.et
39
+ google.com.gi
40
+ google.com.gt
41
+ google.com.hk
42
+ google.com.jm
43
+ google.com.kh
44
+ google.com.ly
45
+ google.com.mt
46
+ google.com.mx
47
+ google.com.my
48
+ google.com.na
49
+ google.com.ng
50
+ google.com.ni
51
+ google.com.om
52
+ google.com.pa
53
+ google.com.pe
54
+ google.com.ph
55
+ google.com.pk
56
+ google.com.pr
57
+ google.com.py
58
+ google.com.qa
59
+ google.com.sa
60
+ google.com.sg
61
+ google.com.sv
62
+ google.com.tr
63
+ google.com.tw
64
+ google.com.ua
65
+ google.com.uy
66
+ google.com.vc
67
+ google.com.vn
68
+ google.co.nz
69
+ google.co.th
70
+ google.co.ug
71
+ google.co.uk
72
+ google.co.uz
73
+ google.co.ve
74
+ google.co.za
75
+ google.co.zm
76
+ google.cz
77
+ google.de
78
+ google.dj
79
+ google.dk
80
+ google.ee
81
+ google.es
82
+ google.fi
83
+ google.fr
84
+ google.ge
85
+ google.gm
86
+ google.gp
87
+ google.gr
88
+ google.hn
89
+ google.hr
90
+ google.ht
91
+ google.hu
92
+ google.ie
93
+ google.is
94
+ google.it
95
+ google.je
96
+ google.jo
97
+ google.kz
98
+ google.li
99
+ google.lk
100
+ google.lt
101
+ google.lu
102
+ google.lv
103
+ google.ma
104
+ google.md
105
+ google.mn
106
+ google.mu
107
+ google.nl
108
+ google.no
109
+ google.nu
110
+ google.pl
111
+ google.pl
112
+ google.pt
113
+ google.ro
114
+ google.ru
115
+ google.rw
116
+ google.se
117
+ google.si
118
+ google.sk
119
+ google.sm
120
+ google.sn
121
+ google.to
122
+ google.tt
@@ -1,30 +1,31 @@
1
-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'google_web_translate/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'google_web_translate'
8
- spec.version = GoogleWebTranslate::VERSION
9
- spec.authors = ['Andrew']
10
- spec.email = ['sobakasu@gmail.com']
11
-
12
- spec.summary = 'Text translation using the google web interface'
13
- spec.homepage = 'https://github.com/sobakasu/google_web_translate'
14
- spec.license = 'MIT'
15
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
- f.match(%r{^(test|spec|features)/})
17
- end
18
- spec.bindir = 'bin'
19
- spec.executables = %w[google_web_translate]
20
- spec.require_paths = ['lib']
21
-
22
- spec.add_development_dependency 'bundler', '~> 1.16'
23
- spec.add_development_dependency 'rake', '~> 10.0'
24
- spec.add_development_dependency 'rspec', '~> 3.0'
25
- spec.add_development_dependency 'simplecov'
26
- spec.add_development_dependency 'webmock'
27
-
28
- spec.add_dependency 'execjs'
29
- spec.add_dependency 'thor'
30
- end
1
+
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'google_web_translate/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'google_web_translate'
8
+ spec.version = GoogleWebTranslate::VERSION
9
+ spec.authors = ['Andrew']
10
+ spec.email = ['sobakasu@gmail.com']
11
+
12
+ spec.summary = 'Text translation using the google web interface'
13
+ spec.homepage = 'https://github.com/sobakasu/google_web_translate'
14
+ spec.license = 'MIT'
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = 'bin'
19
+ spec.executables = %w[google_web_translate]
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.16'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
25
+ spec.add_development_dependency 'simplecov'
26
+ spec.add_development_dependency 'webmock'
27
+
28
+ spec.add_dependency 'concurrent-ruby'
29
+ spec.add_dependency 'execjs'
30
+ spec.add_dependency 'thor'
31
+ end
@@ -1,5 +1,6 @@
1
- require 'google_web_translate/version.rb'
2
- require 'google_web_translate/string_escaping.rb'
3
- require 'google_web_translate/http_client.rb'
4
- require 'google_web_translate/result.rb'
5
- require 'google_web_translate/api.rb'
1
+ require 'google_web_translate/version.rb'
2
+ require 'google_web_translate/server_list.rb'
3
+ require 'google_web_translate/string_escaping.rb'
4
+ require 'google_web_translate/http_client.rb'
5
+ require 'google_web_translate/result.rb'
6
+ require 'google_web_translate/api.rb'
@@ -1,138 +1,142 @@
1
- require 'execjs'
2
- require 'json'
3
-
4
- module GoogleWebTranslate
5
- # interface to the google web translation api
6
- class API
7
- def initialize(options = {})
8
- @dt = options[:dt] || DEFAULT_DT
9
- @token_ttl = options[:token_ttl] || DEFAULT_TOKEN_TTL
10
- @debug = options[:debug]
11
- @http_client = options[:http_client] || HTTPClient.new(options)
12
- end
13
-
14
- def translate(string, from, to)
15
- data = fetch_translation(string, from, to)
16
- Result.new(data)
17
- end
18
-
19
- def languages
20
- @languages ||= begin
21
- html = fetch_main
22
- html.scan(/\['(\w{2})','(\w{2})'\]/).flatten.uniq.sort
23
- end
24
- end
25
-
26
- private
27
-
28
- URL_MAIN = 'https://translate.google.com'.freeze
29
- URL_TRANSLATE_1 = URL_MAIN + '/translate_a/single'.freeze
30
- DEFAULT_DT = %w[at bd ex ld md qca rw rm ss t].freeze
31
- DEFAULT_TOKEN_TTL = 3600
32
-
33
- def fetch_translation(string, from, to)
34
- json = fetch_url_body(translate_url(string, from, to))
35
- # File.open("response.json", "w") { |f| f.puts json }
36
- JSON.parse(json)
37
- end
38
-
39
- def fetch_url_response(url)
40
- @http_client.get(url.to_s)
41
- end
42
-
43
- def fetch_url_body(url)
44
- uri = URI.parse(url)
45
- uri = URI.join(URL_MAIN, url) if uri.relative?
46
- debug("fetch #{uri}")
47
- response = fetch_url_response(uri)
48
- # debug("response: #{response.body}")
49
- response.body
50
- end
51
-
52
- def valid_token?
53
- @token_updated_at && Time.now - @token_updated_at < @token_ttl
54
- end
55
-
56
- def fetch_main(options = {})
57
- @html = nil if options[:no_cache]
58
- @html ||= fetch_url_body(URL_MAIN)
59
- end
60
-
61
- def fetch_desktop_module(html)
62
- html =~ /([^="]*desktop_module_main.js)/
63
- url = Regexp.last_match(1)
64
- raise 'unable to find desktop module' unless url
65
- fetch_url_body(url)
66
- end
67
-
68
- def munge_module(js)
69
- js.gsub(/((?:var\s+)?\w+\s*=\s*\w+\.createElement.*?;)/) do |_i|
70
- 'return "";'
71
- end
72
- end
73
-
74
- def compile_js(html)
75
- desktop_module_js = munge_module(fetch_desktop_module(html))
76
- window_js = File.read(File.join(__dir__, '..', 'js', 'window.js'))
77
- js = window_js + desktop_module_js
78
- File.open('generated.js', 'w') { |f| f.puts(js) } if debug?
79
- ExecJS.compile(js)
80
- end
81
-
82
- def update_token
83
- # download main page
84
- html = fetch_main(no_cache: true)
85
- # extract tkk from html
86
- @tkk = extract_tkk(html)
87
- # compile desktop module javascript
88
- @js_context = compile_js(html)
89
- @token_updated_at = Time.now
90
- end
91
-
92
- def tk(string)
93
- update_token unless valid_token?
94
- @js_context.call('setWindowProperty', 'TKK', @tkk)
95
- # tk = @js_context.call("wq", string)
96
- tk = @js_context.call('generateToken', string, @tkk)
97
- (tk.split('=') || [])[1]
98
- end
99
-
100
- def tk_js
101
- File.read(File.join(__dir__, 'google_web.js'))
102
- end
103
-
104
- def extract_tkk(html)
105
- raise 'TKK not found' unless html =~ /TKK=eval\('(.*?)'\);/
106
- tkk_code = Regexp.last_match(1)
107
- # tkk_code = Translatomatic::StringEscaping.unescape(tkk_code)
108
- tkk_code = StringEscaping.unescape(tkk_code)
109
- debug("tkk code unescaped: #{tkk_code}")
110
- tkk = ExecJS.eval(tkk_code)
111
- # tkk = context.call(nil)
112
- debug("evaluated tkk: #{tkk}")
113
- tkk
114
- end
115
-
116
- def translate_url(string, from, to)
117
- tk = tk(string)
118
- debug("tk: #{tk}")
119
- query = {
120
- sl: from, tl: to, ie: 'UTF-8', oe: 'UTF-8',
121
- q: string, dt: @dt, tk: tk,
122
- # not sure what these are for
123
- client: 't', hl: 'en', otf: 1, ssel: 4, tsel: 6, kc: 5
124
- }
125
- url = URI.parse(URL_TRANSLATE_1)
126
- url.query = URI.encode_www_form(query)
127
- url.to_s
128
- end
129
-
130
- def debug(msg)
131
- puts msg if debug?
132
- end
133
-
134
- def debug?
135
- @debug
136
- end
137
- end
138
- end
1
+ require 'execjs'
2
+ require 'json'
3
+
4
+ module GoogleWebTranslate
5
+ # interface to the google web translation api
6
+ class API
7
+ def initialize(options = {})
8
+ @dt = options[:dt] || DEFAULT_DT
9
+ @token_ttl = options[:token_ttl] || DEFAULT_TOKEN_TTL
10
+ @debug = options[:debug]
11
+ @http_client = options[:http_client] || HTTPClient.new(options)
12
+ @rate_limit = options[:rate_limit] || DEFAULT_RATE_LIMIT
13
+ end
14
+
15
+ def translate(string, from, to)
16
+ data = fetch_translation(string, from, to)
17
+ Result.new(data)
18
+ end
19
+
20
+ def languages
21
+ @languages ||= begin
22
+ html = fetch_main
23
+ html.scan(/\['(\w{2})','(\w{2})'\]/).flatten.uniq.sort
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ URL_MAIN = 'https://translate.google.com'.freeze
30
+ TRANSLATE_PATH = '/translate_a/single'.freeze
31
+ DEFAULT_DT = %w[at bd ex ld md qca rw rm ss t].freeze
32
+ DEFAULT_TOKEN_TTL = 3600
33
+ DEFAULT_RATE_LIMIT = 5
34
+
35
+ def fetch_translation(string, from, to)
36
+ server = ServerList.next_server(@rate_limit)
37
+ json = fetch_url_body(translate_url(server, string, from, to))
38
+ # File.write("response.json", json) if debug?
39
+ debug("response: #{json}")
40
+ JSON.parse(json)
41
+ end
42
+
43
+ def fetch_url_response(url)
44
+ @http_client.get(url.to_s)
45
+ end
46
+
47
+ def fetch_url_body(url)
48
+ uri = URI.parse(url)
49
+ uri = URI.join(URL_MAIN, url) if uri.relative?
50
+ debug("fetch #{uri}")
51
+ response = fetch_url_response(uri)
52
+ response.body
53
+ end
54
+
55
+ def valid_token?
56
+ @token_updated_at && Time.now - @token_updated_at < @token_ttl
57
+ end
58
+
59
+ def fetch_main(options = {})
60
+ @html = nil if options[:no_cache]
61
+ @html ||= fetch_url_body(URL_MAIN)
62
+ end
63
+
64
+ def fetch_desktop_module(html)
65
+ html =~ /([^="]*desktop_module_main.js)/
66
+ url = Regexp.last_match(1)
67
+ raise 'unable to find desktop module' unless url
68
+ fetch_url_body(url)
69
+ end
70
+
71
+ def munge_module(js)
72
+ js.gsub(/((?:var\s+)?\w+\s*=\s*\w+\.createElement.*?;)/) do |_i|
73
+ 'return "";'
74
+ end
75
+ end
76
+
77
+ def compile_js(html)
78
+ desktop_module_js = munge_module(fetch_desktop_module(html))
79
+ window_js = File.read(File.join(__dir__, '..', 'js', 'window.js'))
80
+ js = window_js + desktop_module_js
81
+ # File.write('generated.js', js) if debug?
82
+ ExecJS.compile(js)
83
+ end
84
+
85
+ def update_token
86
+ # download main page
87
+ html = fetch_main(no_cache: true)
88
+ # extract tkk from html
89
+ @tkk = extract_tkk(html)
90
+ # compile desktop module javascript
91
+ @js_context = compile_js(html)
92
+ @token_updated_at = Time.now
93
+ end
94
+
95
+ def tk(string)
96
+ update_token unless valid_token?
97
+ @js_context.call('setWindowProperty', 'TKK', @tkk)
98
+ # tk = @js_context.call("wq", string)
99
+ tk = @js_context.call('generateToken', string, @tkk)
100
+ (tk.split('=') || [])[1]
101
+ end
102
+
103
+ def tk_js
104
+ File.read(File.join(__dir__, 'google_web.js'))
105
+ end
106
+
107
+ def extract_tkk(html)
108
+ raise 'TKK not found' unless html =~ /TKK=eval\('(.*?)'\);/
109
+ tkk_code = Regexp.last_match(1)
110
+ # tkk_code = Translatomatic::StringEscaping.unescape(tkk_code)
111
+ tkk_code = StringEscaping.unescape(tkk_code)
112
+ debug("tkk code unescaped: #{tkk_code}")
113
+ tkk = ExecJS.eval(tkk_code)
114
+ # tkk = context.call(nil)
115
+ debug("evaluated tkk: #{tkk}")
116
+ tkk
117
+ end
118
+
119
+ def translate_url(server, string, from, to)
120
+ tk = tk(string)
121
+ debug("tk: #{tk}")
122
+ query = {
123
+ sl: from, tl: to, ie: 'UTF-8', oe: 'UTF-8',
124
+ q: string, dt: @dt, tk: tk,
125
+ # not sure what these are for
126
+ client: 't', hl: 'en', otf: 1, ssel: 4, tsel: 6, kc: 5
127
+ }
128
+ url = "https://#{server.host}" + TRANSLATE_PATH
129
+ uri = URI.parse(url)
130
+ uri.query = URI.encode_www_form(query)
131
+ uri.to_s
132
+ end
133
+
134
+ def debug(msg)
135
+ puts msg if debug?
136
+ end
137
+
138
+ def debug?
139
+ @debug
140
+ end
141
+ end
142
+ end
@@ -1,18 +1,18 @@
1
- require 'thor'
2
- require 'pp'
3
-
4
- module GoogleWebTranslate
5
- # Command line interface
6
- class CLI < Thor
7
- desc 'string from to', 'translate a string from one language to another'
8
- method_option :dt, type: :array, desc: 'data types'
9
- def translate(string, from, to)
10
- api_options = { debug: ENV['DEBUG'] }
11
- api_options[:dt] = options[:dt] if options[:dt]
12
-
13
- api = API.new(api_options)
14
- result = api.translate(string, from, to)
15
- pp result.to_h
16
- end
17
- end
18
- end
1
+ require 'thor'
2
+ require 'pp'
3
+
4
+ module GoogleWebTranslate
5
+ # Command line interface
6
+ class CLI < Thor
7
+ desc 'string from to', 'translate a string from one language to another'
8
+ method_option :dt, type: :array, desc: 'data types'
9
+ def translate(string, from, to)
10
+ api_options = { debug: ENV['DEBUG'] }
11
+ api_options[:dt] = options[:dt] if options[:dt]
12
+
13
+ api = API.new(api_options)
14
+ result = api.translate(string, from, to)
15
+ pp result.to_h
16
+ end
17
+ end
18
+ end
@@ -1,26 +1,26 @@
1
- require 'net/http'
2
-
3
- module GoogleWebTranslate
4
- # HTTP client functionality
5
- class HTTPClient
6
- def self.user_agent
7
- gem_version = "GoogleWebTranslate/#{VERSION}"
8
- platform_version = "(#{RUBY_PLATFORM}) #{RUBY_ENGINE}/#{RUBY_VERSION}"
9
- gem_version + ' ' + platform_version
10
- end
11
-
12
- def initialize(options = {})
13
- @user_agent = options[:user_agent] || self.class.user_agent
14
- end
15
-
16
- def get(url)
17
- uri = URI.parse(url)
18
- request = Net::HTTP::Get.new(uri)
19
- request['User-Agent'] = @user_agent
20
- options = { use_ssl: uri.scheme == 'https' }
21
- Net::HTTP.start(uri.host, uri.port, options) do |http|
22
- http.request(request)
23
- end
24
- end
25
- end
26
- end
1
+ require 'net/http'
2
+
3
+ module GoogleWebTranslate
4
+ # HTTP client functionality
5
+ class HTTPClient
6
+ def self.user_agent
7
+ gem_version = "GoogleWebTranslate/#{VERSION}"
8
+ platform_version = "(#{RUBY_PLATFORM}) #{RUBY_ENGINE}/#{RUBY_VERSION}"
9
+ gem_version + ' ' + platform_version
10
+ end
11
+
12
+ def initialize(options = {})
13
+ @user_agent = options[:user_agent] || self.class.user_agent
14
+ end
15
+
16
+ def get(url)
17
+ uri = URI.parse(url)
18
+ request = Net::HTTP::Get.new(uri)
19
+ request['User-Agent'] = @user_agent
20
+ options = { use_ssl: uri.scheme == 'https' }
21
+ Net::HTTP.start(uri.host, uri.port, options) do |http|
22
+ http.request(request)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,54 +1,54 @@
1
- module GoogleWebTranslate
2
- # Translation results
3
- class Result
4
- attr_reader :raw
5
-
6
- # @private
7
- DATA_INDICES = {
8
- translation: [0, 0, 0], # dt:t
9
- alternatives: [5, 0, 2], # dt:at
10
- dictionary: [1], # dt: bd
11
- synonyms: [11], # dt:ss
12
- definitions: [12, 0], # dt:md
13
- examples: [13, 0], # dt:ex
14
- see_also: [14, 0], # dt:rw
15
- }.freeze
16
-
17
- DATA_INDICES.each_key { |key| attr_reader key }
18
-
19
- def initialize(data)
20
- @raw = data
21
- @keys = []
22
- @properties = {}
23
-
24
- DATA_INDICES.each do |key, indices|
25
- indices = indices.dup
26
- extract_data(key, *indices)
27
- end
28
-
29
- @alternatives = @alternatives.collect { |i| i[0] } if @alternatives
30
- @keys.each { |key| @properties[key] = instance_variable_get("@#{key}") }
31
- end
32
-
33
- def to_h
34
- @properties
35
- end
36
-
37
- private
38
-
39
- def extract_data(name, *indices)
40
- value = array_value(@raw, *indices)
41
- return if value.nil?
42
- instance_variable_set("@#{name}", value)
43
- @keys.push(name)
44
- end
45
-
46
- def array_value(array, *indices)
47
- return nil if array.nil?
48
- index = indices.shift
49
- value = array[index]
50
- return value if indices.empty?
51
- array_value(value, *indices)
52
- end
53
- end
54
- end
1
+ module GoogleWebTranslate
2
+ # Translation results
3
+ class Result
4
+ attr_reader :raw
5
+
6
+ # @private
7
+ DATA_INDICES = {
8
+ translation: [0, 0, 0], # dt:t
9
+ alternatives: [5, 0, 2], # dt:at
10
+ dictionary: [1], # dt: bd
11
+ synonyms: [11], # dt:ss
12
+ definitions: [12, 0], # dt:md
13
+ examples: [13, 0], # dt:ex
14
+ see_also: [14, 0], # dt:rw
15
+ }.freeze
16
+
17
+ DATA_INDICES.each_key { |key| attr_reader key }
18
+
19
+ def initialize(data)
20
+ @raw = data
21
+ @keys = []
22
+ @properties = {}
23
+
24
+ DATA_INDICES.each do |key, indices|
25
+ indices = indices.dup
26
+ extract_data(key, *indices)
27
+ end
28
+
29
+ @alternatives = @alternatives.collect { |i| i[0] } if @alternatives
30
+ @keys.each { |key| @properties[key] = instance_variable_get("@#{key}") }
31
+ end
32
+
33
+ def to_h
34
+ @properties
35
+ end
36
+
37
+ private
38
+
39
+ def extract_data(name, *indices)
40
+ value = array_value(@raw, *indices)
41
+ return if value.nil?
42
+ instance_variable_set("@#{name}", value)
43
+ @keys.push(name)
44
+ end
45
+
46
+ def array_value(array, *indices)
47
+ return nil if array.nil?
48
+ index = indices.shift
49
+ value = array[index]
50
+ return value if indices.empty?
51
+ array_value(value, *indices)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,146 @@
1
+ require 'concurrent'
2
+ require 'resolv'
3
+ require 'json'
4
+
5
+ module GoogleWebTranslate
6
+ # @private
7
+ SERVER_ATTRIBUTES = %i[host ip resolved_at last_used_at
8
+ counter available].freeze
9
+
10
+ Server = Struct.new(*SERVER_ATTRIBUTES) do
11
+ def to_json(*args)
12
+ result = {}
13
+ each_pair { |key, value| result[key] = value }
14
+ result.to_json(args)
15
+ end
16
+ end
17
+
18
+ class ServerList
19
+ class << self
20
+ def servers
21
+ update_servers if @servers.nil?
22
+ @servers.dup
23
+ end
24
+
25
+ def next_server(rate_limit = nil)
26
+ @mutex ||= Mutex.new
27
+ @mutex.synchronize do
28
+ @counter ||= 0
29
+ @counter += 1
30
+
31
+ list = servers.sort_by { |i| i.counter || 0 }
32
+ server = list[0]
33
+ server.counter = @counter
34
+ sleep(rate_limit_delay(server, rate_limit))
35
+ server.last_used_at = Time.now
36
+ server
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ MAX_TTL = 86_400
43
+
44
+ def rate_limit_delay(server, rate_limit)
45
+ return 0 unless rate_limit && server.last_used_at
46
+ delay = rate_limit - (Time.now - server.last_used_at)
47
+ delay < 0 || ENV['TEST'] ? 0 : delay
48
+ end
49
+
50
+ def update_servers
51
+ server_list = read_server_data
52
+ pool = Concurrent::CachedThreadPool.new
53
+ # puts "updating #{server_list.length} servers"
54
+ server_list.each do |server|
55
+ pool.post { update_server(server) }
56
+ end
57
+
58
+ pool.shutdown
59
+ pool.wait_for_termination
60
+ @servers = unique_servers(server_list)
61
+ # puts "#{@servers.length} unique servers found"
62
+ save_server_data(server_list)
63
+ end
64
+
65
+ def update_server(server)
66
+ now = Time.now.to_i
67
+ if server.resolved_at.nil? ||
68
+ now - server.resolved_at > MAX_TTL || !server.available
69
+ server.resolved_at = now
70
+ server.ip = resolve_ip(server.host)
71
+ end
72
+ server.available = true
73
+ rescue Resolv::ResolvError
74
+ # puts "server #{server.host} is unavailable: #{e}"
75
+ server.available = false
76
+ end
77
+
78
+ def data_dir
79
+ File.join(__dir__, '..', '..', 'data')
80
+ end
81
+
82
+ def server_data_path
83
+ File.join(data_dir, 'server_data.txt')
84
+ end
85
+
86
+ def url_list_path
87
+ File.join(data_dir, 'urls.txt')
88
+ end
89
+
90
+ def hostnames
91
+ names = []
92
+ lines = File.read(url_list_path).split(/[\r\n]+/)
93
+ lines.each do |host|
94
+ next unless host && !host.empty?
95
+ names << "translate.#{host}"
96
+ end
97
+ names
98
+ end
99
+
100
+ def unique_servers(list)
101
+ server_by_ip = {}
102
+ list.each do |server|
103
+ next unless server.available
104
+ server_by_ip[server.ip] = server
105
+ end
106
+ server_by_ip.values
107
+ end
108
+
109
+ def initial_data
110
+ hostnames.collect do |host|
111
+ server = Server.new
112
+ server.host = host
113
+ server
114
+ end
115
+ end
116
+
117
+ def read_server_data
118
+ return initial_data unless File.exist?(server_data_path)
119
+ data = JSON.parse(File.read(server_data_path))
120
+ server_list = []
121
+ data.each do |entry|
122
+ attributes = SERVER_ATTRIBUTES.collect { |i| entry[i.to_s] }
123
+ server = Server.new(*attributes)
124
+ next unless server.host && !server.host.empty?
125
+ server.counter = 0
126
+ server_list << server
127
+ end
128
+ server_list
129
+ end
130
+
131
+ def save_server_data(servers)
132
+ File.write(server_data_path, servers.to_json)
133
+ end
134
+
135
+ def resolver
136
+ resolver = Resolv::DNS.new
137
+ resolver.timeouts = 5
138
+ resolver
139
+ end
140
+
141
+ def resolve_ip(host)
142
+ resolver.getaddress(host).to_s
143
+ end
144
+ end
145
+ end
146
+ end
@@ -1,3 +1,3 @@
1
- module GoogleWebTranslate
2
- VERSION = '0.2.2'.freeze
3
- end
1
+ module GoogleWebTranslate
2
+ VERSION = '0.2.3'.freeze
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google_web_translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-23 00:00:00.000000000 Z
11
+ date: 2018-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: concurrent-ruby
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: execjs
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -128,12 +142,14 @@ files:
128
142
  - bin/console
129
143
  - bin/google_web_translate
130
144
  - bin/setup
145
+ - data/urls.txt
131
146
  - google-web-translate.gemspec
132
147
  - lib/google_web_translate.rb
133
148
  - lib/google_web_translate/api.rb
134
149
  - lib/google_web_translate/cli.rb
135
150
  - lib/google_web_translate/http_client.rb
136
151
  - lib/google_web_translate/result.rb
152
+ - lib/google_web_translate/server_list.rb
137
153
  - lib/google_web_translate/string_escaping.rb
138
154
  - lib/google_web_translate/version.rb
139
155
  - lib/js/window.js