diff --git a/lib/chronicle_bot.rb b/lib/chronicle_bot.rb new file mode 100755 index 0000000..a261bf8 --- /dev/null +++ b/lib/chronicle_bot.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'faraday' +require 'json' +require 'matrix_sdk' + +# Chronicle Bot +module Chronicle + # Require any addons + Dir[File.join(__dir__, 'addons', '*.rb')].each do |file| + require file + end + + # 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 + @@default_allowed_commands = %w[ping] + @@room_allowed_commands = {} + + # Update allowed commands + def add_allowed_commands(cmds, msgid='') + if msgid + @@room_allowed_commands[msgid].concat(cmds) + else + @@room_allowed_commands.each { |_,v| v.concat(cmds) } + end + end + + # Establish or return allowed commands for a room + def allowed_commands(msgid) + @@room_allowed_commands[msgid] ||= @@default_allowed_commands + end + + # 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 + include Matrix + + def initialize(hs_url, access_token) + @hs_url = hs_url + @token = access_token + 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 + + def on_message(message) + return unless message.content[:msgtype] == 'm.text' + + msgstr = message.content[:body] + msgid = message.room_id + cmds = allowed_commands(msgid).join('|') + + return unless msgstr =~ /^!#{cmds}\s*/ + + msgstr.match(/^!(#{cmds})\s*/) do |m| + send( + "handle_#{m.to_s[1..-1].strip}", + client, + message + ) + end + end + + def client + @client ||= MatrixSdk::Client.new( + @hs_url, + access_token: @token, + client_cache: :none + ) + end + + def deep_copy(hash) + Marshal.load(Marshal.dump(hash)) + end + end + end +end