browse_ai 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bffa5df7a1da9581dcfcfa89c210a0ea078349e98edd4dcdb5d7d66a5e0ff6df
4
+ data.tar.gz: 59670edeb76def4ea436b0547527759b2637f239ef91db7ea560da04692a8d6a
5
+ SHA512:
6
+ metadata.gz: 31a5722b43a96ded76d0c2dc24430fc5c524772d1f01daa15d898d6a0b2c39682752cc3630eeb5b9a62e7362b8c868999f916f1215f521f391459727934b0c17
7
+ data.tar.gz: 0bdae608d3bf6da3a6aa55f35760da25c74e67adb0419cabb7ac4bc6bb937034721d251b48cadbb2e850249ad4bc2aaef161968f4ed489171d10cab47ad98109
data/.DS_Store ADDED
Binary file
@@ -0,0 +1,12 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "bundler"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
12
+ open-pull-requests-limit: 10
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/fixtures/vcr_cassettes/
10
+ /tmp/
11
+ .idea/
12
+ *.bundle
13
+ *.so
14
+ *.o
15
+ *.a
16
+ *.gem
17
+ mkmf.log
18
+ /config/gem_secret.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in browse_ai.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 David Iorns
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Browse AI Ruby Client
2
+
3
+ A Ruby wrapper for the Browse AI API.
4
+
5
+ https://www.browse.ai/docs/api/v2
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'browse_ai'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install browse_ai
22
+
23
+ ## Usage
24
+
25
+ Set the client API key.
26
+ ```
27
+ BrowseAi.api_key = <your API key>
28
+ ```
29
+
30
+ Get robots.
31
+ ```
32
+ robots = BrowseAi.client.get_robots
33
+ ```
34
+
35
+ Get a robot.
36
+ ```
37
+ robot = BrowseAi.client.get_robot(id: '<robot UUID>')
38
+ ```
39
+
40
+ Get a robot's tasks.
41
+ ```
42
+ tasks = BrowseAi.client.get_tasks(robot_id: '<robot UUID>')
43
+ ```
44
+
45
+ Get a robot's task.
46
+ ```
47
+ task = BrowseAi.client.get_tasks(robot_id: '<robot UUID>', id: '<task UUID>')
48
+ ```
49
+
50
+ Get a robot's monitors.
51
+ ```
52
+ monitors = BrowseAi.client.get_monitors(robot_id: '<robot UUID>')
53
+ ```
54
+
55
+ Get a robot's monitor.
56
+ ```
57
+ monitor = BrowseAi.client.get_monitors(robot_id: '<robot UUID>', id: '<monitor UUID>')
58
+ ```
59
+
60
+ Get a robot's webhooks.
61
+ ```
62
+ webhooks = BrowseAi.client.get_webhooks(robot_id: '<robot UUID>')
63
+ ```
64
+
65
+ ## Testing
66
+
67
+ Testing uses [RSpec](https://github.com/rspec/rspec), [Webmock](https://github.com/bblimke/webmock) and [VCR](https://github.com/vcr/vcr).
68
+
69
+ <strong>Tests will make real requests to the API endpoints</strong>. Ensure your test and production data is separated.
70
+
71
+ <strong>Tests currently require real data to exist for each resource.</strong>
72
+ Tests will fail if no data is present in your account for a specific resource.
73
+ For example `spec/browse_ai/dsl/robots_spec.rb` will fail if you have not yet added a robot to your account.
74
+
75
+ ### Test setup
76
+ - Copy and rename `config/gem_secret.yml.example` to `config/gem_secret.yml` . <strong>Do not check `config/gem_secret.yml` into version control</strong>.
77
+ - In `config/gem_secret.yml` fill in your test API key. `browse_ai_api_key: '<your API key>'`
78
+ - Run rspec `bundle exec rspec`
79
+
80
+ ## API Documentation
81
+ https://www.browse.ai/docs/api/v2
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it ( https://github.com/dior001/browse-ai-ruby/fork )
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/browse_ai.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'browse_ai/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'browse_ai'
7
+ spec.version = BrowseAi::VERSION
8
+ spec.authors = ['David Iorns']
9
+ spec.email = ['david.iorns@gmail.com']
10
+ spec.summary = 'A Ruby wrapper for the Browse AI API. https://www.browse.ai/docs/api/v2'
11
+ spec.homepage = 'https://www.browse.ai/'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.required_ruby_version = '>= 3.0.0'
20
+
21
+ spec.add_dependency 'activesupport', '~> 8.0', '>= 8.0.2'
22
+ spec.add_dependency 'faraday', '~> 2.13', '>= 2.13.1'
23
+ spec.add_dependency 'faraday-multipart', '~> 1.1'
24
+ spec.add_dependency 'mime-types', '~> 3.7'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 2.6', '>= 2.6.9'
27
+ spec.add_development_dependency 'rake', '~> 13.3'
28
+ spec.add_development_dependency 'rspec', '~> 3.13', '>= 3.13.1'
29
+ spec.add_development_dependency 'vcr', '~> 6.3', '>= 6.3.1'
30
+ spec.add_development_dependency 'webmock', '~> 3.25', '>= 3.25.1'
31
+ end
@@ -0,0 +1 @@
1
+ browse_ai_api_key: 'YOUR TESTING API KEY GOES HERE'
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,78 @@
1
+ require 'faraday'
2
+ require 'faraday/multipart'
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'active_support/all'
6
+ require 'browse_ai/dsl'
7
+ require 'browse_ai/errors'
8
+ require 'browse_ai/utils'
9
+
10
+ module BrowseAi
11
+ class Client
12
+ include DSL
13
+ include Errors
14
+ include Utils
15
+
16
+ REQUESTS = %i[get post put delete]
17
+ HEADERS = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
18
+
19
+ def initialize(api_key = BrowseAi.api_key, url = BrowseAi.url)
20
+ @api_key = api_key
21
+ @url = url
22
+
23
+ # Setup HTTP request connection to UpWoof.
24
+ @connection ||= Faraday.new do |builder|
25
+ builder.request :multipart
26
+ builder.request :url_encoded
27
+ builder.response :logger
28
+ builder.adapter Faraday.default_adapter
29
+ end
30
+ end
31
+
32
+ # @param [:get, :post, :put, :delete] method.
33
+ # @param [String] path.
34
+ # @param [Hash] query (optional).
35
+ # @param [Hash] headers request headers (optional).
36
+ # @raise [ArgumentError] If the response is blank.
37
+ # @raise [ResourceNotFoundError] If the response code is 404.
38
+ # @raise [ClientError] If the response code is not in the success range.
39
+ # @return [Faraday::Response] server response.
40
+ def request(method, path, query = {}, headers = {})
41
+ headers.reverse_merge!(default_headers)
42
+
43
+ unless REQUESTS.include?(method)
44
+ raise ArgumentError,
45
+ "Unsupported method #{method.inspect}. Only :get, :post, :put, :delete are allowed"
46
+ end
47
+
48
+ token_url = UrlHelper.build_url(path: "#{@url}#{path}")
49
+ payload = nil
50
+ if query.present?
51
+ accept = headers.present? ? headers['Accept'] : nil
52
+ payload = if accept.present? && accept == 'application/json'
53
+ JSON.generate(query)
54
+ else
55
+ query
56
+ end
57
+ end
58
+ response = @connection.run_request(method, token_url, payload, headers)
59
+
60
+ case response.status.to_i
61
+ when 200..299
62
+ response
63
+ when 404
64
+ raise ResourceNotFoundError.new(response: response)
65
+ else
66
+ raise ClientError.new(response: response)
67
+ end
68
+ end
69
+
70
+ def default_headers
71
+ {
72
+ 'Accept' => 'application/json',
73
+ 'Content-Type' => 'application/json',
74
+ 'Authorization' => "Bearer #{@api_key}"
75
+ }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ require 'browse_ai/dsl'
2
+
3
+ module BrowseAi
4
+ module DSL::Monitors
5
+ # GET /Monitors
6
+ # Get monitors.
7
+ # @param [String] robot_id The ID of the robot the monitors belong to.
8
+ # @return [Array, nil].
9
+ def get_monitors(robot_id:)
10
+ raise ArgumentError, 'Robot ID cannot be blank' if robot_id.blank?
11
+
12
+ Resources::Monitor.parse(request(:get, "robots/#{robot_id}/monitors/", nil), %w[monitors items])
13
+ end
14
+
15
+ # GET /Monitor/{id}
16
+ # Get a monitor.
17
+ # @param [String] id A monitor's ID.
18
+ # @raise [ArgumentError] If the method arguments are blank.
19
+ # @return [BrowseAi::Resources::Monitor, nil].
20
+ def get_monitor(robot_id:, id:)
21
+ raise ArgumentError, 'Robot ID cannot be blank' if robot_id.blank?
22
+ raise ArgumentError, 'ID cannot be blank' if id.blank?
23
+
24
+ Resources::Monitor.parse(request(:get, "robots/#{robot_id}/monitors/#{id}"), ['monitor'])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'browse_ai/dsl'
2
+
3
+ module BrowseAi
4
+ module DSL::Robots
5
+ # GET /Robots
6
+ # Get robots.
7
+ # @return [Array, nil].
8
+ def get_robots
9
+ Resources::Robot.parse(request(:get, 'robots/', nil), %w[robots items])
10
+ end
11
+
12
+ # GET /Robot/{id}
13
+ # Get a robot.
14
+ # @param [String] id A robot's ID.
15
+ # @raise [ArgumentError] If the method arguments are blank.
16
+ # @return [BrowseAi::Resources::Robot, nil].
17
+ def get_robot(id:)
18
+ raise ArgumentError, 'ID cannot be blank' if id.blank?
19
+
20
+ Resources::Robot.parse(request(:get, "robots/#{id}"), ['robot'])
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'browse_ai/dsl'
2
+
3
+ module BrowseAi
4
+ module DSL::Tasks
5
+ # GET /Tasks
6
+ # Get tasks.
7
+ # @param [String] robot_id The ID of the robot the tasks belong to.
8
+ # @return [Array, nil].
9
+ def get_tasks(robot_id:)
10
+ raise ArgumentError, 'Robot ID cannot be blank' if robot_id.blank?
11
+
12
+ Resources::Task.parse(request(:get, "robots/#{robot_id}/tasks/", nil), %w[tasks items])
13
+ end
14
+
15
+ # GET /Task/{id}
16
+ # Get a task.
17
+ # @param [String] id A task's ID.
18
+ # @raise [ArgumentError] If the method arguments are blank.
19
+ # @return [BrowseAi::Resources::Task, nil].
20
+ def get_task(robot_id:, id:)
21
+ raise ArgumentError, 'Robot ID cannot be blank' if robot_id.blank?
22
+ raise ArgumentError, 'ID cannot be blank' if id.blank?
23
+
24
+ Resources::Task.parse(request(:get, "robots/#{robot_id}/tasks/#{id}"), ['task'])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ require 'browse_ai/dsl'
2
+
3
+ module BrowseAi
4
+ module DSL::Webhooks
5
+ # GET /Webhooks
6
+ # Get webhooks.
7
+ # @param [String] robot_id The ID of the robot the webhooks belong to.
8
+ # @return [Array, nil].
9
+ def get_webhooks(robot_id:)
10
+ raise ArgumentError, 'Robot ID cannot be blank' if robot_id.blank?
11
+
12
+ Resources::Webhook.parse(request(:get, "robots/#{robot_id}/webhooks/", nil), %w[webhooks items])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ require 'browse_ai'
2
+
3
+ module BrowseAi
4
+ module DSL
5
+ end
6
+ end
7
+
8
+ require 'browse_ai/dsl/monitors'
9
+ require 'browse_ai/dsl/robots'
10
+ require 'browse_ai/dsl/tasks'
11
+ require 'browse_ai/dsl/webhooks'
12
+ require 'browse_ai/utils'
13
+ require 'mime-types'
14
+
15
+ module BrowseAi
16
+ module DSL
17
+ include Monitors
18
+ include Robots
19
+ include Tasks
20
+ include Utils
21
+ include Webhooks
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module BrowseAi
2
+ module Errors
3
+ class ClientError < StandardError
4
+ def initialize(response:)
5
+ @response = response
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ require 'browse_ai/errors/client_error'
2
+
3
+ module BrowseAi
4
+ module Errors
5
+ class ResourceNotFoundError < ClientError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ require 'browse_ai'
2
+
3
+ module BrowseAi
4
+ module Errors
5
+ end
6
+ end
7
+
8
+ require 'browse_ai/errors/resource_not_found_error'
9
+ require 'browse_ai/errors/client_error'
@@ -0,0 +1,8 @@
1
+ require 'browse_ai/resources/object'
2
+
3
+ module BrowseAi
4
+ module Resources
5
+ class Monitor < BrowseAi::Resources::Object
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,69 @@
1
+ module BrowseAi::Resources::Object::Attributes
2
+ module ClassMethods
3
+ def attributes
4
+ @attributes ||=
5
+ if superclass.respond_to?(:attributes)
6
+ superclass.attributes.dup
7
+ else
8
+ Hash.new { |hash, key| hash[key] = ::Object }
9
+ end
10
+ end
11
+
12
+ # @return [Module] module holding all attribute accessors
13
+ def attributes_module
14
+ @attributes_module ||= const_set(:AttributeMethods, Module.new)
15
+ end
16
+
17
+ def define_attribute_accessor(name, type = nil)
18
+ type ||= attributes[name.to_sym] || Object
19
+ attributes_module.send(:define_method, name) do
20
+ deserialize_attribute(name, type)
21
+ end
22
+ end
23
+
24
+ def attribute(name, type = String)
25
+ attributes[name] = type
26
+
27
+ define_attribute_accessor(name, type)
28
+ end
29
+
30
+ alias has_many attribute
31
+ end
32
+
33
+ def self.included(base)
34
+ base.extend(ClassMethods)
35
+ super
36
+ end
37
+
38
+ def attributes
39
+ {}.tap do |result|
40
+ __getobj__.keys.each do |key|
41
+ attribute = key.to_s.downcase
42
+ result[attribute] = public_send(attribute)
43
+ end
44
+ end
45
+ end
46
+
47
+ def method_missing(name, *args, &block)
48
+ attribute = name.to_s.upcase
49
+ if __getobj__.key?(attribute)
50
+ self.class.define_attribute_accessor(name)
51
+ deserialize_attribute(name, self.class.attributes[name.to_sym])
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def respond_to_missing?(name, include_all = false)
60
+ __getobj__.key?(name.to_s.upcase) || super(name, include_all)
61
+ end
62
+
63
+ # @param [String, Symbol] name
64
+ # @param [Class, #to_s] type
65
+ def deserialize_attribute(name, type)
66
+ raw = __getobj__[name.to_s.upcase]
67
+ self.class.serializer_for(type).deserialize(raw)
68
+ end
69
+ end
@@ -0,0 +1,104 @@
1
+ require 'logger'
2
+ module BrowseAi
3
+ module Resources
4
+ class Object
5
+ module Serializers
6
+ module Object
7
+ def self.serialize(value)
8
+ value.to_s
9
+ end
10
+
11
+ def self.deserialize(value)
12
+ case value
13
+ when Array
14
+ value.map { |v| v.deep_transform_keys { |key| key.downcase } }
15
+ when Hash
16
+ value.deep_transform_keys { |key| key.downcase }
17
+ else
18
+ value
19
+ end
20
+ end
21
+ end
22
+
23
+ module Time
24
+ def self.serialize(value)
25
+ value.utc.xmlschema
26
+ end
27
+
28
+ def self.deserialize(value)
29
+ ::Time.parse(value)
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ # @return [Hash] corresponding serializers for different attributes
35
+ def serializers
36
+ @serializers ||= {}
37
+ end
38
+
39
+ # @param [String, Symbol] type type of attribute to be serialized or deserialized
40
+ # @return [#serialize, #deserialize] serializer for provided type
41
+ def serializer_for(type)
42
+ serializers[type] ||=
43
+ begin
44
+ class_symbol = type.to_s.to_sym
45
+ if type.respond_to?(:deserialize) && type.respond_to?(:serialize)
46
+ type
47
+ elsif Serializers.constants.include?(class_symbol)
48
+ Serializers.const_get(class_symbol)
49
+ elsif Resources.constants.include?(class_symbol)
50
+ Resources.const_get(class_symbol)
51
+ else
52
+ Serializers::Object
53
+ end
54
+ end
55
+ end
56
+
57
+ # Deserialize a Faraday response.
58
+ # @param [Faraday::Response] response.
59
+ # @raise [ArgumentError] If the response is blank.
60
+ # @return [Object, nil].
61
+ def deserialize(response, root = [])
62
+ raise ArgumentError, 'Response cannot be blank' if response.blank?
63
+
64
+ attributes = response.body
65
+ begin
66
+ attributes = JSON.parse(response.body)
67
+ attributes = attributes.dig(*root) if attributes.is_a?(Hash) && root.any?
68
+
69
+ case attributes
70
+ when Array
71
+ return attributes.map { |object| new(object) }
72
+ when Hash
73
+ return new(attributes)
74
+ end
75
+ rescue JSON::ParserError
76
+ logger = Logger.new(STDOUT)
77
+ logger.error("Could not parse: #{response.body}")
78
+ end
79
+ nil
80
+ end
81
+
82
+ alias parse deserialize
83
+
84
+ def serialize(object)
85
+ object.serialize
86
+ end
87
+ end
88
+
89
+ def self.included(base)
90
+ super
91
+ base.extend ClassMethods
92
+ end
93
+
94
+ def serialize
95
+ {}.tap do |result|
96
+ attributes.each do |name, value|
97
+ result[name.upcase] = self.class.serializer_for(name).serialize(value)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,29 @@
1
+ require 'delegate'
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module BrowseAi
6
+ module Resources
7
+ class Object < SimpleDelegator
8
+ require 'browse_ai/resources/object/serializers'
9
+ require 'browse_ai/resources/object/attributes'
10
+
11
+ include Serializers
12
+ include Attributes
13
+
14
+ # Define common attributes, applicable to different resources
15
+ attribute :date_created_utc, Time
16
+ attribute :date_updated_utc, Time
17
+
18
+ def inspect
19
+ "#<#{self.class.name}:#{format('0x00%x', (object_id << 1))} #{inspect_attributes}>"
20
+ end
21
+
22
+ private
23
+
24
+ def inspect_attributes
25
+ attributes.map { |key, value| "@#{key}=#{value.inspect}" }.join(' ')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ require 'browse_ai/resources/object'
2
+
3
+ module BrowseAi
4
+ module Resources
5
+ class Robot < BrowseAi::Resources::Object
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'browse_ai/resources/object'
2
+
3
+ module BrowseAi
4
+ module Resources
5
+ class Task < BrowseAi::Resources::Object
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'browse_ai/resources/object'
2
+
3
+ module BrowseAi
4
+ module Resources
5
+ class Webhook < BrowseAi::Resources::Object
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require 'browse_ai'
2
+
3
+ module BrowseAi
4
+ module Resources
5
+ end
6
+ end
7
+
8
+ require 'browse_ai/resources/monitor'
9
+ require 'browse_ai/resources/object'
10
+ require 'browse_ai/resources/robot'
11
+ require 'browse_ai/resources/task'
12
+ require 'browse_ai/resources/webhook'
@@ -0,0 +1,17 @@
1
+ module BrowseAi
2
+ module Utils
3
+ class UrlHelper
4
+ # Build a URL with a querystring containing optional params if supplied.
5
+ # @param [String] path The name of the resource path as per the URL e.g. contacts.
6
+ # @param [Hash] params A hash of params we're turning into a querystring (optional).
7
+ # @return [String] The URL of the resource with required params.
8
+ def self.build_url(path:, params: {})
9
+ params.delete_if { |_k, v| v.blank? }
10
+ params = params.to_query
11
+ query = path
12
+ query << ('?' + params) unless params.blank?
13
+ query
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ require 'browse_ai'
2
+
3
+ module BrowseAi
4
+ module Utils
5
+ end
6
+ end
7
+
8
+ require 'browse_ai/utils/url_helper'
@@ -0,0 +1,3 @@
1
+ module BrowseAi
2
+ VERSION = '1.0.0'
3
+ end
data/lib/browse_ai.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'browse_ai/version'
2
+
3
+ module BrowseAi
4
+ autoload :Client, 'browse_ai/client'
5
+ autoload :DSL, 'browse_ai/dsl'
6
+ autoload :Resources, 'browse_ai/resources'
7
+ autoload :Errors, 'browse_ai/errors'
8
+ autoload :Utils, 'browse_ai/utils'
9
+
10
+ class << self
11
+ # @return [String]
12
+ attr_accessor :api_key
13
+ attr_accessor :url, :logger
14
+ end
15
+
16
+ self.url = 'https://api.browse.ai/v2/'
17
+
18
+ module_function
19
+
20
+ # @return [BrowseAi::Client]
21
+ def client
22
+ @client ||= Client.new(BrowseAi.api_key, BrowseAi.url)
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrowseAi::Client do
4
+ subject(:client) { BrowseAi::Client.new }
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrowseAi::DSL::Monitors do
4
+ def robot_id
5
+ VCR.use_cassette('get_robots') do
6
+ robots = BrowseAi.client.get_robots
7
+ robots.first['id']
8
+ end
9
+ end
10
+
11
+ # GET /monitors
12
+ describe '#get_monitors' do
13
+ it 'returns an array of monitors' do
14
+ VCR.use_cassette('get_monitors') do
15
+ monitors = BrowseAi.client.get_monitors(robot_id:)
16
+ expect(monitors).to be_a(Array)
17
+ expect(monitors.first).to be_a(Monitor)
18
+ end
19
+ end
20
+ end
21
+
22
+ # GET /monitors/{id}
23
+ describe '#get_monitor' do
24
+ it 'returns an monitor' do
25
+ id = VCR.use_cassette('get_monitors') do
26
+ monitors = BrowseAi.client.get_monitors(robot_id:)
27
+ monitors.first['id']
28
+ end
29
+
30
+ VCR.use_cassette('get_monitor') do
31
+ expect(BrowseAi.client.get_monitor(robot_id:, id:)).to be_a(Monitor)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrowseAi::DSL::Robots do
4
+ # GET /robots
5
+ describe '#get_robots' do
6
+ it 'returns an array of robots' do
7
+ VCR.use_cassette('get_robots') do
8
+ robots = BrowseAi.client.get_robots
9
+ expect(robots).to be_a(Array)
10
+ expect(robots.first).to be_a(Robot)
11
+ end
12
+ end
13
+ end
14
+
15
+ # GET /robots/{id}
16
+ describe '#get_robot' do
17
+ it 'returns an robot' do
18
+ id = VCR.use_cassette('get_robots') do
19
+ robots = BrowseAi.client.get_robots
20
+ robots.first['id']
21
+ end
22
+
23
+ VCR.use_cassette('get_robot') do
24
+ expect(BrowseAi.client.get_robot(id:)).to be_a(Robot)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrowseAi::DSL::Tasks do
4
+ def robot_id
5
+ VCR.use_cassette('get_robots') do
6
+ robots = BrowseAi.client.get_robots
7
+ robots.first['id']
8
+ end
9
+ end
10
+
11
+ # GET /tasks
12
+ describe '#get_tasks' do
13
+ it 'returns an array of tasks' do
14
+ VCR.use_cassette('get_tasks') do
15
+ tasks = BrowseAi.client.get_tasks(robot_id:)
16
+ expect(tasks).to be_a(Array)
17
+ expect(tasks.first).to be_a(Task)
18
+ end
19
+ end
20
+ end
21
+
22
+ # GET /tasks/{id}
23
+ describe '#get_task' do
24
+ it 'returns an task' do
25
+ id = VCR.use_cassette('get_tasks') do
26
+ tasks = BrowseAi.client.get_tasks(robot_id:)
27
+ tasks.first['id']
28
+ end
29
+
30
+ VCR.use_cassette('get_task') do
31
+ expect(BrowseAi.client.get_task(robot_id:, id:)).to be_a(Task)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrowseAi::DSL::Webhooks do
4
+ def robot_id
5
+ VCR.use_cassette('get_robots') do
6
+ robots = BrowseAi.client.get_robots
7
+ robots.first['id']
8
+ end
9
+ end
10
+
11
+ # GET /webhooks
12
+ describe '#get_webhooks' do
13
+ it 'returns an array of webhooks' do
14
+ VCR.use_cassette('get_webhooks') do
15
+ webhooks = BrowseAi.client.get_webhooks(robot_id:)
16
+ expect(webhooks).to be_a(Array)
17
+ expect(webhooks.first).to be_a(Webhook)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ require 'webmock/rspec'
2
+ require 'browse_ai'
3
+ require 'vcr'
4
+ include BrowseAi::Resources
5
+
6
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
7
+ cnf = YAML.load_file(File.join(APP_ROOT, 'config/gem_secret.yml'))
8
+ browse_ai_api_key = cnf['browse_ai_api_key']
9
+
10
+ VCR.configure do |c|
11
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
12
+ c.hook_into :webmock
13
+ c.filter_sensitive_data('<BROWSE_AI_API_KEY>') { browse_ai_api_key }
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ config.before do
18
+ BrowseAi.api_key = browse_ai_api_key
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,244 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: browse_ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Iorns
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-06-25 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 8.0.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '8.0'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 8.0.2
32
+ - !ruby/object:Gem::Dependency
33
+ name: faraday
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.13'
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.13.1
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.13'
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.13.1
52
+ - !ruby/object:Gem::Dependency
53
+ name: faraday-multipart
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '1.1'
59
+ type: :runtime
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '1.1'
66
+ - !ruby/object:Gem::Dependency
67
+ name: mime-types
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '3.7'
73
+ type: :runtime
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.7'
80
+ - !ruby/object:Gem::Dependency
81
+ name: bundler
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '2.6'
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.6.9
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.6'
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 2.6.9
100
+ - !ruby/object:Gem::Dependency
101
+ name: rake
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: '13.3'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '13.3'
114
+ - !ruby/object:Gem::Dependency
115
+ name: rspec
116
+ requirement: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: '3.13'
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 3.13.1
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.13'
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: 3.13.1
134
+ - !ruby/object:Gem::Dependency
135
+ name: vcr
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: '6.3'
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: 6.3.1
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '6.3'
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: 6.3.1
154
+ - !ruby/object:Gem::Dependency
155
+ name: webmock
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '3.25'
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 3.25.1
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - "~>"
169
+ - !ruby/object:Gem::Version
170
+ version: '3.25'
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 3.25.1
174
+ email:
175
+ - david.iorns@gmail.com
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files: []
179
+ files:
180
+ - ".DS_Store"
181
+ - ".github/dependabot.yml"
182
+ - ".gitignore"
183
+ - Gemfile
184
+ - LICENSE.txt
185
+ - README.md
186
+ - Rakefile
187
+ - browse_ai.gemspec
188
+ - config/gem_secret.yml.example
189
+ - lib/.DS_Store
190
+ - lib/browse_ai.rb
191
+ - lib/browse_ai/client.rb
192
+ - lib/browse_ai/dsl.rb
193
+ - lib/browse_ai/dsl/monitors.rb
194
+ - lib/browse_ai/dsl/robots.rb
195
+ - lib/browse_ai/dsl/tasks.rb
196
+ - lib/browse_ai/dsl/webhooks.rb
197
+ - lib/browse_ai/errors.rb
198
+ - lib/browse_ai/errors/client_error.rb
199
+ - lib/browse_ai/errors/resource_not_found_error.rb
200
+ - lib/browse_ai/resources.rb
201
+ - lib/browse_ai/resources/monitor.rb
202
+ - lib/browse_ai/resources/object.rb
203
+ - lib/browse_ai/resources/object/attributes.rb
204
+ - lib/browse_ai/resources/object/serializers.rb
205
+ - lib/browse_ai/resources/robot.rb
206
+ - lib/browse_ai/resources/task.rb
207
+ - lib/browse_ai/resources/webhook.rb
208
+ - lib/browse_ai/utils.rb
209
+ - lib/browse_ai/utils/url_helper.rb
210
+ - lib/browse_ai/version.rb
211
+ - spec/browse_ai/client_spec.rb
212
+ - spec/browse_ai/dsl/monitors_spec.rb
213
+ - spec/browse_ai/dsl/robots_spec.rb
214
+ - spec/browse_ai/dsl/tasks_spec.rb
215
+ - spec/browse_ai/dsl/webhooks_spec.rb
216
+ - spec/spec_helper.rb
217
+ homepage: https://www.browse.ai/
218
+ licenses:
219
+ - MIT
220
+ metadata: {}
221
+ rdoc_options: []
222
+ require_paths:
223
+ - lib
224
+ required_ruby_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: 3.0.0
229
+ required_rubygems_version: !ruby/object:Gem::Requirement
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ version: '0'
234
+ requirements: []
235
+ rubygems_version: 3.6.6
236
+ specification_version: 4
237
+ summary: A Ruby wrapper for the Browse AI API. https://www.browse.ai/docs/api/v2
238
+ test_files:
239
+ - spec/browse_ai/client_spec.rb
240
+ - spec/browse_ai/dsl/monitors_spec.rb
241
+ - spec/browse_ai/dsl/robots_spec.rb
242
+ - spec/browse_ai/dsl/tasks_spec.rb
243
+ - spec/browse_ai/dsl/webhooks_spec.rb
244
+ - spec/spec_helper.rb