rodbot 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e6caa3fc8d7a38fb433cc01dedd9e521f2a7c2cd79ebaa4f78396c39f1a5cfc
4
- data.tar.gz: 8d7f438f66d0d8494c7629ea3f1f363f730ce34a0c5178aaca0ee580e6a7a512
3
+ metadata.gz: eac36ded2dc61a62f3a5e9c236987ff66f0aadf65f216b96932375af2cec3241
4
+ data.tar.gz: '099b12406aa01b8ec165efb25a3faef9fd1efeed3a8c9a7c0c0bb05febadf2e8'
5
5
  SHA512:
6
- metadata.gz: 7ddbdf27810f750712ff0a7990ec6da94d084a289c9364d380ae33d1107dd6693ce60b099ea0826aba551a61fc3033b2ea6c6808ef67aa367eac29802cae5e30
7
- data.tar.gz: 5160f0113df885f7ec1597b18eaee70420d82eb0cc11b66d54ee7bb5642f7e95224b1a33c97ced6d75bd89ad6b061627e4a518811a828bbc10c6d66cfbcb4194
6
+ metadata.gz: 06d6953cd85005effd3a54da59564e52a27d27cb42f61ccc2aeb0dfa32627a38d96f9e07fd9ba7d533fdf4210180d228582e5b39672a4124eba9831abb443016
7
+ data.tar.gz: cec0a4d433b447c34fa91ff7a7fd9df416648851ef5b8156fb3d01579ac2d1abb26e7738ee000b1bdb322575dc99c5f8ac635843e1ecfc07d439b3b3403fddb9
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -2,11 +2,17 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 0.3.4
6
+
7
+ #### Additions
8
+ * Support to post to secondary rooms with `Rodbot.say`
9
+ * `Rodbot::Message` container class for messages with meta data
10
+
5
11
  ## 0.3.3
6
12
 
7
13
  #### Additions
8
14
  * Support placeholders when using `Rodbot.say` and add `[[EVERYBODY]]`
9
- placeholder to mention all hands in a room or channel.
15
+ placeholder to mention all hands in a room or channel
10
16
 
11
17
  ## 0.3.2
12
18
 
data/README.md CHANGED
@@ -178,7 +178,7 @@ The **relay service** act as glue between the **app service** and external commu
178
178
 
179
179
  Each relay service does three things:
180
180
 
181
- * **Proactive:** It creates and listens to a local TCP socket. Plain text or Markdown sent to this socket is forwarded as a message to the corresponding communication network. This text may have multiple lines, use the EOT character (`\x04` alias Ctrl-D) to mark the end.
181
+ * **Proactive:** It creates and listens to a local TCP socket which accepts and forwards messages. See below for more on this.
182
182
  * **Reactive:** It reads messages, detects commands usually beginning with a `!`, forwards them to the **app service** and writes the HTTP response back as a message to the communication network.
183
183
  * **Test:** It detects the `!ping` command and replies with "pong" *without* hitting the **app service**.
184
184
 
@@ -190,6 +190,14 @@ rodbot simulator
190
190
 
191
191
  Enter the command `!pay EUR 123` and you see the request `GET /pay?argument=EUR+123` hitting the **app service**.
192
192
 
