deathbycaptcha 4.1.5 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
1
+ module DeathByCaptcha
2
+
3
+ # HTTP client for DeathByCaptcha API.
4
+ #
5
+ class Client::HTTP < Client
6
+
7
+ BASE_URL = 'http://api.dbcapi.me/api'
8
+
9
+ # Retrieve information from an uploaded captcha.
10
+ #
11
+ # @param [Integer] captcha_id Numeric ID of the captcha.
12
+ #
13
+ # @return [DeathByCaptcha::Captcha] The captcha object.
14
+ #
15
+ def captcha(captcha_id)
16
+ response = perform("captcha/#{captcha_id}")
17
+ DeathByCaptcha::Captcha.new(response)
18
+ end
19
+
20
+ # Report incorrectly solved captcha for refund.
21
+ #
22
+ # @param [Integer] captcha_id Numeric ID of the captcha.
23
+ #
24
+ # @return [DeathByCaptcha::Captcha] The captcha object.
25
+ #
26
+ def report!(captcha_id)
27
+ response = perform("captcha/#{captcha_id}/report", :post)
28
+ DeathByCaptcha::Captcha.new(response)
29
+ end
30
+
31
+ # Retrieve your user information (which has the current credit balance)
32
+ #
33
+ # @return [DeathByCaptcha::User] The user object.
34
+ #
35
+ def user
36
+ response = perform('user')
37
+ DeathByCaptcha::User.new(response)
38
+ end
39
+
40
+ # Retrieve DeathByCaptcha server status.
41
+ #
42
+ # @return [DeathByCaptcha::ServerStatus] The server status object.
43
+ #
44
+ def status
45
+ response = perform('status')
46
+ DeathByCaptcha::ServerStatus.new(response)
47
+ end
48
+
49
+ # Upload a captcha to DeathByCaptcha.
50
+ #
51
+ # This method will not return the solution. It's only useful if you want to
52
+ # implement your own "decode" function.
53
+ #
54
+ # @return [DeathByCaptcha::Captcha] The captcha object (not solved yet).
55
+ #
56
+ def upload(raw64)
57
+ response = perform('captcha', :post_multipart, captchafile: "base64:#{raw64}")
58
+ DeathByCaptcha::Captcha.new(response)
59
+ end
60
+
61
+ private
62
+
63
+ # Perform an HTTP request to the DeathByCaptcha API.
64
+ #
65
+ # @param [String] action API method name.
66
+ # @param [Symbol] method HTTP method (:get, :post, :post_multipart).
67
+ # @param [Hash] payload Data to be sent through the HTTP request.
68
+ #
69
+ # @return [Hash] Response from the DeathByCaptcha API.
70
+ #
71
+ def perform(action, method = :get, payload = {})
72
+ payload.merge!(username: self.username, password: self.password)
73
+
74
+ headers = { 'User-Agent' => DeathByCaptcha::API_VERSION }
75
+
76
+ if method == :post
77
+ uri = URI("#{BASE_URL}/#{action}")
78
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
79
+ req.set_form_data(payload)
80
+
81
+ elsif method == :post_multipart
82
+ uri = URI("#{BASE_URL}/#{action}")
83
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
84
+ boundary, body = prepare_multipart_data(payload)
85
+ req.content_type = "multipart/form-data; boundary=#{boundary}"
86
+ req.body = body
87
+
88
+ else
89
+ uri = URI("#{BASE_URL}/#{action}?#{URI.encode_www_form(payload)}")
90
+ req = Net::HTTP::Get.new(uri.request_uri, headers)
91
+ end
92
+
93
+ res = Net::HTTP.start(uri.hostname, uri.port) do |http|
94
+ http.request(req)
95
+ end
96
+
97
+ case res
98
+ when Net::HTTPSuccess, Net::HTTPSeeOther
99
+ Hash[URI.decode_www_form(res.body)]
100
+ when Net::HTTPForbidden
101
+ raise DeathByCaptcha::APIForbidden
102
+ when Net::HTTPBadRequest
103
+ raise DeathByCaptcha::APIBadRequest
104
+ when Net::HTTPRequestEntityTooLarge
105
+ raise DeathByCaptcha::APICaptchaTooLarge
106
+ when Net::HTTPServiceUnavailable
107
+ raise DeathByCaptcha::APIServiceUnavailable
108
+ else
109
+ raise DeathByCaptcha::APIResponseError.new(res.body)
110
+ end
111
+ end
112
+
113
+ # Prepare the multipart data to be sent via a :post_multipart request.
114
+ #
115
+ # @param [Hash] payload Data to be prepared via a multipart post.
116
+ #
117
+ # @return [String,String] Boundary and body for the multipart post.
118
+ #
119
+ def prepare_multipart_data(payload)
120
+ boundary = "infosimples" + rand(1_000_000).to_s # a random unique string
121
+
122
+ content = []
123
+ payload.each do |param, value|
124
+ content << '--' + boundary
125
+ content << "Content-Disposition: form-data; name=\"#{param}\""
126
+ content << ''
127
+ content << value
128
+ end
129
+ content << '--' + boundary + '--'
130
+ content << ''
131
+
132
+ [boundary, content.join("\r\n")]
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,113 @@
1
+ module DeathByCaptcha
2
+
3
+ # Socket client for DeathByCaptcha API.
4
+ #
5
+ class Client::Socket < Client
6
+
7
+ HOST = 'api.dbcapi.me'
8
+ PORTS = (8123..8130).to_a
9
+
10
+ # Retrieve information from an uploaded captcha.
11
+ #
12
+ # @param [Integer] captcha_id Numeric ID of the captcha.
13
+ #
14
+ # @return [DeathByCaptcha::Captcha] The captcha object.
15
+ #
16
+ def captcha(captcha_id)
17
+ response = perform('captcha', captcha: captcha_id)
18
+ DeathByCaptcha::Captcha.new(response)
19
+ end
20
+
21
+ # Report incorrectly solved captcha for refund.
22
+ #
23
+ # @param [Integer] captcha_id Numeric ID of the captcha.
24
+ #
25
+ # @return [DeathByCaptcha::Captcha] The captcha object.
26
+ #
27
+ def report!(captcha_id)
28
+ response = perform('report', captcha: captcha_id)
29
+ DeathByCaptcha::Captcha.new(response)
30
+ end
31
+
32
+ # Retrieve your user information (which has the current credit balance)
33
+ #
34
+ # @return [DeathByCaptcha::User] The user object.
35
+ #
36
+ def user
37
+ response = perform('user')
38
+ DeathByCaptcha::User.new(response)
39
+ end
40
+
41
+ # Retrieve DeathByCaptcha server status. This method won't use a Socket
42
+ # connection, it will use an HTTP connection.
43
+ #
44
+ # @return [DeathByCaptcha::ServerStatus] The server status object.
45
+ #
46
+ def status
47
+ http_client.status
48
+ end
49
+
50
+ # Upload a captcha to DeathByCaptcha.
51
+ #
52
+ # This method will not return the solution. It's only useful if you want to
53
+ # implement your own "decode" function.
54
+ #
55
+ # @return [DeathByCaptcha::Captcha] The captcha object (not solved yet).
56
+ #
57
+ def upload(raw64)
58
+ response = perform('upload', captcha: raw64)
59
+ DeathByCaptcha::Captcha.new(response)
60
+ end
61
+
62
+ private
63
+
64
+ # Perform a Socket communication with the DeathByCaptcha API.
65
+ #
66
+ # @param [String] action API method name.
67
+ # @param [Hash] payload Data to be exchanged in the communication.
68
+ #
69
+ # @return [Hash] Response from the DeathByCaptcha API.
70
+ #
71
+ def perform(action, payload = {})
72
+ payload.merge!(
73
+ cmd: action,
74
+ version: DeathByCaptcha::API_VERSION,
75
+ username: self.username,
76
+ password: self.password
77
+ )
78
+
79
+ response = ::Socket.tcp(HOST, PORTS.sample) do |socket|
80
+ socket.puts payload.to_json
81
+ socket.read
82
+ end
83
+
84
+ begin
85
+ response = JSON.parse(response)
86
+ rescue
87
+ raise DeathByCaptcha::APIResponseError.new("invalid JSON: #{response}")
88
+ end
89
+
90
+ if !(error = response['error'].to_s).empty?
91
+ case error
92
+ when 'not-logged-in', 'invalid-credentials', 'banned', 'insufficient-funds'
93
+ raise DeathByCaptcha::APIForbidden
94
+ when 'invalid-captcha'
95
+ raise DeathByCaptcha::APIBadRequest
96
+ when 'service-overload'
97
+ raise DeathByCaptcha::APIServiceUnavailable
98
+ else
99
+ raise DeathByCaptcha::APIResponseError.new(error)
100
+ end
101
+ end
102
+
103
+ response
104
+ end
105
+
106
+ # Return a cached http client for methods that doesn't work with sockets.
107
+ #
108
+ def http_client
109
+ @http_client ||= DeathByCaptcha.new(self.username, self.password, :http)
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,62 @@
1
+ module DeathByCaptcha
2
+
3
+ # This is the base DeathByCaptcha exception class. Rescue it if you want to
4
+ # catch any exception that might be raised.
5
+ #
6
+ class Error < Exception
7
+ end
8
+
9
+ class InvalidClientConnection < Error
10
+ def initialize
11
+ super('You have specified an invalid client connection (valid connections are :socket, :http)')
12
+ end
13
+ end
14
+
15
+ class InvalidCaptcha < Error
16
+ def initialize
17
+ super('The captcha is empty or invalid')
18
+ end
19
+ end
20
+
21
+ class Timeout < Error
22
+ def initialize
23
+ super('The captcha was not solved in the expected time')
24
+ end
25
+ end
26
+
27
+ class IncorrectSolution < Error
28
+ def initialize
29
+ super('CAPTCHA was not solved by DeathByCaptcha. Try again.')
30
+ end
31
+ end
32
+
33
+ class APIResponseError < Error
34
+ def initialize(info)
35
+ super("Invalid API response: #{info}")
36
+ end
37
+ end
38
+
39
+ class APIForbidden < Error
40
+ def initialize
41
+ super('Access denied, please check your credentials and/or balance')
42
+ end
43
+ end
44
+
45
+ class APIServiceUnavailable < Error
46
+ def initialize
47
+ super('CAPTCHA was rejected due to service overload, try again later')
48
+ end
49
+ end
50
+
51
+ class APIBadRequest < Error
52
+ def initialize
53
+ super('CAPTCHA was rejected by the service, check if it\'s a valid image')
54
+ end
55
+ end
56
+
57
+ class APICaptchaTooLarge < Error
58
+ def initialize
59
+ super('CAPTCHA image size is too large')
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ module DeathByCaptcha
2
+
3
+ # Base class of a model object returned by DeathByCaptcha API.
4
+ #
5
+ class Model
6
+ def initialize(values = {})
7
+ values.each do |key, value|
8
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'deathbycaptcha/models/captcha'
15
+ require 'deathbycaptcha/models/server_status'
16
+ require 'deathbycaptcha/models/user'
@@ -0,0 +1,20 @@
1
+ module DeathByCaptcha
2
+
3
+ # Model of a Captcha returned by DeathByCaptcha API.
4
+ #
5
+ class Captcha < DeathByCaptcha::Model
6
+ attr_accessor :captcha, :is_correct, :text
7
+
8
+ def id
9
+ @captcha
10
+ end
11
+
12
+ def is_correct=(value)
13
+ @is_correct = ['1', true].include?(value)
14
+ end
15
+
16
+ def captcha=(value)
17
+ @captcha = value.to_i
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module DeathByCaptcha
2
+
3
+ # Model of a server status returned by DeathByCaptcha API.
4
+ #
5
+ class ServerStatus < DeathByCaptcha::Model
6
+ attr_accessor :todays_accuracy, :solved_in, :is_service_overloaded
7
+
8
+ def todays_accuracy=(value)
9
+ @todays_accuracy = value.to_f
10
+ end
11
+
12
+ def solved_in=(value)
13
+ @solved_in = value.to_i
14
+ end
15
+
16
+ def is_service_overloaded=(value)
17
+ @is_service_overloaded = ['1', true].include?(value)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module DeathByCaptcha
2
+
3
+ # Model of a User returned by DeathByCaptcha API.
4
+ #
5
+ class User < DeathByCaptcha::Model
6
+ attr_accessor :is_banned, :balance, :rate, :user
7
+
8
+ def id
9
+ @user
10
+ end
11
+
12
+ def is_banned=(value)
13
+ @is_banned = ['1', true].include?(value)
14
+ end
15
+
16
+ def balance=(value)
17
+ @balance = value.to_f
18
+ end
19
+
20
+ def rate=(value)
21
+ @rate = value.to_f
22
+ end
23
+
24
+ def user=(value)
25
+ @user = value.to_i
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ # Patches are added so older versions of Ruby work with this gem
2
+ #
3
+ unless ''.respond_to?(:empty?)
4
+ class String
5
+ def empty?
6
+ self.length == 0
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
1
  module DeathByCaptcha
