lita 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lita/handler.rb CHANGED
@@ -2,19 +2,20 @@ module Lita
2
2
  class Handler
3
3
  extend Forwardable
4
4
 
5
- attr_reader :redis
5
+ attr_reader :redis, :robot
6
6
  private :redis
7
7
 
8
- def_delegators :@message, :args, :command?, :scan
8
+ def_delegators :@message, :args, :command?, :scan, :user
9
9
 
10
- class Route < Struct.new(:pattern, :method_name, :command)
10
+ class Route < Struct.new(:pattern, :method_name, :command, :required_groups)
11
11
  alias_method :command?, :command
12
12
  end
13
13
 
14
14
  class << self
15
- def route(pattern, to: nil, command: false)
15
+ def route(pattern, to: nil, command: false, restrict_to: nil)
16
16
  @routes ||= []
17
- @routes << Route.new(pattern, to, command)
17
+ required_groups = restrict_to.nil? ? nil : Array(restrict_to)
18
+ @routes << Route.new(pattern, to, command, required_groups)
18
19
  end
19
20
 
20
21
  def dispatch(robot, message)
@@ -27,21 +28,24 @@ module Lita
27
28
  matches_for_route(route, instance)
28
29
  )
29
30
  end
30
- end
31
+ end if defined?(@routes)
31
32
  end
32
33
 
33
34
  private
34
35
 
35
36
  def route_applies?(route, instance)
36
- if route.pattern === instance.message_body
37
- if route.command?
38
- return instance.command?
39
- else
40
- return true
41
- end
37
+ # Message must match the pattern
38
+ return unless route.pattern === instance.message_body
39
+
40
+ # Message must be a command if the route requires a command
41
+ return if route.command? && !instance.command?
42
+
43
+ # User must be in auth group if route is restricted
44
+ return if route.required_groups && route.required_groups.none? do |group|
45
+ Authorization.user_in_group?(instance.user, group)
42
46
  end
43
47
 
44
- false
48
+ true
45
49
  end
46
50
 
47
51
  def matches_for_route(route, instance)
@@ -0,0 +1,60 @@
1
+ module Lita
2
+ module Handlers
3
+ class Authorization < Handler
4
+ route(/^auth\s+add/, to: :add, command: true)
5
+ route(/^auth\s+remove/, to: :remove, command: true)
6
+
7
+ def self.help
8
+ robot_name = Lita.config.robot.name
9
+
10
+ {
11
+ "#{robot_name}: auth add USER GROUP" => "Add USER to authorization group GROUP. Requires admin privileges.",
12
+ "#{robot_name}: auth remove USER GROUP" => "Remove USER from authorization group GROUP. Requires admin privileges."
13
+ }
14
+ end
15
+
16
+ def add(matches)
17
+ return unless valid_message?
18
+
19
+ if Lita::Authorization.add_user_to_group(@user, @group)
20
+ reply "#{@user.name} was added to #{@group}."
21
+ else
22
+ reply "#{@user.name} was already in #{@group}."
23
+ end
24
+ end
25
+
26
+ def remove(matches)
27
+ return unless valid_message?
28
+
29
+ if Lita::Authorization.remove_user_from_group(@user, @group)
30
+ reply "#{@user.name} was removed from #{@group}."
31
+ else
32
+ reply "#{@user.name} was not in #{@group}."
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def valid_message?
39
+ command, identifier, @group = args
40
+
41
+ unless identifier && @group
42
+ reply "Format: #{robot.name} auth add USER GROUP"
43
+ return
44
+ end
45
+
46
+ @user = User.find_by_id(identifier)
47
+ @user = User.find_by_name(identifier) unless @user
48
+
49
+ unless @user
50
+ reply %{No user was found with the identifier "#{identifier}".}
51
+ return
52
+ end
53
+
54
+ true
55
+ end
56
+ end
57
+
58
+ Lita.register_handler(Authorization)
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ module Lita
2
+ module Handlers
3
+ class Help < Handler
4
+ route(/^help\s*(.+)?/, to: :help, command: true)
5
+
6
+ def self.help
7
+ robot_name = Lita.config.robot.name
8
+
9
+ {
10
+ "#{robot_name}: help" => "Lists help information for terms and commands #{robot_name} will respond to.",
11
+ "#{robot_name}: help COMMAND" => "Lists help information for terms and commands starting with COMMAND."
12
+ }
13
+ end
14
+
15
+ def help(matches)
16
+ commands = {}
17
+
18
+ Lita.handlers.each do |handler|
19
+ commands.merge!(handler.help) if handler.respond_to?(:help)
20
+ end
21
+
22
+ filter = matches[0][0]
23
+ if filter
24
+ robot_name = Lita.config.robot.name
25
+ commands.select! do |key, value|
26
+ /^#{filter}/i === key.sub(/^\s*@?#{robot_name}[:,]?\s*/, "")
27
+ end
28
+ end
29
+
30
+ message = commands.map do |command, description|
31
+ "#{command} - #{description}"
32
+ end.join("\n")
33
+
34
+ reply message
35
+ end
36
+ end
37
+
38
+ Lita.register_handler(Help)
39
+ end
40
+ end
data/lib/lita/message.rb CHANGED
@@ -6,6 +6,7 @@ module Lita
6
6
  alias_method :message, :body
