muaraicaptcha 1.1.0
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 +7 -0
- data/README.md +67 -0
- data/lib/muaraicaptcha.rb +136 -0
- metadata +48 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1030792ff79fdc91401c0d9b1f0d8e6454526158bb194abdba16709a3620d191
|
|
4
|
+
data.tar.gz: 6266c3710a1edc4c1d59d0dd66d9dd0cc315df945c30d0600ba303d963436128
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 983ea8895eb487ff6f9e0836e7f635c61ec9a6864bba1f6cac2ee441b6b36060eb8e127e6f96cf17bcc23e4cb67139ea9dcb4ac40603cb96e2d595ec6256a281
|
|
7
|
+
data.tar.gz: d5cf56bdc2eff3ab4868dbfe13f9d4d680e14f333601079d96d610c4265a0c00780dc2f96b0c38cd9038a7b812e71b71d06e848b691cb042954654e92ba6c789
|
data/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# MuaraiCaptcha — Ruby SDK
|
|
4
|
+
|
|
5
|
+
Official Ruby client for the [MuaraiCaptcha](https://muaraicaptcha.com) solving API.
|
|
6
|
+
|
|
7
|
+
[](https://rubygems.org/gems/muaraicaptcha)
|
|
8
|
+
[](../../LICENSE)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
Zero dependencies — standard library only (`net/http`).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
gem install muaraicaptcha
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
require "muaraicaptcha"
|
|
24
|
+
|
|
25
|
+
client = MuaraiCaptcha::Client.new("YOUR_API_KEY")
|
|
26
|
+
|
|
27
|
+
solution = client.solve(
|
|
28
|
+
"type" => "TurnstileTaskProxyless",
|
|
29
|
+
"websiteURL" => "https://example.com",
|
|
30
|
+
"websiteKey" => "0x4AAAAAAA..."
|
|
31
|
+
)
|
|
32
|
+
puts solution["token"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
client = MuaraiCaptcha::Client.new(client_key, base_url: "...", timeout: 30)
|
|
39
|
+
|
|
40
|
+
client.get_balance # => Float (USD)
|
|
41
|
+
client.create_task(task) # => Integer taskId
|
|
42
|
+
client.get_task_result(task_id) # => Hash solution, or nil while processing
|
|
43
|
+
client.report_incorrect(task_id) # auto-refund a bad solve
|
|
44
|
+
client.solve(task, poll_interval: 5, timeout: 180) # create + poll
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Error handling
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
begin
|
|
51
|
+
client.solve(task)
|
|
52
|
+
rescue MuaraiCaptcha::ApiError => e
|
|
53
|
+
puts "#{e.code}: #{e.description}" # e.g. ERROR_ZERO_BALANCE
|
|
54
|
+
rescue MuaraiCaptcha::TimeoutError
|
|
55
|
+
puts "gave up waiting"
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Documentation
|
|
60
|
+
|
|
61
|
+
- [Task types & parameters](../../docs/task-types.md)
|
|
62
|
+
- [Error codes](../../docs/error-codes.md)
|
|
63
|
+
- [Full API reference](../../docs/api-reference.md)
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT © MuaraiCaptcha
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# MuaraiCaptcha — official Ruby SDK.
|
|
4
|
+
#
|
|
5
|
+
# Solve reCAPTCHA, hCaptcha, Turnstile, FunCaptcha, image captchas and more
|
|
6
|
+
# through the MuaraiCaptcha API (https://muaraicaptcha.com).
|
|
7
|
+
#
|
|
8
|
+
# require "muaraicaptcha"
|
|
9
|
+
# client = MuaraiCaptcha::Client.new("YOUR_API_KEY")
|
|
10
|
+
# solution = client.solve("type" => "TurnstileTaskProxyless",
|
|
11
|
+
# "websiteURL" => "https://example.com",
|
|
12
|
+
# "websiteKey" => "0x4AAAAAAA...")
|
|
13
|
+
# puts solution["token"]
|
|
14
|
+
|
|
15
|
+
require "json"
|
|
16
|
+
require "net/http"
|
|
17
|
+
require "uri"
|
|
18
|
+
|
|
19
|
+
module MuaraiCaptcha
|
|
20
|
+
VERSION = "1.1.0"
|
|
21
|
+
DEFAULT_BASE_URL = "https://api.muaraicaptcha.com"
|
|
22
|
+
|
|
23
|
+
# Base error for the SDK.
|
|
24
|
+
class Error < StandardError; end
|
|
25
|
+
|
|
26
|
+
# Raised when the API returns a non-zero errorId.
|
|
27
|
+
class ApiError < Error
|
|
28
|
+
attr_reader :code, :description
|
|
29
|
+
|
|
30
|
+
def initialize(code, description)
|
|
31
|
+
@code = code
|
|
32
|
+
@description = description
|
|
33
|
+
super("#{code}: #{description}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Raised when a job does not resolve within the timeout.
|
|
38
|
+
class TimeoutError < Error; end
|
|
39
|
+
|
|
40
|
+
class Client
|
|
41
|
+
# @param client_key [String] API key from console.muaraicaptcha.com
|
|
42
|
+
# @param base_url [String] override the API base URL
|
|
43
|
+
# @param timeout [Numeric] per-request timeout in seconds
|
|
44
|
+
def initialize(client_key, base_url: DEFAULT_BASE_URL, timeout: 30)
|
|
45
|
+
raise Error, "client_key is required" if client_key.nil? || client_key.empty?
|
|
46
|
+
|
|
47
|
+
@client_key = client_key
|
|
48
|
+
@base_url = base_url.sub(%r{/+\z}, "")
|
|
49
|
+
@timeout = timeout
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Float] account balance in USD
|
|
53
|
+
def get_balance
|
|
54
|
+
res = post("/v1/account/balance", "clientKey" => @client_key)
|
|
55
|
+
res.fetch("balance", 0).to_f
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param task [Hash] task object with "type" + fields
|
|
59
|
+
# @return [Integer] jobId
|
|
60
|
+
def create_job(task)
|
|
61
|
+
res = post("/v1/job/create", "clientKey" => @client_key, "task" => task)
|
|
62
|
+
res.fetch("jobId").to_i
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Poll once. Returns the solution Hash when ready, or nil while processing.
|
|
66
|
+
# Raises ApiError if the job failed.
|
|
67
|
+
def get_job_result(job_id)
|
|
68
|
+
res = post("/v1/job/result", "clientKey" => @client_key, "jobId" => job_id)
|
|
69
|
+
case res["status"]
|
|
70
|
+
when "ready"
|
|
71
|
+
res.fetch("solution", {})
|
|
72
|
+
when "failed"
|
|
73
|
+
raise ApiError.new(
|
|
74
|
+
res.fetch("errorCode", "ERROR_CAPTCHA_UNSOLVABLE"),
|
|
75
|
+
res.fetch("errorDescription", "Job failed")
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Report a bad solution — the charge is refunded automatically.
|
|
81
|
+
def report_incorrect(job_id)
|
|
82
|
+
post("/v1/job/report", "clientKey" => @client_key, "jobId" => job_id, "correct" => false)
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Create a job and poll until solved. Returns the solution Hash.
|
|
87
|
+
def solve(task, poll_interval: 5, timeout: 180)
|
|
88
|
+
job_id = create_job(task)
|
|
89
|
+
deadline = monotonic + timeout
|
|
90
|
+
sleep([poll_interval, 3].min)
|
|
91
|
+
while monotonic < deadline
|
|
92
|
+
solution = get_job_result(job_id)
|
|
93
|
+
return solution if solution
|
|
94
|
+
sleep(poll_interval)
|
|
95
|
+
end
|
|
96
|
+
raise TimeoutError, "job #{job_id} did not resolve within #{timeout}s"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- backward-compatible aliases (deprecated, kept for 1.0.x users) ---
|
|
100
|
+
alias create_task create_job
|
|
101
|
+
alias get_task_result get_job_result
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def monotonic
|
|
106
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def post(path, body)
|
|
110
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
111
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
112
|
+
http.use_ssl = (uri.scheme == "https")
|
|
113
|
+
http.read_timeout = @timeout
|
|
114
|
+
http.open_timeout = @timeout
|
|
115
|
+
|
|
116
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
|
117
|
+
req["Content-Type"] = "application/json"
|
|
118
|
+
req["Accept"] = "application/json"
|
|
119
|
+
req["User-Agent"] = "muaraicaptcha-ruby/#{VERSION}"
|
|
120
|
+
req.body = JSON.generate(body)
|
|
121
|
+
|
|
122
|
+
res = http.request(req)
|
|
123
|
+
payload = JSON.parse(res.body)
|
|
124
|
+
|
|
125
|
+
if payload.is_a?(Hash) && payload["errorId"].to_i != 0
|
|
126
|
+
raise ApiError.new(
|
|
127
|
+
payload.fetch("errorCode", "ERROR_UNKNOWN"),
|
|
128
|
+
payload.fetch("errorDescription", "Unknown error")
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
payload
|
|
132
|
+
rescue JSON::ParserError
|
|
133
|
+
raise Error, "invalid JSON response (HTTP #{res&.code})"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: muaraicaptcha
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- MuaraiCaptcha
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Solve reCAPTCHA, hCaptcha, Turnstile, FunCaptcha, image captchas and
|
|
14
|
+
more via MuaraiCaptcha.
|
|
15
|
+
email:
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/muaraicaptcha.rb
|
|
22
|
+
homepage: https://muaraicaptcha.com
|
|
23
|
+
licenses:
|
|
24
|
+
- MIT
|
|
25
|
+
metadata:
|
|
26
|
+
source_code_uri: https://github.com/MuaraiCaptcha/MuaraiCaptcha
|
|
27
|
+
documentation_uri: https://github.com/MuaraiCaptcha/MuaraiCaptcha/tree/main/docs
|
|
28
|
+
github_repo: https://github.com/MuaraiCaptcha/MuaraiCaptcha
|
|
29
|
+
post_install_message:
|
|
30
|
+
rdoc_options: []
|
|
31
|
+
require_paths:
|
|
32
|
+
- lib
|
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '2.7'
|
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
requirements: []
|
|
44
|
+
rubygems_version: 3.4.19
|
|
45
|
+
signing_key:
|
|
46
|
+
specification_version: 4
|
|
47
|
+
summary: Official Ruby SDK for the MuaraiCaptcha solving API
|
|
48
|
+
test_files: []
|