193
+ #### TCP Socket
194
+
195
+ The TCP socket is primarily used by other Rodbot services to forward messages to the corresponding external communication network. However, you can use these sockets for non-Rodbot processes as well e.g. to issue notifications when events happen on the host running Rodbot.
196
+
197
+ Simply connect to a socket and submit the message as plain text or Markdown in UTF-8. Multiple lines are allowed, to finish and post the message, append the EOT character (`\x04` alias Ctrl-D).
198
+
199
+ Such simple messages are always posted to the primary room (aka: channel, group etc) of the communication network. For more complex scenarios, please take a look at [message objects](https://www.rubydoc.info/gems/rodbot/Rodbot/Message) which may contain meta information as well.
200
+
193
201
  ### Schedule Service
194
202
 
195
203
  The **schedule service** is a [Clockwork process](https://github.com/Rykian/clockwork) which triggers Ruby code asynchronously as configured in `config/schedule.rb`.
@@ -0,0 +1 @@
1
+ ab32834eec05a817fafa249e06330e30cb39e08b7234b46b9ae9787c03f6900ab7e6202a7c364f9ae93d5028669561ef31bcc1445f9e8990d6f2f992e60f4032
@@ -0,0 +1,89 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodbot
4
+
5
+ # Generic serializable chat message container
6
+ #
7
+ # The primary purpose of message objects is their ability to contain both
8
+ # the actual message text as well as meta data such as the room in which the
9
+ # message was or will be posted.
10
+ #
11
+ # Furthermore, they can be serialized (+dump+) and then recreated (+new+)
12
+ # from that. You can also create a serialized message string outside of RodBot
13
+ # performing the following steps:
14
+ #
15
+ # 1. Create a JSON hash which contains the keys +class+ (with static value
16
+ # +Rodbot::Message+), +text+ and optionally +room+.
17
+ # 2. Encode it as Base64 without newlines.
18
+ # 3. Prefix the result with {Rodbot::Serializer::PRELUDE}.
19
+ #
20
+ # Example for Shell:
21
+ # string='{"class":"Rodbot::Message",text":"hello, world","room":"general"}'
22
+ # string=$(echo $string | base64)
23
+ # string="data:application/json;base64,$string"
24
+ # echo $string
25
+ class Message
26
+
27
+ # Raw message text
28
+ #
29
+ # @return [String, nil]
30
+ attr_reader :text
31
+
32
+ # Room (aka: channel, group etc depending on the chat service) in which
33
+ # the message was or will be posted
34
+ #
35
+ # @return [String, nil]
36
+ attr_accessor :room
37
+
38
+ # Initialize message from raw message text
39
+ #
40
+ # @param text [String] raw message text
41
+ # @param room [String, nil] room in which the message was or will be posted
42
+ def initialize(text, room: nil)
43
+ @text, @room = text, room
44
+ end
45
+
46
+ # Initialize message from either message object previously serialized with
47
+ # +dump+ or from raw message text
48
+ #
49
+ # @param string [String] string returned by +dump+ or raw message text
50
+ # @param room [String, nil] room in which the message was or will be posted
51
+ # @raise [ArgumentError] if the string is not valid Base64, JSON or does not
52
+ # contain the key +"class":"Rodbot::Message"+
53
+ def self.new(string, room: nil)
54
+ allocate.instance_eval do
55
+ serializer = Rodbot::Serializer.new(string)
56
+ if serializer.deserializable?
57
+ hash = serializer.hash
58
+ fail(ArgumentError, "not a dumped message") unless hash['class'] == self.class.to_s
59
+ initialize(hash['text'], room: room || hash['room'])
60
+ else
61
+ initialize(string.force_encoding('UTF-8'), room: room)
62
+ end
63
+ self
64
+ end
65
+ end
66
+
67
+ # Serialize the message
68
+ #
69
+ # @return [String] serialized and encoded +self+
70
+ def dump
71
+ Rodbot::Serializer.new(to_h).string
72
+ end
73
+
74
+ # Convert message to Hash
75
+ #
76
+ # @return [Hash]
77
+ def to_h
78
+ { class: self.class.to_s, text: text, room: room }
79
+ end
80
+
81
+ # Whether two messages are equal
82
+ #
83
+ # @return [Boolean]
84
+ def ==(other)
85
+ to_h == other.to_h
86
+ end
87
+
88
+ end
89
+ end
@@ -10,7 +10,7 @@ module Rodbot
10
10
  r.post '' do
11
11
  r.halt 200 if request.env['HTTP_X_GITHUB_EVENT'] == 'ping'
12
12
  r.halt 400 unless request.env['HTTP_X_GITHUB_EVENT'] == 'workflow_run'
13
- r.halt 401 unless authorized? request
13
+ r.halt 401 unless authorized?
14
14
  json = JSON.parse(request.body.read)
15
15
  project = json.dig('repository', 'full_name')
16
16
  status = json.dig('workflow_run', 'status')
@@ -22,7 +22,7 @@ module Rodbot
22
22
 
23
23
  private
24
24
 
25
- def authorized?(request)
25
+ def authorized?
26
26
  Rodbot.config(:plugin, :github_webhook, :secret_tokens).to_s.split(':').any? do |secret|
27
27
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, request.body.read)
28
28
  request.body.rewind
@@ -8,7 +8,7 @@ module Rodbot
8
8
  class Routes < ::App
9
9
  route do |r|
10
10
  r.post '' do
11
- r.halt 401 unless authorized? request
11
+ r.halt 401 unless authorized?
12
12
  json = JSON.parse(request.body.read)
13
13
  r.halt 400 unless json['object_kind'] == 'pipeline'
14
14
  project = json.dig('project', 'path_with_namespace')
@@ -20,7 +20,7 @@ module Rodbot
20
20
 
21
21
  private
22
22
 
23
- def authorized?(request)
23
+ def authorized?
24
24
  Rodbot.config(:plugin, :gitlab_webhook, :secret_tokens).to_s.split(':').include?(request.env['HTTP_X_GITLAB_TOKEN'])
25
25
  end
26
26
 
@@ -40,3 +40,15 @@ You might want to use the credentials facilities of Rodbot to encrypt the token.
40
40
  Once Rodbot is restarted, the Matrix relay will automatically accept the invitation and start listening. To check whether the relay works fine, just say +!ping+ in the room, you should receive a "pong" in reply.
41
41
 
42
42
  Any room message beginning with "!" is considered a bot command.
43
+
44
+ To post messages to the primary channel configured with the plugin:
45
+
46
+ ```
47
+ Rodbot.say('Hello, world!')
48
+ ```
49
+
50
+ It's possible to post to other, secondary channels, provided you have previously added the bot app to those secondary channels as [described above](#preparation):
51
+
52
+ ```
53
+ Rodbot.say('Hello, world!', room: '#general:matrix.org')
54
+ ```
@@ -38,7 +38,7 @@ module Rodbot
38
38
  end
39
39
 
40
40
  memoize def client
41
- MatrixSdk::Client.new(homeserver, access_token: access_token, client_cache: :some)
41
+ MatrixSdk::Client.new(homeserver, access_token: access_token, client_cache: :some).tap(&:reload_rooms!)
42
42
  end
43
43
 
44
44
  memoize def room
@@ -57,10 +57,10 @@ module Rodbot
57
57
  server = TCPServer.new(*bind)
58
58
  loop do
59
59
  Thread.start(server.accept) do |remote|
60
- md = remote.gets("\x04").chop
60
+ message = Rodbot::Message.new(remote.gets("\x04").chop)
61
61
  remote.close
62
- md.force_encoding('UTF-8')
63
- room.send_html md.psub(placeholders).md_to_html
62
+ target_room = message.room ? client.find_room(message.room) : room
63
+ target_room.send_html message.text.psub(placeholders).md_to_html
64
64
  end
65
65
  end
66
66
  end
@@ -46,3 +46,17 @@ You might want to use the credentials facilities of Rodbot to encrypt the token.
46
46
  Once Rodbot is restarted, the Slack relay starts listening. To check whether the relay works fine, just say +!ping+ in the channel, you should receive a "pong" in reply.
47
47
 
48
48
  Any room message beginning with "!" is considered a bot command.
49
+
50
+ To post messages to the primary channel configured with the plugin:
51
+
52
+ ```
53
+ Rodbot.say('Hello, world!')
54
+ ```
55
+
56
+ It's possible to post to other, secondary channels, provided you have previously added the bot app to those secondary channels as [described above](#preparation):
57
+
58
+ ```
59
+ Rodbot.say('Hello, world!', room: '#general')
60
+ ```
61
+
62
+ Please note that Rodbot uses the term "room" for what is called a channel on Slack.
@@ -39,12 +39,11 @@ module Rodbot
39
39
  server = TCPServer.new(*bind)
40
40
  loop do
41
41
  Thread.start(server.accept) do |remote|
42
- md = remote.gets("\x04").chop
42
+ message = Rodbot::Message.new(remote.gets("\x04").chop)
43
43
  remote.close
44
- md.force_encoding('UTF-8')
45
44
  client.web_client.chat_postMessage(
46
- channel: channel_id,
47
- text: md_to_slack_text(md.psub(placeholders)),
45
+ channel: message.room || channel_id,
46
+ text: md_to_slack_text(message.text.psub(placeholders)),
48
47
  as_user: true
49
48
  )
50
49
  end
data/lib/rodbot/relay.rb CHANGED
@@ -18,15 +18,16 @@ module Rodbot
18
18
  # "say true" configured in their corresponding config blocks. To further
19
19
  # narrow it to exactly one relay service, use the +on+ argument.
20
20
  #
21
- # @param message [String] message to post
21
+ # @param text [String] message to post
22
22
  # @param on [Symbol, nil] post via this relay service only
23
+ # @param room [String, nil] post to this room (aka: channel, group etc)
23
24
  # @return [Boolean] +false+ if at least one relay refused the connection or
24
25
  # +true+ otherwise
25
- def say(message, on: nil)
26
+ def say(text, on: nil, room: nil)
26
27
  Rodbot.config(:plugin).select do |extension, config|
27
28
  config[:say] == true && (!on || extension == on)
28
29
  end.keys.inject(true) do |success, extension|
29
- write(message, extension) && success
30
+ write(Rodbot::Message.new(text, room: room).dump, extension) && success
30
31
  end
31
32
  end
32
33
 
@@ -0,0 +1,65 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'json'
4
+ require 'base64'
5
+
6
+ module Rodbot
7
+
8
+ # Simple yet safe Hash to String serializer
9
+ #
10
+ # Keep in mind that hash keys will always be deserialized as strings!
11
+ #
12
+ # Example:
13
+ # hash = { 'foo' => 'bar' }
14
+ # string = Serializer.new(hash).string
15
+ # # => "data:application/json;base64,eyJmb28iOiJiYXIifQ=="
16
+ # hash = Serializer.new(string).hash
17
+ # # => { 'foo' => 'bar' }
18
+ class Serializer
19
+
20
+ # Prelude string for serialized hash
21
+ PRELUDE = 'data:application/json;base64,'
22
+
23
+ # @params object [Hash, String] either a Hash (to serialize) or a String
24
+ # (to deserialize)
25
+ def initialize(object)
26
+ case object
27
+ when Hash then @hash = object
28
+ when String then @string = object
29
+ else fail ArgumentError, "must be either Hash or String"
30
+ end
31
+ end
32
+
33
+ # @return [String] Hash serialized to String
34
+ def string
35
+ @string ||= begin
36
+ fail "object is not serializable" unless serializable?
37
+ @hash.to_json.then { PRELUDE + Base64.strict_encode64(_1) }
38
+ end
39
+ end
40
+
41
+ # @return [Hash] String deserialized to Hash
42
+ # @raise [RuntimeError] when deserialization fails
43
+ def hash
44
+ @hash ||= begin
45
+ fail "object is not deserializable" unless deserializable?
46
+ JSON.load(Base64.strict_decode64(@string.delete_prefix(PRELUDE)))
47
+ end
48
+ rescue ArgumentError
49
+ raise "invalid Base64"
50
+ rescue JSON::ParserError
51
+ raise "invalid JSON"
52
+ end
53
+
54
+ # @return [Boolean] whether the object passed with +new+ is serializable
55
+ def serializable?
56
+ !!@hash
57
+ end
58
+
59
+ # @return [Boolean] whether the object passed with +new+ is deserializable
60
+ def deserializable?
61
+ @string && @string.match?(/\A#{PRELUDE}/)
62
+ end
63
+
64
+ end
65
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rodbot
4
- VERSION = "0.3.3"
4
+ VERSION = "0.3.4"
5
5
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Schwyn
@@ -29,7 +29,7 @@ cert_chain:
29
29
  kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v
30
30
  fwIwU1MKlFBdsjkd
31
31
  -----END CERTIFICATE-----
32
- date: 2023-10-21 00:00:00.000000000 Z
32
+ date: 2023-10-24 00:00:00.000000000 Z
33
33
  dependencies:
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: zeitwerk
@@ -453,6 +453,7 @@ files:
453
453
  - checksums/rodbot-0.3.1.gem.sha512
454
454
  - checksums/rodbot-0.3.2.gem.sha512
455
455
  - checksums/rodbot-0.3.3.gem.sha512
456
+ - checksums/rodbot-0.3.4.gem.sha512
456
457
  - doc/rodbot.afphoto
457
458
  - doc/rodbot.avif
458
459
  - exe/rodbot
@@ -483,6 +484,7 @@ files:
483
484
  - lib/rodbot/generator.rb
484
485
  - lib/rodbot/log.rb
485
486
  - lib/rodbot/memoize.rb
487
+ - lib/rodbot/message.rb
486
488
  - lib/rodbot/plugins.rb
487
489
  - lib/rodbot/plugins/github_webhook/README.github_webhook.md
488
490
  - lib/rodbot/plugins/github_webhook/app.rb
@@ -501,6 +503,7 @@ files:
501
503
  - lib/rodbot/rack.rb
502
504
  - lib/rodbot/refinements.rb
503
505
  - lib/rodbot/relay.rb
506
+ - lib/rodbot/serializer.rb
504
507
  - lib/rodbot/services.rb
505
508
  - lib/rodbot/services/app.rb
506
509
  - lib/rodbot/services/relay.rb
metadata.gz.sig CHANGED
Binary file