fetchserp 0.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/LICENSE.txt +3 -0
- data/README.md +65 -0
- data/lib/fetchserp/client.rb +187 -0
- data/lib/fetchserp/error.rb +15 -0
- data/lib/fetchserp/response.rb +57 -0
- data/lib/fetchserp/version.rb +3 -0
- data/lib/fetchserp.rb +17 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9b128502340ba4f1c344156a2216fee1213ae91392acd691acd7ad46c0e42789
|
4
|
+
data.tar.gz: 00b3d29b3419eda1e5a64b08791e78816db3e0ce434db56f8258da6de6b74ca1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f2d54374bfef6a701cbc120dfee4804fe316bf10f8d98c585b1708401f9072f6bf40e9a58dc9555fbff94dd897f8b2a303d89fbf1338bd227032065e3ea53d9d
|
7
|
+
data.tar.gz: 0f443b9233cf6c120b695d660700ad212ed61a193360d50cdf9adafe9d9fe3016e194997db861254ef84aa12313bb2ef1cb3594a09fbbe7d3391da15946eafe7
|
data/LICENSE.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# FetchSERP Ruby SDK
|
2
|
+
|
3
|
+
A lightweight, hand-crafted Ruby SDK for the FetchSERP REST API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem "fetchserp", git: "https://github.com/fetchserp/fetchserp-ruby.git"
|
11
|
+
```
|
12
|
+
|
13
|
+
Then bundle install:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
## Quick Start
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "fetchserp"
|
23
|
+
|
24
|
+
client = FetchSERP::Client.new(api_key: ENV["FETCHSERP_API_KEY"])
|
25
|
+
|
26
|
+
# Get backlinks for a domain
|
27
|
+
response = client.backlinks(domain: "example.com")
|
28
|
+
puts response.data
|
29
|
+
|
30
|
+
# Fetch Google SERP results
|
31
|
+
serp = client.serp(query: "best ruby gems", country: "us")
|
32
|
+
puts serp.data["results"]
|
33
|
+
```
|
34
|
+
|
35
|
+
The client will raise `FetchSERP::Error` on any HTTP error (network, 4xx, 5xx) so you can handle failures gracefully.
|
36
|
+
|
37
|
+
## Supported Endpoints
|
38
|
+
|
39
|
+
* `/api/v1/backlinks`
|
40
|
+
* `/api/v1/domain_emails`
|
41
|
+
* `/api/v1/domain_infos`
|
42
|
+
* `/api/v1/keywords_search_volume`
|
43
|
+
* `/api/v1/keywords_suggestions`
|
44
|
+
* `/api/v1/long_tail_keywords_generator`
|
45
|
+
* `/api/v1/page_indexation`
|
46
|
+
* `/api/v1/ranking`
|
47
|
+
* `/api/v1/scrape`
|
48
|
+
* `/api/v1/scrape_domain`
|
49
|
+
* `/api/v1/scrape_js`
|
50
|
+
* `/api/v1/scrape_js_with_proxy`
|
51
|
+
* `/api/v1/serp`
|
52
|
+
* `/api/v1/serp_html`
|
53
|
+
* `/api/v1/serp_js` & `/api/v1/serp_js/{uuid}`
|
54
|
+
* `/api/v1/serp_text`
|
55
|
+
* `/api/v1/user`
|
56
|
+
* `/api/v1/web_page_ai_analysis`
|
57
|
+
* `/api/v1/web_page_seo_analysis`
|
58
|
+
|
59
|
+
## Contributing
|
60
|
+
|
61
|
+
Bug reports and pull requests are welcome!
|
62
|
+
|
63
|
+
## License
|
64
|
+
|
65
|
+
GPL-3.0
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "json"
|
4
|
+
require_relative "response"
|
5
|
+
|
6
|
+
module FetchSERP
|
7
|
+
# Client is the main entry point for interacting with the FetchSERP API
|
8
|
+
class Client
|
9
|
+
DEFAULT_BASE_URL = "https://www.fetchserp.com".freeze
|
10
|
+
|
11
|
+
# @param api_key [String] Your FetchSERP API key (required)
|
12
|
+
# @param base_url [String] Override the API base url (rarely needed)
|
13
|
+
# @param timeout [Integer] Request timeout in seconds (default: 30)
|
14
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30)
|
15
|
+
raise ArgumentError, "api_key is required" if api_key.to_s.strip.empty?
|
16
|
+
|
17
|
+
@api_key = api_key
|
18
|
+
@base_url = base_url
|
19
|
+
@timeout = timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
###########################################################
|
23
|
+
# Public endpoint helpers
|
24
|
+
###########################################################
|
25
|
+
|
26
|
+
def backlinks(**params)
|
27
|
+
get("/api/v1/backlinks", params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def domain_emails(**params)
|
31
|
+
get("/api/v1/domain_emails", params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def domain_infos(**params)
|
35
|
+
get("/api/v1/domain_infos", params)
|
36
|
+
end
|
37
|
+
|
38
|
+
def keywords_search_volume(**params)
|
39
|
+
get("/api/v1/keywords_search_volume", params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def keywords_suggestions(**params)
|
43
|
+
get("/api/v1/keywords_suggestions", params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def long_tail_keywords_generator(**params)
|
47
|
+
get("/api/v1/long_tail_keywords_generator", params)
|
48
|
+
end
|
49
|
+
|
50
|
+
def page_indexation(**params)
|
51
|
+
get("/api/v1/page_indexation", params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ranking(**params)
|
55
|
+
get("/api/v1/ranking", params)
|
56
|
+
end
|
57
|
+
|
58
|
+
def scrape(**params)
|
59
|
+
get("/api/v1/scrape", params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def scrape_domain(**params)
|
63
|
+
get("/api/v1/scrape_domain", params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def scrape_js(**params)
|
67
|
+
post("/api/v1/scrape_js", params)
|
68
|
+
end
|
69
|
+
|
70
|
+
def scrape_js_with_proxy(**params)
|
71
|
+
post("/api/v1/scrape_js_with_proxy", params)
|
72
|
+
end
|
73
|
+
|
74
|
+
def serp(**params)
|
75
|
+
get("/api/v1/serp", params)
|
76
|
+
end
|
77
|
+
|
78
|
+
def serp_html(**params)
|
79
|
+
get("/api/v1/serp_html", params)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Step 1: returns uuid
|
83
|
+
def serp_js(**params)
|
84
|
+
get("/api/v1/serp_js", params)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Step 2: pass uuid param (uuid: "...")
|
88
|
+
def serp_js_content(uuid:)
|
89
|
+
raise ArgumentError, "uuid is required" if uuid.to_s.empty?
|
90
|
+
get("/api/v1/serp_js/#{uuid}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def serp_text(**params)
|
94
|
+
get("/api/v1/serp_text", params)
|
95
|
+
end
|
96
|
+
|
97
|
+
def user
|
98
|
+
get("/api/v1/user")
|
99
|
+
end
|
100
|
+
|
101
|
+
def web_page_ai_analysis(**params)
|
102
|
+
get("/api/v1/web_page_ai_analysis", params)
|
103
|
+
end
|
104
|
+
|
105
|
+
def web_page_seo_analysis(**params)
|
106
|
+
get("/api/v1/web_page_seo_analysis", params)
|
107
|
+
end
|
108
|
+
|
109
|
+
###########################################################
|
110
|
+
# Low-level helpers
|
111
|
+
###########################################################
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def get(path, params = nil)
|
116
|
+
request(:get, path, params: params)
|
117
|
+
end
|
118
|
+
|
119
|
+
def post(path, params = nil, body: nil)
|
120
|
+
request(:post, path, params: params, body: body)
|
121
|
+
end
|
122
|
+
|
123
|
+
def request(method, path, params: nil, body: nil)
|
124
|
+
uri = URI.join(@base_url, path)
|
125
|
+
|
126
|
+
# merge query params
|
127
|
+
if params&.any?
|
128
|
+
query_hash = params.reject { |_, v| v.nil? }
|
129
|
+
existing = URI.decode_www_form(uri.query || "")
|
130
|
+
new_qs = query_hash.flat_map { |k, v| Array(v).map { |val| [k.to_s, val.to_s] } }
|
131
|
+
uri.query = URI.encode_www_form(existing + new_qs)
|
132
|
+
end
|
133
|
+
|
134
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
135
|
+
http.use_ssl = uri.scheme == "https"
|
136
|
+
http.open_timeout = @timeout
|
137
|
+
http.read_timeout = @timeout
|
138
|
+
|
139
|
+
request_class = case method
|
140
|
+
when :get then Net::HTTP::Get
|
141
|
+
when :post then Net::HTTP::Post
|
142
|
+
else
|
143
|
+
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
144
|
+
end
|
145
|
+
|
146
|
+
req = request_class.new(uri)
|
147
|
+
headers.each { |k, v| req[k] = v }
|
148
|
+
req.body = body.to_json if body
|
149
|
+
|
150
|
+
response = perform_request_with_redirect(http, req, limit: 5)
|
151
|
+
|
152
|
+
# Raise on HTTP error codes
|
153
|
+
code_int = response.code.to_i
|
154
|
+
if code_int >= 400
|
155
|
+
raise FetchSERP::Error.new("HTTP #{code_int}", status: code_int, body: response.body)
|
156
|
+
end
|
157
|
+
|
158
|
+
Response.new(response)
|
159
|
+
end
|
160
|
+
|
161
|
+
def perform_request_with_redirect(http, req, limit: 5)
|
162
|
+
raise FetchSERP::Error, "Too many redirects" if limit.zero?
|
163
|
+
|
164
|
+
response = http.request(req)
|
165
|
+
case response
|
166
|
+
when Net::HTTPRedirection
|
167
|
+
new_location = response["location"]
|
168
|
+
new_uri = URI.parse(new_location)
|
169
|
+
new_http = Net::HTTP.new(new_uri.host, new_uri.port)
|
170
|
+
new_http.use_ssl = new_uri.scheme == "https"
|
171
|
+
new_req = req.class.new(new_uri)
|
172
|
+
headers.each { |k, v| new_req[k] = v }
|
173
|
+
perform_request_with_redirect(new_http, new_req, limit: limit - 1)
|
174
|
+
else
|
175
|
+
response
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def headers
|
180
|
+
{
|
181
|
+
"Authorization" => "Bearer #{@api_key}",
|
182
|
+
"Content-Type" => "application/json",
|
183
|
+
"User-Agent" => "fetchserp-ruby-sdk/#{FetchSERP::VERSION}"
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module FetchSERP
|
2
|
+
# Generic error class for all FetchSERP related exceptions
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :status, :body
|
5
|
+
|
6
|
+
# @param message [String]
|
7
|
+
# @param status [Integer, nil] HTTP status code if available
|
8
|
+
# @param body [String, Hash, nil] Raw response body (string or parsed JSON)
|
9
|
+
def initialize(message = nil, status: nil, body: nil)
|
10
|
+
super(message)
|
11
|
+
@status = status
|
12
|
+
@body = body
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module FetchSERP
|
4
|
+
# Simple wrapper to expose response JSON via `data` and raw HTTP response
|
5
|
+
class Response
|
6
|
+
attr_reader :http_response
|
7
|
+
|
8
|
+
def initialize(http_response)
|
9
|
+
@http_response = http_response
|
10
|
+
@parsed_body = parse_body
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns parsed JSON body (if JSON) or raw body otherwise
|
14
|
+
# @return [Object]
|
15
|
+
def body
|
16
|
+
@parsed_body
|
17
|
+
end
|
18
|
+
|
19
|
+
# Shortcut accessor for common API structure { "data": ... }
|
20
|
+
def data
|
21
|
+
if @parsed_body.is_a?(Hash) && @parsed_body.key?("data")
|
22
|
+
@parsed_body["data"]
|
23
|
+
else
|
24
|
+
@parsed_body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Allow convenient hash-like access
|
29
|
+
def [](key)
|
30
|
+
body[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_body
|
36
|
+
raw_body = @http_response.body
|
37
|
+
|
38
|
+
# If body already parsed (e.g., Hash), return directly.
|
39
|
+
return raw_body unless raw_body.is_a?(String)
|
40
|
+
|
41
|
+
content_type = if @http_response.respond_to?(:headers)
|
42
|
+
@http_response.headers["content-type"].to_s
|
43
|
+
else
|
44
|
+
@http_response["content-type"].to_s
|
45
|
+
end
|
46
|
+
if content_type.include?("json")
|
47
|
+
begin
|
48
|
+
JSON.parse(raw_body)
|
49
|
+
rescue JSON::ParserError
|
50
|
+
raw_body
|
51
|
+
end
|
52
|
+
else
|
53
|
+
raw_body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/fetchserp.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "fetchserp/version"
|
2
|
+
require_relative "fetchserp/error"
|
3
|
+
require_relative "fetchserp/response"
|
4
|
+
require_relative "fetchserp/client"
|
5
|
+
|
6
|
+
module FetchSERP
|
7
|
+
class << self
|
8
|
+
# Creates a new default client. Convenience wrapper around FetchSERP::Client.new
|
9
|
+
#
|
10
|
+
# @param api_key [String] Your FetchSERP API key
|
11
|
+
# @param kwargs [Hash] Forwarded to Client.initialize
|
12
|
+
# @return [FetchSERP::Client]
|
13
|
+
def new(api_key:, **kwargs)
|
14
|
+
Client.new(api_key: api_key, **kwargs)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fetchserp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- FetchSERP
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-06-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A lightweight Ruby client for interacting with the FetchSERP API. Provides
|
14
|
+
helpers for authentication and convenient Ruby methods for each endpoint.
|
15
|
+
email:
|
16
|
+
- contact@fetchserp.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/fetchserp.rb
|
24
|
+
- lib/fetchserp/client.rb
|
25
|
+
- lib/fetchserp/error.rb
|
26
|
+
- lib/fetchserp/response.rb
|
27
|
+
- lib/fetchserp/version.rb
|
28
|
+
homepage: https://fetchserp.com
|
29
|
+
licenses:
|
30
|
+
- GPL-3.0-or-later
|
31
|
+
metadata: {}
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.7.0
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubygems_version: 3.5.11
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Ruby SDK for the FetchSERP REST API
|
51
|
+
test_files: []
|