chatrix-bot 1.0.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'chatrix/bot/command'
5
+ require 'chatrix/bot/pattern'
6
+
7
+ module Chatrix
8
+ class Bot
9
+ # Base class for bot plugins.
10
+ #
11
+ # Plugins will get their class name (lowercased) as their main
12
+ # command name. Meaning if you make a class as such:
13
+ # class MyPlugin < Plugin
14
+ # It will be called using `!myplugin` in the chat.
15
+ #
16
+ # Additional command aliases can be specified with TODO.
17
+ class Plugin
18
+ # Initializes a new Plugin instance.
19
+ # @param bot [Chatrix::Bot] The bot instance in control of the plugin.
20
+ def initialize(bot)
21
+ @bot = bot
22
+ @log = @bot.log
23
+ end
24
+
25
+ # Handles a plugin command.
26
+ #
27
+ # @param room [Room] The room the command was sent in.
28
+ # @param sender [User] The user who issued the command.
29
+ # @param name [String] The name of the command.
30
+ # @param body [String] The command body (text after command name).
31
+ def handle_command(room, sender, name, body)
32
+ command = self.class.command name
33
+
34
+ unless @bot.admin? sender
35
+ check_command_permissions(room, command, sender)
36
+ end
37
+
38
+ data = command.parse body
39
+ meth = command.handler || :on_command
40
+ send(meth, room, sender, command.name, data) if respond_to? meth
41
+ end
42
+
43
+ # Parses a message from a room.
44
+ # If there are any patterns registred for the plugin that matches the
45
+ # message body, the relevant handlers on the plugin will be invoked for
46
+ # the message.
47
+ #
48
+ # All messages, regardless of patterns, will be passed to the
49
+ # `:on_message` method, if it is defined.
50
+ #
51
+ # @param room [Room] The room the message was sent in.
52
+ # @param sender [User] The user who sent the message.
53
+ # @param message [Message] The message that was sent.
54
+ def parse_message(room, message)
55
+ send(:on_message, room, message) if respond_to? :on_message
56
+ pattern = self.class.match message.body
57
+ handle_match(room, message, pattern) if pattern
58
+ rescue => e
59
+ @log.error "Error while parsing message in #{self.class}: #{e.inspect}"
60
+ end
61
+
62
+ private
63
+
64
+ def handle_match(room, message, pattern)
65
+ meth = pattern.handler || :on_match
66
+ return unless respond_to? meth
67
+ send meth, room, message, pattern.match(message.body)
68
+ end
69
+
70
+ def check_command_permissions(room, command, sender)
71
+ user_power = sender.power_in room
72
+ raise PermissionError unless user_power >= self.class.command_power
73
+ command.test
74
+ end
75
+
76
+ class << self
77
+ attr_reader :commands
78
+
79
+ attr_reader :command_power
80
+
81
+ def inherited(subclass)
82
+ {
83
+ :@commands => [], :@patterns => [], :@command_power => 0
84
+ }.each { |var, val| subclass.instance_variable_set(var, val) }
85
+ end
86
+
87
+ def command?(name)
88
+ !command(name).nil?
89
+ end
90
+
91
+ # Gets the command with the specified name (or alias).
92
+ # @param name [String] The name to search for, can also be an alias.
93
+ # @return [Command] The command with the specified name or alias.
94
+ def command(name)
95
+ @commands.find { |c| c.name_or_alias?(name) }
96
+ end
97
+
98
+ # Attempts to match the given string against all the patterns defined
99
+ # for the plugin.
100
+ # @param text [String] The text to check for matches.
101
+ # @return [Pattern, nil] The result of the first successful match,
102
+ # or `nil` if no match was found.
103
+ def match(text)
104
+ @patterns.find { |p| p.match? text }
105
+ end
106
+
107
+ protected
108
+
109
+ # Registers a command that the plugin should respond to.
110
+ #
111
+ # @param command [String] The name of the command.
112
+ # @param syntax [String] Command syntax, this is used to determine
113
+ # the parameters accepted by the command. See {Parameter} for more
114
+ # information on parameters and {Command} for more information
115
+ # regarding the structure of the syntax string.
116
+ # @param help [String] Help text for the command.
117
+ # @param opts [Hash] Additional options.
118
+ #
119
+ # @option opts [Symbol] :handler Name of the method in the plugin class
120
+ # that should handle this command.
121
+ # @option opts [Array<String>] :aliases A list of aliases that can be
122
+ # used to call this command in addition to the main name.
123
+ def register_command(command, syntax, help, opts = {})
124
+ @commands.push Command.new command.to_s.downcase, syntax, help, opts
125
+ end
126
+
127
+ # Registers a RegEx pattern that the plugin should listen to
128
+ # with an optional explicit handler for the match.
129
+ #
130
+ # @param pattern [Regexp] The RegEx pattern to add.
131
+ # @param handler [Symbol, nil] The method to call on the plugin when
132
+ # a matching message is detected.
133
+ def register_pattern(pattern, handler = nil)
134
+ @patterns.push Pattern.new pattern, handler
135
+ end
136
+
137
+ # Adds RegEx patterns to the plugin.
138
+ # @param patterns [Regexp] RegEx patterns to add.
139
+ def register_patterns(*patterns)
140
+ patterns.each { |p| @patterns.push Pattern.new p }
141
+ end
142
+
143
+ def command_restriction(level)
144
+ @command_power = level
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'chatrix/bot/plugins'
5
+
6
+ module Chatrix
7
+ class Bot
8
+ # Manages plugins for the bot.
9
+ class PluginManager
10
+ def initialize(bot)
11
+ @bot = bot
12
+
13
+ @log = @bot.log
14
+
15
+ # The plugins hash will have the plugin type as the key, and the
16
+ # instance of the plugin as the value.
17
+ @plugins = {}
18
+
19
+ add_standard_plugins
20
+ end
21
+
22
+ def add(type)
23
+ unless type < Plugin
24
+ raise ArgumentError, 'Argument must be a plugin type'
25
+ end
26
+
27
+ return if @plugins.key? type
28
+
29
+ @log.info "Adding new plugin: #{type}"
30
+
31
+ @plugins[type] = type.new @bot
32
+ end
33
+
34
+ def remove(type)
35
+ return unless @plugins.key? type
36
+ @log.info "Removing plugin: #{type}"
37
+ plugin = @plugins[type]
38
+ plugin.removed if plugin.respond_to? :removed
39
+ @plugins[type] = nil
40
+ end
41
+
42
+ def types
43
+ @plugins.keys
44
+ end
45
+
46
+ def plugins
47
+ @plugins.values
48
+ end
49
+
50
+ def parse_message(room, message)
51
+ return parse_command(room, message) if Command.command? message.body
52
+ plugins.each { |p| p.parse_message room, message }
53
+ end
54
+
55
+ private
56
+
57
+ def parse_command(room, message)
58
+ data = Command.parse message.body
59
+
60
+ type = types.find { |t| t.command? data[:name] }
61
+
62
+ plugin = @plugins[type]
63
+
64
+ send_command(plugin, room, message.sender, data) if plugin
65
+ end
66
+
67
+ def send_command(plugin, room, sender, data)
68
+ plugin.handle_command(room, sender, data[:name], data[:body])
69
+ rescue PermissionError
70
+ room.messaging.send_notice <<~EOF
71
+ I'm sorry, #{sender}, I cannot let you do that.
72
+ EOF
73
+ rescue => e
74
+ @log.error "Error parsing #{data[:name]} command in #{room}"
75
+ room.messaging.send_notice "Command error: #{e.class}:#{e.message}"
76
+ end
77
+
78
+ def add_standard_plugins
79
+ Plugins.constants.each { |c| add(Plugins.const_get(c)) }
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'chatrix/bot/plugin'
5
+
6
+ module Chatrix
7
+ class Bot
8
+ # Contains a number of standard plugins for the bot.
9
+ module Plugins
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'chatrix/bot/plugins/help'
15
+ require 'chatrix/bot/plugins/echo'
16
+ require 'chatrix/bot/plugins/hug'
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Chatrix
5
+ class Bot
6
+ module Plugins
7
+ # Provides a command to make the bot echo what was passed in as
8
+ # argument to the command.
9
+ class Echo < Plugin
10
+ command_restriction 50
11
+
12
+ register_command 'echo', '<text>',
13
+ 'Makes the bot echo the specified text to chat',
14
+ aliases: ['say'], handler: :say
15
+
16
+ register_command 'act', '<text>',
17
+ 'Makes the bot perform an emote',
18
+ aliases: %w(em emote), handler: :emote
19
+
20
+ register_command 'notice', '<text>', 'Sends a notice', handler: :notice
21
+
22
+ def say(room, _sender, _command, args)
23
+ room.messaging.send_message args[:text]
24
+ end
25
+
26
+ def emote(room, _sender, _command, args)
27
+ room.messaging.send_emote args[:text]
28
+ end
29
+
30
+ def notice(room, _sender, _command, args)
31
+ room.messaging.send_notice args[:text]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chatrix
4
+ class Bot
5
+ module Plugins
6
+ # Provides a help command.
7
+ class Help < Plugin
8
+ register_command 'help', '[command]',
9
+ 'Gives general help or specific help for a command',
10
+ handler: :help, aliases: ['h']
11
+
12
+ def help(room, sender, _command, args)
13
+ @log.debug "#{sender} is requesting help"
14
+ command = args[:command]
15
+ command.nil? ? general_help(room) : command_help(room, command)
16
+ end
17
+
18
+ private
19
+
20
+ def general_help(room)
21
+ commands = []
22
+
23
+ @bot.plugin_manager.types.each do |type|
24
+ commands.concat type.commands.map(&:name)
25
+ end
26
+
27
+ room.messaging.send_notice <<~EOF
28
+ Available commands: #{commands.uniq.join ', '}
29
+ For help about a command, type #{Command.stylize('help')} <command>
30
+ EOF
31
+ end
32
+
33
+ def command_help(room, command)
34
+ type = @bot.plugin_manager.types.find { |t| t.command? command }
35
+
36
+ cmd = type.command(command) if type
37
+
38
+ if cmd
39
+ room.messaging.send_notice <<~EOF
40
+ Command #{cmd.name} provided by #{type} plugin.
41
+ #{cmd.usage}
42
+ EOF
43
+ else
44
+ room.messaging.send_notice 'No plugin found providing that command'
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chatrix
4
+ class Bot
5
+ module Plugins
6
+ # Plugin that hugs sad people!
7
+ class Hug < Plugin
8
+ # Matches :(, :'(, D:, D':, ):, )':, ;-;, ;_;
9
+ register_pattern(/(?<!\S)(?:\:'?\(|D'?:|\)'?:|;[\-_];)(?!\S)/, :hug)
10
+ register_pattern(/(?<!\S)(?:\:'?c)(?!\S)/i, :hug)
11
+
12
+ # Method to handle messages that contain sad faces.
13
+ def hug(room, message, _match)
14
+ sender = message.sender
15
+ @log.debug "#{sender} is in need of some love!"
16
+ room.messaging.send_emote "hugs #{sender.displayname || sender}"
17
+ @log.debug 'Love sent'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chatrix
4
+ class Bot
5
+ # The current version of chatrix-bot.
6
+ VERSION = '1.0.0.pre'
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chatrix-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre
5
+ platform: ruby
6
+ authors:
7
+ - Adam Hellberg
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chatrix
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ description:
42
+ email:
43
+ - sharparam@sharparam.com
44
+ executables:
45
+ - chatrix-bot
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".editorconfig"
50
+ - ".gitignore"
51
+ - ".rspec"
52
+ - ".rubocop.yml"
53
+ - ".travis.yml"
54
+ - ".yardopts"
55
+ - Gemfile
56
+ - Guardfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - bin/console
61
+ - bin/setup
62
+ - chatrix-bot.gemspec
63
+ - exe/chatrix-bot
64
+ - lib/chatrix/bot.rb
65
+ - lib/chatrix/bot/command.rb
66
+ - lib/chatrix/bot/config.rb
67
+ - lib/chatrix/bot/errors.rb
68
+ - lib/chatrix/bot/parameter.rb
69
+ - lib/chatrix/bot/pattern.rb
70
+ - lib/chatrix/bot/plugin.rb
71
+ - lib/chatrix/bot/plugin_manager.rb
72
+ - lib/chatrix/bot/plugins.rb
73
+ - lib/chatrix/bot/plugins/echo.rb
74
+ - lib/chatrix/bot/plugins/help.rb
75
+ - lib/chatrix/bot/plugins/hug.rb
76
+ - lib/chatrix/bot/version.rb
77
+ homepage: https://github.com/Sharparam/chatrix-bot
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 2.3.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">"
93
+ - !ruby/object:Gem::Version
94
+ version: 1.3.1
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.5.1
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: A Ruby chatbot for Matrix with plugin support
101
+ test_files: []
102
+ has_rdoc: