codebot 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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +32 -0
  3. data/.github/ISSUE_TEMPLATE/formatter_issue.md +20 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  5. data/.gitignore +10 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +11 -0
  8. data/.travis.yml +26 -0
  9. data/CODE_OF_CONDUCT.md +46 -0
  10. data/CONTRIBUTING.md +15 -0
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +75 -0
  13. data/LICENSE +21 -0
  14. data/README.md +230 -0
  15. data/Rakefile +29 -0
  16. data/bin/console +8 -0
  17. data/codebot.gemspec +49 -0
  18. data/exe/codebot +7 -0
  19. data/lib/codebot.rb +8 -0
  20. data/lib/codebot/channel.rb +134 -0
  21. data/lib/codebot/command_error.rb +17 -0
  22. data/lib/codebot/config.rb +125 -0
  23. data/lib/codebot/configuration_error.rb +17 -0
  24. data/lib/codebot/core.rb +76 -0
  25. data/lib/codebot/cryptography.rb +38 -0
  26. data/lib/codebot/event.rb +62 -0
  27. data/lib/codebot/ext/cinch/ssl_extensions.rb +37 -0
  28. data/lib/codebot/formatter.rb +242 -0
  29. data/lib/codebot/formatters.rb +109 -0
  30. data/lib/codebot/formatters/.rubocop.yml +2 -0
  31. data/lib/codebot/formatters/commit_comment.rb +43 -0
  32. data/lib/codebot/formatters/fork.rb +40 -0
  33. data/lib/codebot/formatters/gitlab_issue_hook.rb +56 -0
  34. data/lib/codebot/formatters/gitlab_job_hook.rb +77 -0
  35. data/lib/codebot/formatters/gitlab_merge_request_hook.rb +57 -0
  36. data/lib/codebot/formatters/gitlab_note_hook.rb +119 -0
  37. data/lib/codebot/formatters/gitlab_pipeline_hook.rb +51 -0
  38. data/lib/codebot/formatters/gitlab_push_hook.rb +83 -0
  39. data/lib/codebot/formatters/gitlab_wiki_page_hook.rb +56 -0
  40. data/lib/codebot/formatters/gollum.rb +67 -0
  41. data/lib/codebot/formatters/issue_comment.rb +41 -0
  42. data/lib/codebot/formatters/issues.rb +41 -0
  43. data/lib/codebot/formatters/ping.rb +79 -0
  44. data/lib/codebot/formatters/public.rb +30 -0
  45. data/lib/codebot/formatters/pull_request.rb +71 -0
  46. data/lib/codebot/formatters/pull_request_review_comment.rb +49 -0
  47. data/lib/codebot/formatters/push.rb +172 -0
  48. data/lib/codebot/formatters/watch.rb +38 -0
  49. data/lib/codebot/integration.rb +195 -0
  50. data/lib/codebot/integration_manager.rb +225 -0
  51. data/lib/codebot/ipc_client.rb +83 -0
  52. data/lib/codebot/ipc_server.rb +79 -0
  53. data/lib/codebot/irc_client.rb +102 -0
  54. data/lib/codebot/irc_connection.rb +156 -0
  55. data/lib/codebot/message.rb +37 -0
  56. data/lib/codebot/metadata.rb +15 -0
  57. data/lib/codebot/network.rb +240 -0
  58. data/lib/codebot/network_manager.rb +181 -0
  59. data/lib/codebot/options.rb +49 -0
  60. data/lib/codebot/options/base.rb +55 -0
  61. data/lib/codebot/options/core.rb +126 -0
  62. data/lib/codebot/options/integration.rb +101 -0
  63. data/lib/codebot/options/network.rb +109 -0
  64. data/lib/codebot/payload.rb +32 -0
  65. data/lib/codebot/request.rb +51 -0
  66. data/lib/codebot/sanitizers.rb +130 -0
  67. data/lib/codebot/serializable.rb +101 -0
  68. data/lib/codebot/shortener.rb +43 -0
  69. data/lib/codebot/thread_controller.rb +70 -0
  70. data/lib/codebot/user_error.rb +13 -0
  71. data/lib/codebot/validation_error.rb +17 -0
  72. data/lib/codebot/web_listener.rb +107 -0
  73. data/lib/codebot/web_server.rb +58 -0
  74. data/webhook.png +0 -0
  75. metadata +249 -0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codebot
4
+ # This class provides a consistent interface for subclasses that manage a
5
+ # thread.
6
+ class ThreadController
7
+ # Creates a new thread controller.
8
+ def initialize
9
+ @thread = nil
10
+ end
11
+
12
+ # Suspends execution of the calling thread until the managed thread exits.
13
+ #
14
+ # @return [Thread, nil] the dead thead, or +nil+ if no thread was active
15
+ def join
16
+ @thread.join if running?
17
+ end
18
+
19
+ # Checks whether the managed thread is currently running.
20
+ #
21
+ # @return [Boolean] +true+ if the managed thread is alive,
22
+ # +false+ otherwise
23
+ def running?
24
+ !@thread.nil? && @thread.alive?
25
+ end
26
+
27
+ # Starts a new managed thread if no thread is currently running.
28
+ # The thread invokes the +run+ method of the class that manages it.
29
+ #
30
+ # @param arg the argument to pass to the +#run+ method
31
+ # @return [Thread, nil] the newly created thread, or +nil+ if
32
+ # there was already a running thread
33
+ def start(arg = nil)
34
+ return if running?
35
+
36
+ @thread = Thread.new do
37
+ Thread.current.abort_on_exception = true
38
+ run(arg)
39
+ end
40
+ end
41
+
42
+ # Starts a new managed thread. The thread invokes the +run+ method of the
43
+ # class that manages it.
44
+ #
45
+ # @param arg the argument to pass to the +run+ method
46
+ # @return [Thread] the newly created thread
47
+ # @raise [RuntimeError] if there was already a running thread
48
+ def start!(arg = nil)
49
+ raise "#{self.class.name} is already running" unless start(arg)
50
+ end
51
+
52
+ # Stops the managed thread if a thread is currently running.
53
+ #
54
+ # @return [Thread, nil] the stopped thread, or +nil+ if
55
+ # no thread was running
56
+ def stop
57
+ return unless running?
58
+
59
+ @thread.exit
60
+ end
61
+
62
+ # Stops the managed thread.
63
+ #
64
+ # @return [Thread] the stopped thread
65
+ # @raise [RuntimeError] if there was no running thread
66
+ def stop!
67
+ raise "#{self.class.name} is already stopped" unless stop
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codebot
4
+ # This class serves as a parent class for errors caused by user actions.
5
+ class UserError < RuntimeError
6
+ # Constructs a new user error.
7
+ #
8
+ # @param message [String] the error message
9
+ def initialize(message)
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/user_error'
4
+
5
+ module Codebot
6
+ # This exception stores information about an error that occurred due to a
7
+ # failed validation, for example when a network with an invalid name is
8
+ # created.
9
+ class ValidationError < UserError
10
+ # Constructs a new validation error.
11
+ #
12
+ # @param message [String] the error message
13
+ def initialize(message)
14
+ super
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/cryptography'
4
+ require 'codebot/event'
5
+ require 'codebot/integration_manager'
6
+ require 'codebot/request'
7
+
8
+ module Codebot
9
+ # This module provides methods for processing incoming webhooks and
10
+ # dispatching them to the IRC client.
11
+ module WebListener
12
+ # Handles a POST request.
13
+ #
14
+ # @param core [Core] the bot to dispatch requests to
15
+ # @param request [Sinatra::Request] the request to handle
16
+ # @param params [Hash] the request parameters
17
+ def handle_post(core, request, params)
18
+ payload = params['payload'] || request.body.read
19
+ dispatch(core, request, *params['splat'], payload)
20
+ rescue JSON::ParserError
21
+ [400, 'Invalid JSON payload']
22
+ end
23
+
24
+ # Finds the integration associated with an endpoint.
25
+ #
26
+ # @param config [Config] the configuration containing the integrations
27
+ # @param endpoint [String] the endpoint
28
+ def integration_for(config, endpoint)
29
+ IntegrationManager.new(config).find_integration_by_endpoint(endpoint)
30
+ end
31
+
32
+ # Dispatches a received payload to the IRC client.
33
+ #
34
+ # @param core [Core] the bot to dispatch this request to
35
+ # @param request [Sinatra::Request] the request received by the web server
36
+ # @param endpoint [String] the endpoint at which the request was received
37
+ # @param payload [String] the payload that was sent to the endpoint
38
+ # @return [Array<Integer, String>] HTTP status code and response
39
+ def dispatch(core, request, endpoint, payload)
40
+ intg = integration_for(core.config, endpoint)
41
+ return [404, 'Endpoint not registered'] if intg.nil?
42
+ return [403, 'Invalid signature'] unless valid?(request, intg, payload)
43
+
44
+ req = create_request(intg, request, payload)
45
+ return req if req.is_a? Array
46
+
47
+ core.irc_client.dispatch(req)
48
+ [202, 'Accepted']
49
+ end
50
+
51
+ def create_gitlab_event(request)
52
+ event = request.env['HTTP_X_GITLAB_EVENT']
53
+ return [400, 'Missing event header'] if event.nil? || event.empty?
54
+
55
+ Event.symbolize("Gitlab #{event}".tr(' ', '_'))
56
+ end
57
+
58
+ def create_github_event(request)
59
+ event = request.env['HTTP_X_GITHUB_EVENT']
60
+ return [400, 'Missing event header'] if event.nil? || event.empty?
61
+
62
+ Event.symbolize(event)
63
+ end
64
+
65
+ # Creates a new request for the webhook.
66
+ #
67
+ # @param integration [Integration] the integration for which the request
68
+ # was made
69
+ # @param request [Sinatra::Request] the request received by the web server
70
+ # @param payload [String] the payload that was sent to the endpoint
71
+ # @return [Request, Array<Integer, String>] the created request, or an
72
+ # array containing HTTP status
73
+ # code and response if the
74
+ # request was invalid
75
+ def create_request(integration, request, payload)
76
+ event = if integration.gitlab
77
+ create_gitlab_event(request)
78
+ else
79
+ create_github_event(request)
80
+ end
81
+ return event if event.is_a? Array
82
+
83
+ return [501, 'Event not yet supported'] if event.nil?
84
+
85
+ Request.new(integration, event, payload)
86
+ end
87
+
88
+ # Verifies a webhook signature.
89
+ #
90
+ # @param request [Sinatra::Request] the request received by the web server
91
+ # @param integration [Integration] the integration for which the request
92
+ # was made
93
+ # @param payload [String] the payload that was sent to the endpoint
94
+ # @return [Boolean] whether the signature is valid
95
+ def valid?(request, integration, payload)
96
+ return true unless integration.verify_payloads?
97
+
98
+ secret = integration.secret
99
+ if integration.gitlab
100
+ secret == request.env['HTTP_X_GITLAB_TOKEN']
101
+ else
102
+ request_signature = request.env['HTTP_X_HUB_SIGNATURE']
103
+ Cryptography.valid_signature?(payload, secret, request_signature)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'codebot/thread_controller'
4
+ require 'codebot/web_listener'
5
+ require 'codebot/user_error'
6
+
7
+ require 'sinatra'
8
+
9
+ module Codebot
10
+ # This class manages a {WebListener} that runs in a separate thread.
11
+ class WebServer < ThreadController
12
+ extend Sanitizers
13
+
14
+ # Creates a new web server.
15
+ #
16
+ # @param core [Core] the bot this server belongs to
17
+ def initialize(core)
18
+ @core = core
19
+ end
20
+
21
+ # Starts this web server.
22
+ def run(*)
23
+ create_server.run!
24
+ end
25
+
26
+ # Creates a +Proc+ for configuring this web server.
27
+ #
28
+ # @return [Proc] the proc
29
+ def self.configuration
30
+ server = self
31
+ proc do
32
+ disable :traps
33
+ set :bind, ENV['CODEBOT_BIND'] unless ENV['CODEBOT_BIND'].to_s.empty?
34
+ port = ENV['CODEBOT_PORT']
35
+ if port.is_a?(String) && !port.empty?
36
+ set :port, (server.valid! port, server.valid_port(port), nil,
37
+ invalid_error: 'invalid port %s')
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Creates a new Sinatra server for handling incoming requests.
45
+ #
46
+ # @return [Class] the created server
47
+ def create_server
48
+ core = @core
49
+ Sinatra.new do
50
+ include WebListener
51
+
52
+ configure { instance_eval(&WebServer.configuration) }
53
+ post('/*') { handle_post(core, request, params) }
54
+ error(Sinatra::NotFound) { [405, 'Method not allowed'] }
55
+ end
56
+ end
57
+ end
58
+ end
Binary file
metadata ADDED
@@ -0,0 +1,249 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codebot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Janik Rabe
8
+ - Ola Bini
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2019-03-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '12.3'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '12.3'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.8'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.8'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubocop
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.65.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.65.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: cinch
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.3'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: cinch-identify
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.7'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.7'
98
+ - !ruby/object:Gem::Dependency
99
+ name: irb
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '1.0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '1.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: sinatra
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '2.0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '2.0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: thor
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: 0.20.0
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 0.20.0
140
+ description: Codebot is an IRC bot that receives GitHub webhooks and forwards them
141
+ to IRC channels. It is designed to send messages in a format similar to that of
142
+ the official GitHub IRC Service. Codebot is able to stay connected after sending
143
+ messages. This eliminates the delays and visual clutter caused by reconnecting each
144
+ time a new message has to be delivered.
145
+ email:
146
+ - codebot@olabini.se
147
+ executables:
148
+ - codebot
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - ".github/ISSUE_TEMPLATE.md"
153
+ - ".github/ISSUE_TEMPLATE/formatter_issue.md"
154
+ - ".github/PULL_REQUEST_TEMPLATE.md"
155
+ - ".gitignore"
156
+ - ".rspec"
157
+ - ".rubocop.yml"
158
+ - ".travis.yml"
159
+ - CODE_OF_CONDUCT.md
160
+ - CONTRIBUTING.md
161
+ - Gemfile
162
+ - Gemfile.lock
163
+ - LICENSE
164
+ - README.md
165
+ - Rakefile
166
+ - bin/console
167
+ - codebot.gemspec
168
+ - exe/codebot
169
+ - lib/codebot.rb
170
+ - lib/codebot/channel.rb
171
+ - lib/codebot/command_error.rb
172
+ - lib/codebot/config.rb
173
+ - lib/codebot/configuration_error.rb
174
+ - lib/codebot/core.rb
175
+ - lib/codebot/cryptography.rb
176
+ - lib/codebot/event.rb
177
+ - lib/codebot/ext/cinch/ssl_extensions.rb
178
+ - lib/codebot/formatter.rb
179
+ - lib/codebot/formatters.rb
180
+ - lib/codebot/formatters/.rubocop.yml
181
+ - lib/codebot/formatters/commit_comment.rb
182
+ - lib/codebot/formatters/fork.rb
183
+ - lib/codebot/formatters/gitlab_issue_hook.rb
184
+ - lib/codebot/formatters/gitlab_job_hook.rb
185
+ - lib/codebot/formatters/gitlab_merge_request_hook.rb
186
+ - lib/codebot/formatters/gitlab_note_hook.rb
187
+ - lib/codebot/formatters/gitlab_pipeline_hook.rb
188
+ - lib/codebot/formatters/gitlab_push_hook.rb
189
+ - lib/codebot/formatters/gitlab_wiki_page_hook.rb
190
+ - lib/codebot/formatters/gollum.rb
191
+ - lib/codebot/formatters/issue_comment.rb
192
+ - lib/codebot/formatters/issues.rb
193
+ - lib/codebot/formatters/ping.rb
194
+ - lib/codebot/formatters/public.rb
195
+ - lib/codebot/formatters/pull_request.rb
196
+ - lib/codebot/formatters/pull_request_review_comment.rb
197
+ - lib/codebot/formatters/push.rb
198
+ - lib/codebot/formatters/watch.rb
199
+ - lib/codebot/integration.rb
200
+ - lib/codebot/integration_manager.rb
201
+ - lib/codebot/ipc_client.rb
202
+ - lib/codebot/ipc_server.rb
203
+ - lib/codebot/irc_client.rb
204
+ - lib/codebot/irc_connection.rb
205
+ - lib/codebot/message.rb
206
+ - lib/codebot/metadata.rb
207
+ - lib/codebot/network.rb
208
+ - lib/codebot/network_manager.rb
209
+ - lib/codebot/options.rb
210
+ - lib/codebot/options/base.rb
211
+ - lib/codebot/options/core.rb
212
+ - lib/codebot/options/integration.rb
213
+ - lib/codebot/options/network.rb
214
+ - lib/codebot/payload.rb
215
+ - lib/codebot/request.rb
216
+ - lib/codebot/sanitizers.rb
217
+ - lib/codebot/serializable.rb
218
+ - lib/codebot/shortener.rb
219
+ - lib/codebot/thread_controller.rb
220
+ - lib/codebot/user_error.rb
221
+ - lib/codebot/validation_error.rb
222
+ - lib/codebot/web_listener.rb
223
+ - lib/codebot/web_server.rb
224
+ - webhook.png
225
+ homepage: https://github.com/olabini/codebot
226
+ licenses:
227
+ - MIT
228
+ metadata: {}
229
+ post_install_message:
230
+ rdoc_options: []
231
+ require_paths:
232
+ - lib
233
+ required_ruby_version: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: 2.2.0
238
+ required_rubygems_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0'
243
+ requirements: []
244
+ rubyforge_project:
245
+ rubygems_version: 2.7.8
246
+ signing_key:
247
+ specification_version: 4
248
+ summary: Forward GitHub webhooks to IRC channels
249
+ test_files: []