russian_post 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b004d7e8725e3d084956ead13b4e170b135309a
4
+ data.tar.gz: 1e9a5e5a24281d852341963fc6d7270602cc2822
5
+ SHA512:
6
+ metadata.gz: cc8680522729c6b95a06754a8a213610af79a77cc2ab12f9d6e4106cde61dd5e4b0e864b150f228ae3ba3198c214602d8d256ac17ddf0a953e9c1bcfaede08c9
7
+ data.tar.gz: f62d87d7981d586d74b5968e3f3e6a845bfd3503366b38ff5af093dca3057701dcf55dfc9ce7ef72e162b2f4e670389fe77b6443177fd36b7b7464589fd98365
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ app.rb
2
+ views
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ russian_post (0.0.1)
5
+ chunky_png (~> 1.2)
6
+ excon (~> 0.20)
7
+ nokogiri (~> 1.5)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.3.3)
13
+ chunky_png (1.2.7)
14
+ crack (0.3.2)
15
+ excon (0.20.1)
16
+ nokogiri (1.5.9)
17
+ rake (10.0.3)
18
+ webmock (1.11.0)
19
+ addressable (>= 2.2.7)
20
+ crack (>= 0.3.2)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ bundler (~> 1.3)
27
+ rake
28
+ russian_post!
29
+ webmock (~> 1.11)
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Russian Post Tools
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'russian_post'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install russian_post
16
+
17
+ ## Usage
18
+
19
+ ### Captcha recognizer
20
+
21
+ ```ruby
22
+ require 'russian_post'
23
+
24
+ RussianPost::Captcha.for_url(captcha_url).text
25
+ # => 96950
26
+
27
+ RussianPost::Captcha.for_data(png_blob).text
28
+ # => 43455
29
+ ```
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |i|
4
+ i.libs << 'test'
5
+ i.test_files = FileList['test/**/*_test.rb']
6
+ i.verbose = true
7
+ end
8
+
9
+ desc "Run tests"
10
+ task :default => :test
@@ -0,0 +1,26 @@
1
+
2
+ class RussianPost::Captcha::Pattern
3
+
4
+ TARGET_MATCH_RATE = 1.0
5
+
6
+ attr_reader :points, :character
7
+
8
+ def initialize(points, character)
9
+ @points, @character = points, character
10
+ end
11
+
12
+ def match?(image, x, y)
13
+ matching = 0
14
+
15
+ points.each do |px, py|
16
+ cx, cy = x + px, y + py
17
+
18
+ break if cx >= image.width || cy >= image.height || image[cx, cy] == 255
19
+
20
+ matching += 1
21
+ end
22
+
23
+ matching / points.size >= TARGET_MATCH_RATE
24
+ end
25
+
26
+ end
@@ -0,0 +1,67 @@
1
+ require 'russian_post/captcha/pattern'
2
+
3
+ class RussianPost::Captcha::Patterns
4
+ attr_reader :patterns
5
+
6
+ class << self
7
+
8
+ def built_in
9
+ @builtin_patterns ||= RussianPost::Captcha::Patterns.new(File.expand_path('characters.dat', File.dirname(__FILE__)))
10
+ end
11
+
12
+ end
13
+
14
+ def initialize(file)
15
+ @patterns = load_patterns(file)
16
+ end
17
+
18
+ def each(&block)
19
+ @patterns.each(&block)
20
+ end
21
+
22
+ def add(pattern)
23
+ @patterns << pattern
24
+ end
25
+
26
+ def find(image, x, y)
27
+ each do |pattern|
28
+ return pattern if pattern.match?(image, x, y)
29
+ end
30
+
31
+ return false
32
+ end
33
+
34
+ private
35
+
36
+ def serialize(patterns)
37
+ array_hash = Hash.new{ |hash, key| hash[key] = []}
38
+
39
+ data = patterns.inject(array_hash) do |result, pattern|
40
+ result[pattern.character] << pattern.points
41
+ result
42
+ end
43
+
44
+ Marshal.dump(data)
45
+ end
46
+
47
+ def deserialize(data)
48
+ raw_data = Marshal.load(data)
49
+
50
+ raw_data.inject([]) do |result, data|
51
+ character, patterns = data
52
+
53
+ result += patterns.map { |points| RussianPost::Captcha::Pattern.new(points, character) }
54
+ end
55
+ end
56
+
57
+ def save_patterns(file, patterns)
58
+ File.open(file, 'wb') { |file| file.write(serialize(patterns)) }
59
+ end
60
+
61
+ def load_patterns(file)
62
+ raise "Specified patterns file not exists (#{file})" unless File.exists?(file)
63
+
64
+ deserialize(File.open(file, 'rb') { |file| file.read })
65
+ end
66
+
67
+ end
@@ -0,0 +1,99 @@
1
+ require 'excon'
2
+ require 'chunky_png'
3
+
4
+ module RussianPost
5
+
6
+ class Captcha
7
+
8
+ require 'russian_post/captcha/patterns'
9
+
10
+ attr_reader :patterns
11
+
12
+ class << self
13
+
14
+ def for_url(url, patterns = nil)
15
+ RussianPost::Captcha.new(url, nil, patterns)
16
+ end
17
+
18
+ def for_data(data, patterns = nil)
19
+ RussianPost::Captcha.new(nil, data, patterns)
20
+ end
21
+
22
+ end
23
+
24
+ def image
25
+ @data ||= fetch_image
26
+ end
27
+
28
+ def text
29
+ @recognized_text ||= prepare_text(recognize)
30
+ end
31
+
32
+ def valid?
33
+ text.size == 5
34
+ end
35
+
36
+ def recognize
37
+ @recognize ||= recognize!
38
+ end
39
+
40
+ def recognize!
41
+
42
+ captcha_image = grayscale(image)
43
+
44
+ results = {}
45
+
46
+ for x in 0..captcha_image.width - 2
47
+ for y in 0..captcha_image.height - 2
48
+ result = patterns.find(captcha_image, x, y)
49
+
50
+ results[x] = result if result
51
+ end
52
+ end
53
+
54
+ Hash[results.sort].values
55
+ end
56
+
57
+ private
58
+
59
+ def initialize(url = nil, data = nil, patterns = nil) # private constructor
60
+ @url = url
61
+ @data = ChunkyPNG::Image.from_blob(data) if data
62
+ @patterns ||= RussianPost::Captcha::Patterns.built_in
63
+ end
64
+
65
+ def prepare_text(results)
66
+ results.map(&:character).join('')
67
+ end
68
+
69
+ def grayscale(captcha_image)
70
+ captcha_image.pixels.map! { |color| color = ChunkyPNG::Color.grayscale_teint(color); color = (color >= 255) ? 255 : 0 ; color }
71
+
72
+ captcha_image
73
+ end
74
+
75
+ def fetch_image
76
+ raise "URL not specified" unless @url
77
+
78
+ data = Excon.get(@url).body
79
+
80
+
81
+
82
+ unless data.start_with?("\x89PNG".force_encoding("ASCII-8BIT")) # not png header
83
+ if data =~ /<input id=\"key\" name=\"key\" value=\"([0-9]+)\"\/>/ # tough security huh
84
+ data = Excon.post(@url, body: "key=#{$1}").body
85
+ end
86
+
87
+ if data =~ /Object moved/ # faulty captcha
88
+ raise "Russian Post captcha service error"
89
+ end
90
+ end
91
+
92
+ File.open('test.png', 'wb') { |f| f.write(data) }
93
+
94
+ ChunkyPNG::Image.from_blob(data)
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,11 @@
1
+ require 'chunky_png'
2
+
3
+ module RussianPost
4
+
5
+ require 'russian_post/version'
6
+
7
+ require 'russian_post/captcha'
8
+
9
+ require 'russian_post/tracking'
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ module RussianPost
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'russian_post/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "russian_post"
9
+ spec.version = RussianPost::VERSION
10
+ spec.authors = ["t3hk0d3"]
11
+ spec.email = ["clouster@yandex.ru"]
12
+ spec.description = %q{RussianPost tools}
13
+ spec.summary = %q{RussianPost tools}
14
+ spec.homepage = "http://github.com/t3hk0d3/russian_post"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "excon", "~> 0.20"
22
+ spec.add_dependency "chunky_png", "~> 1.2"
23
+ spec.add_dependency "nokogiri", "~> 1.5"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "webmock", "~> 1.11"
28
+ end
Binary file
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class RussianPost::Captcha::PatternTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @image = load_captcha_image
7
+
8
+ @points = [[5, 0], [6, 0], [7, 0], [8, 0], [10, 0], [4, 1], [10, 1], [10, 2], [10, 3], [5, 4], [10, 4], [1, 5], [5, 5], [10, 5],
9
+ [5, 6], [10, 6], [0, 7], [5, 7], [0, 8], [3, 8], [4, 8], [0, 9], [11, 9], [11, 10], [0, 11], [1, 11], [2, 11], [3, 11], [5, 11],
10
+ [5, 12], [10, 13], [5, 14], [6, 14], [7, 14], [8, 14], [9, 14]]
11
+
12
+ @pattern = RussianPost::Captcha::Pattern.new(@points, '4')
13
+ end
14
+
15
+ def test_find_pattern
16
+ assert @pattern.match?(@image, 4, 3)
17
+ end
18
+
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class RussianPost::Captcha::PatternsTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @image = load_captcha_image
7
+
8
+ @patterns = RussianPost::Captcha::Patterns.built_in # @FIXME use mock patterns instead
9
+
10
+ end
11
+
12
+ def test_builtin_patterns
13
+ assert @patterns.patterns.size > 0
14
+ end
15
+
16
+ def test_find
17
+ assert_equal '4', @patterns.find(@image, 4, 3).character
18
+ end
19
+
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ module RussianPost
4
+
5
+ class CaptchaTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @url = 'http://www.russianpost.ru/CaptchaService/CaptchaImage.ashx?Id=361256433'
9
+ end
10
+
11
+ def test_recognize
12
+ captcha = RussianPost::Captcha.for_data(load_captcha_file)
13
+
14
+ assert_equal '49396', captcha.text
15
+ end
16
+
17
+ def test_fetch
18
+ stub_get = stub_request(:get, @url).to_return(:body => load_captcha_file, :headers => { 'Content-Type' => 'image/png' })
19
+
20
+ RussianPost::Captcha.for_url(@url).recognize!
21
+
22
+ assert_requested(stub_get)
23
+ end
24
+
25
+ def test_cunning_fetch
26
+ @tough_security = "<html><head></head><body onload=\"document.myform.submit();\"><form method=\"post\" name=\"myform\" style=\"visibility:hidden;\"><input id=\"key\" name=\"key\" value=\"117413\"/><input type=\"submit\"/></form></body></html>\r\n\r\n"
27
+
28
+ stub_get = stub_request(:get, @url).to_return(:body => @tough_security, :headers => { 'Content-Type' => 'text/html' })
29
+ stub_post = stub_request(:post, @url).with(:body => 'key=117413').to_return(:body => load_captcha_file, :headers => { 'Content-Type' => 'image/png' })
30
+
31
+ RussianPost::Captcha.for_url(@url).recognize!
32
+
33
+ assert_requested(stub_get)
34
+ assert_requested(stub_post)
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,23 @@
1
+ $:.unshift File.expand_path("../lib/russian_post/", File.dirname(__FILE__))
2
+
3
+ require 'test/unit'
4
+ require 'webmock/test_unit'
5
+
6
+ require 'russian_post'
7
+
8
+ class Test::Unit::TestCase
9
+
10
+ def load_captcha_file
11
+ File.open('test/fixtures/captcha.png', 'rb') { |f| f.read }
12
+ end
13
+
14
+
15
+ def load_captcha_image
16
+ image = ChunkyPNG::Image.from_file('test/fixtures/captcha.png')
17
+ image.pixels.map! { |color| color = ChunkyPNG::Color.grayscale_teint(color); color = (color >= 255) ? 255 : 0 ; color }
18
+
19
+ image
20
+ end
21
+
22
+ end
23
+
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: russian_post
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - t3hk0d3
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: excon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.20'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.20'
27
+ - !ruby/object:Gem::Dependency
28
+ name: chunky_png
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.11'
97
+ description: RussianPost tools
98
+ email:
99
+ - clouster@yandex.ru
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - Gemfile.lock
107
+ - README.md
108
+ - Rakefile
109
+ - lib/russian_post/captcha.rb
110
+ - lib/russian_post/captcha/characters.dat
111
+ - lib/russian_post/captcha/pattern.rb
112
+ - lib/russian_post/captcha/patterns.rb
113
+ - lib/russian_post/russian_post.rb
114
+ - lib/russian_post/version.rb
115
+ - russian_post.gemspec
116
+ - test/fixtures/captcha.png
117
+ - test/russian_post/captcha/pattern_test.rb
118
+ - test/russian_post/captcha/patterns_test.rb
119
+ - test/russian_post/captcha_test.rb
120
+ - test/test_helper.rb
121
+ homepage: http://github.com/t3hk0d3/russian_post
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.0.3
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: RussianPost tools
145
+ test_files:
146
+ - test/fixtures/captcha.png
147
+ - test/russian_post/captcha/pattern_test.rb
148
+ - test/russian_post/captcha/patterns_test.rb
149
+ - test/russian_post/captcha_test.rb
150
+ - test/test_helper.rb
151
+ has_rdoc: