lita-xmpp 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d65417c4dc7ef7d0be92d6db60e0db9eed3455c9
4
+ data.tar.gz: d36fd22fb5d5632f8b57afb3125cb58a469abda4
5
+ SHA512:
6
+ metadata.gz: e0d6e17bbf0d0a3fc24c7d711d128067fa8613f383bc75f4ea763e33e7f5f671d1ea70a950be8b793340a9355f53a0ef1d207ede7bb6d9232f1546646393e333
7
+ data.tar.gz: b77da06f7083964364a7b608679e2eb2a49a5b6a7544fc8cf5270cdbed7d4437d3e8fcb41ad95d80b2902b0a07b0ec2295596c6535613f6f06abfd381b0e8692
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ lita_config.rb
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script: bundle exec rspec
5
+ before_install:
6
+ - gem update --system
7
+ notifications:
8
+ webhooks:
9
+ urls:
10
+ - https://lita-freenode.herokuapp.com/travis
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Jimmy Cuadra
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # lita-xmpp
2
+
3
+ WIP, probably has some bugs.
4
+
5
+ **lita-xmpp** is an adapter for [Lita](https://github.com/jimmycuadra/lita) that allows you to use the robot with [HipChat](https://www.hipchat.com/).
6
+
7
+ ## Installation
8
+
9
+ Add lita-xmpp to your Lita instance's Gemfile:
10
+
11
+ ``` ruby
12
+ gem "lita-xmpp"
13
+ ```
14
+
15
+ ## Configuration
16
+
17
+
18
+ ### Required attributes
19
+
20
+ * `jid` (String) - The JID of your robot's account. Default: `nil`.
21
+ * `password` (String) - The password for your robot's account. Default: `nil`.
22
+
23
+ ### Optional attributes
24
+
25
+ * `debug` (Boolean) - If `true`, turns on the underlying Jabber library's (xmpp4r) logger, which is fairly verbose. Default: `false`.
26
+ * `rooms` (Symbol, Array<String>) - An array of room JIDs that Lita should join upon connection. Can also be the symbol `:all`, which will cause Lita to discover and join all rooms. Default: `nil` (no rooms).
27
+ * `muc_domain` (String) - The XMPP Multi-User Chat domain to use.
28
+
29
+ There's no need to set `config.robot.mention_name` manually. The adapter will load the proper mention name from the XMPP roster upon connection.
30
+
31
+ ### Example
32
+
33
+ ``` ruby
34
+ Lita.configure do |config|
35
+ config.robot.name = "Lita Bot"
36
+ config.robot.adapter = :xmpp
37
+ config.adapter.jid = "12345_123456@myserver.com"
38
+ config.adapter.password = "secret"
39
+ config.adapter.debug = false
40
+ config.adapter.rooms = :all
41
+ config.adapter.muc_domain = "conf.myserver.com"
42
+ config.mention_name = "bot"
43
+ end
44
+ ```
45
+
46
+ ## License
47
+
48
+ [MIT](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/lita-xmpp.rb ADDED
@@ -0,0 +1 @@
1
+ require "lita/adapters/xmpp"
@@ -0,0 +1,78 @@
1
+ require "lita"
2
+ require "lita/adapters/xmpp/connector"
3
+
4
+ module Lita
5
+ module Adapters
6
+ class Xmpp < Adapter
7
+ require_configs :jid, :password
8
+
9
+ attr_reader :connector
10
+
11
+ def initialize(robot)
12
+ super
13
+
14
+ set_default_config_values
15
+
16
+ @connector = Connector.new(
17
+ robot,
18
+ config.jid,
19
+ config.password,
20
+ debug: config.debug,
21
+ connect_domain: config.connect_domain
22
+ )
23
+ end
24
+
25
+ def run
26
+ connector.connect
27
+ connector.join_rooms(config.muc_domain, rooms)
28
+ sleep
29
+ rescue Interrupt
30
+ shut_down
31
+ end
32
+
33
+ def send_messages(target, strings)
34
+ if target.room
35
+ connector.message_muc(target.room, strings)
36
+ else
37
+ connector.message_jid(target.user.id, strings)
38
+ end
39
+ end
40
+
41
+ def send_raw_messages(target, strings)
42
+ if target.room
43
+ connector.message_muc(target.room, strings, true)
44
+ else
45
+ connector.message_jid(target.user.id, strings)
46
+ end
47
+ end
48
+
49
+ def set_topic(target, topic)
50
+ connector.set_topic(target.room, topic)
51
+ end
52
+
53
+ def shut_down
54
+ connector.shut_down
55
+ end
56
+
57
+ private
58
+
59
+ def config
60
+ Lita.config.adapter
61
+ end
62
+
63
+ def rooms
64
+ if config.rooms == :all
65
+ connector.list_rooms(config.muc_domain)
66
+ else
67
+ Array(config.rooms)
68
+ end
69
+ end
70
+
71
+ def set_default_config_values
72
+ config.debug = false if config.debug.nil?
73
+ end
74
+ end
75
+
76
+ Lita.register_adapter(:xmpp, Xmpp)
77
+ end
78
+ end
@@ -0,0 +1,90 @@
1
+ module Lita
2
+ module Adapters
3
+ class Xmpp < Adapter
4
+ class Callback
5
+ attr_reader :robot, :roster
6
+
7
+ def initialize(robot, roster)
8
+ @robot = robot
9
+ @roster = roster
10
+ @start_time = Time.now.utc
11
+ end
12
+
13
+ def private_message(client)
14
+ return
15
+ client.add_message_callback do |m|
16
+ next if m.type == :error || m.body.nil?
17
+ user = user_by_jid(m.from)
18
+ source = Source.new(user)
19
+ message = Message.new(robot, m.body, source)
20
+ message.command!
21
+ Lita.logger.debug("Dispatching PM to Lita from #{user.id}.")
22
+ robot.receive(message)
23
+ end
24
+ end
25
+
26
+ def muc_message(muc)
27
+ muc.on_message do |time, nick, text|
28
+ if time.is_a?(Time) && time < @start_time
29
+ Lita.logger.debug "#{time} < #{@start_time} Skipping #{nick}: #{text}"
30
+ next
31
+ else
32
+ user = user_by_name(nick)
33
+ source = Source.new(user, muc.jid.bare.to_s)
34
+ message = Message.new(robot, text, source)
35
+ Lita.logger.debug(
36
+ "Dispatching message to Lita from #{user.id} in MUC #{muc.jid}."
37
+ )
38
+ robot.receive(message)
39
+ end
40
+ end
41
+ end
42
+
43
+ def roster_update
44
+ roster.add_update_callback do |old_item, item|
45
+ jid = item.attributes["jid"]
46
+ Lita.logger.debug("Updating record for user with ID: #{jid}.")
47
+ create_user(item.attributes)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def jid_without_domain(jid)
54
+ jid.to_s.downcase.sub(/@.*/, '')
55
+ end
56
+
57
+ def create_user(user_data)
58
+ name = user_data['name'].downcase.gsub(/\s+/, '.')
59
+ User.create(
60
+ user_data["jid"],
61
+ name: name,
62
+ mention_name: jid_without_domain(user_data['jid'])
63
+ )
64
+ end
65
+
66
+ def user_by_jid(jid)
67
+ Lita.logger.debug("Looking up user with JID: #{jid}.")
68
+ create_user(roster[jid].attributes)
69
+ end
70
+
71
+ def user_by_name(name)
72
+ name = name.to_s.downcase
73
+ Lita.logger.info("Looking up user with name: #{name}.")
74
+ items = roster.items.detect do |jid, item|
75
+ Lita.logger.debug("Checking #{jid}: #{jid_without_domain(jid)} => #{name}.")
76
+ jid_without_domain(jid) == name || jid_without_domain(jid).sub(/\./, ' ') == name
77
+ end
78
+
79
+ if items
80
+ Lita.logger.info("Looking up user by jid: #{items.first}.")
81
+ user_by_jid(items.first)
82
+ else
83
+ Lita.logger.warn "No user with the name #{name.inspect} was found in the roster"
84
+ User.new(nil, name: name)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,138 @@
1
+ require "lita/encoding_patches"
2
+ require "lita/adapters/xmpp/callback"
3
+
4
+ require "xmpp4r"
5
+ require "xmpp4r/roster/helper/roster"
6
+ require "xmpp4r/muc/helper/simplemucclient"
7
+ require "xmpp4r/muc/helper/mucbrowser"
8
+
9
+ module Lita
10
+ module Adapters
11
+ class Xmpp < Adapter
12
+ class Connector
13
+ attr_reader :robot, :client, :roster
14
+
15
+ def initialize(robot, jid, password, debug: false, connect_domain: nil)
16
+ @robot = robot
17
+ @jid = jid
18
+ @password = password
19
+ @client = Jabber::Client.new(@jid)
20
+ @connect_domain = connect_domain
21
+
22
+ if debug
23
+ Lita.logger.info("Enabling Jabber log.")
24
+ Jabber.debug = true
25
+ end
26
+ end
27
+
28
+ def jid
29
+ @jid.to_s
30
+ end
31
+
32
+ def connect
33
+ client_connect
34
+ load_roster
35
+ register_message_callback
36
+ send_presence
37
+ end
38
+
39
+ def join_rooms(muc_domain, rooms)
40
+ rooms.each do |room_name|
41
+ muc = Jabber::MUC::SimpleMUCClient.new(client)
42
+ room_jid = normalized_jid(room_name, muc_domain, robot.name)
43
+ mucs[room_jid.bare.to_s] = muc
44
+ register_muc_message_callback(muc)
45
+ Lita.logger.info("Joining room: #{room_jid}.")
46
+ muc.join(room_jid)
47
+ end
48
+ end
49
+
50
+ def list_rooms(muc_domain)
51
+ Lita.logger.debug("Querying server for list of rooms.")
52
+ browser = Jabber::MUC::MUCBrowser.new(client)
53
+ browser.muc_rooms(muc_domain).map { |jid, name| jid.to_s }
54
+ end
55
+
56
+ def message_jid(user_jid, strings)
57
+ strings.each do |s|
58
+ Lita.logger.debug("Sending message to JID #{user_jid}: #{s}")
59
+ message = Jabber::Message.new(user_jid, s)
60
+ message.type = :chat
61
+ client.send(message)
62
+ end
63
+ end
64
+
65
+ def message_muc(room_jid, strings, raw = false)
66
+ if muc = mucs[room_jid]
67
+ if raw
68
+ Lita.logger.debug("Sending raw message to MUC #{room_jid}: #{strings}")
69
+ muc.send(strings)
70
+ else
71
+ strings.each do |s|
72
+ Lita.logger.debug("Sending message to MUC #{room_jid}: #{s}")
73
+ muc.say(s)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def mucs
80
+ @mucs ||= {}
81
+ end
82
+
83
+ def set_topic(room_jid, topic)
84
+ muc = mucs[room_jid]
85
+ if muc
86
+ Lita.logger.debug("Setting topic for MUC #{room_jid}: #{topic}")
87
+ muc.subject = topic
88
+ end
89
+ end
90
+
91
+ def shut_down
92
+ Lita.logger.info("Disconnecting from XMPP.")
93
+ client.close
94
+ end
95
+
96
+ private
97
+
98
+ def send_presence
99
+ Lita.logger.debug("Sending initial XMPP presence.")
100
+ client.send(Jabber::Presence.new(:chat))
101
+ end
102
+
103
+ def client_connect
104
+ Lita.logger.info("Connecting to XMPP. #{@connect_domain}")
105
+ client.connect(@connect_domain)
106
+ Lita.logger.debug("Authenticating with XMPP.")
107
+ client.auth(@password)
108
+ end
109
+
110
+ def register_message_callback
111
+ Callback.new(robot, roster).private_message(client)
112
+ end
113
+
114
+ def register_muc_message_callback(muc)
115
+ Callback.new(robot, roster).muc_message(muc)
116
+ end
117
+
118
+ def load_roster
119
+ Lita.logger.debug("Loading roster.")
120
+ @roster = Jabber::Roster::Helper.new(client, false)
121
+ Callback.new(robot, roster).roster_update
122
+ roster.get_roster
123
+ roster.wait_for_roster
124
+ end
125
+
126
+ def normalized_jid(jid, domain, resource)
127
+ jid = Jabber::JID.new(jid)
128
+ jid.resource = resource
129
+ unless jid.node
130
+ jid.node = jid.domain
131
+ jid.domain = domain
132
+ end
133
+ jid
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,27 @@
1
+ # xmpp4r and REXML are bad and should feel bad.
2
+ # https://github.com/ln/xmpp4r/issues/3#issuecomment-1739952
3
+ require 'socket'
4
+ class TCPSocket
5
+ def external_encoding
6
+ Encoding::BINARY
7
+ end
8
+ end
9
+
10
+ require 'rexml/source'
11
+ class REXML::IOSource
12
+ alias_method :encoding_assign, :encoding=
13
+ def encoding=(value)
14
+ encoding_assign(value) if value
15
+ end
16
+ end
17
+
18
+ begin
19
+ # OpenSSL is optional and can be missing
20
+ require 'openssl'
21
+ class OpenSSL::SSL::SSLSocket
22
+ def external_encoding
23
+ Encoding::BINARY
24
+ end
25
+ end
26
+ rescue
27
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "lita-xmpp"
3
+ spec.version = "1.0.1"
4
+ spec.authors = ["Justin Mazzi"]
5
+ spec.email = ["jmazzi@gmail.com"]
6
+ spec.description = %q{A XMPP adapter for Lita.}
7
+ spec.summary = %q{A XMPP adapter for the Lita chat robot.}
8
+ spec.homepage = "https://github.com/jmazzi/lita-xmpp"
9
+ spec.license = "MIT"
10
+
11
+ spec.files = `git ls-files`.split($/)
12
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
13
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+ spec.require_paths = ["lib"]
15
+
16
+ spec.add_runtime_dependency "lita", "~> 2.0"
17
+ spec.add_runtime_dependency "gmcmillan-xmpp4r", "~> 0.5"
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.3"
20
+ spec.add_development_dependency "rake"
21
+ spec.add_development_dependency "rspec", "~> 2.14"
22
+ spec.add_development_dependency "simplecov"
23
+ spec.add_development_dependency "coveralls"
24
+ end
@@ -0,0 +1,126 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::HipChat::Callback, lita: true do
4
+ subject { described_class.new(robot, roster) }
5
+
6
+ let(:robot) { double("Lita::Robot") }
7
+ let(:roster) do
8
+ double("Jabber::Roster::Helper", items: { "user_id" => roster_item })
9
+ end
10
+ let(:user) { double("Lita::User", id: "user_id") }
11
+ let(:source) { double("Lita::Source") }
12
+ let(:message) { double("Lita::Message") }
13
+ let(:roster_item) do
14
+ double("Jabber::Roster::RosterItem", attributes: {
15
+ "jid" => "user_id",
16
+ "name" => "Carl",
17
+ "mention_name" => "@Carl"
18
+ }, iname: "Carl")
19
+ end
20
+
21
+ before do
22
+ allow(roster).to receive(:[]).with("user_id").and_return(roster_item)
23
+ allow(Lita::User).to receive(:create).with(
24
+ "user_id",
25
+ name: "Carl",
26
+ mention_name: "@Carl"
27
+ ).and_return(user)
28
+ end
29
+
30
+ it "has a robot" do
31
+ expect(subject.robot).to eq(robot)
32
+ end
33
+
34
+ it "has a roster" do
35
+ expect(subject.roster).to eq(roster)
36
+ end
37
+
38
+ describe "#private_message" do
39
+ let(:client) { double("Jabber::Client") }
40
+ let(:jabber_message) do
41
+ double("Jabber::Message", type: :chat, from: "user_id", body: "foo")
42
+ end
43
+
44
+ before do
45
+ allow(client).to receive(:add_message_callback).and_yield(jabber_message)
46
+ end
47
+
48
+ it "sends the message to the robot with the proper source and body" do
49
+ allow(Lita::Source).to receive(:new).with(user).and_return(source)
50
+ allow(Lita::Message).to receive(:new).with(
51
+ robot,
52
+ "foo",
53
+ source
54
+ ).and_return(message)
55
+ expect(message).to receive(:command!)
56
+ expect(robot).to receive(:receive).with(message)
57
+ subject.private_message(client)
58
+ end
59
+
60
+ it "skips the message if it's an error type" do
61
+ allow(jabber_message).to receive(:type).and_return(:error)
62
+ expect(robot).not_to receive(:receive)
63
+ subject.private_message(client)
64
+ end
65
+
66
+ it "skips the message if the body is nil" do
67
+ allow(jabber_message).to receive(:body).and_return(nil)
68
+ expect(robot).not_to receive(:receive)
69
+ subject.private_message(client)
70
+ end
71
+ end
72
+
73
+ describe "#muc_message" do
74
+ let(:jid) { double("Jabber::JID", bare: "room_id") }
75
+ let(:muc) { double("Jabber::MUC::SimpleMUCClient", jid: jid) }
76
+
77
+ before do
78
+ allow(muc).to receive(:on_message).and_yield(nil, "Carl", "foo")
79
+ end
80
+
81
+ it "sends the message to the robot with the proper source and body" do
82
+ allow(Lita::Source).to receive(:new).with(
83
+ user,
84
+ "room_id"
85
+ ).and_return(source)
86
+ allow(Lita::Message).to receive(:new).with(
87
+ robot,
88
+ "foo",
89
+ source
90
+ ).and_return(message)
91
+ expect(robot).to receive(:receive).with(message)
92
+ subject.muc_message(muc)
93
+ end
94
+
95
+ it "creates a temporary source user if the JID isn't in the roster" do
96
+ roster = double("Jabber::Roster::Helper", items: {})
97
+ allow(muc).to receive(:on_message).and_yield(nil, "Unknown", "foo")
98
+ allow(Lita::Source).to receive(:new).with(
99
+ an_instance_of(Lita::User),
100
+ "room_id"
101
+ ).and_return(source)
102
+ allow(Lita::Message).to receive(:new).with(
103
+ robot,
104
+ "foo",
105
+ source
106
+ ).and_return(message)
107
+ expect(robot).to receive(:receive).with(message)
108
+ subject.muc_message(muc)
109
+ end
110
+ end
111
+
112
+ describe "#roster_update" do
113
+ before do
114
+ allow(roster).to receive(:add_update_callback).and_yield(nil, roster_item)
115
+ end
116
+
117
+ it "finds/creates a user object for the roster item" do
118
+ expect(Lita::User).to receive(:create).with(
119
+ "user_id",
120
+ name: "Carl",
121
+ mention_name: "@Carl"
122
+ )
123
+ subject.roster_update
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,200 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::HipChat::Connector, lita: true do
4
+ subject { described_class.new(robot, "user", "secret") }
5
+
6
+ let(:client) { double("Jabber::Client").as_null_object }
7
+ let(:robot) { double("Lita::Robot", name: "Lita" ) }
8
+
9
+ before { allow(subject).to receive(:client).and_return(client) }
10
+
11
+ it "sets the JID properly when only a node is supplied" do
12
+ subject = described_class.new(robot, "user", "secret")
13
+ expect(subject.jid).to eq("user@chat.hipchat.com/bot")
14
+ end
15
+
16
+ it "sets the JID properly when a node and domain are supplied" do
17
+ subject = described_class.new(robot, "user@example.com", "secret")
18
+ expect(subject.jid).to eq("user@example.com/bot")
19
+ end
20
+
21
+ it "sets the JID properly when a resource is supplied" do
22
+ subject = described_class.new(robot, "user@example.com/wrong", "secret")
23
+ expect(subject.jid).to eq("user@example.com/bot")
24
+ end
25
+
26
+ it "turns on the xmpp4r logger if debug: true is supplied" do
27
+ expect(Jabber).to receive(:debug=).with(true)
28
+ subject = described_class.new(robot, "user", "secret", debug: true)
29
+ end
30
+
31
+ describe "#connect" do
32
+ let(:presence) { double("Jabber::Presence") }
33
+ let(:roster) { double("Jabber::Roster::Helper").as_null_object }
34
+ let(:callback) { double("Lita::Adapters::HipChat::Callback") }
35
+
36
+ before do
37
+ allow(Jabber::Presence).to receive(:new).and_return(presence)
38
+ allow(Jabber::Roster::Helper).to receive(:new).with(
39
+ client,
40
+ false
41
+ ).and_return(roster)
42
+ allow(Lita::Adapters::HipChat::Callback).to receive(:new).and_return(
43
+ callback
44
+ )
45
+ allow(callback).to receive(:private_message)
46
+ allow(callback).to receive(:roster_update)
47
+ allow(robot).to receive(:mention_name=)
48
+ end
49
+
50
+ it "connects to HipChat" do
51
+ expect(subject.client).to receive(:connect)
52
+ subject.connect
53
+ end
54
+
55
+ it "authenticates with the supplied password" do
56
+ expect(subject.client).to receive(:auth).with("secret")
57
+ subject.connect
58
+ end
59
+
60
+ it "sends an initial presence of :chat" do
61
+ expect(Jabber::Presence).to receive(:new).with(:chat).and_return(presence)
62
+ expect(subject.client).to receive(:send).with(presence)
63
+ subject.connect
64
+ end
65
+
66
+ it "registers a message callback" do
67
+ expect(Lita::Adapters::HipChat::Callback).to receive(:new).with(
68
+ robot,
69
+ roster
70
+ ).and_return(callback)
71
+ expect(callback).to receive(:private_message).with(client)
72
+ subject.connect
73
+ end
74
+
75
+ it "loads a roster" do
76
+ expect(roster).to receive(:wait_for_roster)
77
+ subject.connect
78
+ end
79
+
80
+ it "assigns the robot's mention_name with info from the roster" do
81
+ allow(roster).to receive(:[]).with(subject.jid).and_return(
82
+ double("Jabber::Roster::RosterItem", attributes: {
83
+ "mention_name" => "LitaBot"
84
+ })
85
+ )
86
+ expect(robot).to receive(:mention_name=).with("LitaBot")
87
+ subject.connect
88
+ end
89
+ end
90
+
91
+ describe "#join rooms" do
92
+ let(:muc_domain) { "conf.hipchat.com" }
93
+ let(:rooms) { ["muc_1", "muc_2"] }
94
+ let(:muc_1) { double("Jabber::MUC::SimpleMUCClient").as_null_object }
95
+ let(:muc_2) { double("Jabber::MUC::SimpleMUCClient").as_null_object }
96
+ let(:callback) { double("Lita::Adapters::HipChat::Callback") }
97
+ let(:roster) { double("Jabber::Roster::Helper") }
98
+
99
+ before do
100
+ allow(Jabber::MUC::SimpleMUCClient).to receive(:new).with(
101
+ client
102
+ ).and_return(muc_1, muc_2)
103
+ allow(Lita::Adapters::HipChat::Callback).to receive(:new).and_return(
104
+ callback
105
+ )
106
+ allow(callback).to receive(:muc_message)
107
+ allow(subject).to receive(:roster).and_return(roster)
108
+ end
109
+
110
+ it "creates a SimpleMUCClient for each room" do
111
+ subject.join_rooms(muc_domain, rooms)
112
+ expect(subject.mucs).to eq(
113
+ "muc_1@conf.hipchat.com" => muc_1,
114
+ "muc_2@conf.hipchat.com" => muc_2,
115
+ )
116
+ end
117
+
118
+ it "registers a message callback for each room" do
119
+ expect(Lita::Adapters::HipChat::Callback).to receive(:new).with(
120
+ robot,
121
+ roster
122
+ ).and_return(callback)
123
+ expect(callback).to receive(:muc_message).with(muc_1)
124
+ expect(callback).to receive(:muc_message).with(muc_2)
125
+ subject.join_rooms(muc_domain, rooms)
126
+ end
127
+
128
+ it "joins each room" do
129
+ expect(muc_1).to receive(:join)
130
+ expect(muc_2).to receive(:join)
131
+ subject.join_rooms(muc_domain, rooms)
132
+ end
133
+ end
134
+
135
+ describe "#list_rooms" do
136
+ let(:browser) { double("Jabber::MUC::MUCBrowser") }
137
+
138
+ before do
139
+ allow(Jabber::MUC::MUCBrowser).to receive(:new).with(client).and_return(
140
+ browser
141
+ )
142
+ end
143
+
144
+ it "returns an array of room JIDs for the MUC domain" do
145
+ allow(browser).to receive(:muc_rooms).with("conf.hipchat.com").and_return(
146
+ "123_456@conf.hipchat.com" => "Room 1",
147
+ "789_012@conf.hipchat.com" => "Room 2"
148
+ )
149
+ expect(subject.list_rooms("conf.hipchat.com")).to eq([
150
+ "123_456@conf.hipchat.com",
151
+ "789_012@conf.hipchat.com"
152
+ ])
153
+ end
154
+ end
155
+
156
+ describe "#message_jid" do
157
+ let(:message_1) { double("Jabber::Message") }
158
+ let(:message_2) { double("Jabber::Message") }
159
+
160
+ it "sends the messages to the user" do
161
+ allow(Jabber::Message).to receive(:new).with("jid", "foo").and_return(
162
+ message_1
163
+ )
164
+ allow(Jabber::Message).to receive(:new).with("jid", "bar").and_return(
165
+ message_2
166
+ )
167
+ expect(message_1).to receive(:type=).with(:chat)
168
+ expect(message_2).to receive(:type=).with(:chat)
169
+ expect(client).to receive(:send).with(message_1)
170
+ expect(client).to receive(:send).with(message_2)
171
+ subject.message_jid("jid", ["foo", "bar"])
172
+ end
173
+ end
174
+
175
+ describe "#message_muc" do
176
+ it "sends the messages to the room" do
177
+ muc = double("Jabber::MUC::SimpleMUCClient")
178
+ allow(subject).to receive(:mucs).and_return("jid" => muc)
179
+ expect(muc).to receive(:say).with("foo")
180
+ expect(muc).to receive(:say).with("bar")
181
+ subject.message_muc("jid", ["foo", "bar"])
182
+ end
183
+ end
184
+
185
+ describe "#set_topic" do
186
+ it "sets the room's topic to the supplied message" do
187
+ muc = double("Jabber::MUC::SimpleMUCClient")
188
+ allow(subject).to receive(:mucs).and_return("jid" => muc)
189
+ expect(muc).to receive(:subject=).with("New topic")
190
+ subject.set_topic("jid", "New topic")
191
+ end
192
+ end
193
+
194
+ describe "#shut_down" do
195
+ it "closes the client connection" do
196
+ expect(subject.client).to receive(:close)
197
+ subject.shut_down
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,121 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Adapters::HipChat do
4
+ before do
5
+ Lita.configure do |config|
6
+ config.adapter.jid = "jid"
7
+ config.adapter.password = "secret"
8
+ config.adapter.rooms = nil
9
+ config.adapter.muc_domain = nil
10
+ end
11
+
12
+ allow(described_class::Connector).to receive(:new).and_return(connector)
13
+ end
14
+
15
+ subject { described_class.new(robot) }
16
+
17
+ let(:robot) { double("Lita::Robot") }
18
+ let(:connector) { double("Lita::Adapters::HipChat::Connector") }
19
+
20
+ it "registers with Lita" do
21
+ expect(Lita.adapters[:hipchat]).to eql(described_class)
22
+ end
23
+
24
+ it "requires config.jid and config.password" do
25
+ Lita.clear_config
26
+ expect(Lita.logger).to receive(:fatal).with(/jid, password/)
27
+ expect { subject }.to raise_error(SystemExit)
28
+ end
29
+
30
+ describe "#run" do
31
+ before do
32
+ allow(subject.connector).to receive(:connect)
33
+ allow(subject.connector).to receive(:join_rooms)
34
+ allow(subject).to receive(:sleep)
35
+ end
36
+
37
+ it "connects to HipChat" do
38
+ expect(subject.connector).to receive(:connect)
39
+ subject.run
40
+ end
41
+
42
+ it "joins rooms with a custom muc_domain" do
43
+ Lita.config.adapter.muc_domain = "foo.bar.com"
44
+ expect(subject.connector).to receive(:join_rooms).with(
45
+ "foo.bar.com",
46
+ anything
47
+ )
48
+ subject.run
49
+ end
50
+
51
+ it "joins all rooms when config.rooms is :all" do
52
+ all_rooms = ["room_1_id", "room_2_id"]
53
+ Lita.config.adapter.rooms = :all
54
+ allow(subject.connector).to receive(:list_rooms).with(
55
+ "conf.hipchat.com"
56
+ ).and_return(all_rooms)
57
+ expect(subject.connector).to receive(:join_rooms).with(
58
+ "conf.hipchat.com",
59
+ all_rooms
60
+ )
61
+ subject.run
62
+ end
63
+
64
+ it "joins rooms specified by config.rooms" do
65
+ custom_rooms = ["my_room_1_id", "my_room_2_id"]
66
+ Lita.config.adapter.rooms = custom_rooms
67
+ expect(subject.connector).to receive(:join_rooms).with(
68
+ "conf.hipchat.com",
69
+ custom_rooms
70
+ )
71
+ subject.run
72
+ end
73
+
74
+ it "sleeps the main thread" do
75
+ expect(subject).to receive(:sleep)
76
+ subject.run
77
+ end
78
+
79
+ it "disconnects gracefully on interrupt" do
80
+ expect(subject).to receive(:shut_down)
81
+ allow(subject).to receive(:sleep).and_raise(Interrupt)
82
+ subject.run
83
+ end
84
+ end
85
+
86
+ describe "#send_messages" do
87
+ it "sends messages to rooms" do
88
+ source = double("Lita::Source", room: "room_id")
89
+ expect(subject.connector).to receive(:message_muc).with(
90
+ "room_id",
91
+ ["Hello!"]
92
+ )
93
+ subject.send_messages(source, ["Hello!"])
94
+ end
95
+
96
+ it "sends private messages to users" do
97
+ user = double("Lita::User", id: "user_id")
98
+ source = double("Lita::Source", room: nil, user: user)
99
+ expect(subject.connector).to receive(:message_jid).with(
100
+ "user_id",
101
+ ["Hello!"]
102
+ )
103
+ subject.send_messages(source, ["Hello!"])
104
+ end
105
+ end
106
+
107
+ describe "#set_topic" do
108
+ it "sets a new topic for a room" do
109
+ source = double("Lita::Source", room: "room_id")
110
+ expect(subject.connector).to receive(:set_topic).with("room_id", "Topic")
111
+ subject.set_topic(source, "Topic")
112
+ end
113
+ end
114
+
115
+ describe "#shut_down" do
116
+ it "shuts down the connector" do
117
+ expect(subject.connector).to receive(:shut_down)
118
+ subject.shut_down
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,10 @@
1
+ require "simplecov"
2
+ require "coveralls"
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start { add_filter "/spec/" }
8
+
9
+ require "lita-hipchat"
10
+ require "lita/rspec"
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lita-xmpp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Mazzi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lita
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: gmcmillan-xmpp4r
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.14'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A XMPP adapter for Lita.
112
+ email:
113
+ - jmazzi@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - Gemfile
121
+ - LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - lib/lita-xmpp.rb
125
+ - lib/lita/adapters/xmpp.rb
126
+ - lib/lita/adapters/xmpp/callback.rb
127
+ - lib/lita/adapters/xmpp/connector.rb
128
+ - lib/lita/encoding_patches.rb
129
+ - lita-hipchat.gemspec
130
+ - spec/lita/adapters/hipchat/callback_spec.rb
131
+ - spec/lita/adapters/hipchat/connector_spec.rb
132
+ - spec/lita/adapters/hipchat_spec.rb
133
+ - spec/spec_helper.rb
134
+ homepage: https://github.com/jmazzi/lita-xmpp
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.1.3
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: A XMPP adapter for the Lita chat robot.
158
+ test_files:
159
+ - spec/lita/adapters/hipchat/callback_spec.rb
160
+ - spec/lita/adapters/hipchat/connector_spec.rb
161
+ - spec/lita/adapters/hipchat_spec.rb
162
+ - spec/spec_helper.rb