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.



143
144
145
# File 'lib/active_matrix/bot/base.rb', line 143

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



181
182
183
184
# File 'lib/active_matrix/bot/base.rb', line 181

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



301
302
303
# File 'lib/active_matrix/bot/base.rb', line 301

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):



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

def command(command, desc: nil, notes: nil, only: nil, **params, &)
  args = params[:args] || convert_to_lambda(&).parameters.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.compact.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)


309
310
311
312
313
# File 'lib/active_matrix/bot/base.rb', line 309

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



240
241
242
# File 'lib/active_matrix/bot/base.rb', line 240

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



233
234
235
# File 'lib/active_matrix/bot/base.rb', line 233

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



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

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)


319
320
321
322
323
# File 'lib/active_matrix/bot/base.rb', line 319

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



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

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



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

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



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

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



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

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



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

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



172
173
174
175
# File 'lib/active_matrix/bot/base.rb', line 172

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



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

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)


427
428
429
# File 'lib/active_matrix/bot/base.rb', line 427

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)


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
227
228
# File 'lib/active_matrix/bot/base.rb', line 192

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



138
139
140
# File 'lib/active_matrix/bot/base.rb', line 138

def self.settings
  self
end

Instance Method Details

#botObject



595
596
597
# File 'lib/active_matrix/bot/base.rb', line 595

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)


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
554
555
# File 'lib/active_matrix/bot/base.rb', line 527

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)


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
584
585
# File 'lib/active_matrix/bot/base.rb', line 557

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



620
621
622
623
624
# File 'lib/active_matrix/bot/base.rb', line 620

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)


591
592
593
# File 'lib/active_matrix/bot/base.rb', line 591

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



599
600
601
# File 'lib/active_matrix/bot/base.rb', line 599

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

#senderObject



603
604
605
# File 'lib/active_matrix/bot/base.rb', line 603

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

#sender_admin?Boolean

Helpers for checking power levels

Returns:

  • (Boolean)


608
609
610
# File 'lib/active_matrix/bot/base.rb', line 608

def sender_admin?
  sender&.admin? room
end

#sender_moderator?Boolean

Returns:

  • (Boolean)


612
613
614
# File 'lib/active_matrix/bot/base.rb', line 612

def sender_moderator?
  sender&.moderator? room
end

#settingsObject

Access settings defined with Base.set



133
134
135
# File 'lib/active_matrix/bot/base.rb', line 133

def settings
  self.class.settings
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