chronicle_bot/lib/chronicle_bot.rb
Bill Niblock 6fe1a7a251 Update how Addons are used
After some more reading, update the logic for handling the addons.

Leverage an "addons module" (Addon), and require that each addon be a
class within that module. Within that class, optionally include a
`self.register` method, which will be called when the bot is
initialized. This method takes one parameter: the bot
instance.Registering the addon adds the bot's instance to an instance of
the addon, and vice versa, allowing the instances to communicate.
Additionally, registering adds the addons commands.

Each registered addon must also include a `matrix_command` method, which
takes the message String from Matrix. The intention here is to include
additional `*_command` methods for different protocols, without having
to change much else. Adding additional protocols will determine how well
that works.

chronicle_bot.rb: Redo addon logic (details above)
addons/utils.rb: Update for new addon logic (details above)
addons/roller.rb: Update for new addon logic (details above)
2021-02-14 21:06:46 -05:00

140 lines
3.3 KiB
Ruby
Executable file

# frozen_string_literal: true
require 'faraday'
require 'json'
require 'matrix_sdk'
# Require any addons
Dir[File.join(__dir__, 'addons', '*.rb')].each do |file|
require file
end
# Chronicle Bot
module Chronicle
# A filter to simplify syncs
BOT_FILTER = {
presence: { types: [] },
account_data: { types: [] },
room: {
ephemeral: { types: [] },
state: {
types: ['m.room.*'],
lazy_load_members: true
},
timeline: {
types: ['m.room.message']
},
account_data: { types: [] }
}
}.freeze
# Chronicle Bot for Matrix
module Matrix
# Begin the beast
def self.start(args)
unless args.length >= 2
raise "Usage: #{$PROGRAM_NAME} [-d] homeserver_url access_token"
end
if args.first == '-d'
Thread.abort_on_exception = true
MatrixSdk.debug!
args.shift
end
bot = ChronicleBot.new args[0], args[1]
bot.run
end
# The bot
class ChronicleBot
attr_reader :all_commands, :cmd_prefix
def initialize(hs_url, access_token)
@hs_url = hs_url
@token = access_token
@cmd_prefix = '!'
@all_commands = {}
@allowed_commands = {}
register_commands
end
# All available commands
def available_commands(addon, commands)
commands.each do |command|
@all_commands[command] = addon
end
end
def client
@client ||= MatrixSdk::Client.new(
@hs_url,
access_token: @token,
client_cache: :all
)
end
def deep_copy(hash)
Marshal.load(Marshal.dump(hash))
end
def disable_commands(*commands)
commands.each do |command|
@all_commands.delete(command)
end
end
def on_message(message)
return unless message.content[:msgtype] == 'm.text'
msgstr = message.content[:body]
roomid = message.room_id
cmds = @all_commands.keys.join('|')
return unless msgstr =~ /^#{@cmd_prefix}#{cmds}\s*/
msgstr.match(/^#{@cmd_prefix}(#{cmds})\s*/) do |m|
@all_commands[m.to_s[1..-1].strip].matrix_command(message)
end
end
def register_commands
Chronicle::Addon.constants.each do |addon|
cmd = Object.const_get("Chronicle::Addon::#{addon.to_s}")
if cmd.methods.include?(:register)
instance, commands = cmd.send(:register, self)
available_commands(instance, commands)
end
end
end
# Run Chronicle
def run
# Join all invited rooms
client.on_invite_event.add_handler { |ev| client.join_room(ev[:room_id]) }
# Run an empty sync to get to a `since` token without old data.
# Storing the `since` token is also valid for bot use-cases, but in the
# case of ping responses there's never any need to reply to old data.
empty_sync = deep_copy(BOT_FILTER)
empty_sync[:room].map { |_k, v| v[:types] = [] }
client.sync filter: empty_sync
# Read all message events
client.on_event.add_handler('m.room.message') { |ev| on_message(ev) }
loop do
begin
client.sync filter: BOT_FILTER
rescue MatrixSdk::MatrixError => e
puts e
end
end
end
end
end
end