shamebot 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 +7 -0
- data/.gitignore +13 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +58 -0
- data/Rakefile +33 -0
- data/Readme.md +23 -0
- data/VERSION +1 -0
- data/bin/shamebot +186 -0
- data/lib/shamebot.rb +1 -0
- data/lib/shamebot/metadata.rb +26 -0
- data/shamebot.gemspec +24 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4b2ee07b476080c883e634f018cb592c6711bdd2
|
4
|
+
data.tar.gz: 5ada437031bfef93ae685e07b493881d320516ca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c0197735f7035b1ba8b4e74d92da55274ac6a3044115aa0115d27f5312181720fb4a6082c576dc47c44f379f0faee22861d501d04d1239bf02eb735b86b1bc76
|
7
|
+
data.tar.gz: 4794f144c0b6b8dc9169ebee977d0f4d39de63e7b3aad568e85ead6f57a1e3f84d2ee29eaf6c52b7ff66f8063465587558807ae6a58b161a8995bdcc9a66eac3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
shamebot (0.0.0)
|
5
|
+
gitlab (~> 3.1.0)
|
6
|
+
hipchat (~> 1)
|
7
|
+
sinatra (~> 1)
|
8
|
+
sys-proctable (~> 0)
|
9
|
+
trollop (~> 2)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
coderay (1.1.0)
|
15
|
+
gitlab (3.1.0)
|
16
|
+
httparty
|
17
|
+
terminal-table
|
18
|
+
hipchat (1.4.0)
|
19
|
+
httparty
|
20
|
+
httparty (0.13.3)
|
21
|
+
json (~> 1.8)
|
22
|
+
multi_xml (>= 0.5.2)
|
23
|
+
json (1.8.1)
|
24
|
+
method_source (0.8.2)
|
25
|
+
minitest (5.5.0)
|
26
|
+
multi_xml (0.5.5)
|
27
|
+
pry (0.10.1)
|
28
|
+
coderay (~> 1.1.0)
|
29
|
+
method_source (~> 0.8.1)
|
30
|
+
slop (~> 3.4)
|
31
|
+
rack (1.6.0)
|
32
|
+
rack-protection (1.5.3)
|
33
|
+
rack
|
34
|
+
rake (10.4.2)
|
35
|
+
rubygems-tasks (0.2.4)
|
36
|
+
sinatra (1.4.5)
|
37
|
+
rack (~> 1.4)
|
38
|
+
rack-protection (~> 1.4)
|
39
|
+
tilt (~> 1.3, >= 1.3.4)
|
40
|
+
slop (3.6.0)
|
41
|
+
sys-proctable (0.9.4)
|
42
|
+
terminal-table (1.4.5)
|
43
|
+
tilt (1.4.1)
|
44
|
+
trollop (2.0)
|
45
|
+
version (1.0.0)
|
46
|
+
yard (0.8.7.6)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
minitest
|
53
|
+
pry
|
54
|
+
rake
|
55
|
+
rubygems-tasks
|
56
|
+
shamebot!
|
57
|
+
version
|
58
|
+
yard
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
Rake::TestTask.new(:test) do |test|
|
8
|
+
test.libs << 'lib' << 'test'
|
9
|
+
test.test_files = FileList['test/test*.rb']
|
10
|
+
test.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :test
|
14
|
+
|
15
|
+
|
16
|
+
require 'yard'
|
17
|
+
YARD::Rake::YardocTask.new do |t|
|
18
|
+
t.files = %w[ --readme Readme.md lib/**/*.rb - VERSION ]
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
require 'rubygems/tasks'
|
23
|
+
Gem::Tasks.new({
|
24
|
+
push: true,
|
25
|
+
sign: {}
|
26
|
+
}) do |tasks|
|
27
|
+
tasks.console.command = 'pry'
|
28
|
+
end
|
29
|
+
Gem::Tasks::Sign::Checksum.new sha2: true
|
30
|
+
|
31
|
+
|
32
|
+
require 'rake/version_task'
|
33
|
+
Rake::VersionTask.new
|
data/Readme.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Shamebot
|
2
|
+
|
3
|
+
Shame users who commit with bad messages.
|
4
|
+
|
5
|
+
$ cat config.json
|
6
|
+
{
|
7
|
+
"nick": "Shame (bot)",
|
8
|
+
"room": "YOUR_ROOM",
|
9
|
+
"gitlab": {
|
10
|
+
"endpoint": "GITLAB_V3_API_ENDPOINT",
|
11
|
+
"private_token": "GITLAB_PRIVATE_TOKEN",
|
12
|
+
"user_agent": "shamebot"
|
13
|
+
},
|
14
|
+
"hipchat": {
|
15
|
+
"auth_token": "HIPCHAT_V1_API_TOKEN",
|
16
|
+
"auth_token_v2": "HIPCHAT_V2_API_TOKEN"
|
17
|
+
},
|
18
|
+
"jira": {
|
19
|
+
"user": "JIRA_USERNAME",
|
20
|
+
"pass": "JIRA_PASSWORD"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
$ shamebot config.json
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/shamebot
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'json'
|
3
|
+
require 'date'
|
4
|
+
require 'time'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
require 'trollop'
|
8
|
+
|
9
|
+
Opts = Trollop::options do
|
10
|
+
version Shamebot::VERSION
|
11
|
+
banner Shamebot::ART + "\n\n" + <<-EOS.gsub(/^ /, '')
|
12
|
+
#{Shamebot::SUMMARY}
|
13
|
+
|
14
|
+
Usage: shamebot [<options>]
|
15
|
+
|
16
|
+
Options:
|
17
|
+
EOS
|
18
|
+
opt :config, 'Path to configuration', type: :string, required: true, short: 'c'
|
19
|
+
opt :debug, 'Enable debugging', default: false, short: 'd'
|
20
|
+
opt :port, 'Port to listen on', default: 4567, short: 'p'
|
21
|
+
opt :env, 'Sinatra environment', default: 'production', short: 'e'
|
22
|
+
opt :bind, 'Interface to listen on', default: '0.0.0.0', short: 'b'
|
23
|
+
end
|
24
|
+
|
25
|
+
DEBUG = Opts[:debug]
|
26
|
+
CONFIG = JSON.parse File.read(Opts[:config])
|
27
|
+
|
28
|
+
unless DEBUG
|
29
|
+
at_exit do
|
30
|
+
puts "Writing back to '#{Opts[:config]}'"
|
31
|
+
File.open(Opts[:config], 'w') do |f|
|
32
|
+
f.puts JSON.pretty_generate(CONFIG)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
# Part 1. Setup configuration from JSON file
|
41
|
+
JIRA_TAG = /\b([a-z]{2,4})\-(\d+)\b/i
|
42
|
+
MERGE_COMMIT = /\b(merge|merging)\b/i
|
43
|
+
|
44
|
+
def die message
|
45
|
+
$stderr.puts message
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
49
|
+
unless CONFIG.has_key?('hipchat') && CONFIG['hipchat'].has_key?('auth_token')
|
50
|
+
die "Error: 'hipchat:auth_token' required in configuration"
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
GITLAB = {
|
55
|
+
'endpoint' => (CONFIG['gitlab']['endpoint'] rescue die("Error: 'gitlab:endpoint' required in configuration")),
|
56
|
+
'private_token' => (CONFIG['gitlab']['private_token'] rescue die("Error: 'gitlab:private_token' required in configuration")),
|
57
|
+
'user_agent' => (CONFIG['gitlab']['user_agent'] rescue die("Error: 'gitlab:user_agent' required in configuration"))
|
58
|
+
}
|
59
|
+
BOTNAME = CONFIG.has_key?('nick') ? CONFIG['nick'] : 'shamebot'
|
60
|
+
ROOM = CONFIG.has_key?('room') ? CONFIG['room'] : 'test'
|
61
|
+
|
62
|
+
TEMPLATES = CONFIG.has_key?('templates') ? CONFIG['templates'] : [
|
63
|
+
'%{name}, shame on you for not including a valid JIRA tag in your commit message! %{urls}'
|
64
|
+
]
|
65
|
+
|
66
|
+
def random_template
|
67
|
+
TEMPLATES[rand(0...TEMPLATES.length)]
|
68
|
+
end
|
69
|
+
|
70
|
+
CONFIG['shamings'] = CONFIG.has_key?('shamings') ? CONFIG['shamings'] : {}
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
# Part 2. Get connected to Hipchat and Gitlab
|
76
|
+
require 'hipchat'
|
77
|
+
require 'gitlab'
|
78
|
+
|
79
|
+
HIPCHAT = HipChat::Client.new CONFIG['hipchat']['auth_token']
|
80
|
+
|
81
|
+
Gitlab.configure do |config|
|
82
|
+
config.endpoint = GITLAB['endpoint']
|
83
|
+
config.private_token = GITLAB['private_token']
|
84
|
+
config.user_agent = GITLAB['user_agent']
|
85
|
+
end
|
86
|
+
|
87
|
+
def shame_user msg, args, room, hipchat, botname
|
88
|
+
hipchat[room].send(botname, msg % args, {
|
89
|
+
:notify => true,
|
90
|
+
:color => 'red',
|
91
|
+
:message_format => 'text'
|
92
|
+
})
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
# Part 3. Take requests from Gitlab WebHooks
|
99
|
+
require 'sinatra'
|
100
|
+
|
101
|
+
set :port, Opts[:port]
|
102
|
+
set :environment, Opts[:env]
|
103
|
+
set :bind, Opts[:bind]
|
104
|
+
set :raise_errors, true
|
105
|
+
set :dump_errors, true
|
106
|
+
set :show_exceptions, true
|
107
|
+
set :logging, ::Logger::DEBUG if DEBUG
|
108
|
+
|
109
|
+
get '/' do
|
110
|
+
content_type :text
|
111
|
+
"shamebot #{Shamebot::VERSION}"
|
112
|
+
end
|
113
|
+
|
114
|
+
post '/' do
|
115
|
+
now = Time.now
|
116
|
+
last_half_hour = now - 30 * 60
|
117
|
+
request.body.rewind
|
118
|
+
data = JSON.parse request.body.read
|
119
|
+
|
120
|
+
# Bad commits don't contain JIRA tags...
|
121
|
+
# And they aren't merge commits
|
122
|
+
commits = data['commits'].delete_if { |c| c['message'] =~ MERGE_COMMIT }
|
123
|
+
good_commits, bad_commits = commits.partition { |c| c['message'] =~ JIRA_TAG }
|
124
|
+
|
125
|
+
# Good commits might contain bogus JIRA tags
|
126
|
+
bad_commits += good_commits.keep_if do |c|
|
127
|
+
good_tag = false
|
128
|
+
c['message'].scan(JIRA_TAG).each do |(project, number)|
|
129
|
+
tag = '%s-%d' % [project.upcase, number.to_i]
|
130
|
+
tag_page = `curl --silent http://jira.bluejeansnet.com/browse/#{tag}` rescue ''
|
131
|
+
next if tag_page =~ /The issue you are trying to view does not exist/i
|
132
|
+
good_tag = true
|
133
|
+
break
|
134
|
+
end
|
135
|
+
!good_tag
|
136
|
+
end
|
137
|
+
|
138
|
+
# Grab user info from Gitlab
|
139
|
+
begin
|
140
|
+
user = Gitlab.user(data['user_id'].to_i)
|
141
|
+
rescue
|
142
|
+
$stderr.puts "Warning: Gitlab user for commit author not found"
|
143
|
+
return
|
144
|
+
end
|
145
|
+
|
146
|
+
# For whatever reason we get a lot of duplicate POSTs, so we need to
|
147
|
+
# keep track of which commits have already triggered shamings so we
|
148
|
+
# don't end up repeatedly shaming users for the same mistake. We also
|
149
|
+
# make sure the commits aren't too old.
|
150
|
+
new_bad_commits = []
|
151
|
+
bad_commits.each do |commit|
|
152
|
+
unless DEBUG
|
153
|
+
next if CONFIG['shamings'].has_key? commit['id']
|
154
|
+
commit_time = DateTime.strptime(commit['timestamp']).to_time
|
155
|
+
next unless commit_time >= last_half_hour
|
156
|
+
end
|
157
|
+
new_bad_commits << commit
|
158
|
+
CONFIG['shamings'][commit['id']] = true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Shame the user with a random template
|
162
|
+
commit_ids = new_bad_commits.map { |c| c['id'] }
|
163
|
+
commit_urls = new_bad_commits.map { |c| c['url'] }
|
164
|
+
puts "%s: %s" % [ user.username, commit_ids.inspect ]
|
165
|
+
|
166
|
+
hipchat_users = []
|
167
|
+
0.upto(5) do |i|
|
168
|
+
hipchat_users += HTTParty.get("https://api.hipchat.com/v2/user", :query => { 'start-index' => 100 * i, :start_index => 100 * i, 'max-results' => 100, :max_results => 1000, :auth_token => CONFIG['hipchat']['auth_token_v2'] })['items']
|
169
|
+
end
|
170
|
+
|
171
|
+
hipchat_user = hipchat_users.select { |u| u['name'] =~ /#{user.name}/i }
|
172
|
+
$stderr.puts hipchat_user
|
173
|
+
|
174
|
+
if hipchat_user.empty?
|
175
|
+
hipchat_user = user.name
|
176
|
+
else
|
177
|
+
hipchat_user = '@' + hipchat_user.first['mention_name']
|
178
|
+
end
|
179
|
+
|
180
|
+
shame_user(random_template, {
|
181
|
+
:name => hipchat_user,
|
182
|
+
:nick => user.username,
|
183
|
+
:urls => commit_urls.join(' ')
|
184
|
+
}, ROOM, HIPCHAT, BOTNAME) unless commit_ids.empty? || DEBUG
|
185
|
+
return commit_ids.inspect
|
186
|
+
end
|
data/lib/shamebot.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'shamebot/metadata'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shamebot
|
2
|
+
|
3
|
+
# We use a VERSION file to tie into our build pipeline
|
4
|
+
VERSION = File.read(File.join(File.dirname(__FILE__), '..', '..', 'VERSION')).strip
|
5
|
+
|
6
|
+
# We don't really do all that much, be humble
|
7
|
+
SUMMARY = 'Shame users who commit with bad messages'
|
8
|
+
|
9
|
+
# Your benevolent dictator for life
|
10
|
+
AUTHOR = 'Sean Clemmer'
|
11
|
+
|
12
|
+
# Turn here to strangle your dictator
|
13
|
+
EMAIL = 'sclemmer@bluejeans.com'
|
14
|
+
|
15
|
+
# Every project deserves its own ASCII art
|
16
|
+
ART = <<-'EOART' % VERSION
|
17
|
+
|
18
|
+
_____ __ __ ____ ___ ___ ___ ____ ___ ______
|
19
|
+
/ ___/| | | / || | | / _]| \ / \ | |
|
20
|
+
( \_ | | || o || _ _ | / [_ | o )| || |
|
21
|
+
\__ || _ || || \_/ || _]| || O ||_| |_|
|
22
|
+
/ \ || | || _ || | || [_ | O || | | |
|
23
|
+
\ || | || | || | || || || | | |
|
24
|
+
\___||__|__||__|__||___|___||_____||_____| \___/ |__| v%s
|
25
|
+
EOART
|
26
|
+
end
|
data/shamebot.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path(File.join('..', 'lib'), __FILE__)
|
3
|
+
require 'shamebot'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'shamebot'
|
7
|
+
s.version = Shamebot::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.author = Shamebot::AUTHOR
|
10
|
+
s.email = Shamebot::EMAIL
|
11
|
+
s.summary = Shamebot::SUMMARY
|
12
|
+
s.description = Shamebot::SUMMARY + '.'
|
13
|
+
|
14
|
+
s.add_runtime_dependency 'sinatra', '~> 1'
|
15
|
+
s.add_runtime_dependency 'hipchat', '~> 1'
|
16
|
+
s.add_runtime_dependency 'gitlab', '~> 3.1.0'
|
17
|
+
s.add_runtime_dependency 'trollop', '~> 2'
|
18
|
+
s.add_runtime_dependency 'sys-proctable', '~> 0'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File::basename(f) }
|
23
|
+
s.require_paths = %w[ lib ]
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shamebot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sean Clemmer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hipchat
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gitlab
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: trollop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sys-proctable
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Shame users who commit with bad messages.
|
84
|
+
email: sclemmer@bluejeans.com
|
85
|
+
executables:
|
86
|
+
- shamebot
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- Gemfile.lock
|
93
|
+
- Rakefile
|
94
|
+
- Readme.md
|
95
|
+
- VERSION
|
96
|
+
- bin/shamebot
|
97
|
+
- lib/shamebot.rb
|
98
|
+
- lib/shamebot/metadata.rb
|
99
|
+
- shamebot.gemspec
|
100
|
+
homepage:
|
101
|
+
licenses: []
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.2.2
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: Shame users who commit with bad messages
|
123
|
+
test_files: []
|