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 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
@@ -0,0 +1,3 @@
1
+ FetchSERP Ruby SDK is licensed under the GNU General Public License v3.0.
2
+
3
+ See https://www.gnu.org/licenses/gpl-3.0.en.html for the full text.
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
@@ -0,0 +1,3 @@
1
+ module FetchSERP
2
+ VERSION = "0.1.0"
3
+ 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: []