2
- VERSION = "4.1.5"
3
- API_VERSION = "DBC/Ruby v4.1.2"
4
- end
2
+ VERSION = "5.0.0"
3
+ API_VERSION = "DBC/Ruby v#{VERSION}"
4
+ end
@@ -0,0 +1,3 @@
1
+ username: myusername
2
+ password: mypassword
3
+ captcha_id: 12345 # use any ID of a captcha solved on your account
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ username = CREDENTIALS['username']
4
+ password = CREDENTIALS['password']
5
+ captcha_id = CREDENTIALS['captcha_id']
6
+ image64 = Base64.encode64(File.open('captchas/1.png', 'rb').read)
7
+
8
+ describe DeathByCaptcha::Client do
9
+ describe '.create' do
10
+ context 'http' do
11
+ let(:client) { DeathByCaptcha.new(username, password, :http) }
12
+ it { expect(client).to be_a(DeathByCaptcha::Client::HTTP) }
13
+ end
14
+
15
+ context 'socket' do
16
+ let(:client) { DeathByCaptcha.new(username, password, :socket) }
17
+ it { expect(client).to be_a(DeathByCaptcha::Client::Socket) }
18
+ end
19
+
20
+ context 'default' do
21
+ let(:client) { DeathByCaptcha.new(username, password) }
22
+ it { expect(client).to be_a(DeathByCaptcha::Client::Socket) }
23
+ end
24
+
25
+ context 'other' do
26
+ it { expect {
27
+ DeathByCaptcha.new(username, password, :other)
28
+ }.to raise_error(DeathByCaptcha::InvalidClientConnection)
29
+ }
30
+ end
31
+ end
32
+
33
+ context 'http' do
34
+ subject(:client) { DeathByCaptcha.new(username, password, :http) }
35
+
36
+ describe '#load_captcha' do
37
+ it { expect(client.send(:load_captcha, url: 'http://bit.ly/1xXZcKo')).to eq(image64) }
38
+ it { expect(client.send(:load_captcha, path: 'captchas/1.png')).to eq(image64) }
39
+ it { expect(client.send(:load_captcha, file: File.open('captchas/1.png', 'rb'))).to eq(image64) }
40
+ it { expect(client.send(:load_captcha, raw: File.open('captchas/1.png', 'rb').read)).to eq(image64) }
41
+ it { expect(client.send(:load_captcha, raw64: image64)).to eq(image64) }
42
+ it { expect(client.send(:load_captcha, other: nil)).to eq('') }
43
+ end
44
+ end
45
+ end
46
+
47
+ shared_examples 'a client' do
48
+ describe '#captcha' do
49
+ before(:all) { @captcha = @client.captcha(captcha_id) }
50
+ it { expect(@captcha).to be_a(DeathByCaptcha::Captcha) }
51
+ it { expect(@captcha.text.size).to be > 0 }
52
+ it { expect([true, false]).to include(@captcha.is_correct) }
53
+ it { expect(@captcha.id).to be > 0 }
54
+ it { expect(@captcha.id).to eq(@captcha.captcha) }
55
+ end
56
+
57
+ describe '#user' do
58
+ before(:all) { @user = @client.user() }
59
+ it { expect(@user).to be_a(DeathByCaptcha::User) }
60
+ it { expect([true, false]).to include(@user.is_banned) }
61
+ it { expect(@user.balance).to be > 0 }
62
+ it { expect(@user.rate).to be > 0 }
63
+ it { expect(@user.id).to eq(@user.user) }
64
+ end
65
+
66
+ describe '#status' do
67
+ before(:all) { @status = @client.status() }
68
+ it { expect(@status).to be_a(DeathByCaptcha::ServerStatus) }
69
+ it { expect(@status.todays_accuracy).to be > 0 }
70
+ it { expect(@status.solved_in).to be > 0 }
71
+ it { expect([true, false]).to include(@status.is_service_overloaded) }
72
+ end
73
+
74
+ describe '#decode!' do
75
+ before(:all) { @captcha = @client.decode!(raw64: image64) }
76
+ it { expect(@captcha).to be_a(DeathByCaptcha::Captcha) }
77
+ it { expect(@captcha.text).to eq 'infosimples' }
78
+ it { expect(@captcha.is_correct).to be true }
79
+ it { expect(@captcha.id).to be > 0 }
80
+ it { expect(@captcha.id).to eq(@captcha.captcha) }
81
+ end
82
+ end
83
+
84
+ describe DeathByCaptcha::Client::HTTP do
85
+ it_behaves_like 'a client' do
86
+ before(:all) { @client = DeathByCaptcha.new(username, password, :http) }
87
+ end
88
+ end
89
+
90
+ describe DeathByCaptcha::Client::Socket do
91
+ it_behaves_like 'a client' do
92
+ before(:all) { @client = DeathByCaptcha.new(username, password, :socket) }
93
+ end
94
+ end