rodbot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }