vpndetection 1.0.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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +171 -0
  4. data/lib/vpndetection/api/database_api.rb +351 -0
  5. data/lib/vpndetection/api/lookup_api.rb +85 -0
  6. data/lib/vpndetection/api_client.rb +397 -0
  7. data/lib/vpndetection/api_error.rb +58 -0
  8. data/lib/vpndetection/api_model_base.rb +88 -0
  9. data/lib/vpndetection/bogon.rb +60 -0
  10. data/lib/vpndetection/bogons.rb +67 -0
  11. data/lib/vpndetection/cache.rb +35 -0
  12. data/lib/vpndetection/client.rb +133 -0
  13. data/lib/vpndetection/configuration.rb +326 -0
  14. data/lib/vpndetection/database.rb +167 -0
  15. data/lib/vpndetection/errors.rb +107 -0
  16. data/lib/vpndetection/models/class_detail.rb +169 -0
  17. data/lib/vpndetection/models/dataset_checksums.rb +174 -0
  18. data/lib/vpndetection/models/dataset_checksums_response.rb +216 -0
  19. data/lib/vpndetection/models/dataset_format_size.rb +201 -0
  20. data/lib/vpndetection/models/dataset_list.rb +166 -0
  21. data/lib/vpndetection/models/dataset_metadata.rb +280 -0
  22. data/lib/vpndetection/models/dataset_metadata_column.rb +199 -0
  23. data/lib/vpndetection/models/download.rb +276 -0
  24. data/lib/vpndetection/models/download_list.rb +166 -0
  25. data/lib/vpndetection/models/error_envelope.rb +164 -0
  26. data/lib/vpndetection/models/licensed_dataset.rb +358 -0
  27. data/lib/vpndetection/models/licensed_version.rb +262 -0
  28. data/lib/vpndetection/models/lookup_error.rb +166 -0
  29. data/lib/vpndetection/models/lookup_response.rb +343 -0
  30. data/lib/vpndetection/models/proxy_detail.rb +199 -0
  31. data/lib/vpndetection/models/vpn_detail.rb +179 -0
  32. data/lib/vpndetection/result.rb +211 -0
  33. data/lib/vpndetection/retries.rb +33 -0
  34. data/lib/vpndetection/transport.rb +116 -0
  35. data/lib/vpndetection/version.rb +5 -0
  36. data/lib/vpndetection.rb +40 -0
  37. metadata +109 -0
