rodbot 0.1.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 (77) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +653 -0
  6. data/exe/rodbot +7 -0
  7. data/lib/roda/plugins/rodbot.rb +36 -0
  8. data/lib/rodbot/async.rb +45 -0
  9. data/lib/rodbot/cli/command.rb +25 -0
  10. data/lib/rodbot/cli/commands/console.rb +23 -0
  11. data/lib/rodbot/cli/commands/credentials.rb +19 -0
  12. data/lib/rodbot/cli/commands/deploy.rb +20 -0
  13. data/lib/rodbot/cli/commands/new.rb +21 -0
  14. data/lib/rodbot/cli/commands/simulator.rb +17 -0
  15. data/lib/rodbot/cli/commands/start.rb +26 -0
  16. data/lib/rodbot/cli/commands/stop.rb +15 -0
  17. data/lib/rodbot/cli/commands/version.rb +15 -0
  18. data/lib/rodbot/cli/commands.rb +18 -0
  19. data/lib/rodbot/cli.rb +9 -0
  20. data/lib/rodbot/config.rb +157 -0
  21. data/lib/rodbot/constants.rb +13 -0
  22. data/lib/rodbot/db/hash.rb +71 -0
  23. data/lib/rodbot/db/redis.rb +61 -0
  24. data/lib/rodbot/db.rb +91 -0
  25. data/lib/rodbot/dispatcher.rb +125 -0
  26. data/lib/rodbot/env.rb +48 -0
  27. data/lib/rodbot/error.rb +19 -0
  28. data/lib/rodbot/generator.rb +108 -0
  29. data/lib/rodbot/log.rb +67 -0
  30. data/lib/rodbot/memoize.rb +86 -0
  31. data/lib/rodbot/plugins/github_webhook/README.github_webhook.md +42 -0
  32. data/lib/rodbot/plugins/github_webhook/app.rb +46 -0
  33. data/lib/rodbot/plugins/gitlab_webhook/README.gitlab_webhook.md +40 -0
  34. data/lib/rodbot/plugins/gitlab_webhook/app.rb +40 -0
  35. data/lib/rodbot/plugins/hal/README.hal.md +15 -0
  36. data/lib/rodbot/plugins/hal/app.rb +22 -0
  37. data/lib/rodbot/plugins/matrix/README.matrix.md +42 -0
  38. data/lib/rodbot/plugins/matrix/relay.rb +113 -0
  39. data/lib/rodbot/plugins/otp/README.otp.md +82 -0
  40. data/lib/rodbot/plugins/otp/app.rb +47 -0
  41. data/lib/rodbot/plugins/word_of_the_day/README.word_of_the_day.md +13 -0
  42. data/lib/rodbot/plugins/word_of_the_day/schedule.rb +51 -0
  43. data/lib/rodbot/plugins.rb +81 -0
  44. data/lib/rodbot/rack.rb +50 -0
  45. data/lib/rodbot/refinements.rb +118 -0
  46. data/lib/rodbot/relay.rb +104 -0
  47. data/lib/rodbot/services/app.rb +72 -0
  48. data/lib/rodbot/services/relay.rb +37 -0
  49. data/lib/rodbot/services/schedule.rb +29 -0
  50. data/lib/rodbot/services.rb +32 -0
  51. data/lib/rodbot/simulator.rb +60 -0
  52. data/lib/rodbot/version.rb +5 -0
  53. data/lib/rodbot.rb +60 -0
  54. data/lib/templates/deploy/docker/compose.yaml.gerb +37 -0
  55. data/lib/templates/deploy/docker-split/compose.yaml.gerb +52 -0
  56. data/lib/templates/deploy/procfile/Procfile.gerb +1 -0
  57. data/lib/templates/deploy/procfile-split/Procfile.gerb +5 -0
  58. data/lib/templates/deploy/render/render.yaml.gerb +0 -0
  59. data/lib/templates/deploy/render-split/render.yaml.gerb +0 -0
  60. data/lib/templates/new/LICENSE.txt +22 -0
  61. data/lib/templates/new/README.md +4 -0
  62. data/lib/templates/new/app/app.rb +11 -0
  63. data/lib/templates/new/app/routes/help.rb +19 -0
  64. data/lib/templates/new/app/views/layout.erb +12 -0
  65. data/lib/templates/new/app/views/root.erb +5 -0
  66. data/lib/templates/new/config/rodbot.rb +8 -0
  67. data/lib/templates/new/config/schedule.rb +5 -0
  68. data/lib/templates/new/config.ru +3 -0
  69. data/lib/templates/new/gems.locked +104 -0
  70. data/lib/templates/new/gems.rb +15 -0
  71. data/lib/templates/new/guardfile.rb +9 -0
  72. data/lib/templates/new/public/assets/images/rodbot.avif +0 -0
  73. data/lib/templates/new/public/assets/stylesheets/base.css +18 -0
  74. data/lib/templates/new/rakefile.rb +28 -0
  75. data.tar.gz.sig +0 -0
  76. metadata +510 -0
  77. metadata.gz.sig +3 -0
