miteru 1.0.1 → 1.2.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 +4 -4
- data/.github/workflows/test.yml +2 -3
- data/lib/miteru/attachement.rb +1 -1
- data/lib/miteru/configuration.rb +21 -4
- data/lib/miteru/crawler.rb +15 -8
- data/lib/miteru/database.rb +9 -1
- data/lib/miteru/downloader.rb +11 -3
- data/lib/miteru/feeds/ayashige.rb +4 -4
- data/lib/miteru/feeds/feed.rb +33 -0
- data/lib/miteru/feeds/phishing_database.rb +1 -1
- data/lib/miteru/feeds/phishstats.rb +1 -1
- data/lib/miteru/feeds/urlscan.rb +2 -2
- data/lib/miteru/feeds/urlscan_pro.rb +3 -3
- data/lib/miteru/feeds.rb +19 -37
- data/lib/miteru/http_client.rb +1 -1
- data/lib/miteru/kit.rb +26 -4
- data/lib/miteru/mixin.rb +47 -0
- data/lib/miteru/notifiers/base.rb +15 -0
- data/lib/miteru/notifiers/slack.rb +29 -0
- data/lib/miteru/notifiers/urlscan.rb +37 -0
- data/lib/miteru/record.rb +1 -0
- data/lib/miteru/version.rb +1 -1
- data/lib/miteru/website.rb +7 -2
- data/lib/miteru.rb +23 -2
- data/miteru.gemspec +19 -15
- metadata +81 -35
- data/lib/miteru/notifier.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c858dd689d566a4a12270d7474b9174d60deeabb23f61e1a62440040558f0a3c
|
4
|
+
data.tar.gz: 0f53873e358ab18712c0e9d7c5fc6b6cd863464b467d78e82983ca2386834115
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76359bf3d5883acdfdef815045d634a286be8985f21408f9f91349a7e048077309ab468d095d4b49e9e26b19018b4d280b2290beb8620b5d7729e1b9fc5d9586
|
7
|
+
data.tar.gz: 9bb8d92dbb325a74c38a8f05a4ed5967831a70601597d6d45235d9a1571228dcfcc543e255628b763b977bf2a649b3976779a2765c8ba19dd174dc988d275320
|
data/.github/workflows/test.yml
CHANGED
@@ -42,18 +42,17 @@ jobs:
|
|
42
42
|
ruby: [2.7, "3.0"]
|
43
43
|
|
44
44
|
steps:
|
45
|
-
- uses: actions/checkout@
|
45
|
+
- uses: actions/checkout@v3
|
46
46
|
- name: Set up Ruby
|
47
47
|
uses: ruby/setup-ruby@v1
|
48
48
|
with:
|
49
49
|
ruby-version: ${{ matrix.ruby }}
|
50
|
+
bundler: latest
|
50
51
|
bundler-cache: true
|
51
52
|
|
52
53
|
- name: Install dependencies
|
53
54
|
run: |
|
54
55
|
sudo apt-get -yqq install libpq-dev libmysqlclient-dev
|
55
|
-
gem install bundler
|
56
|
-
bundle install
|
57
56
|
|
58
57
|
- name: Test with PostgreSQL
|
59
58
|
env:
|
data/lib/miteru/attachement.rb
CHANGED
data/lib/miteru/configuration.rb
CHANGED
@@ -37,12 +37,21 @@ module Miteru
|
|
37
37
|
# @return [String]
|
38
38
|
attr_accessor :slack_channel
|
39
39
|
|
40
|
+
# @return [String]
|
41
|
+
attr_accessor :urlscan_api_key
|
42
|
+
|
43
|
+
# @return [String]
|
44
|
+
attr_accessor :urlscan_submit_visibility
|
45
|
+
|
40
46
|
# @return [Array<String>]
|
41
47
|
attr_reader :valid_extensions
|
42
48
|
|
43
49
|
# @return [Array<String>]
|
44
50
|
attr_reader :valid_mime_types
|
45
51
|
|
52
|
+
# @return [Integer]
|
53
|
+
attr_reader :file_maxsize
|
54
|
+
|
46
55
|
def initialize
|
47
56
|
@auto_download = false
|
48
57
|
@ayashige = false
|
@@ -52,10 +61,14 @@ module Miteru
|
|
52
61
|
@size = 100
|
53
62
|
@threads = Parallel.processor_count
|
54
63
|
@verbose = false
|
55
|
-
@database = ENV
|
64
|
+
@database = ENV.fetch("MITERU_DATABASE", "miteru.db")
|
65
|
+
@file_maxsize = ENV.fetch("FILE_MAXSIZE", 1024 * 1024 * 100).to_i
|
66
|
+
|
67
|
+
@slack_webhook_url = ENV.fetch("SLACK_WEBHOOK_URL", nil)
|
68
|
+
@slack_channel = ENV.fetch("SLACK_CHANNEL", "#general")
|
56
69
|
|
57
|
-
@
|
58
|
-
@
|
70
|
+
@urlscan_api_key = ENV.fetch("URLSCAN_API_KEY", nil)
|
71
|
+
@urlscan_submit_visibility = ENV.fetch("URLSCAN_SUBMIT_VISIBILITY", "public")
|
59
72
|
|
60
73
|
@valid_extensions = [".zip", ".rar", ".7z", ".tar", ".gz"].freeze
|
61
74
|
@valid_mime_types = ["application/zip", "application/vnd.rar", "application/x-7z-compressed", "application/x-tar", "application/gzip"]
|
@@ -82,7 +95,11 @@ module Miteru
|
|
82
95
|
end
|
83
96
|
|
84
97
|
def slack_webhook_url?
|
85
|
-
|
98
|
+
!@slack_webhook_url.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
def urlscan_api_key?
|
102
|
+
!@urlscan_api_key.nil?
|
86
103
|
end
|
87
104
|
end
|
88
105
|
|
data/lib/miteru/crawler.rb
CHANGED
@@ -11,11 +11,10 @@ module Miteru
|
|
11
11
|
def initialize
|
12
12
|
@downloader = Downloader.new(Miteru.configuration.download_to)
|
13
13
|
@feeds = Feeds.new
|
14
|
-
@notifier = Notifier.new
|
15
14
|
end
|
16
15
|
|
17
|
-
def crawl(
|
18
|
-
website = Website.new(url)
|
16
|
+
def crawl(entry)
|
17
|
+
website = Website.new(entry.url, entry.source)
|
19
18
|
downloader.download_kits(website.kits) if website.has_kits? && auto_download?
|
20
19
|
notify(website) if website.has_kits? || verbose?
|
21
20
|
rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError => _e
|
@@ -23,11 +22,11 @@ module Miteru
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def execute
|
26
|
-
|
27
|
-
|
25
|
+
suspicious_entries = feeds.suspicious_entries
|
26
|
+
Miteru.logger.info "Loaded #{suspicious_entries.length} URLs to crawl. (crawling in #{threads} threads)" if verbose?
|
28
27
|
|
29
|
-
Parallel.each(
|
30
|
-
crawl
|
28
|
+
Parallel.each(suspicious_entries, in_threads: threads) do |entry|
|
29
|
+
crawl entry
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -36,7 +35,9 @@ module Miteru
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def notify(website)
|
39
|
-
|
38
|
+
Parallel.each(notifiers) do |notifier|
|
39
|
+
notifier.notify website
|
40
|
+
end
|
40
41
|
end
|
41
42
|
|
42
43
|
def auto_download?
|
@@ -47,6 +48,12 @@ module Miteru
|
|
47
48
|
Miteru.configuration.verbose?
|
48
49
|
end
|
49
50
|
|
51
|
+
private
|
52
|
+
|
53
|
+
def notifiers
|
54
|
+
@notifiers ||= [Notifiers::Slack.new, Notifiers::UrlScan.new].select(&:notifiable?)
|
55
|
+
end
|
56
|
+
|
50
57
|
class << self
|
51
58
|
def execute
|
52
59
|
new.execute
|
data/lib/miteru/database.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_record"
|
4
4
|
|
5
|
-
class InitialSchema < ActiveRecord::Migration[
|
5
|
+
class InitialSchema < ActiveRecord::Migration[7.0]
|
6
6
|
def change
|
7
7
|
create_table :records, if_not_exists: true do |t|
|
8
8
|
t.string :hash, null: false, index: { unique: true }
|
@@ -19,6 +19,12 @@ class InitialSchema < ActiveRecord::Migration[6.1]
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
class V11Schema < ActiveRecord::Migration[7.0]
|
23
|
+
def change
|
24
|
+
add_column :records, :source, :string, if_not_exists: true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
22
28
|
def adapter
|
23
29
|
return "postgresql" if Miteru.configuration.database.start_with?("postgresql://", "postgres://")
|
24
30
|
return "mysql2" if Miteru.configuration.database.start_with?("mysql2://")
|
@@ -44,6 +50,7 @@ module Miteru
|
|
44
50
|
ActiveRecord::Migration.verbose = false
|
45
51
|
|
46
52
|
InitialSchema.migrate(:up)
|
53
|
+
V11Schema.migrate(:up)
|
47
54
|
rescue StandardError => _e
|
48
55
|
# Do nothing
|
49
56
|
end
|
@@ -57,6 +64,7 @@ module Miteru
|
|
57
64
|
return unless ActiveRecord::Base.connected?
|
58
65
|
|
59
66
|
InitialSchema.migrate(:down)
|
67
|
+
V11Schema.migrate(:down)
|
60
68
|
end
|
61
69
|
end
|
62
70
|
end
|
data/lib/miteru/downloader.rb
CHANGED
@@ -26,7 +26,15 @@ module Miteru
|
|
26
26
|
begin
|
27
27
|
downloaded_as = HTTPClient.download(kit.url, destination)
|
28
28
|
rescue Down::Error => e
|
29
|
-
|
29
|
+
Miteru.logger.error "Failed to download: #{kit.url} (#{e})"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
# check filesize
|
34
|
+
size = File.size downloaded_as
|
35
|
+
if size > Miteru.configuration.file_maxsize
|
36
|
+
Miteru.logger.info "#{kit.url}'s filesize exceeds the limit: #{size}"
|
37
|
+
FileUtils.rm downloaded_as
|
30
38
|
return
|
31
39
|
end
|
32
40
|
|
@@ -35,14 +43,14 @@ module Miteru
|
|
35
43
|
ActiveRecord::Base.connection_pool.with_connection do
|
36
44
|
# Remove a downloaded file if it is not unique
|
37
45
|
unless Record.unique_hash?(hash)
|
38
|
-
|
46
|
+
Miteru.logger.info "Don't download #{kit.url}. The same hash is already recorded. (SHA256: #{hash})."
|
39
47
|
FileUtils.rm downloaded_as
|
40
48
|
return
|
41
49
|
end
|
42
50
|
|
43
51
|
# Record a kit in DB
|
44
52
|
Record.create_by_kit_and_hash(kit, hash)
|
45
|
-
|
53
|
+
Miteru.logger.info "Download #{kit.url} as #{downloaded_as}"
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
@@ -7,13 +7,13 @@ module Miteru
|
|
7
7
|
class Feeds
|
8
8
|
class Ayashige < Feed
|
9
9
|
HOST = "ayashige.herokuapp.com"
|
10
|
-
URL = "https://#{HOST}"
|
10
|
+
URL = "https://#{HOST}"
|
11
11
|
|
12
12
|
def urls
|
13
|
-
url = url_for("/
|
13
|
+
url = url_for("/api/v1/domains/")
|
14
14
|
res = JSON.parse(get(url))
|
15
15
|
|
16
|
-
domains = res.map { |item| item["
|
16
|
+
domains = res.map { |item| item["fqdn"] }
|
17
17
|
domains.map do |domain|
|
18
18
|
[
|
19
19
|
"https://#{domain}",
|
@@ -21,7 +21,7 @@ module Miteru
|
|
21
21
|
]
|
22
22
|
end.flatten
|
23
23
|
rescue HTTPResponseError, HTTP::Error, JSON::ParserError => e
|
24
|
-
|
24
|
+
Miteru.logger.error "Failed to load ayashige feed (#{e})"
|
25
25
|
[]
|
26
26
|
end
|
27
27
|
|
data/lib/miteru/feeds/feed.rb
CHANGED
@@ -3,10 +3,43 @@
|
|
3
3
|
module Miteru
|
4
4
|
class Feeds
|
5
5
|
class Feed
|
6
|
+
include Mixins::URL
|
7
|
+
|
8
|
+
def source
|
9
|
+
@source ||= self.class.to_s.split("::").last
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Return URLs
|
14
|
+
#
|
15
|
+
# @return [Array<String>] URLs
|
16
|
+
#
|
6
17
|
def urls
|
7
18
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
8
19
|
end
|
9
20
|
|
21
|
+
#
|
22
|
+
# Return entries
|
23
|
+
#
|
24
|
+
# @return [Array<Miteru::Entry>]
|
25
|
+
#
|
26
|
+
def entries
|
27
|
+
breakdowend_urls.map do |url|
|
28
|
+
Entry.new(url, source)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Return breakdowned URLs
|
34
|
+
#
|
35
|
+
# @return [Array<String>] Breakdowned URLs
|
36
|
+
#
|
37
|
+
def breakdowend_urls
|
38
|
+
urls.select { |url| url.start_with?("http://", "https://") }.map do |url|
|
39
|
+
breakdown(url, Miteru.configuration.directory_traveling?)
|
40
|
+
end.flatten.uniq
|
41
|
+
end
|
42
|
+
|
10
43
|
private
|
11
44
|
|
12
45
|
def get(url)
|
data/lib/miteru/feeds/urlscan.rb
CHANGED
@@ -13,13 +13,13 @@ module Miteru
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def api
|
16
|
-
@api ||= ::UrlScan::API.new
|
16
|
+
@api ||= ::UrlScan::API.new(Miteru.configuration.urlscan_api_key)
|
17
17
|
end
|
18
18
|
|
19
19
|
def urls
|
20
20
|
urls_from_community_feed
|
21
21
|
rescue ::UrlScan::ResponseError => e
|
22
|
-
|
22
|
+
Miteru.logger.error "Failed to load urlscan.io feed (#{e})"
|
23
23
|
[]
|
24
24
|
end
|
25
25
|
|
@@ -6,20 +6,20 @@ module Miteru
|
|
6
6
|
class Feeds
|
7
7
|
class UrlScanPro < Feed
|
8
8
|
def api
|
9
|
-
@api ||= ::UrlScan::API.new
|
9
|
+
@api ||= ::UrlScan::API.new(Miteru.configuration.urlscan_api_key)
|
10
10
|
end
|
11
11
|
|
12
12
|
def urls
|
13
13
|
urls_from_pro_feed
|
14
14
|
rescue ::UrlScan::ResponseError => e
|
15
|
-
|
15
|
+
Miteru.logger.error "Failed to load urlscan.io pro feed (#{e})"
|
16
16
|
[]
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def api_key?
|
22
|
-
|
22
|
+
Miteru.configuration.urlscan_api_key?
|
23
23
|
end
|
24
24
|
|
25
25
|
def urls_from_pro_feed
|
data/lib/miteru/feeds.rb
CHANGED
@@ -8,6 +8,18 @@ require_relative "./feeds/urlscan"
|
|
8
8
|
require_relative "./feeds/urlscan_pro"
|
9
9
|
|
10
10
|
module Miteru
|
11
|
+
class Entry
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :url
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :source
|
16
|
+
|
17
|
+
def initialize(url, source)
|
18
|
+
@url = url
|
19
|
+
@source = source
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
11
23
|
class Feeds
|
12
24
|
IGNORE_EXTENSIONS = %w[.htm .html .php .asp .aspx .exe .txt].freeze
|
13
25
|
|
@@ -21,43 +33,13 @@ module Miteru
|
|
21
33
|
].compact
|
22
34
|
end
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
feed.urls.select { |url| url.start_with?("http://", "https://") }
|
32
|
-
end.flatten.uniq
|
33
|
-
|
34
|
-
urls.map { |url| breakdown(url) }.flatten.uniq.sort.each { |url| arr << url }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def breakdown(url)
|
39
|
-
begin
|
40
|
-
uri = URI.parse(url)
|
41
|
-
rescue URI::InvalidURIError => _e
|
42
|
-
return []
|
43
|
-
end
|
44
|
-
|
45
|
-
base = "#{uri.scheme}://#{uri.hostname}"
|
46
|
-
return [base] unless directory_traveling?
|
47
|
-
|
48
|
-
segments = uri.path.split("/")
|
49
|
-
return [base] if segments.length.zero?
|
50
|
-
|
51
|
-
urls = (0...segments.length).map { |idx| "#{base}#{segments[0..idx].join("/")}" }
|
52
|
-
|
53
|
-
urls.reject do |breakdowned_url|
|
54
|
-
# Reject a url which ends with specific extension names
|
55
|
-
invalid_extension? breakdowned_url
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def invalid_extension?(url)
|
60
|
-
IGNORE_EXTENSIONS.any? { |ext| url.end_with? ext }
|
36
|
+
#
|
37
|
+
# Returns a list of suspicious entries
|
38
|
+
#
|
39
|
+
# @return [Array<Entry>]
|
40
|
+
#
|
41
|
+
def suspicious_entries
|
42
|
+
@suspicious_entries ||= @feeds.map(&:entries).flatten.uniq(&:url)
|
61
43
|
end
|
62
44
|
end
|
63
45
|
end
|
data/lib/miteru/http_client.rb
CHANGED
@@ -7,7 +7,7 @@ require "uri"
|
|
7
7
|
module Miteru
|
8
8
|
class HTTPClient
|
9
9
|
DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
|
10
|
-
URLSCAN_UA = "miteru/#{Miteru::VERSION}"
|
10
|
+
URLSCAN_UA = "miteru/#{Miteru::VERSION}"
|
11
11
|
|
12
12
|
attr_reader :ssl_context
|
13
13
|
|
data/lib/miteru/kit.rb
CHANGED
@@ -9,10 +9,27 @@ module Miteru
|
|
9
9
|
VALID_EXTENSIONS = Miteru.configuration.valid_extensions
|
10
10
|
VALID_MIME_TYPES = Miteru.configuration.valid_mime_types
|
11
11
|
|
12
|
-
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :url
|
13
14
|
|
14
|
-
|
15
|
+
# @return [String]
|
16
|
+
attr_reader :source
|
17
|
+
|
18
|
+
# @return [Integer, nil]
|
19
|
+
attr_reader :status
|
20
|
+
|
21
|
+
# @return [Integer, nil]
|
22
|
+
attr_reader :content_length
|
23
|
+
|
24
|
+
# @return [String, nil]
|
25
|
+
attr_reader :mime_type
|
26
|
+
|
27
|
+
# @return [Hash, nil]
|
28
|
+
attr_reader :headers
|
29
|
+
|
30
|
+
def initialize(url, source)
|
15
31
|
@url = url
|
32
|
+
@source = source
|
16
33
|
|
17
34
|
@content_length = nil
|
18
35
|
@mime_type = nil
|
@@ -45,8 +62,12 @@ module Miteru
|
|
45
62
|
"#{base_dir}/#{filename_to_download}"
|
46
63
|
end
|
47
64
|
|
65
|
+
def downloaded?
|
66
|
+
File.exist?(filepath_to_download)
|
67
|
+
end
|
68
|
+
|
48
69
|
def filesize
|
49
|
-
return nil unless
|
70
|
+
return nil unless downloaded?
|
50
71
|
|
51
72
|
File.size filepath_to_download
|
52
73
|
end
|
@@ -54,7 +75,8 @@ module Miteru
|
|
54
75
|
def filename_with_size
|
55
76
|
return filename unless filesize
|
56
77
|
|
57
|
-
|
78
|
+
kb = (filesize.to_f / 1024.0).ceil
|
79
|
+
"#{filename}(#{kb}KB)"
|
58
80
|
end
|
59
81
|
|
60
82
|
def id
|
data/lib/miteru/mixin.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Miteru
|
2
|
+
module Mixins
|
3
|
+
module URL
|
4
|
+
IGNORE_EXTENSIONS = %w[.htm .html .php .asp .aspx .exe .txt].freeze
|
5
|
+
|
6
|
+
#
|
7
|
+
# Validate extension of a URL
|
8
|
+
#
|
9
|
+
# @param [String] url
|
10
|
+
#
|
11
|
+
# @return [Boolean]
|
12
|
+
#
|
13
|
+
def invalid_extension?(url)
|
14
|
+
IGNORE_EXTENSIONS.any? { |ext| url.end_with? ext }
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Breakdown a URL into URLs
|
19
|
+
#
|
20
|
+
# @param [String] url
|
21
|
+
# @param [Boolean] enable_directory_traveling
|
22
|
+
#
|
23
|
+
# @return [Array<String>]
|
24
|
+
#
|
25
|
+
def breakdown(url, enable_directory_traveling)
|
26
|
+
begin
|
27
|
+
uri = URI.parse(url)
|
28
|
+
rescue URI::InvalidURIError => _e
|
29
|
+
return []
|
30
|
+
end
|
31
|
+
|
32
|
+
base = "#{uri.scheme}://#{uri.hostname}"
|
33
|
+
return [base] unless enable_directory_traveling
|
34
|
+
|
35
|
+
segments = uri.path.split("/")
|
36
|
+
return [base] if segments.length.zero?
|
37
|
+
|
38
|
+
urls = (0...segments.length).map { |idx| "#{base}#{segments[0..idx].join("/")}" }
|
39
|
+
|
40
|
+
urls.reject do |breakdowned_url|
|
41
|
+
# Reject a url which ends with specific extension names
|
42
|
+
invalid_extension? breakdowned_url
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Miteru
|
4
|
+
module Notifiers
|
5
|
+
class Slack < Base
|
6
|
+
#
|
7
|
+
# Notifiy to Slack
|
8
|
+
#
|
9
|
+
# @param [Miteru::Website website
|
10
|
+
#
|
11
|
+
def notify(website)
|
12
|
+
attachement = Attachement.new(website.url)
|
13
|
+
kits = website.kits.select(&:downloaded?)
|
14
|
+
|
15
|
+
if notifiable? && kits.any?
|
16
|
+
notifier = Slack::Notifier.new(Miteru.configuration.slack_webhook_url, channel: Miteru.configuration.slack_channel)
|
17
|
+
notifier.post(text: website.message.capitalize, attachments: attachement.to_a)
|
18
|
+
end
|
19
|
+
|
20
|
+
message = kits.any? ? website.message.colorize(:light_red) : website.message
|
21
|
+
Miteru.logger.info "#{website.url}: #{message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def notifiable?
|
25
|
+
Miteru.configuration.slack_webhook_url? && Miteru.configuration.post_to_slack?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "urlscan"
|
4
|
+
|
5
|
+
module Miteru
|
6
|
+
module Notifiers
|
7
|
+
class UrlScan < Base
|
8
|
+
#
|
9
|
+
# Notifiy to urlscan.io
|
10
|
+
#
|
11
|
+
# @param [Miteru::Website website
|
12
|
+
#
|
13
|
+
def notify(website)
|
14
|
+
kits = website.kits.select(&:downloaded?)
|
15
|
+
return unless notifiable? && kits.any?
|
16
|
+
|
17
|
+
kits.each { |kit| submit(kit.url) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def notifiable?
|
21
|
+
Miteru.configuration.urlscan_api_key?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def api
|
27
|
+
@api ||= ::UrlScan::API.new(Miteru.configuration.urlscan_api_key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def submit(url)
|
31
|
+
api.submit(url, tags: ["miteru", "phishkit"], visibility: Miteru.configuration.urlscan_submit_visibility)
|
32
|
+
rescue StandardError
|
33
|
+
# do nothing
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/miteru/record.rb
CHANGED
data/lib/miteru/version.rb
CHANGED
data/lib/miteru/website.rb
CHANGED
@@ -6,10 +6,15 @@ module Miteru
|
|
6
6
|
class Website
|
7
7
|
VALID_EXTENSIONS = Miteru.configuration.valid_extensions
|
8
8
|
|
9
|
+
# @return [String]
|
9
10
|
attr_reader :url
|
10
11
|
|
11
|
-
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :source
|
14
|
+
|
15
|
+
def initialize(url, source)
|
12
16
|
@url = url
|
17
|
+
@source = source
|
13
18
|
end
|
14
19
|
|
15
20
|
def title
|
@@ -18,7 +23,7 @@ module Miteru
|
|
18
23
|
|
19
24
|
def kits
|
20
25
|
@kits ||= links.filter_map do |link|
|
21
|
-
kit = Kit.new(link)
|
26
|
+
kit = Kit.new(link, source)
|
22
27
|
kit.valid? ? kit : nil
|
23
28
|
end
|
24
29
|
end
|
data/lib/miteru.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "memist"
|
4
|
+
require "semantic_logger"
|
5
|
+
|
3
6
|
require "miteru/version"
|
4
7
|
|
5
8
|
require "miteru/configuration"
|
@@ -7,6 +10,12 @@ require "miteru/database"
|
|
7
10
|
|
8
11
|
require "miteru/record"
|
9
12
|
|
13
|
+
require "miteru/mixin"
|
14
|
+
|
15
|
+
require "miteru/notifiers/base"
|
16
|
+
require "miteru/notifiers/slack"
|
17
|
+
require "miteru/notifiers/urlscan"
|
18
|
+
|
10
19
|
require "miteru/error"
|
11
20
|
require "miteru/http_client"
|
12
21
|
require "miteru/kit"
|
@@ -14,8 +23,20 @@ require "miteru/website"
|
|
14
23
|
require "miteru/downloader"
|
15
24
|
require "miteru/feeds"
|
16
25
|
require "miteru/attachement"
|
17
|
-
require "miteru/notifier"
|
18
26
|
require "miteru/crawler"
|
19
27
|
require "miteru/cli"
|
20
28
|
|
21
|
-
|
29
|
+
# Load .env
|
30
|
+
require "dotenv/load"
|
31
|
+
|
32
|
+
module Miteru
|
33
|
+
class << self
|
34
|
+
include Memist::Memoizable
|
35
|
+
def logger
|
36
|
+
SemanticLogger.default_level = :info
|
37
|
+
SemanticLogger.add_appender(io: $stderr, formatter: :color)
|
38
|
+
SemanticLogger["Miteru"]
|
39
|
+
end
|
40
|
+
memoize :logger
|
41
|
+
end
|
42
|
+
end
|
data/miteru.gemspec
CHANGED
@@ -9,6 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.version = Miteru::VERSION
|
10
10
|
spec.authors = ["Manabu Niseki"]
|
11
11
|
spec.email = ["manabu.niseki@gmail.com"]
|
12
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
12
13
|
|
13
14
|
spec.summary = "An experimental phishing kit detector"
|
14
15
|
spec.description = "An experimental phishing kit detector"
|
@@ -24,28 +25,31 @@ Gem::Specification.new do |spec|
|
|
24
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
26
|
spec.require_paths = ["lib"]
|
26
27
|
|
27
|
-
spec.add_development_dependency "bundler", "~> 2.
|
28
|
-
spec.add_development_dependency "coveralls_reborn", "~> 0.
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.3"
|
29
|
+
spec.add_development_dependency "coveralls_reborn", "~> 0.25"
|
29
30
|
spec.add_development_dependency "glint", "~> 0.1"
|
30
31
|
spec.add_development_dependency "mysql2", "~> 0.5"
|
31
|
-
spec.add_development_dependency "overcommit", "~> 0.
|
32
|
-
spec.add_development_dependency "pg", "~> 1.
|
32
|
+
spec.add_development_dependency "overcommit", "~> 0.59"
|
33
|
+
spec.add_development_dependency "pg", "~> 1.4"
|
33
34
|
spec.add_development_dependency "rake", "~> 13.0"
|
34
|
-
spec.add_development_dependency "rspec", "~> 3.
|
35
|
-
spec.add_development_dependency "standard", "~> 1.
|
36
|
-
spec.add_development_dependency "vcr", "~> 6.
|
37
|
-
spec.add_development_dependency "webmock", "~> 3.
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.11"
|
36
|
+
spec.add_development_dependency "standard", "~> 1.14"
|
37
|
+
spec.add_development_dependency "vcr", "~> 6.1"
|
38
|
+
spec.add_development_dependency "webmock", "~> 3.17"
|
38
39
|
spec.add_development_dependency "webrick", "~> 1.7.0"
|
39
40
|
|
40
|
-
spec.add_dependency "activerecord", "~>
|
41
|
+
spec.add_dependency "activerecord", "~> 7.0"
|
41
42
|
spec.add_dependency "colorize", "~> 0.8"
|
42
|
-
spec.add_dependency "
|
43
|
-
spec.add_dependency "
|
44
|
-
spec.add_dependency "
|
45
|
-
spec.add_dependency "
|
43
|
+
spec.add_dependency "dotenv", "2.8.1"
|
44
|
+
spec.add_dependency "down", "~> 5.3"
|
45
|
+
spec.add_dependency "http", "~> 5.1"
|
46
|
+
spec.add_dependency "memist", "2.0.2"
|
47
|
+
spec.add_dependency "oga", "~> 3.4"
|
48
|
+
spec.add_dependency "parallel", "~> 1.22"
|
49
|
+
spec.add_dependency "semantic_logger", "4.11.0"
|
46
50
|
spec.add_dependency "slack-notifier", "~> 2.4"
|
47
51
|
spec.add_dependency "sqlite3", "~> 1.4"
|
48
|
-
spec.add_dependency "thor", "~> 1.
|
49
|
-
spec.add_dependency "urlscan", "~> 0.
|
52
|
+
spec.add_dependency "thor", "~> 1.2"
|
53
|
+
spec.add_dependency "urlscan", "~> 0.8"
|
50
54
|
spec.add_dependency "uuidtools", "~> 2.2"
|
51
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: miteru
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.2.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:
|
11
|
+
date: 2022-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
19
|
+
version: '2.3'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '2.
|
26
|
+
version: '2.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: coveralls_reborn
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.25'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
40
|
+
version: '0.25'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: glint
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,28 +72,28 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
75
|
+
version: '0.59'
|
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: '0.
|
82
|
+
version: '0.59'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: pg
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '1.
|
89
|
+
version: '1.4'
|
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: '1.
|
96
|
+
version: '1.4'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,56 +114,56 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '3.
|
117
|
+
version: '3.11'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '3.
|
124
|
+
version: '3.11'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: standard
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '1.
|
131
|
+
version: '1.14'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '1.
|
138
|
+
version: '1.14'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: vcr
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version: '6.
|
145
|
+
version: '6.1'
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: '6.
|
152
|
+
version: '6.1'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: webmock
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '3.
|
159
|
+
version: '3.17'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '3.
|
166
|
+
version: '3.17'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: webrick
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,14 +184,14 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '
|
187
|
+
version: '7.0'
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: '
|
194
|
+
version: '7.0'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: colorize
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,62 +206,104 @@ dependencies:
|
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0.8'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: dotenv
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - '='
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 2.8.1
|
216
|
+
type: :runtime
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - '='
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 2.8.1
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: down
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
212
226
|
requirements:
|
213
227
|
- - "~>"
|
214
228
|
- !ruby/object:Gem::Version
|
215
|
-
version: '5.
|
229
|
+
version: '5.3'
|
216
230
|
type: :runtime
|
217
231
|
prerelease: false
|
218
232
|
version_requirements: !ruby/object:Gem::Requirement
|
219
233
|
requirements:
|
220
234
|
- - "~>"
|
221
235
|
- !ruby/object:Gem::Version
|
222
|
-
version: '5.
|
236
|
+
version: '5.3'
|
223
237
|
- !ruby/object:Gem::Dependency
|
224
238
|
name: http
|
225
239
|
requirement: !ruby/object:Gem::Requirement
|
226
240
|
requirements:
|
227
241
|
- - "~>"
|
228
242
|
- !ruby/object:Gem::Version
|
229
|
-
version: '5.
|
243
|
+
version: '5.1'
|
230
244
|
type: :runtime
|
231
245
|
prerelease: false
|
232
246
|
version_requirements: !ruby/object:Gem::Requirement
|
233
247
|
requirements:
|
234
248
|
- - "~>"
|
235
249
|
- !ruby/object:Gem::Version
|
236
|
-
version: '5.
|
250
|
+
version: '5.1'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: memist
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - '='
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: 2.0.2
|
258
|
+
type: :runtime
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - '='
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: 2.0.2
|
237
265
|
- !ruby/object:Gem::Dependency
|
238
266
|
name: oga
|
239
267
|
requirement: !ruby/object:Gem::Requirement
|
240
268
|
requirements:
|
241
269
|
- - "~>"
|
242
270
|
- !ruby/object:Gem::Version
|
243
|
-
version: '3.
|
271
|
+
version: '3.4'
|
244
272
|
type: :runtime
|
245
273
|
prerelease: false
|
246
274
|
version_requirements: !ruby/object:Gem::Requirement
|
247
275
|
requirements:
|
248
276
|
- - "~>"
|
249
277
|
- !ruby/object:Gem::Version
|
250
|
-
version: '3.
|
278
|
+
version: '3.4'
|
251
279
|
- !ruby/object:Gem::Dependency
|
252
280
|
name: parallel
|
253
281
|
requirement: !ruby/object:Gem::Requirement
|
254
282
|
requirements:
|
255
283
|
- - "~>"
|
256
284
|
- !ruby/object:Gem::Version
|
257
|
-
version: '1.
|
285
|
+
version: '1.22'
|
258
286
|
type: :runtime
|
259
287
|
prerelease: false
|
260
288
|
version_requirements: !ruby/object:Gem::Requirement
|
261
289
|
requirements:
|
262
290
|
- - "~>"
|
263
291
|
- !ruby/object:Gem::Version
|
264
|
-
version: '1.
|
292
|
+
version: '1.22'
|
293
|
+
- !ruby/object:Gem::Dependency
|
294
|
+
name: semantic_logger
|
295
|
+
requirement: !ruby/object:Gem::Requirement
|
296
|
+
requirements:
|
297
|
+
- - '='
|
298
|
+
- !ruby/object:Gem::Version
|
299
|
+
version: 4.11.0
|
300
|
+
type: :runtime
|
301
|
+
prerelease: false
|
302
|
+
version_requirements: !ruby/object:Gem::Requirement
|
303
|
+
requirements:
|
304
|
+
- - '='
|
305
|
+
- !ruby/object:Gem::Version
|
306
|
+
version: 4.11.0
|
265
307
|
- !ruby/object:Gem::Dependency
|
266
308
|
name: slack-notifier
|
267
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -296,28 +338,28 @@ dependencies:
|
|
296
338
|
requirements:
|
297
339
|
- - "~>"
|
298
340
|
- !ruby/object:Gem::Version
|
299
|
-
version: '1.
|
341
|
+
version: '1.2'
|
300
342
|
type: :runtime
|
301
343
|
prerelease: false
|
302
344
|
version_requirements: !ruby/object:Gem::Requirement
|
303
345
|
requirements:
|
304
346
|
- - "~>"
|
305
347
|
- !ruby/object:Gem::Version
|
306
|
-
version: '1.
|
348
|
+
version: '1.2'
|
307
349
|
- !ruby/object:Gem::Dependency
|
308
350
|
name: urlscan
|
309
351
|
requirement: !ruby/object:Gem::Requirement
|
310
352
|
requirements:
|
311
353
|
- - "~>"
|
312
354
|
- !ruby/object:Gem::Version
|
313
|
-
version: '0.
|
355
|
+
version: '0.8'
|
314
356
|
type: :runtime
|
315
357
|
prerelease: false
|
316
358
|
version_requirements: !ruby/object:Gem::Requirement
|
317
359
|
requirements:
|
318
360
|
- - "~>"
|
319
361
|
- !ruby/object:Gem::Version
|
320
|
-
version: '0.
|
362
|
+
version: '0.8'
|
321
363
|
- !ruby/object:Gem::Dependency
|
322
364
|
name: uuidtools
|
323
365
|
requirement: !ruby/object:Gem::Requirement
|
@@ -370,7 +412,10 @@ files:
|
|
370
412
|
- lib/miteru/feeds/urlscan_pro.rb
|
371
413
|
- lib/miteru/http_client.rb
|
372
414
|
- lib/miteru/kit.rb
|
373
|
-
- lib/miteru/
|
415
|
+
- lib/miteru/mixin.rb
|
416
|
+
- lib/miteru/notifiers/base.rb
|
417
|
+
- lib/miteru/notifiers/slack.rb
|
418
|
+
- lib/miteru/notifiers/urlscan.rb
|
374
419
|
- lib/miteru/record.rb
|
375
420
|
- lib/miteru/version.rb
|
376
421
|
- lib/miteru/website.rb
|
@@ -380,7 +425,8 @@ files:
|
|
380
425
|
homepage: https://github.com/ninoseki/miteru
|
381
426
|
licenses:
|
382
427
|
- MIT
|
383
|
-
metadata:
|
428
|
+
metadata:
|
429
|
+
rubygems_mfa_required: 'true'
|
384
430
|
post_install_message:
|
385
431
|
rdoc_options: []
|
386
432
|
require_paths:
|
@@ -396,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
396
442
|
- !ruby/object:Gem::Version
|
397
443
|
version: '0'
|
398
444
|
requirements: []
|
399
|
-
rubygems_version: 3.2.
|
445
|
+
rubygems_version: 3.2.14
|
400
446
|
signing_key:
|
401
447
|
specification_version: 4
|
402
448
|
summary: An experimental phishing kit detector
|
data/lib/miteru/notifier.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "colorize"
|
4
|
-
require "slack-notifier"
|
5
|
-
|
6
|
-
module Miteru
|
7
|
-
class Notifier
|
8
|
-
def notify(url:, kits:, message:)
|
9
|
-
attachement = Attachement.new(url)
|
10
|
-
kits = kits.select(&:filesize)
|
11
|
-
|
12
|
-
if notifiable? && kits.any?
|
13
|
-
notifier = Slack::Notifier.new(Miteru.configuration.slack_webhook_url, channel: Miteru.configuration.slack_channel)
|
14
|
-
notifier.post(text: message.capitalize, attachments: attachement.to_a)
|
15
|
-
end
|
16
|
-
|
17
|
-
message = message.colorize(:light_red) if kits.any?
|
18
|
-
puts "#{url}: #{message}"
|
19
|
-
end
|
20
|
-
|
21
|
-
def notifiable?
|
22
|
-
Miteru.configuration.slack_webhook_url? && Miteru.configuration.post_to_slack?
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|