rodbot 0.3.2 → 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: 0bf9f1ee8fd09d93797a5ba276aa6fec09be9620e540f118240525c73a6fda6b
4
- data.tar.gz: b36fdd850e38596aaf7da805107d0cbbbcb46de823e76cad151676fbcc14dd9c
3
+ metadata.gz: eac36ded2dc61a62f3a5e9c236987ff66f0aadf65f216b96932375af2cec3241
4
+ data.tar.gz: '099b12406aa01b8ec165efb25a3faef9fd1efeed3a8c9a7c0c0bb05febadf2e8'
5
5
  SHA512:
6
- metadata.gz: 37a9346640167dc0543d9361b1d53789e363d27e6c57129fde777897f7e8ec3fff51e735129accdfaf8caee943a29fa19daae2ea5acdab6023d11d7dbf4ebaaa
7
- data.tar.gz: 6fec833877807a7b6dc85e19682c5bf88172ad2ff32ce0d0c5e83565cecbf60e54517f1aaeda451e17ee610f5338969bd05ffe5a8c002b649c28c3e35f25949b
6
+ metadata.gz: 06d6953cd85005effd3a54da59564e52a27d27cb42f61ccc2aeb0dfa32627a38d96f9e07fd9ba7d533fdf4210180d228582e5b39672a4124eba9831abb443016
7
+ data.tar.gz: cec0a4d433b447c34fa91ff7a7fd9df416648851ef5b8156fb3d01579ac2d1abb26e7738ee000b1bdb322575dc99c5f8ac635843e1ecfc07d439b3b3403fddb9
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
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
+
11
+ ## 0.3.3
12
+
13
+ #### Additions
14
+ * Support placeholders when using `Rodbot.say` and add `[[EVERYBODY]]`
15
+ placeholder to mention all hands in a room or channel
16
+
5
17
  ## 0.3.2
6
18
 
7
19
  #### Additions
data/README.md CHANGED
@@ -166,6 +166,7 @@ The response may contain special tags which have to be replace appropriately by
166
166
  Tag | Replaced with
167
167
  ----|--------------
168
168
  `[[SENDER]]` | Mention the sender of the command.
169
+ `[[EVERYBODY]]` | Mention everybody.
169
170
 
170
171
  #### Other Routes
171
172
 
@@ -177,7 +178,7 @@ The **relay service** act as glue between the **app service** and external commu
177
178
 
178
179
  Each relay service does three things:
179
180
 
180
- * **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.
181
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.
182
183
  * **Test:** It detects the `!ping` command and replies with "pong" *without* hitting the **app service**.
183
184
 
@@ -189,6 +190,14 @@ rodbot simulator
189
190
 
190
191
  Enter the command `!pay EUR 123` and you see the request `GET /pay?argument=EUR+123` hitting the **app service**.
191
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
+
192
201
  ### Schedule Service
193
202
 
194
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
+ de9183abfb13dd5098ca5e8e2cba6b0e27f349c2e90b85ef387e5b81730ff7d911f29b47b7e4900719e198514d097513f109916c6e64f2b9361237b35b5b0a70
@@ -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
- body = remote.gets("\x04")
60
+ message = Rodbot::Message.new(remote.gets("\x04").chop)
61
61
  remote.close
62
- body.force_encoding('UTF-8')
63
- room.send_html body.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
@@ -78,12 +78,13 @@ module Rodbot
78
78
  def reply_to(message)
79
79
  command(*message.content[:body][1..].split(/\s+/, 2)).
80
80
  md_to_html.
81
- psub(placeholders(client.get_user(message.sender)))
81
+ psub(placeholders(sender: client.get_user(message.sender)))
82
82
  end
83
83
 
84
- def placeholders(sender)
84
+ def placeholders(locals={})
85
85
  {
86
- sender: "https://matrix.to/#/#{sender.id}"
86
+ sender: ("https://matrix.to/#/#{locals[:sender].id}" if locals[:sender]),
87
+ everybody: "https://matrix.to/#room"
87
88
  }
88
89
  end
89
90
 
@@ -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
- body = remote.gets("\x04")
42
+ message = Rodbot::Message.new(remote.gets("\x04").chop)
43
43
  remote.close
44
- body.force_encoding('UTF-8')
45
44
  client.web_client.chat_postMessage(
46
- channel: channel_id,
47
- text: md_to_slack_text(body),
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
@@ -70,14 +69,15 @@ module Rodbot
70
69
 
71
70
  def reply_to(message)
72
71
  command(*message.text[1..].split(/\s+/, 2)).
73
- psub(placeholders(message.user)).
72
+ psub(placeholders(sender: message.user)).
74
73
  then { md_to_slack_text(_1) }
75
74
  end
76
75
 
77
76
  # @see https://api.slack.com/reference/surfaces/formatting
78
- def placeholders(sender)
77
+ def placeholders(locals={})
79
78
  {
80
- sender: "<@#{sender}>"
79
+ sender: ("<@#{locals[:sender]}>" if locals[:sender]),
80
+ everybody: "<!channel>"
81
81
  }
82
82
  end
83
83
 
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.2"
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.2
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-18 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
@@ -452,6 +452,8 @@ files:
452
452
  - checksums/rodbot-0.3.0.gem.sha512
453
453
  - checksums/rodbot-0.3.1.gem.sha512
454
454
  - checksums/rodbot-0.3.2.gem.sha512
455
+ - checksums/rodbot-0.3.3.gem.sha512
456
+ - checksums/rodbot-0.3.4.gem.sha512
455
457
  - doc/rodbot.afphoto
456
458
  - doc/rodbot.avif
457
459
  - exe/rodbot
@@ -482,6 +484,7 @@ files:
482
484
  - lib/rodbot/generator.rb
483
485
  - lib/rodbot/log.rb
484
486
  - lib/rodbot/memoize.rb
487
+ - lib/rodbot/message.rb
485
488
  - lib/rodbot/plugins.rb
486
489
  - lib/rodbot/plugins/github_webhook/README.github_webhook.md
487
490
  - lib/rodbot/plugins/github_webhook/app.rb
@@ -500,6 +503,7 @@ files:
500
503
  - lib/rodbot/rack.rb
501
504
  - lib/rodbot/refinements.rb
502
505
  - lib/rodbot/relay.rb
506
+ - lib/rodbot/serializer.rb
503
507
  - lib/rodbot/services.rb
504
508
  - lib/rodbot/services/app.rb
505
509
  - lib/rodbot/services/relay.rb
metadata.gz.sig CHANGED
Binary file