7
7
 
8
8
  def_delegators :@body, :scan
9
+ def_delegators :@source, :user
9
10
 
10
11
  def initialize(robot, body, source)
11
12
  @robot = robot
data/lib/lita/robot.rb CHANGED
@@ -13,6 +13,8 @@ module Lita
13
13
 
14
14
  def run
15
15
  @adapter.run
16
+ rescue Interrupt
17
+ shut_down
16
18
  end
17
19
 
18
20
  def send_messages(target, *strings)
@@ -20,6 +22,14 @@ module Lita
20
22
  end
21
23
  alias_method :send_message, :send_messages
22
24
 
25
+ def set_topic(target, topic)
26
+ @adapter.set_topic(target, topic)
27
+ end
28
+
29
+ def shut_down
30
+ @adapter.shut_down
31
+ end
32
+
23
33
  private
24
34
 
25
35
  def load_adapter
@@ -27,7 +37,8 @@ module Lita
27
37
  adapter_class = Lita.adapters[adapter_name.to_sym]
28
38
 
29
39
  unless adapter_class
30
- raise UnknownAdapterError.new("Unknown adapter: :#{adapter_name}")
40
+ Lita.logger.fatal("Unknown adapter: :#{adapter_name}.")
41
+ abort
31
42
  end
32
43
 
33
44
  @adapter = adapter_class.new(self)
data/lib/lita/rspec.rb CHANGED
@@ -3,18 +3,34 @@ module Lita
3
3
  def self.included(base)
4
4
  base.class_eval do
5
5
  let(:robot) { Robot.new }
6
+ let(:source) { Source.new(user) }
7
+ let(:user) { User.new("1", name: "Test User") }
6
8
 
7
9
  before do
8
10
  allow(Lita).to receive(:handlers).and_return([described_class])
9
11
  stub_const("Lita::REDIS_NAMESPACE", "lita.test")
10
12
  keys = Lita.redis.keys("*")
11
13
  Lita.redis.del(keys) unless keys.empty?
14
+ allow(robot).to receive(:send_messages)
12
15
  end
13
16
  end
14
17
  end
15
18
 
19
+ def expect_reply(*arguments, invert: false)
20
+ method = invert ? :not_to : :to
21
+ expect(robot).public_send(
22
+ method,
23
+ receive(:send_messages).with(source, *arguments)
24
+ )
25
+ end
26
+ alias_method :expect_replies, :expect_reply
27
+
28
+ def expect_no_reply(*arguments)
29
+ expect_reply(*arguments, invert: true)
30
+ end
31
+ alias_method :expect_no_replies, :expect_no_reply
32
+
16
33
  def send_test_message(body)
17
- source = Source.new("Test User")
18
34
  message = Message.new(robot, body, source)
19
35
  robot.receive(message)
20
36
  end
@@ -47,5 +63,5 @@ module Lita
47
63
  end
