Class: ActiveMatrix::Bot::Base

Inherits:
Object
  • Object
show all
Extended by:
Extensions
Includes:
Logging
Defined in:
lib/active_matrix/bot/base.rb

Direct Known Subclasses

Instance, MultiInstanceBase

Defined Under Namespace

Classes: RequestHandler

Constant Summary collapse

CALLERS_TO_IGNORE =
[
  /\/matrix_sdk\/.+\.rb$/,                            # all ActiveMatrix code
  /^\(.*\)$/,                                         # generated code
  /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
  /bundler(\/(?:runtime|inline))?\.rb/,               # bundler require hacks
  /<internal:/                                        # internal in ruby >= 1.9.2
].freeze
EMPTY_BOT_FILTER =

A filter that should only result in a valid sync token and no other data

{
  account_data: { types: [] },
  event_fields: [],
  presence: { types: [] },
  room: {
    account_data: { types: [] },
    ephemeral: { types: [] },
    state: {
      types: [],
      lazy_load_members: true
    },
    timeline: {
      types: []
    }
  }
}.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extensions

events, ignore_inspect

Methods included from Logging

included

Constructor Details

#initialize(hs_url, **params) ⇒ Base

Returns a new instance of Base.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/active_matrix/bot/base.rb', line 31

def initialize(hs_url, **params)
  @client = case hs_url
            when ActiveMatrix::Api
              ActiveMatrix::Client.new hs_url
            when ActiveMatrix::Client
              hs_url
            when %r{^https?://.*}
              ActiveMatrix::Client.new hs_url, **params
            else
              ActiveMatrix::Client.new_for_domain hs_url, **params
            end

  @client.on_event.add_handler { |ev| _handle_event(ev) }
  @client.on_invite_event.add_handler do |ev|
    break unless settings.accept_invites?

    logger.info "Received invite to #{ev[:room_id]}, joining."
    client.join_room(ev[:room_id])
  end

  @event = nil

  logger.warn 'The bot abstraction is not fully finalized and can be expected to change.'
end

Class Attribute Details

.handlersObject (readonly)

Returns the value of attribute handlers.



141
142
143
# File 'lib/active_matrix/bot/base.rb', line 141

def handlers
  @handlers
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



26
27
28
# File 'lib/active_matrix/bot/base.rb', line 26

def client
  @client
end

#eventObject (readonly)

Returns the value of attribute event.



26
27
28
# File 'lib/active_matrix/bot/base.rb', line 26

def event
  @event
end

#loggerObject



56
57
58
59
60
# File 'lib/active_matrix/bot/base.rb', line 56

def logger
  return @logger if instance_variable_defined?(:@logger) && @logger

  self.class.logger
end

Class Method Details

.all_handlers(type: :command) ⇒ Array[RequestHandler]

Retrieves all registered - including inherited - handlers for the bot

Parameters:

  • type (:command, :event, :all) (defaults to: :command)

    Which handler type to return, or :all to return all handlers regardless of type

Returns:

  • (Array[RequestHandler])

    The registered handlers for the bot and parents



179
180
181
182
# File 'lib/active_matrix/bot/base.rb', line 179

def all_handlers(type: :command)
  parent = superclass&.all_handlers(type: type) if superclass.respond_to? :all_handlers
  (parent || {}).merge(@handlers.select { |_, h| type == :all || h.type == type }).compact
end

.client(&block) ⇒ Object

Registers a block to be run when configuring the client, before starting the sync



299
300
301
# File 'lib/active_matrix/bot/base.rb', line 299

def client(&block)
  @client_handler = block
end

.command(command, desc: nil, notes: nil, only: nil, **params) ⇒ Object

Note:

Due to the way blocks are handled, required parameters won’t block execution. If your command requires all parameters to be valid, you will need to check for nil yourself.

Note:

Execution will be performed with a ActiveMatrix::Bot::Request object as self. To access the bot instance, use ActiveMatrix::Bot::Request#bot

Register a bot command

Parameters:

  • command (String)

    The command to register, will be routed based on the prefix and bot NameError

  • desc (String) (defaults to: nil)

    A human-readable description for the command

  • only (Symbol, Proc, Array[Symbol,Proc]) (defaults to: nil)

    What limitations does this command have? Can use :DM, :Admin, :Mod

  • params (Hash)

    a customizable set of options

Options Hash (**params):

  • (Object)


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/active_matrix/bot/base.rb', line 255

def command(command, desc: nil, notes: nil, only: nil, **params, &)
  args = params[:args] || convert_to_lambda(&).parameters.filter_map do |type, name|
    case type
    when :req
      name.to_s.upcase
    when :opt
      "[#{name.to_s.upcase}]"
    when :rest
      "[#{name.to_s.upcase}...]"
    end
  end.join(' ')

  logger.debug "Registering command #{command} with args #{args}"

  add_handler(
    command.to_s.downcase,
    type: :command,
    args: args,
    desc: desc,
    notes: notes,
    only: [only].flatten.compact,
    &
  )
end

.command?(command, ignore_inherited: false) ⇒ Boolean

Check if a command is registered

Parameters:

  • command (String)

    The command to check

  • ignore_inherited (Booleen) (defaults to: false)

    Should the check ignore any inherited commands and only check local registrations

Returns:

  • (Boolean)


307
308
309
310
311
# File 'lib/active_matrix/bot/base.rb', line 307

def command?(command, ignore_inherited: false)
  return @handlers[command.to_s.downcase]&.command? if ignore_inherited

  all_handlers[command.to_s.downcase]&.command? || false
end

.disable(*opts) ⇒ Object

Same as calling ‘set :option, false` for each of the given options.

Parameters:

  • opts (Array[Symbol])

    The options to set to false



238
239
240
# File 'lib/active_matrix/bot/base.rb', line 238

def disable(*opts)
  opts.each { |key| set(key, false) }
end

.enable(*opts) ⇒ Object

Same as calling ‘set :option, true` for each of the given options.

Parameters:

  • opts (Array[Symbol])

    The options to set to true



231
232
233
# File 'lib/active_matrix/bot/base.rb', line 231

def enable(*opts)
  opts.each { |key| set(key, true) }
end

.event(event, only: nil, **_params) ⇒ Object

Note:

Currently it’s only possible to register one handler per event type

Register a Matrix event

Parameters:

  • event (String)

    The ID for the event to register

  • only (Symbol, Proc, Array[Symbol,Proc]) (defaults to: nil)

    The limitations to when the event should be handled

  • params (Hash)

    a customizable set of options



287
288
289
290
291
292
293
294
295
296
# File 'lib/active_matrix/bot/base.rb', line 287

def event(event, only: nil, **_params, &)
  logger.debug "Registering event #{event}"

  add_handler(
    event.to_s,
    type: :event,
    only: [only].flatten.compact,
    &
  )
end

.event?(event, ignore_inherited: false) ⇒ Boolean

Check if an event is registered

Parameters:

  • event (String)

    The event type to check

  • ignore_inherited (Booleen) (defaults to: false)

    Should the check ignore any inherited events and only check local registrations

Returns:

  • (Boolean)


317
318
319
320
321
# File 'lib/active_matrix/bot/base.rb', line 317

def event?(event, ignore_inherited: false)
  return @handlers[event]&.event? if ignore_inherited

  all_handlers(type: :event)[event]&.event? || false
end

.get_command(command, ignore_inherited: false) ⇒ RequestHandler?

Retrieves the RequestHandler for a given command

Parameters:

  • command (String)

    The command to retrieve

  • ignore_inherited (Booleen) (defaults to: false)

    Should the retrieval ignore any inherited commands and only check local registrations

Returns:

  • (RequestHandler, nil)

    The registered handler for the command if any



328
329
330
331
332
333
334
# File 'lib/active_matrix/bot/base.rb', line 328

def get_command(command, ignore_inherited: false)
  if ignore_inherited && @handlers[command]&.command?
    @handlers[command]
  elsif !ignore_inherited && all_handlers[command]&.command?
    all_handlers[command]
  end
end

.get_event(event, ignore_inherited: false) ⇒ RequestHandler?

Retrieves the RequestHandler for a given event

Parameters:

  • event (String)

    The event type to retrieve

  • ignore_inherited (Booleen) (defaults to: false)

    Should the retrieval ignore any inherited events and only check local registrations

Returns:

  • (RequestHandler, nil)

    The registered handler for the event if any



341
342
343
344
345
346
347
# File 'lib/active_matrix/bot/base.rb', line 341

def get_event(event, ignore_inherited: false)
  if ignore_inherited && @handlers[event]&.event?
    @handlers[event]
  elsif !ignore_inherited && all_handlers(type: :event)[event]&.event?
    all_handlers(type: :event)[event]
  end
end

.loggerObject



62
63
64
# File 'lib/active_matrix/bot/base.rb', line 62

def self.logger
  @logger ||= ActiveMatrix.logger
end

.quit!Object

Stops any running instance of the bot



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/active_matrix/bot/base.rb', line 372

def quit!
  return unless running?

  active_bot.logger.info "Stopping #{settings.bot_name}..."

  if settings.store_sync_token
    begin
      active_bot.client.api.(
        active_bot.client.mxid, "dev.ananace.ruby-sdk.#{settings.bot_name}",
        { sync_token: active_bot.client.sync_token }
      )
    rescue StandardError => e
      active_bot.logger.error "Failed to save sync token, #{e.class}: #{e}"
    end
  end

  active_bot.client.logout if login?

  active_bot.client.api.stop_inflight
  active_bot.client.stop_listener_thread

  set :active_bot, nil
end

.remove_command(command) ⇒ Object

Note:

This will only affect local commands, not ones inherited

Removes a registered command from the bot

Parameters:

  • command (String)

    The command to remove



353
354
355
356
357
358
# File 'lib/active_matrix/bot/base.rb', line 353

def remove_command(command)
  return false unless @handlers[command]&.command?

  @handers.delete command
  true
end

.remove_event(event) ⇒ Object

Note:

This will only affect local event, not ones inherited

Removes a registered event from the bot

Parameters:

  • event (String)

    The event to remove



364
365
366
367
368
369
# File 'lib/active_matrix/bot/base.rb', line 364

def remove_event(event)
  return false unless @handlers[event]&.event?

  @handers.delete event
  true
end

.reset!Object

Reset the bot class, removing any local handlers that have been registered



170
171
172
173
# File 'lib/active_matrix/bot/base.rb', line 170

def reset!
  @handlers = {}
  @client_handler = nil
end

.run!(options = {}) ⇒ Object

Starts the bot up

Parameters:

  • options (Hash) (defaults to: {})

    Settings to apply using Base.set



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/active_matrix/bot/base.rb', line 399

def run!(options = {}, &)
  return if running?

  set options

  bot_settings = settings.respond_to?(:bot_settings) ? settings.bot_settings : {}
  bot_settings.merge!(
    threadsafe: settings.threadsafe,
    client_cache: settings.client_cache,
    sync_filter: settings.sync_filter
  )

  bot_settings[:auth] = if settings.access_token?
                          { access_token: settings.access_token }
                        else
                          { username: settings.username, password: settings.password }
                        end

  begin
    start_bot(bot_settings, &)
  ensure
    quit!
  end
end

.running?Boolean

Check whether the self-hosted server is running or not.

Returns:

  • (Boolean)


425
426
427
# File 'lib/active_matrix/bot/base.rb', line 425

def running?
  active_bot?
end

.set(option, value = (not_set = true), ignore_setter = false, &block) ⇒ Object

Set a class-wide option for the bot

Parameters:

  • option (Symbol, Hash)

    The option/options to set

  • value (Proc, Symbol, Integer, Boolean, Hash, nil) (defaults to: (not_set = true))

    The value to set for the option, should be ignored if option is a Hash

  • ignore_setter (Boolean) (defaults to: false)

    Should any existing setter method be ignored during assigning of the option

Yield Returns:

  • The value that the option should return when requested, as an alternative to passing the Proc as value

Raises:

  • (ArgumentError)


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/active_matrix/bot/base.rb', line 190

def set(option, value = (not_set = true), ignore_setter = false, &block) # rubocop:disable Style/OptionalBooleanParameter
  raise ArgumentError if block && !not_set

  if block
    value = block
    not_set = false
  end

  if not_set
    raise ArgumentError unless option.respond_to?(:each)

    option.each { |k, v| set(k, v) }
    return self
  end

  return send("#{option}=", value) if respond_to?("#{option}=") && !ignore_setter

  setter = proc { |val| set option, val, true }
  getter = proc { value }

  case value
  when Proc
    getter = value
  when Symbol, Integer, FalseClass, TrueClass, NilClass
    getter = value.inspect
  when Hash
    setter = proc do |val|
      val = value.merge val if val.is_a? Hash
      set option, val, true
    end
  end

  define_singleton("#{option}=", setter)
  define_singleton(option, getter)
  define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
  self
end

.settingsObject

Access settings defined with Base.set



136
137
138
# File 'lib/active_matrix/bot/base.rb', line 136

def self.settings
  self
end

Instance Method Details

#botObject



593
594
595
# File 'lib/active_matrix/bot/base.rb', line 593

def bot
  self
end

#command?(command, **params) ⇒ Boolean

Checks for the existence of a command

Parameters:

  • command (String)

    The command to check

Returns:

  • (Boolean)

See Also:



120
121
122
# File 'lib/active_matrix/bot/base.rb', line 120

def command?(command, **params)
  self.class.command?(command, **params)
end

#command_allowed?(command, event) ⇒ Boolean

Returns:

  • (Boolean)


525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/active_matrix/bot/base.rb', line 525

def command_allowed?(command, event)
  pre_event = @event

  return false unless command? command

  handler = get_command(command)
  return true if (handler.data[:only] || []).empty?

  # Avoid modifying input data for a checking method
  @event = ActiveMatrix::Response.new(client.api, event.dup)
  return false if [handler.data[:only]].flatten.compact.any? do |only|
    if only.is_a? Proc
      !instance_exec(&only)
    else
      case only.to_s.downcase.to_sym
      when :dm
        !room.dm?(members_only: true)
      when :admin
        !sender_admin?
      when :mod
        !sender_moderator?
      end
    end
  end

  true
ensure
  @event = pre_event
end

#event?(event, **params) ⇒ Boolean

Checks for the existence of a handled event

Parameters:

  • event (String)

    The event to check

Returns:

  • (Boolean)

See Also:



128
129
130
# File 'lib/active_matrix/bot/base.rb', line 128

def event?(event, **params)
  self.class.event?(event, **params)
end

#event_allowed?(event) ⇒ Boolean

Returns:

  • (Boolean)


555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/active_matrix/bot/base.rb', line 555

def event_allowed?(event)
  pre_event = @event

  return false unless event? event[:type]

  handler = get_event(event[:type])
  return true if (handler.data[:only] || []).empty?

  # Avoid modifying input data for a checking method
  @event = ActiveMatrix::Response.new(client.api, event.dup)
  return false if [handler.data[:only]].flatten.compact.any? do |only|
    if only.is_a? Proc
      instance_exec(&only)
    else
      case only.to_s.downcase.to_sym
      when :dm
        !room.dm?(members_only: true)
      when :admin
        !sender_admin?
      when :mod
        !sender_moderator?
      end
    end
  end

  true
ensure
  @event = pre_event
end

#expanded_prefixObject

Helpers



618
619
620
621
622
# File 'lib/active_matrix/bot/base.rb', line 618

def expanded_prefix
  return "#{settings.command_prefix}#{settings.bot_name} " if settings.bot_name?

  settings.command_prefix
end

#get_command(command, **params) ⇒ RequestHandler

Gets the handler for a command

Parameters:

  • command (String)

    The command to retrieve

Returns:

See Also:



103
104
105
# File 'lib/active_matrix/bot/base.rb', line 103

def get_command(command, **params)
  self.class.get_command(command, **params)
end

#get_event(event, **params) ⇒ RequestHandler

Gets the handler for an event

Parameters:

  • event (String)

    The event to retrieve

Returns:

See Also:



112
113
114
# File 'lib/active_matrix/bot/base.rb', line 112

def get_event(event, **params)
  self.class.get_event(event, **params)
end

#in_event?Boolean

Helpers for handling events

Returns:

  • (Boolean)


589
590
591
# File 'lib/active_matrix/bot/base.rb', line 589

def in_event?
  !@event.nil?
end

#register_command(command, **params) ⇒ Object

Register a command during runtime

Parameters:

  • command (String)

    The command to register

See Also:



70
71
72
# File 'lib/active_matrix/bot/base.rb', line 70

def register_command(command, **params, &)
  self.class.command(command, **params, &)
end

#register_event(event, **params) ⇒ Object

Register an event during runtime

Parameters:

  • event (String)

    The event to register

See Also:



78
79
80
# File 'lib/active_matrix/bot/base.rb', line 78

def register_event(event, **params, &)
  self.class.event(event, **params, &)
end

#roomObject



597
598
599
# File 'lib/active_matrix/bot/base.rb', line 597

def room
  client.ensure_room(event[:room_id]) if in_event?
end

#senderObject



601
602
603
# File 'lib/active_matrix/bot/base.rb', line 601

def sender
  client.get_user(event[:sender]) if in_event?
end

#sender_admin?Boolean

Helpers for checking power levels

Returns:

  • (Boolean)


606
607
608
# File 'lib/active_matrix/bot/base.rb', line 606

def sender_admin?
  sender&.admin? room
end

#sender_moderator?Boolean

Returns:

  • (Boolean)


610
611
612
# File 'lib/active_matrix/bot/base.rb', line 610

def sender_moderator?
  sender&.moderator? room
end

#unregister_command(command) ⇒ Object

Removes a registered command during runtime

Parameters:

  • command (String)

    The command to remove

See Also:



86
87
88
# File 'lib/active_matrix/bot/base.rb', line 86

def unregister_command(command)
  self.class.remove_command(command)
end

#unregister_event(command) ⇒ Object

Removes a registered event during runtime

Parameters:

  • event (String)

    The event to remove

See Also:



94
95
96
# File 'lib/active_matrix/bot/base.rb', line 94

def unregister_event(command)
  self.class.remove_event(command)
end