virustotalx 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18dd7bc4a5890a35b197f8ce635e736dec2507a9af5405cee5289dd7b2713ea2
4
- data.tar.gz: d4d8c7ce2a065cfc287b8f376d8a15128542a7e1ad95db3f4d0000a32a721bce
3
+ metadata.gz: b93b0c39cdd1dbf4efdeffbe4f9ed6c30892f75a3a73172b1e5eb2db11678334
4
+ data.tar.gz: 38be1389296d3bd50edbf0bdb8906b83a2639a681b0b91b7a9fc1df8539a35b4
5
5
  SHA512:
6
- metadata.gz: 1b2b3ca2a70c7861dcf9c04c8570134157bc0bda833bbac8e25cdc10378968e0cbf075bc80d8523537c1f8398e24f005dae4da7d26ca48d69ef874d1d0942b29
7
- data.tar.gz: a6d86db493ba0045bed293681be7a7d640dbcec2c3bd795bdefd5f666b3dc7d53465b6c15d7268d82044f537c74add8a512d51449fbe06629867415029164cfc
6
+ metadata.gz: 86a227bded77dba22902a4531f09683cfc618c850ae178762f6ba5cd1438dbd546313bcd67f27041af3b562dcb0d1e047b570f160ad5a6db91a773b9e8aa6edc
7
+ data.tar.gz: 1b73b35965ee06d99b02fc5674d30ea234f5a64909a2a8a4dfb12cac6c38df8b648ada8c0aaf981338ca3e61accd267a971ddb3883a8eee0fb25f1774b387d4e
data/README.md CHANGED
@@ -3,8 +3,9 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/virustotalx.svg)](https://badge.fury.io/rb/virustotalx)
4
4
  [![Build Status](https://travis-ci.org/ninoseki/virustotalx.svg?branch=master)](https://travis-ci.org/ninoseki/virustotalx)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/ninoseki/virustotalx/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/virustotalx?branch=master)
6
+ [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/virustotalx/badge)](https://www.codefactor.io/repository/github/ninoseki/virustotalx)
6
7
 
7
- Yet another VirusTotal API wrapper for Ruby
8
+ Yet another VirusTotal API (version 3) wrapper for Ruby.
8
9
 
9
10
  ## Installation
10
11
 
@@ -19,59 +20,88 @@ require "virustotalx"
19
20
  # or
20
21
  require "virustotal"
21
22
 
22
- # when given nothing, it tries to load your API key from ENV["VIRUSTOTASL_API_KEY"]
23
+ # when given nothing, it tries to load your API key from ENV["VIRUSTOAL_API_KEY"]
23
24
  api = VirusTotal::API.new
24
25
  # or you can set it manually
25
26
  api = VirusTotal::API.new(key: "YOUR_API_KEY")
26
27
 
27
28
  hash = "726a2eedb9df3d63ec1b4a7d774a799901f1a2b9"
28
- api.file.report(hash)
29
- api.file.scan("PAHT_TO_FILE")
30
- api.file.rescan(hash)
31
- api.file.upload_url
32
- api.file.download(hash)
33
- api.file.behaviour(hash)
34
- api.file.network_traffic(hash)
35
- api.file.clusters("DATETIME")
36
- api.file.search("resource:#{hash}")
37
-
38
- api.url.report("http://github.com")
39
- api.url.scan("https://github.com/ninoseki/virustotalx")
40
-
41
- api.domain.report("github.com")
42
-
43
- api.ip_address.report("1.1.1.1")
44
-
45
- # it returns nil when given a non-existing resource to #report methods
46
- api.domain.report("a_domain_which_does_not_exist.com")
47
- # => nil
48
- ```
29
+ api.file.get(hash)
30
+ api.file.upload("/tmp/test.txt")
31
+
32
+ api.url.get("http://github.com")
33
+ api.url.analyse("https://github.com/ninoseki/virustotalx")
49
34
 
50
- See `/spec/clients` for more.
35
+ api.domain.get("github.com")
36
+
37
+ api.ip_address.get("1.1.1.1")
38
+ ```
51
39
 
52
40
  ## Supported API endpoints
53
41
 
54
- * [VirusTotal API reference](https://developers.virustotal.com/reference)
55
-
56
- | HTTP Method | URL | Public / Private | API method |
57
- |-------------|-----------------------|------------------|-----------------------------------------------------------|
58
- | GET | /file/report | Public | `VirusTotal::Client::File#report(resource, allinfo: nil)` |
59
- | POST | /file/scan | Public | `VirusTotal::Client::File#scan(path)` |
60
- | GET | /file/scan/upload_url | Private | `VirusTotal::Client::File#upload_url` |
61
- | POST | /file/rescan | Public | `VirusTotal::Client::File#rescan(resource)` |
62
- | GET | /file/download | Private | `VirusTotal::Client::File#download(hash)` |
63
- | GET | /file/behaviour | Private | `VirusTotal::Client::File#behaviour(hash)` |
64
- | GET | /file/network-traffic | Private | `VirusTotal::Client::File#network_traffic(hash)` |
65
- | GET | /file/feed | Private | N/A |
66
- | GET | /file/clusters | Private | `VirusTotal::Client::File#clusters(date)` |
67
- | GET | /file/search | Private | `VirusTotal::Client::File#search(query, offset: nil)` |
68
- | GET | /url/report | Public | `VirusTotal::Client::URL#report(resource, allinfo: nil)` |
69
- | POST | /url/scan | Public | `VirusTotal::Client::URL#scan(url)` |
70
- | GET | /url/feed | Private | N/A |
71
- | GET | /domain/report | Public | `VirusTotal::Client::Domain#report(domain)` |
72
- | GET | /ip-address/report | Public | `VirusTotal::Client::IPAddress(ip)` |
73
- | GET | /comments/ | Public | N/A |
74
- | POST | /comments/put | Public | N/A |
42
+ * [VirusTotal API reference](https://developers.virustotal.com/v3.0/reference#overview)
43
+
44
+ ### Files
45
+
46
+ | HTTP Method | URL | API method |
47
+ |-------------|------------------------------------|------------------------------------------------------------|
48
+ | POST | /files | api.file.upload(filepath) |
49
+ | GET | /files/upload_url | api.file.upload_url |
50
+ | GET | /files/{id} | api.file.get(id) |
51
+ | POST | /files | api.file.upload(path) |
52
+ | POST | /files/{id}/analyse | api.file.analyse(id) |
53
+ | GET | /files/{id}/comments | api.file.comments(id) |
54
+ | POST | /files/{id}/comments | api.file.add_comment(id, text) |
55
+ | GET | /files/{id}/votes | api.file.votes(id) |
56
+ | POST | /files/{id}/votes | api.file.add_vote(id, verdict) |
57
+ | GET | /files/{id}/download_url | api.file.downbload_url(id) |
58
+ | GET | /files/{id}/download | api.file.download(id) |
59
+ | GET | /files/{id}/{relationship} | api.file.`relationship`(id) (e.g. api.file.behaviours(id)) |
60
+ | GET | /file_behaviours/{sandbox_id}/pcap | api.file.pcap(sandbox_id) |
61
+
62
+ ### URLs
63
+
64
+ | HTTP Method | URL | API method |
65
+ |-------------|-----------------------------|----------------------------------------------------------------|
66
+ | POST | /urls | N/A |
67
+ | GET | /urls/{id} | api.url.get(id) |
68
+ | POST | /urls/{id}/analyse | api.url.analyse(id) |
69
+ | GET | /urls/{id}/comments | api.url.comments(id) |
70
+ | POST | /urls/{id}/comments | api.url.add_comment(id) |
71
+ | GET | /urls/{id}/votes | api.url.votes(id) |
72
+ | POST | /urls/{id}/votes | api.url.add_vote(id, text) |
73
+ | GET | /urls/{id}/network_location | api.url.network_location(id) |
74
+ | GET | /urls/{id}/{relationship} | api.url.`relationship`(id) (e.g. api.url.downloaded_files(id)) |
75
+
76
+ Note: you can use a URL as an id.
77
+
78
+ ### Domains
79
+
80
+ | HTTP Method | URL | API method |
81
+ |-------------|----------------------------------|--------------------------------------------------------------|
82
+ | GET | /domains/{domain} | api.domain.get(domain) |
83
+ | GET | /domains/{domain}/comments | api.domain.comment(domain) |
84
+ | POST | /domains/{domain}/comments | api.domain.add_comment(domain, text) |
85
+ | GET | /domains/{domain}/{relationship} | api.domain.`relationship`(domain) (e.g. api.domain.(domain)) |
86
+
87
+ ### IP addresses
88
+
89
+ | HTTP Method | URL | API method |
90
+ |-------------|-----------------------------------|---------------------------------------------------------------------------------|
91
+ | GET | /ip_addresses/{ip} | api.ip_address.get(ip) |
92
+ | GET | /ip_addresses/{ip}/comments | api.ip_address.comments(id) |
93
+ | POST | /ip_addresses/{ip}/comments | api.ip_address.add_comment(id, text) |
94
+ | GET | /ip_addresses/{ip}/{relationship} | api.ip_address.`relationship`(id) (e.g. api.ip_address.communicating_files(ip)) |
95
+
96
+ ### Analyses
97
+
98
+ | HTTP Method | URL | API method |
99
+ |-------------|----------------|----------------------|
100
+ | GET | /analyses/{id} | api.analysis.get(ip) |
101
+
102
+ ## Graphs
103
+
104
+ N/A.
75
105
 
76
106
  ## License
77
107
 
data/lib/virustotal.rb CHANGED
@@ -7,12 +7,13 @@ require "virustotal/errors"
7
7
  require "virustotal/api"
8
8
 
9
9
  require "virustotal/clients/base"
10
+ require "virustotal/clients/object"
10
11
 
12
+ require "virustotal/clients/analysis"
11
13
  require "virustotal/clients/domain"
12
14
  require "virustotal/clients/file"
13
15
  require "virustotal/clients/ip_address"
14
16
  require "virustotal/clients/url"
15
17
 
16
18
  module VirusTotal
17
- class Error < StandardError; end
18
19
  end
@@ -4,6 +4,7 @@ require_relative "clients/base"
4
4
 
5
5
  module VirusTotal
6
6
  class API
7
+ attr_reader :analysis
7
8
  attr_reader :domain
8
9
  attr_reader :file
9
10
  attr_reader :ip_address
@@ -12,6 +13,7 @@ module VirusTotal
12
13
  def initialize(key: ENV["VIRUSTOTAL_API_KEY"])
13
14
  raise ArgumentError, "No API key has been found or provided! (setup your VIRUSTOTAL_API_KEY environment varialbe)" unless key
14
15
 
16
+ @analysis = Client::Analysis.new(key: key)
15
17
  @domain = Client::Domain.new(key: key)
16
18
  @file = Client::File.new(key: key)
17
19
  @ip_address = Client::IPAddress.new(key: key)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VirusTotal
4
+ module Client
5
+ class Analysis < Base
6
+ def get(id)
7
+ _get("/analyses/#{id}") { |json| json }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -8,8 +8,8 @@ module VirusTotal
8
8
  module Client
9
9
  class Base
10
10
  HOST = "www.virustotal.com"
11
- VERSION = "v2"
12
- BASE_URL = "https://#{HOST}/vtapi/#{VERSION}"
11
+ VERSION = "v3"
12
+ BASE_URL = "https://#{HOST}/api/#{VERSION}"
13
13
 
14
14
  attr_reader :key
15
15
 
@@ -24,10 +24,6 @@ module VirusTotal
24
24
  !key.nil?
25
25
  end
26
26
 
27
- def default_params
28
- { apikey: key }
29
- end
30
-
31
27
  def url_for(path)
32
28
  URI(BASE_URL + path)
33
29
  end
@@ -46,58 +42,75 @@ module VirusTotal
46
42
  end
47
43
  end
48
44
 
45
+ def raise_error(code, message)
46
+ code = code.to_s.to_sym
47
+
48
+ table = {
49
+ "400": BadRequestError,
50
+ "401": AuthenticationRequiredError,
51
+ "403": ForbiddenError,
52
+ "404": NotFoundError,
53
+ "409": AlreadyExistsError,
54
+ "429": QuotaExceededError,
55
+ "503": TransientError,
56
+ }
57
+ raise Error, "Unsupported response code returned: #{code} - #{message}" unless table.key?(code)
58
+
59
+ klass = table[code]
60
+ raise klass, message
61
+ end
62
+
49
63
  def request(req)
50
64
  Net::HTTP.start(HOST, 443, https_options) do |http|
65
+ req["x-apikey"] = key
66
+
51
67
  response = http.request(req)
52
68
 
53
- case response.code
54
- when "200"
55
- if response["Content-Type"] == "application/json"
56
- yield JSON.parse(response.body)
69
+ code = response.code.to_i
70
+ body = response.body
71
+ json = JSON.parse(body) if response["Content-Type"].to_s.include?("application/json")
72
+ message = json ? json.dig("message") : body
73
+
74
+ case code
75
+ when 200
76
+ if json
77
+ yield json
57
78
  else
58
- yield response.body
79
+ yield body
59
80
  end
60
- when "204"
61
- raise(RateLimitError, response.body)
62
- when "302"
81
+ when 302
63
82
  yield response["Location"]
64
83
  else
65
- raise(Error, "unsupported response code returned: #{response.code}")
84
+ raise_error code, message
66
85
  end
67
86
  end
68
87
  end
69
88
 
70
- def get(path, params = {}, &block)
89
+ def _get(path, params = {}, &block)
71
90
  uri = url_for(path)
72
- uri.query = URI.encode_www_form(params.merge(default_params))
91
+ uri.query = URI.encode_www_form(params)
73
92
  get = Net::HTTP::Get.new(uri)
74
93
 
75
94
  request(get, &block)
76
95
  end
77
96
 
78
- def post(path, params = {}, &block)
97
+ def _post(path, params = {}, &block)
79
98
  post = Net::HTTP::Post.new(url_for(path))
80
- post.set_form_data params.merge(default_params)
99
+ post.body = JSON.generate(params)
81
100
 
82
101
  request(post, &block)
83
102
  end
84
103
 
85
- def post_with_file(path, file:, filename:, &block)
104
+ def _post_with_file(path, file:, filename:, &block)
86
105
  post = Net::HTTP::Post.new(url_for(path))
87
106
 
88
107
  data = [
89
108
  ["file", file, { "filename": filename }],
90
- ["apikey", key]
91
109
  ]
92
110
  post.set_form(data, "multipart/form-data")
93
111
 
94
112
  request(post, &block)
95
113
  end
96
-
97
- def handle_response_code(json)
98
- response_code = json.dig("response_code").to_i
99
- response_code.zero? ? nil : json
100
- end
101
114
  end
102
115
  end
103
116
  end
@@ -2,11 +2,20 @@
2
2
 
3
3
  module VirusTotal
4
4
  module Client
5
- class Domain < Base
6
- def report(domain)
7
- get("/domain/report", domain: domain) do |json|
8
- handle_response_code json
9
- end
5
+ class Domain < Object
6
+ private
7
+
8
+ def relationships
9
+ %w(
10
+ communicating_files
11
+ downloaded_files
12
+ graphs
13
+ historical_whois
14
+ referrer_files
15
+ resolutions
16
+ siblings
17
+ urls
18
+ ).map(&:to_sym)
10
19
  end
11
20
  end
12
21
  end
@@ -2,47 +2,76 @@
2
2
 
3
3
  module VirusTotal
4
4
  module Client
5
- class File < Base
6
- def report(resource, allinfo: nil)
7
- params = { resource: resource, allinfo: allinfo }.compact
8
- post("/file/report", params) do |json|
9
- handle_response_code json
10
- end
11
- end
12
-
13
- def scan(path)
5
+ class File < Object
6
+ def upload(path)
14
7
  name = ::File.basename(path)
15
8
  data = ::File.read(path)
16
- post_with_file("/file/scan", filename: name, file: data) { |json| json }
9
+ _post_with_file("/files", file: data, filename: name) { |json| json }
17
10
  end
18
11
 
19
- def rescan(resource)
20
- post("/file/rescan", resource: resource) { |json| json }
12
+ def upload_url
13
+ _get("/files/upload_url") { |json| json }
21
14
  end
22
15
 
23
- def upload_url
24
- get("/file/scan/upload_url") { |location| location }
16
+ def analyse(hash)
17
+ _post("/files/#{hash}/analyse") { |json| json }
25
18
  end
26
19
 
27
- def download(hash)
28
- get("/file/download", hash: hash) { |raw| raw }
20
+ def votes(hash)
21
+ _get("/files/#{hash}/votes") { |json| json }
29
22
  end
30
23
 
31
- def behaviour(hash)
32
- get("/file/behaviour", hash: hash) { |json| json }
24
+ def add_vote(hash, verdict)
25
+ params = {
26
+ data: {
27
+ type: "vote",
28
+ attributes: {
29
+ verdict: verdict
30
+ }
31
+ }
32
+ }
33
+ _post("/files/#{hash}/votes", params) { |json| json }
33
34
  end
34
35
 
35
- def network_traffic(hash)
36
- get("/file/network-traffic", hash: hash) { |json| json }
36
+ def download_url(hash)
37
+ _get("/files/#{hash}/download_url") { |json| json }
37
38
  end
38
39
 
39
- def clusters(date)
40
- get("/file/clusters", date: date) { |json| json }
40
+ def download(hash)
41
+ _get("/files/#{hash}/download") { |location| location }
42
+ end
43
+
44
+ def pcap(id)
45
+ _get("/file_behaviours/#{id}/pcap") { |raw| raw }
41
46
  end
42
47
 
43
- def search(query, offset: nil)
44
- params = { query: query, offset: offset }.compact
45
- get("/file/search", params) { |json| json }
48
+ private
49
+
50
+ def relationships
51
+ %w(
52
+ analyses
53
+ behaviours
54
+ bundled_files
55
+ carbonblack_children
56
+ carbonblack_parents
57
+ compressed_parents
58
+ contacted_domains
59
+ contacted_ips
60
+ contacted_urls
61
+ email_parents
62
+ embedded_domains
63
+ embedded_ips
64
+ execution_parents
65
+ graphs
66
+ itw_urls
67
+ overlay_parents
68
+ pcap_parents
69
+ pe_resource_parents
70
+ similar_files
71
+ submissions
72
+ screenshots
73
+ votes
74
+ ).map(&:to_sym)
46
75
  end
47
76
  end
48
77
  end
@@ -2,11 +2,19 @@
2
2
 
3
3
  module VirusTotal
4
4
  module Client
5
- class IPAddress < Base
6
- def report(ip)
7
- get("/ip-address/report", ip: ip) do |json|
8
- handle_response_code json
9
- end
5
+ class IPAddress < Object
6
+ private
7
+
8
+ def relationships
9
+ %w(
10
+ communicating_files
11
+ downloaded_files
12
+ graphs
13
+ historical_whois
14
+ referrer_files
15
+ resolutions
16
+ urls
17
+ ).map(&:to_sym)
10
18
  end
11
19
  end
12
20
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VirusTotal
4
+ module Client
5
+ class Object < Base
6
+ CONVERT_TABLE = {
7
+ ipaddress: "ip_addresses",
8
+ domain: "domains",
9
+ url: "urls",
10
+ file: "file"
11
+ }.freeze
12
+
13
+ def get(id)
14
+ id = to_id(id)
15
+ _get("/#{name}/#{id}") { |json| json }
16
+ end
17
+
18
+ def comments(id)
19
+ id = to_id(id)
20
+ _get("/#{name}/#{id}/comments") { |json| json }
21
+ end
22
+
23
+ def add_comment(id, text)
24
+ id = to_id(id)
25
+ params = {
26
+ data: {
27
+ type: "comment",
28
+ attributes: {
29
+ text: text
30
+ }
31
+ }
32
+ }
33
+ _post("/#{name}/#{id}/comments", params) { |json| json }
34
+ end
35
+
36
+ def relationships
37
+ []
38
+ end
39
+
40
+ def method_missing(method, *args)
41
+ if relationships.include?(method)
42
+ id = to_id(args.first)
43
+ params = args.length == 2 ? args[1] : {}
44
+
45
+ _get("/#{name}/#{id}/#{method}", params) { |json| json }
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def respond_to?(sym, *)
52
+ return true if relationships.include? sym
53
+
54
+ super
55
+ end
56
+
57
+ private
58
+
59
+ def to_id(id)
60
+ id
61
+ end
62
+
63
+ def klass
64
+ self.class.to_s.split("::").last.to_s.downcase.to_sym
65
+ end
66
+
67
+ def name
68
+ CONVERT_TABLE.fetch klass
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,17 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+
3
5
  module VirusTotal
4
6
  module Client
5
- class URL < Base
6
- def report(resource, allinfo: nil)
7
- params = { resource: resource, allinfo: allinfo }.compact
8
- post("/url/report", params) do |json|
9
- handle_response_code json
10
- end
7
+ class URL < Object
8
+ def analyse(url)
9
+ id = to_id(url)
10
+ _post("/urls/#{id}/analyse") { |json| json }
11
+ end
12
+
13
+ def votes(url)
14
+ id = to_id(url)
15
+ _get("/urls/#{id}/votes") { |json| json }
16
+ end
17
+
18
+ def network_location(url)
19
+ id = to_id(url)
20
+ _get("/urls/#{id}/network_location") { |json| json }
21
+ end
22
+
23
+ def add_vote(url, verdict)
24
+ id = to_id(url)
25
+ params = {
26
+ data: {
27
+ type: "vote",
28
+ attributes: {
29
+ verdict: verdict
30
+ }
31
+ }
32
+ }
33
+ _post("/urls/#{id}/votes", params) { |json| json }
34
+ end
35
+
36
+ private
37
+
38
+ def to_id(url)
39
+ Base64.urlsafe_encode64(url).split("=").first
11
40
  end
12
41
 
13
- def scan(url)
14
- post("/url/scan", url: url) { |json| json }
42
+ def relationships
43
+ %w(
44
+ analyses
45
+ downloaded_files
46
+ graphs
47
+ last_serving_ip_address
48
+ redirecting_urls
49
+ submissions
50
+ ).map(&:to_sym)
15
51
  end
16
52
  end
17
53
  end
@@ -2,5 +2,17 @@
2
2
 
3
3
  module VirusTotal
4
4
  class Error < StandardError; end
5
+
6
+ class AlreadyExistsError < Error; end
7
+ class AuthenticationRequiredError < Error; end
8
+ class BadRequestError < Error; end
9
+ class ForbiddenError < Error; end
10
+ class InvalidArgumentError < Error; end
11
+ class NotFoundError < Error; end
12
+ class QuotaExceededError < Error; end
5
13
  class RateLimitError < Error; end
14
+ class TooManyRequestsError < Error; end
15
+ class TransientError < Error; end
16
+ class UserNotActiveError < Error; end
17
+ class WrongCredentialsError < Error; end
6
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VirusTotal
4
- VERSION = "0.1.1"
4
+ VERSION = "1.0.0"
5
5
  end
data/virustotalx.gemspec CHANGED
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "coveralls", "~> 0.8"
29
29
  spec.add_development_dependency "rake", "~> 12.3"
30
30
  spec.add_development_dependency "rspec", "~> 3.8"
31
- spec.add_development_dependency "vcr", "~> 4.0"
32
- spec.add_development_dependency "webmock", "~> 3.5"
31
+ spec.add_development_dependency "vcr", "~> 5.0"
32
+ spec.add_development_dependency "webmock", "~> 3.7"
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: virustotalx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manabu Niseki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-03 00:00:00.000000000 Z
11
+ date: 2019-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,28 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '4.0'
75
+ version: '5.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '4.0'
82
+ version: '5.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: webmock
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '3.5'
89
+ version: '3.7'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '3.5'
96
+ version: '3.7'
97
97
  description: Yet another VirusTotal API wrapper for Ruby
98
98
  email:
99
99
  - manabu.niseki@gmail.com
@@ -112,10 +112,12 @@ files:
112
112
  - bin/setup
113
113
  - lib/virustotal.rb
114
114
  - lib/virustotal/api.rb
115
+ - lib/virustotal/clients/analysis.rb
115
116
  - lib/virustotal/clients/base.rb
116
117
  - lib/virustotal/clients/domain.rb
117
118
  - lib/virustotal/clients/file.rb
118
119
  - lib/virustotal/clients/ip_address.rb
120
+ - lib/virustotal/clients/object.rb
119
121
  - lib/virustotal/clients/url.rb
120
122
  - lib/virustotal/errors.rb
121
123
  - lib/virustotal/version.rb
@@ -140,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
142
  - !ruby/object:Gem::Version
141
143
  version: '0'
142
144
  requirements: []
143
- rubygems_version: 3.0.2
145
+ rubygems_version: 3.0.4
144
146
  signing_key:
145
147
  specification_version: 4
146
148
  summary: Yet another VirusTotal API wrapper for Ruby