48
64
 
49
65
  RSpec.configure do |config|
50
- config.include Lita::RSpec, lita_handler: true
66
+ config.include Lita::RSpec, lita: true
51
67
  end
data/lib/lita/user.rb ADDED
@@ -0,0 +1,54 @@
1
+ module Lita
2
+ class User
3
+ class << self
4
+ def redis
5
+ @redis ||= Redis::Namespace.new("users", redis: Lita.redis)
6
+ end
7
+
8
+ def create(id, metadata = {})
9
+ user = find_by_id(id)
10
+ unless user
11
+ user = new(id, metadata)
12
+ user.save
13
+ end
14
+ user
15
+ end
16
+ alias_method :find, :create
17
+
18
+ def find_by_id(id)
19
+ metadata = redis.hgetall("id:#{id}")
20
+ return new(id, metadata) if metadata.key?("name")
21
+ end
22
+
23
+ def find_by_name(name)
24
+ id = redis.get("name:#{name}")
25
+ find_by_id(id) if id
26
+ end
27
+ end
28
+
29
+ attr_reader :id, :name, :metadata
30
+
31
+ def initialize(id, metadata = {})
32
+ @id = id.to_s
33
+ @metadata = metadata
34
+ @name = @metadata[:name] || @metadata["name"] || @id
35
+ end
36
+
37
+ def save
38
+ redis.pipelined do
39
+ redis.hmset("id:#{id}", *metadata.to_a.flatten)
40
+ redis.set("name:#{name}", id)
41
+ end
42
+ end
43
+
44
+ def ==(other)
45
+ id == other.id && name == other.name
46
+ end
47
+
48
+ private
49
+
50
+ def redis
51
+ self.class.redis
52
+ end
53
+ end
54
+ end
data/lib/lita/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lita
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lita.gemspec CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.required_ruby_version = ">= 2.0.0"
22
+
21
23
  spec.add_runtime_dependency "bundler", "~> 1.3"
22
24
  spec.add_runtime_dependency "redis-namespace", "~> 1.3.0"
23
25
  spec.add_runtime_dependency "thor", "~> 0.18.1"
data/skeleton/Gemfile CHANGED
@@ -7,3 +7,8 @@ gem "lita"
7
7
 
8
8
  # Uncomment to use the IRC adapter
9
9
  # gem "lita-irc"
10
+
11
+ # Add handlers to give Lita new functionality.
12
+ # For example:
13
+ # gem "lita-google-images"
14
+ # gem "lita-karma"
@@ -2,6 +2,16 @@ Lita.configure do |config|
2
2
  # The name your robot will use.
3
3
  config.robot.name = "Lita"
4
4
 
5
+ # The severity of messages to log. Options are:
6
+ # :debug, :info, :warn, :error, :fatal
7
+ # Messages at the selected level and above will be logged.
8
+ config.robot.log_level = :info
9
+
10
+ # An array of user IDs that are considered administrators. These users
11
+ # the ability to add and remove other users from authorization groups.
12
+ # What is considered a user ID will change depending on which adapter you use.
13
+ # config.robot.admins = ["1", "2"]
14
+
5
15
  # The adapter you want to connect with. Make sure you've added the
6
16
  # appropriate gem to the Gemfile.
7
17
  config.robot.adapter = :shell
@@ -3,12 +3,21 @@ require "spec_helper"
3
3
  describe Lita::Adapter do
4
4
  let(:robot) { double("Robot") }
5
5
 
6
+ let(:required_methods) { [:run, :send_messages, :set_topic, :shut_down] }
7
+
6
8
  subject { described_class.new(robot) }
7
9
 
8
10
  it "stores a Robot" do
9
11
  expect(subject.robot).to eql(robot)
10
12
  end
11
13
 
14
+ it "logs a warning if a required method has not been implemented" do
15
+ expect(Lita.logger).to receive(:warn).exactly(required_methods.size).times
16
+ required_methods.each do |method|
17
+ subject.public_send(method)
18
+ end
19
+ end
20
+
12
21
  describe ".require_config" do
13
22
  let(:adapter_class) do