@@ -0,0 +1,72 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack'
4
+ require 'puma'
5
+ require 'roda'
6
+
7
+ module Rodbot
8
+ class Services
9
+ class App
10
+ include Rodbot::Memoize
11
+
12
+ class << self
13
+ include Rodbot::Memoize
14
+
15
+ # URL (including port) to reach the app service locally
16
+ #
17
+ # @return [String] URL
18
+ memoize def url
19
+ [
20
+ (ENV['RODBOT_APP_URL'] || 'http://localhost'),
21
+ Rodbot.config(:port)
22
+ ].join(':')
23
+ end
24
+ end
25
+
26
+ def tasks(**)
27
+ puts "Starting app service on http://#{bind.join(':')}"
28
+ [method(:run)]
29
+ end
30
+
31
+ private
32
+
33
+ def run
34
+ Dir.chdir(Rodbot.env.root)
35
+ Puma::Server.new(app, nil, options).tap do |server|
36
+ server.add_tcp_listener(*bind)
37
+ server.app = ::Rack::CommonLogger.new(app, logger)
38
+ end.run.join
39
+ end
40
+
41
+ memoize def bind
42
+ [
43
+ (ENV['RODBOT_APP_HOST'] || 'localhost'),
44
+ Rodbot.config(:port)
45
+ ]
46
+ end
47
+
48
+ memoize def app
49
+ ::Rack::Builder.parse_file(Rodbot.env.root.join('config.ru').to_s)
50
+ end
51
+
52
+ def options
53
+ {
54
+ lowlevel_error_handler: method(:lowlevel_error_handler),
55
+ log_writer: Puma::LogWriter.null,
56
+ min_threads: Rodbot.config(:app, :threads).min,
57
+ max_threads: Rodbot.config(:app, :threads).max
58
+ }
59
+ end
60
+
61
+ memoize def logger
62
+ Rodbot::Log.logger('app')
63
+ end
64
+
65
+ def lowlevel_error_handler(error)
66
+ logger.error("#{error.message}: #{error.backtrace.first}")
67
+ [500, {}, ['Oops!']]
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,37 @@
1
+ # frozen-string-literal: true
2
+
3
+ using Rodbot::Refinements
4
+
5
+ module Rodbot
6
+ class Services
7
+ class Relay
8
+
9
+ class << self
10
+ include Rodbot::Memoize
11
+
12
+ # URL (including port) to reach the given relay service locally
13
+ #
14
+ # @param name [Symbol] relay service
15
+ # @return [String] URL
16
+ memoize def url(name)
17
+ [
18
+ (ENV["RODBOT_RELAY_URL_#{name.upcase}"] || 'tcp://localhost'),
19
+ Rodbot.config(:port) + 1 + Rodbot.config(:plugin).keys.index(name)
20
+ ].join(':')
21
+ end
22
+ end
23
+
24
+ def tasks(only: nil)
25
+ Rodbot.plugins.extend_relay
26
+ extensions = Rodbot.plugins.extensions[:relay]
27
+ extensions.select! { _1 == only.to_sym } if only
28
+ fail Rodbot::RelayError, "no matching relay plugin configured" if extensions.none?
29
+ extensions.map do |name, path|
30
+ puts "Starting relay service extension #{name} on #{self.class.url(name)}"
31
+ path.constantize.new.loops
32
+ end.flatten
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'clockwork'
4
+ require 'active_support/time'
5
+
6
+ module Rodbot
7
+ class Services
8
+ class Schedule
9
+
10
+ def tasks(**)
11
+ puts "Starting schedule service"
12
+ [method(:run)]
13
+ end
14
+
15
+ private
16
+
17
+ def run
18
+ Clockwork.instance_eval do
19
+ configure { _1[:logger] = Rodbot::Log.logger('schedule') }
20
+ handler { Rodbot::Async.perform(&_1) }
21
+ end
22
+ Rodbot.plugins.extend_schedule
23
+ require Rodbot.env.root.join('config', 'schedule')
24
+ Clockwork.run
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen-string-literal: true
2
+
3
+ using Rodbot::Refinements
4
+
5
+ module Rodbot
6
+
7
+ # Foundation to run app, relay and schedule services
8
+ class Services
9
+ extend Forwardable
10
+
11
+ def_delegator :@dispatcher, :run, :run
12
+ def_delegator :@dispatcher, :interrupt, :interrupt
13
+
14
+ def initialize
15
+ @dispatcher = Rodbot::Dispatcher.new('rodbot')
16
+ end
17
+
18
+ def exist?(service)
19
+ Rodbot::SERVICES.include? service.to_sym
20
+ end
21
+
22
+ def register(service, extension: nil)
23
+ fail(Rodbot::ServiceError, "unknown service #{service}") unless exist? service
24
+ tasks = "rodbot/services/#{service}".constantize.new.tasks(only: extension)
25
+ tasks.each_with_index do |task, index|
26
+ name = [service, (index if tasks.count > 1)].compact.join('-')
27
+ @dispatcher.register(name, &task)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,60 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'readline'
4
+ require 'httparty'
5
+ require 'pastel'
6
+ require 'tty-markdown'
7
+
8
+ using Rodbot::Refinements
9
+
10
+ module Rodbot
11
+
12
+ # Simulate a chat client
13
+ class Simulator
14
+
15
+ # @param sender [String] sender to mimick
16
+ # @param raw [Boolean] whether to display raw Markdown
17
+ def initialize(sender, raw: false)
18
+ @sender, @raw = sender, raw
19
+ @pastel = Pastel.new
20
+ end
21
+
22
+ def run
23
+ puts nil, "Talking to app on #{Rodbot::Services::App.url} as sender #{@pastel.inverse(@sender)}."
24
+ puts 'Type commands beginning with "!" or empty line to exit.', nil
25
+ while (line = Readline.readline("rodbot> ", true)) && !line.empty?
26
+ puts nil, reply_to(line), nil
27
+ end
28
+ puts
29
+ end
30
+
31
+ private
32
+
33
+ def reply_to(message)
34
+ return "(no command given)" unless message.match?(/^!/)
35
+ command, argument = message[1..].split(/\s+/, 2)
36
+ body = begin
37
+ response = Rodbot.request(command, query: { argument: argument })
38
+ case response.code
39
+ when 200 then response.body
40
+ when 404 then "[[SENDER]] I've never heard of `!#{command}`, try `!help` instead. 🤔"
41
+ else fail
42
+ end
43
+ rescue
44
+ "[[SENDER]] I'm having trouble talking to the app. 💣"
45
+ end
46
+ text_for body.psub(placeholders)
47
+ end
48
+
49
+ def placeholders
50
+ {
51
+ sender: @pastel.inverse(@sender)
52
+ }
53
+ end
54
+
55
+ def text_for(markdown)
56
+ @raw ? markdown : TTY::Markdown.parse(markdown, mode: 16).strip
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rodbot
4
+ VERSION = "0.1.0"
5
+ end
data/lib/rodbot.rb ADDED
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ require 'zeitwerk'
6
+ require 'dry/credentials'
7
+ require 'logger'
8
+
9
+ loader = Zeitwerk::Loader.for_gem
10
+ loader.inflector.inflect 'cli' => 'CLI'
11
+ %w(rodbot/plugins roda templates).each { loader.ignore "#{__dir__}/#{_1}" }
12
+ loader.setup
13
+
14
+ # Rodbot foundation
15
+ #
16
+ # Once the Rodbot gem has been required, use +boot+ to spin up the foundation:
17
+ #
18
+ # Rodbot.boot # current directory is root directory
19
+ # Rodbot.boot(root: '/path/to/root') # explicit root directory
20
+ #
21
+ # This gives you access to the following shortcuts (in order of loading):
22
+ #
23
+ # * +Rodbot.env+ -> {Rodbot::Env}
24
+ # * +Rodbot.credentials+ -> {Dry::Credentials}
25
+ # * +Rodbot.config+ -> {Rodbot::Config#config}
26
+ # * +Rodbot.plugins+ -> {Rodbot::Plugins}
27
+ # * +Rodbot.db+ -> {Rodbot::Db#db}
28
+ # * +Rodbot.log+ -> {Rodbot::Log#log}
29
+ # * +Rodbot.request+ -> {Rodbot::Rack#request}
30
+ # * +Rodbot.say+ -> {Rodbot::Relay#say}
31
+ module Rodbot
32
+ include Rodbot::Constants
33
+ extend Dry::Credentials
34
+
35
+ class << self
36
+ extend Forwardable
37
+
38
+ attr_reader :env
39
+ def_delegator :@config, :config
40
+ def_delegator :@plugins, :itself, :plugins
41
+ def_delegator :@db, :itself, :db
42
+ def_delegator :@log, :log
43
+ def_delegator 'Rodbot::Rack', :request
44
+ def_delegator 'Rodbot::Relay', :say
45
+
46
+ def boot(root: nil)
47
+ @env = Rodbot::Env.new(root: root)
48
+ credentials do
49
+ env Rodbot.env.current
50
+ dir ENV['RODBOT_CREDENTIALS_DIR'] || Rodbot.env.root.join('config', 'credentials')
51
+ end
52
+ @config = Rodbot::Config.new(Rodbot.env.root.join('config', 'rodbot.rb').read)
53
+ @plugins = Rodbot::Plugins.new
54
+ @db = (db = @config.config(:db)) && Rodbot::Db.new(db)
55
+ @log = Rodbot::Log.new
56
+ end
57
+ end
58
+ end
59
+
60
+ loader.eager_load_namespace(Rodbot::Error)
@@ -0,0 +1,37 @@
1
+ version: "3.9"
2
+
3
+ x-defaults: &defaults
4
+ build:
5
+ context: .
6
+ dockerfile_inline: |
7
+ FROM ruby:3.2-alpine
8
+ RUN apk update && apk --no-cache add build-base
9
+ ENV RODBOT_ENV="production"
10
+ ENV RACK_ENV="production"
11
+ ENV RACK_ROOT="/var/www"
12
+ ENV TZ="[%= timezone %]"
13
+ RUN mkdir -p /var/www
14
+ WORKDIR /var/www
15
+ COPY . .
16
+ COPY .bundle /usr/local/bundle
17
+ RUN bundle config set without "development test" && \
18
+ bundle install --jobs 20 --retry 5
19
+ environment:
20
+ - RODBOT_APP_HOST=0.0.0.0
21
+ - PRODUCTION_CREDENTIALS_KEY
22
+ restart: "unless-stopped"
23
+
24
+ services:
25
+ rodbot:
26
+ <<: *defaults
27
+ command: "sh -c 'bundle exec rodbot start && sleep infinity'"
28
+ ports:
29
+ - "[%= Rodbot.config(:app, :port) %]"
30
+
31
+ networks:
32
+ default:
33
+ driver: "bridge"
34
+ ipam:
35
+ driver: "default"
36
+ config:
37
+ - subnet: "172.16.72.0/24"
@@ -0,0 +1,52 @@
1
+ version: "3.9"
2
+
3
+ x-defaults: &defaults
4
+ build:
5
+ context: .
6
+ dockerfile_inline: |
7
+ FROM ruby:3.2-alpine
8
+ RUN apk update && apk --no-cache add build-base
9
+ ENV RODBOT_ENV="production"
10
+ ENV RACK_ENV="production"
11
+ ENV RACK_ROOT="/var/www"
12
+ ENV TZ="[%= timezone %]"
13
+ RUN mkdir -p /var/www
14
+ WORKDIR /var/www
15
+ COPY . .
16
+ COPY .bundle /usr/local/bundle
17
+ RUN bundle config set without "development test" && \
18
+ bundle install --jobs 20 --retry 5
19
+ environment:
20
+ - RODBOT_APP_HOST=0.0.0.0
21
+ - RODBOT_APP_URL=http://app
22
+ - RODBOT_RELAY_HOST=0.0.0.0
23
+ [% relay_extensions.each do |name, _| -%]
24
+ - RODBOT_RELAY_URL_[%= name.upcase %]=tcp://relay-[%= name %]
25
+ [% end -%]
26
+ - PRODUCTION_CREDENTIALS_KEY
27
+ restart: "unless-stopped"
28
+
29
+ services:
30
+ app:
31
+ <<: *defaults
32
+ command: "bundle exec rodbot start app"
33
+ ports:
34
+ - "[%= Rodbot.config(:app, :port) %]"
35
+ schedule:
36
+ <<: *defaults
37
+ command: "bundle exec rodbot start schedule"
38
+ [% relay_extensions.each do |name, port| -%]
39
+ relay-[%= name %]:
40
+ <<: *defaults
41
+ command: "bundle exec rodbot start relay [%= name %]"
42
+ expose:
43
+ - [%= port %]
44
+ [% end -%]
45
+
46
+ networks:
47
+ default:
48
+ driver: "bridge"
49
+ ipam:
50
+ driver: "default"
51
+ config:
52
+ - subnet: "172.16.72.0/24"
@@ -0,0 +1 @@
1
+ rodbot: bundle exec rodbot start
@@ -0,0 +1,5 @@
1
+ app: bundle exec rodbot start app
2
+ schedule: bundle exec rodbot start schedule
3
+ [% relay_extensions.each do |name, port| -%]
4
+ relay_[%= name %]: bundle exec rodbot start relay [%= name %]
5
+ [% end -%]
File without changes
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sven Schwyn
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ # {{NAME}}
2
+
3
+ Your friendly neighbourhood bot.
4
+
@@ -0,0 +1,11 @@
1
+ class App < Roda
2
+ plugin :rodbot
3
+
4
+ route do |r|
5
+ r.multi_run
6
+ r.public
7
+ r.root { view :root }
8
+ end
9
+
10
+ run :help, Routes::Help
11
+ end
@@ -0,0 +1,19 @@
1
+ module Routes
2
+ class Help < App
3
+
4
+ route do |r|
5
+
6
+ # GET /help
7
+ r.root do
8
+ response['Content-Type'] = 'text/markdown; charset=utf-8'
9
+ <<~END
10
+ [[SENDER]] I'm Rodbot, what can I do for you today?
11
+
12
+ * `!ping` – check whether I'm listening
13
+ END
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>title</title>
7
+ <link rel="stylesheet" href="/assets/stylesheets/base.css">
8
+ </head>
9
+ <body>
10
+ <%= yield %>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ <main>
2
+ <a href="https://github.com/svoop/rodbot">
3
+ <img src="/assets/images/rodbot.avif">
4
+ </a>
5
+ </main>
@@ -0,0 +1,8 @@
1
+ name 'Rodbot'
2
+ timezone 'Etc/UTC'
3
+
4
+ plugin :hal
5
+
6
+ plugin :word_of_the_day do
7
+ time '10:00'
8
+ end
@@ -0,0 +1,5 @@
1
+ # Regular jobs performed asynchronously at a given time
2
+ module Clockwork
3
+ # every 1.hour, -> { Rodbot.say 'Ping!' }
4
+ # every 1.day, -> { Rodbot.say 'Time for a cup of tea!' }, at: '16:00'
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'rodbot'
2
+
3
+ Rodbot::Rack.boot(self)
@@ -0,0 +1,104 @@
1
+ PATH
2
+ remote: ../../..
3
+ specs:
4
+ rodbot (0.1.0.pre1)
5
+ clockwork (~> 3)
6
+ debug
7
+ dry-cli (~> 1)
8
+ dry-credentials (~> 0)
9
+ httparty (~> 0)
10
+ kramdown (~> 2)
11
+ kramdown-parser-gfm (~> 1)
12
+ matrix_sdk (~> 2)
13
+ pastel (~> 0)
14
+ puma (~> 6, >= 6.2)
15
+ roda (~> 3)
16
+ rotp (~> 6)
17
+ sucker_punch (~> 3)
18
+ tilt (~> 2)
19
+ tty-markdown (~> 0)
20
+ zeitwerk (~> 2)
21
+
22
+ GEM
23
+ remote: https://rubygems.org/
24
+ specs:
25
+ activesupport (7.0.4.3)
26
+ concurrent-ruby (~> 1.0, >= 1.0.2)
27
+ i18n (>= 1.6, < 2)
28
+ minitest (>= 5.1)
29
+ tzinfo (~> 2.0)
30
+ clockwork (3.0.2)
31
+ activesupport
32
+ tzinfo
33
+ concurrent-ruby (1.2.2)
34
+ debug (1.8.0)
35
+ irb (>= 1.5.0)
36
+ reline (>= 0.3.1)
37
+ dry-cli (1.0.0)
38
+ dry-credentials (0.1.0)
39
+ httparty (0.21.0)
40
+ mini_mime (>= 1.0.0)
41
+ multi_xml (>= 0.5.2)
42
+ i18n (1.12.0)
43
+ concurrent-ruby (~> 1.0)
44
+ io-console (0.6.0)
45
+ irb (1.6.4)
46
+ reline (>= 0.3.0)
47
+ kramdown (2.4.0)
48
+ rexml
49
+ kramdown-parser-gfm (1.1.0)
50
+ kramdown (~> 2.0)
51
+ little-plugger (1.1.4)
52
+ logging (2.3.1)
53
+ little-plugger (~> 1.1)
54
+ multi_json (~> 1.14)
55
+ matrix_sdk (2.8.0)
56
+ logging (~> 2)
57
+ mini_mime (1.1.2)
58
+ minitest (5.18.0)
59
+ multi_json (1.15.0)
60
+ multi_xml (0.6.0)
61
+ nio4r (2.5.8)
62
+ pastel (0.8.0)
63
+ tty-color (~> 0.5)
64
+ puma (6.2.0)
65
+ nio4r (~> 2.0)
66
+ rack (3.0.7)
67
+ reline (0.3.3)
68
+ io-console (~> 0.5)
69
+ rexml (3.2.5)
70
+ roda (3.66.0)
71
+ rack
72
+ rotp (6.2.2)
73
+ rouge (4.1.0)
74
+ strings (0.2.1)
75
+ strings-ansi (~> 0.2)
76
+ unicode-display_width (>= 1.5, < 3.0)
77
+ unicode_utils (~> 1.4)
78
+ strings-ansi (0.2.0)
79
+ sucker_punch (3.1.0)
80
+ concurrent-ruby (~> 1.0)
81
+ tilt (2.1.0)
82
+ tty-color (0.6.0)
83
+ tty-markdown (0.7.2)
84
+ kramdown (>= 1.16.2, < 3.0)
85
+ pastel (~> 0.8)
86
+ rouge (>= 3.14, < 5.0)
87
+ strings (~> 0.2.0)
88
+ tty-color (~> 0.5)
89
+ tty-screen (~> 0.8)
90
+ tty-screen (0.8.1)
91
+ tzinfo (2.0.6)
92
+ concurrent-ruby (~> 1.0)
93
+ unicode-display_width (2.4.2)
94
+ unicode_utils (1.4.0)
95
+ zeitwerk (2.6.7)
96
+
97
+ PLATFORMS
98
+ arm64-darwin-22
99
+
100
+ DEPENDENCIES
101
+ rodbot!
102
+
103
+ BUNDLED WITH
104
+ 2.4.12
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rodbot', '~> 0'
4
+
5
+ group :matrix, optional: true do
6
+ gem 'matrix_sdk', '~> 2'
7
+ end
8
+
9
+ group :redis, optional: true do
10
+ gem 'redis', '~> 5'
11
+ end
12
+
13
+ group :otp, optional: true do
14
+ gem 'rotp', '~> 6'
15
+ end
@@ -0,0 +1,9 @@
1
+ clearing :on
2
+
3
+ guard :minitest do
4
+ %i(services).each do |dir|
5
+ watch(%r{^spec/#{dir}/(.+)_spec\.rb})
6
+ watch(%r{^#{dir}/(.+)\.rb}) { "spec/#{dir}/#{_1[1]}_spec.rb" }
7
+ end
8
+ watch(%r{^spec/spec_helper\.rb}) { 'spec' }
9
+ end
@@ -0,0 +1,18 @@
1
+ main {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ height: 100vh;
6
+ }
7
+
8
+ a {
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ width: 50%;
13
+ }
14
+
15
+ a img {
16
+ max-width: 100%;
17
+ height: auto;
18
+ }