virustotalx 0.1.1 → 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 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