14
23
  Class.new(described_class) do
@@ -21,9 +30,8 @@ describe Lita::Adapter do
21
30
  subject { adapter_class.new(robot) }
22
31
 
23
32
  it "ensures that config keys are present on initialization" do
24
- expect do
25
- subject
26
- end.to raise_error(Lita::ConfigError, /foo, bar, baz, blah, bleh/)
33
+ expect(Lita.logger).to receive(:fatal).with(/foo, bar, baz, blah, bleh/)
34
+ expect { subject }.to raise_error(SystemExit)
27
35
  end
28
36
  end
29
37
  end
@@ -12,16 +12,25 @@ describe Lita::Adapters::Shell do
12
12
 
13
13
  it "passes input to the Robot and breaks on an exit message" do
14
14
  expect(subject).to receive(:print).with("#{robot.name} > ").twice
15
- allow(subject).to receive(:gets).and_return("foo", "exit")
15
+ allow($stdin).to receive(:gets).and_return("foo", "exit")
16
16
  expect(robot).to receive(:receive).with(an_instance_of(Lita::Message))
17
+ allow(Thread).to receive(:new) { |&block| block.call }
17
18
  subject.run
18
19
  end
19
20
  end
20
21
 
21
22
  describe "#send_message" do
22
23
  it "prints its input" do
24
+ expect(subject).to receive(:puts)
23
25
  expect(subject).to receive(:puts).with("bar")
24
26
  subject.send_messages(double("target"), "bar")
25
27
  end
26
28
  end
29
+
30
+ describe "#shut_down" do
31
+ it "outputs a blank line" do
32
+ expect(subject).to receive(:puts)
33
+ subject.shut_down
34
+ end
35
+ end
27
36
  end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Authorization, lita: true do
4
+ let(:user) do
5
+ user = double("User")
6
+ allow(user).to receive(:id).and_return("1")
7
+ user
8
+ end
9
+
10
+ describe ".add_user_to_group" do
11
+ it "adds users to an auth group" do
12
+ allow(described_class).to receive(:user_is_admin?).and_return(true)
13
+ described_class.add_user_to_group(user, "employees")
14
+ expect(described_class.user_in_group?(user, "employees")).to be_true
15
+ end
16
+
17
+ it "can only be called by admins" do
18
+ allow(described_class).to receive(:user_is_admin?).and_return(false)
19
+ described_class.add_user_to_group(user, "employees")
20
+ expect(described_class.user_in_group?(user, "employees")).to be_false
21
+ end
22
+ end
23
+
24
+ describe ".remove_user_from_group" do
25
+ it "removes users from an auth group" do
26
+ allow(described_class).to receive(:user_is_admin?).and_return(true)
27
+ described_class.add_user_to_group(user, "employees")
28
+ described_class.remove_user_from_group(user, "employees")
29
+ expect(described_class.user_in_group?(user, "employees")).to be_false
30
+ end
31
+
32
+ it "can only be called by admins" do
33
+ allow(described_class).to receive(:user_is_admin?).and_return(true)
34
+ described_class.add_user_to_group(user, "employees")
35
+ allow(described_class).to receive(:user_is_admin?).and_return(false)
36
+ described_class.remove_user_from_group(user, "employees")
37
+ expect(described_class.user_in_group?(user, "employees")).to be_true
38
+ end
39
+ end
40
+
41
+ describe ".user_in_group?" do
42
+ # Positive case is covered by .add_user_to_group's example.
43
+
44
+ it "returns false if the user is in the group" do
45
+ expect(described_class.user_in_group?(user, "employees")).to be_false
46
+ end
47
+ end
48
+
49
+ describe ".user_is_admin?" do
50
+ it "returns true if the user's ID is in the config" do
51
+ Lita.config.robot.admins = "1"
52
+ expect(described_class.user_is_admin?(user)).to be_true
53
+ end
54
+
55
+ it "returns false if the user's ID is not in the config" do
56
+ Lita.config.robot.admins = nil
57
+ expect(described_class.user_is_admin?(user)).to be_false
58
+ end
59
+ end
60
+ end