@@ -0,0 +1,179 @@
1
+ =begin
2
+ #VPNDetection API
3
+
4
+ #The VPNDetection API: classify any IP address, and download the datasets behind the answers. See https://docs.vpndetection.io for guides and https://github.com/vpndetection-io for the official client libraries.
5
+
6
+ The version of the OpenAPI document: 2026.09.04
7
+ Contact: support@vpndetection.io
8
+ Generated by: https://openapi-generator.tech
9
+ Generator version: 7.25.0
10
+
11
+ =end
12
+
13
+ require 'date'
14
+ require 'time'
15
+
16
+ module VPNDetection
17
+ # What is known about the VPN attribution. Every key is present when the object is populated, empty values included; the object is `{}` when `is_vpn` is false. `confidence` and `method` are max only, so on a lower plan they are absent from a populated object rather than empty.
18
+ class VpnDetail < ApiModelBase
19
+ # The VPN provider, or an empty string for an unattributed range.
20
+ attr_accessor :provider
21
+
22
+ # The most recent date this address was observed as VPN infrastructure.
23
+ attr_accessor :last_seen
24
+
25
+ # How strongly the attribution is supported. Max only.
26
+ attr_accessor :confidence
27
+
28
+ # How the address was attributed to the provider. Max only.
29
+ attr_accessor :method
30
+
31
+ # Attribute mapping from ruby-style variable name to JSON key.
32
+ def self.attribute_map
33
+ {
34
+ :'provider' => :'provider',
35
+ :'last_seen' => :'last_seen',
36
+ :'confidence' => :'confidence',
37
+ :'method' => :'method'
38
+ }
39
+ end
40
+
41
+ # Returns attribute mapping this model knows about
42
+ def self.acceptable_attribute_map
43
+ attribute_map
44
+ end
45
+
46
+ # Returns all the JSON keys this model knows about
47
+ def self.acceptable_attributes
48
+ acceptable_attribute_map.values
49
+ end
50
+
51
+ # Attribute type mapping.
52
+ def self.openapi_types
53
+ {
54
+ :'provider' => :'String',
55
+ :'last_seen' => :'Date',
56
+ :'confidence' => :'String',
57
+ :'method' => :'String'
58
+ }
59
+ end
60
+
61
+ # List of attributes with nullable: true
62
+ def self.openapi_nullable
63
+ Set.new([
64
+ ])
65
+ end
66
+
67
+ # Initializes the object
68
+ # @param [Hash] attributes Model attributes in the form of hash
69
+ def initialize(attributes = {})
70
+ if (!attributes.is_a?(Hash))
71
+ fail ArgumentError, "The input argument (attributes) must be a hash in `VPNDetection::VpnDetail` initialize method"
72
+ end
73
+
74
+ # check to see if the attribute exists and convert string to symbol for hash key
75
+ acceptable_attribute_map = self.class.acceptable_attribute_map
76
+ attributes = attributes.each_with_object({}) { |(k, v), h|
77
+ if (!acceptable_attribute_map.key?(k.to_sym))
78
+ fail ArgumentError, "`#{k}` is not a valid attribute in `VPNDetection::VpnDetail`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
79
+ end
80
+ h[k.to_sym] = v
81
+ }
82
+
83
+ if attributes.key?(:'provider')
84
+ self.provider = attributes[:'provider']
85
+ end
86
+
87
+ if attributes.key?(:'last_seen')
88
+ self.last_seen = attributes[:'last_seen']
89
+ end
90
+
91
+ if attributes.key?(:'confidence')
92
+ self.confidence = attributes[:'confidence']
93
+ end
94
+
95
+ if attributes.key?(:'method')
96
+ self.method = attributes[:'method']
97
+ end
98
+ end
99
+
100
+ # Show invalid properties with the reasons. Usually used together with valid?
101
+ # @return Array for valid properties with the reasons
102
+ def list_invalid_properties
103
+ warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
104
+ invalid_properties = Array.new
105
+ invalid_properties
106
+ end
107
+
108
+ # Check to see if the all the properties in the model are valid
109
+ # @return true if the model is valid
110
+ def valid?
111
+ warn '[DEPRECATED] the `valid?` method is obsolete'
112
+ true
113
+ end
114
+
115
+ # Checks equality by comparing each attribute.
116
+ # @param [Object] Object to be compared
117
+ def ==(o)
118
+ return true if self.equal?(o)
119
+ self.class == o.class &&
120
+ provider == o.provider &&
121
+ last_seen == o.last_seen &&
122
+ confidence == o.confidence &&
123
+ method == o.method
124
+ end
125
+
126
+ # @see the `==` method
127
+ # @param [Object] Object to be compared
128
+ def eql?(o)
129
+ self == o
130
+ end
131
+
132
+ # Calculates hash code according to all attributes.
133
+ # @return [Integer] Hash code
134
+ def hash
135
+ [provider, last_seen, confidence, method].hash
136
+ end
137
+
138
+ # Builds the object from hash
139
+ # @param [Hash] attributes Model attributes in the form of hash
140
+ # @return [Object] Returns the model itself
141
+ def self.build_from_hash(attributes)
142
+ return nil unless attributes.is_a?(Hash)
143
+ attributes = attributes.transform_keys(&:to_sym)
144
+ transformed_hash = {}
145
+ openapi_types.each_pair do |key, type|
146
+ if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
147
+ transformed_hash["#{key}"] = nil
148
+ elsif type =~ /\AArray<(.*)>/i
149
+ # check to ensure the input is an array given that the attribute
150
+ # is documented as an array but the input is not
151
+ if attributes[attribute_map[key]].is_a?(Array)
152
+ transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
153
+ end
154
+ elsif !attributes[attribute_map[key]].nil?
155
+ transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
156
+ end
157
+ end
158
+ new(transformed_hash)
159
+ end
160
+
161
+ # Returns the object in the form of hash
162
+ # @return [Hash] Returns the object in the form of hash
163
+ def to_hash
164
+ hash = {}
165
+ self.class.attribute_map.each_pair do |attr, param|
166
+ value = self.send(attr)
167
+ if value.nil?
168
+ is_nullable = self.class.openapi_nullable.include?(attr)
169
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
170
+ end
171
+
172
+ hash[param] = _to_hash(value)
173
+ end
174
+ hash
175
+ end
176
+
177
+ end
178
+
179
+ end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VPNDetection
4
+ # What a lookup answers.
5
+ #
6
+ # An **absent** member is one your plan does not include. It never means "we
7
+ # could not check", so `nil` and `false` are genuinely different answers: `nil`
8
+ # is "not in your plan", `false` is "checked, and no".
9
+ #
10
+ # Ruby makes that easy to lose, because `nil` and `false` are both falsy. Read
11
+ # the field itself (`is_hosting`) when the difference matters, ask
12
+ # {#included?} when you need to know whether your plan carries it at all, and
13
+ # use the predicate (`hosting?`) when you only care whether the address is
14
+ # flagged. A predicate always answers `true` or `false`, never `nil`.
15
+ #
16
+ # A detail object that is present but empty (`{}`) means the flag above it is
17
+ # false. A populated one always carries every one of its keys. Details are the
18
+ # wire hashes with their original string keys, so `result.vpn['provider']`.
19
+ class Result
20
+ FLAGS = %w[
21
+ is_vpn is_hosting is_relay is_tor is_cdn is_resproxy is_dcproxy is_mobproxy
22
+ ].freeze
23
+
24
+ DETAILS = %w[vpn hosting relay tor cdn resproxy dcproxy mobproxy].freeze
25
+
26
+ # The response exactly as it came off the wire, with its original names.
27
+ # Frozen, because the cache hands this same object to every later caller of
28
+ # the address.
29
+ attr_reader :raw
30
+
31
+ def initialize(raw, bogon: false)
32
+ @raw = deep_freeze(raw)
33
+ @bogon = bogon
34
+ freeze
35
+ end
36
+
37
+ # The address that was looked up, normalized.
38
+ def ip
39
+ @raw['ip']
40
+ end
41
+
42
+ # Set when this answer was computed locally rather than served. Always
43
+ # `true` or `false`.
44
+ def bogon?
45
+ @bogon
46
+ end
47
+
48
+ # Whether your plan includes a member at all, told apart from a member your
49
+ # plan includes that answered false.
50
+ #
51
+ # result.included?(:is_hosting) # => false, not in your plan
52
+ # result.is_hosting # => nil
53
+ def included?(field)
54
+ @raw.key?(field.to_s)
55
+ end
56
+
57
+ # Whether the address is VPN infrastructure. Every plan includes this.
58
+ def is_vpn
59
+ @raw['is_vpn']
60
+ end
61
+
62
+ # `is_vpn` with an absent member read as false.
63
+ def vpn?
64
+ @raw['is_vpn'] == true
65
+ end
66
+
67
+ # Whether the address belongs to a hosting or cloud provider, or nil when
68
+ # your plan does not include it. Starter and above.
69
+ def is_hosting
70
+ @raw['is_hosting']
71
+ end
72
+
73
+ # `is_hosting` with an absent member read as false.
74
+ def hosting?
75
+ @raw['is_hosting'] == true
76
+ end
77
+
78
+ # Whether the address is a privacy relay egress, or nil when your plan does
79
+ # not include it. Starter and above.
80
+ def is_relay
81
+ @raw['is_relay']
82
+ end
83
+
84
+ # `is_relay` with an absent member read as false.
85
+ def relay?
86
+ @raw['is_relay'] == true
87
+ end
88
+
89
+ # Whether the address is a Tor node, or nil when your plan does not include
90
+ # it. Starter and above.
91
+ def is_tor
92
+ @raw['is_tor']
93
+ end
94
+
95
+ # `is_tor` with an absent member read as false.
96
+ def tor?
97
+ @raw['is_tor'] == true
98
+ end
99
+
100
+ # Whether the address belongs to a CDN, or nil when your plan does not
101
+ # include it. Starter and above.
102
+ def is_cdn
103
+ @raw['is_cdn']
104
+ end
105
+
106
+ # `is_cdn` with an absent member read as false.
107
+ def cdn?
108
+ @raw['is_cdn'] == true
109
+ end
110
+
111
+ # Whether the address was seen in a residential proxy pool, or nil when your
112
+ # plan does not include it. Max only.
113
+ def is_resproxy
114
+ @raw['is_resproxy']
115
+ end
116
+
117
+ # `is_resproxy` with an absent member read as false.
118
+ def resproxy?
119
+ @raw['is_resproxy'] == true
120
+ end
121
+
122
+ # Whether the address was seen in a datacenter proxy pool, or nil when your
123
+ # plan does not include it. Max only.
124
+ def is_dcproxy
125
+ @raw['is_dcproxy']
126
+ end
127
+
128
+ # `is_dcproxy` with an absent member read as false.
129
+ def dcproxy?
130
+ @raw['is_dcproxy'] == true
131
+ end
132
+
133
+ # Whether the address was seen in a mobile proxy pool, or nil when your plan
134
+ # does not include it. Max only.
135
+ def is_mobproxy
136
+ @raw['is_mobproxy']
137
+ end
138
+
139
+ # `is_mobproxy` with an absent member read as false.
140
+ def mobproxy?
141
+ @raw['is_mobproxy'] == true
142
+ end
143
+
144
+ # Detail for `is_vpn`: provider, last_seen, and on max confidence and method.
145
+ def vpn
146
+ @raw['vpn']
147
+ end
148
+
149
+ # Detail for `is_hosting`. Scale and above.
150
+ def hosting
151
+ @raw['hosting']
152
+ end
153
+
154
+ # Detail for `is_relay`. Scale and above.
155
+ def relay
156
+ @raw['relay']
157
+ end
158
+
159
+ # Detail for `is_tor`. Scale and above.
160
+ def tor
161
+ @raw['tor']
162
+ end
163
+
164
+ # Detail for `is_cdn`. Scale and above.
165
+ def cdn
166
+ @raw['cdn']
167
+ end
168
+
169
+ # Detail for `is_resproxy`. Max only.
170
+ def resproxy
171
+ @raw['resproxy']
172
+ end
173
+
174
+ # Detail for `is_dcproxy`. Max only.
175
+ def dcproxy
176
+ @raw['dcproxy']
177
+ end
178
+
179
+ # Detail for `is_mobproxy`. Max only.
180
+ def mobproxy
181
+ @raw['mobproxy']
182
+ end
183
+
184
+ def to_h
185
+ @raw
186
+ end
187
+
188
+ def ==(other)
189
+ other.is_a?(Result) && other.raw == @raw && other.bogon? == @bogon
190
+ end
191
+ alias eql? ==
192
+
193
+ def hash
194
+ [@raw, @bogon].hash
195
+ end
196
+
197
+ def inspect
198
+ "#<#{self.class} ip=#{ip.inspect} is_vpn=#{is_vpn.inspect} bogon=#{@bogon}>"
199
+ end
200
+
201
+ private
202
+
203
+ def deep_freeze(value)
204
+ case value
205
+ when Hash then value.each { |_, v| deep_freeze(v) }.freeze
206
+ when Array then value.each { |v| deep_freeze(v) }.freeze
207
+ else value.freeze
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VPNDetection
4
+ # What may be tried again, and how long to wait first.
5
+ #
6
+ # Only a 5xx, a transport failure and a 429 that carried `Retry-After` are
7
+ # worth another attempt. Every other 4xx is a client error and fails on the
8
+ # first try, so a bad dataset id is not asked for three times.
9
+ module Retries
10
+ BACKOFF_SECONDS = 0.25
11
+
12
+ module_function
13
+
14
+ def with_retries(retries)
15
+ attempt = 0
16
+ begin
17
+ yield
18
+ rescue Error => e
19
+ raise unless e.retryable? && attempt < retries
20
+
21
+ attempt += 1
22
+ sleep(delay_for(e, attempt))
23
+ retry
24
+ end
25
+ end
26
+
27
+ # A server-supplied delay always wins, including a `Retry-After: 0`, which
28
+ # is the server saying "immediately" rather than saying nothing.
29
+ def delay_for(error, attempt)
30
+ error.retry_after_seconds || BACKOFF_SECONDS * (2**(attempt - 1))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module VPNDetection
8
+ # The generated wire client, with the three things it gets wrong for this API
9
+ # corrected in one place.
10
+ class Transport < ApiClient
11
+ # The same path template the generated LookupApi holds. Both are asserted
12
+ # against each other in the test suite, because the batch builds its own
13
+ # requests to queue them on a hydra and cannot go through the generated
14
+ # method, which runs each request as it builds it.
15
+ LOOKUP_PATH = '/{ip}'
16
+
17
+ # The generated Configuration applies EVERY security scheme the spec lists,
18
+ # so a keyless client would send `Authorization: Bearer `, an empty
19
+ # `X-Api-Key` and an empty `apikey` query parameter. The API answers 401 to
20
+ # the first of those, which is not what a keyless caller asked for.
21
+ class Config < Configuration
22
+ def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT)
23
+ super()
24
+ uri = URI.parse(base_url)
25
+ self.scheme = uri.scheme
26
+ self.host = uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}"
27
+ self.base_path = uri.path
28
+ self.access_token = api_key
29
+ self.timeout = timeout
30
+ end
31
+
32
+ def auth_settings
33
+ return {} if access_token.nil? || access_token.to_s.empty?
34
+
35
+ {
36
+ 'bearerAuth' => {
37
+ type: 'bearer', in: 'header', key: 'Authorization',
38
+ value: "Bearer #{access_token}"
39
+ }
40
+ }
41
+ end
42
+ end
43
+
44
+ def initialize(config = Config.new)
45
+ super
46
+ @default_headers['User-Agent'] = "vpndetection-ruby/#{VERSION}"
47
+ end
48
+
49
+ # `follow_location = opts[:follow_location] || true` in the generated client
50
+ # is true for every value it can be given, so the database download's 302
51
+ # would be chased and a multi-gigabyte dataset read into memory. Nothing
52
+ # this API serves is meant to be followed.
53
+ def build_request(http_method, path, opts = {})
54
+ request = super
55
+ request.options[:followlocation] = false
56
+ request
57
+ end
58
+
59
+ # A GET for the presigned link the download endpoint hands out.
60
+ #
61
+ # Built here rather than through {#build_request} so it carries NO
62
+ # credential: the presigned URL authorizes itself, and forwarding the API key
63
+ # would hand it to a host with no business holding it. Redirects ARE followed,
64
+ # unlike every other request this client makes, because this one IS the far
65
+ # side of a redirect; the guard exists to stop the API's own 302 pulling a
66
+ # dataset into memory, not to stop object storage from moving a bucket.
67
+ #
68
+ # The whole-request timeout is dropped and only the connect phase is bounded.
69
+ # Ten seconds is a sane ceiling on a lookup and the wrong one on 1.79 GB.
70
+ def storage_request(url)
71
+ options = {
72
+ method: :get,
73
+ headers: { 'User-Agent' => @default_headers['User-Agent'] },
74
+ followlocation: true,
75
+ maxredirs: 5,
76
+ connecttimeout: @config.timeout,
77
+ ssl_verifypeer: @config.verify_ssl,
78
+ ssl_verifyhost: @config.verify_ssl_host ? 2 : 0,
79
+ }
80
+ options[:cainfo] = @config.ssl_ca_cert if @config.ssl_ca_cert
81
+ Typhoeus::Request.new(url, options)
82
+ end
83
+
84
+ def lookup_request(ip)
85
+ build_request(
86
+ :GET, LOOKUP_PATH.sub('{ip}', CGI.escape(ip.to_s)),
87
+ header_params: { 'Accept' => 'application/json' },
88
+ auth_names: %w[bearerAuth apiKeyHeader apiKeyQuery],
89
+ )
90
+ end
91
+
92
+ def self.lookup_result(response)
93
+ raise Error.from_transport(response) if transport_failure?(response)
94
+ raise Error.from_status(response.code, response.headers, response.body) unless response.success?
95
+
96
+ Result.new(parse_object(response))
97
+ end
98
+
99
+ def self.transport_failure?(response)
100
+ response.timed_out? || response.code.to_i.zero?
101
+ end
102
+
103
+ def self.parse_object(response)
104
+ body = JSON.parse(response.body.to_s)
105
+ return body if body.is_a?(Hash)
106
+
107
+ raise Error.new(:server_error, 'the API answered with something other than an object',
108
+ status: response.code)
109
+ rescue JSON::ParserError => e
110
+ raise Error.new(:server_error, "could not parse the response body: #{e.message}",
111
+ status: response.code)
112
+ end
113
+
114
+ private_class_method :transport_failure?, :parse_object
115
+ end
116
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VPNDetection
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vpndetection/version'
4
+
5
+ # The wire layer, generated from spec/openapi.yaml by scripts/generate.sh.
6
+ # Models are loaded by glob because the set of them is the generator's to
7
+ # decide; they subclass ApiModelBase and so must follow it.
8
+ require 'vpndetection/api_client'
9
+ require 'vpndetection/api_error'
10
+ require 'vpndetection/api_model_base'
11
+ require 'vpndetection/configuration'
12
+ Dir[File.join(__dir__, 'vpndetection', 'models', '*.rb')].sort.each { |model| require model }
13
+ Dir[File.join(__dir__, 'vpndetection', 'api', '*.rb')].sort.each { |api| require api }
14
+
15
+ require 'vpndetection/bogons'
16
+ require 'vpndetection/errors'
17
+ require 'vpndetection/result'
18
+ require 'vpndetection/bogon'
19
+ require 'vpndetection/cache'
20
+ require 'vpndetection/retries'
21
+ require 'vpndetection/transport'
22
+ require 'vpndetection/database'
23
+ require 'vpndetection/client'
24
+
25
+ # The official Ruby client library for the VPNDetection API.
26
+ #
27
+ # client = VPNDetection::Client.new
28
+ # client.lookup('45.83.91.1').is_vpn # => true
29
+ module VPNDetection
30
+ # Whether an address is a bogon: private, loopback, link-local, documentation,
31
+ # multicast or otherwise not routable on the public internet, including the
32
+ # IPv6 equivalents and the 6to4 and Teredo ranges that wrap them.
33
+ #
34
+ # These can never be VPN or proxy infrastructure, so a client answers them
35
+ # itself and they never cost a request. The same predicate is on the client,
36
+ # which is how the README teaches it.
37
+ def self.bogon?(ip)
38
+ Bogon.bogon?(ip)
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vpndetection
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mslm Dev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: lru_redux
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: typhoeus
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.4'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
40
+ description: Query the VPNDetection API for anonymity detection including VPNs, residential
41
+ proxies, Tor nodes, hosting servers, CDNs and relays.
42
+ email:
43
+ - support@vpndetection.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - README.md
50
+ - lib/vpndetection.rb
51
+ - lib/vpndetection/api/database_api.rb
52
+ - lib/vpndetection/api/lookup_api.rb
53
+ - lib/vpndetection/api_client.rb
54
+ - lib/vpndetection/api_error.rb
55
+ - lib/vpndetection/api_model_base.rb
56
+ - lib/vpndetection/bogon.rb
57
+ - lib/vpndetection/bogons.rb
58
+ - lib/vpndetection/cache.rb
59
+ - lib/vpndetection/client.rb
60
+ - lib/vpndetection/configuration.rb
61
+ - lib/vpndetection/database.rb
62
+ - lib/vpndetection/errors.rb
63
+ - lib/vpndetection/models/class_detail.rb
64
+ - lib/vpndetection/models/dataset_checksums.rb
65
+ - lib/vpndetection/models/dataset_checksums_response.rb
66
+ - lib/vpndetection/models/dataset_format_size.rb
67
+ - lib/vpndetection/models/dataset_list.rb
68
+ - lib/vpndetection/models/dataset_metadata.rb
69
+ - lib/vpndetection/models/dataset_metadata_column.rb
70
+ - lib/vpndetection/models/download.rb
71
+ - lib/vpndetection/models/download_list.rb
72
+ - lib/vpndetection/models/error_envelope.rb
73
+ - lib/vpndetection/models/licensed_dataset.rb
74
+ - lib/vpndetection/models/licensed_version.rb
75
+ - lib/vpndetection/models/lookup_error.rb
76
+ - lib/vpndetection/models/lookup_response.rb
77
+ - lib/vpndetection/models/proxy_detail.rb
78
+ - lib/vpndetection/models/vpn_detail.rb
79
+ - lib/vpndetection/result.rb
80
+ - lib/vpndetection/retries.rb
81
+ - lib/vpndetection/transport.rb
82
+ - lib/vpndetection/version.rb
83
+ homepage: https://vpndetection.io
84
+ licenses:
85
+ - MIT
86
+ metadata:
87
+ homepage_uri: https://vpndetection.io
88
+ source_code_uri: https://github.com/vpndetection-io/sdk-ruby
89
+ bug_tracker_uri: https://github.com/vpndetection-io/sdk-ruby/issues
90
+ changelog_uri: https://github.com/vpndetection-io/sdk-ruby/releases
91
+ documentation_uri: https://docs.vpndetection.io
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '3.1'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.6.9
107
+ specification_version: 4
108
+ summary: Official Ruby client library for the VPNDetection API.
109
+ test_files: []