lita-sonos-commander 1.0.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71a47b802acd8b74a12165728bd3b0fded035ab5
4
- data.tar.gz: 49ecca4439098ff7827f4d2b6ac78ddde816e4e3
3
+ metadata.gz: 8df08a9fd7ffbbe94ff51a5cc8ffbb77c26ab062
4
+ data.tar.gz: 6ce9bbb507eeec26eb03b8dc5715393762e487c6
5
5
  SHA512:
6
- metadata.gz: dae726c2765371f5459269bc7c95a6468f224d32cf0f64de0656c932241e3bc24c36028959114135512adcfa876a26ed2d390fb85acd500fe9bb22f8f9f106fd
7
- data.tar.gz: 4d22468f6a65ea06c68d426628d1908d68c53606c2cbc301ad0b26e3f6e776593a55099ce71eeea8d04e518a09f0e5dd016d8d1784720d4aa0e6353f6e5a926d
6
+ metadata.gz: e9699b15935e3210d61dcc4414097d3a176de14674ec8fbe5849ece61e0db6b751636a006ec71890d58e4126a21f9ac7dc1603ac6fb1d1a5bcb44f4290191310
7
+ data.tar.gz: 5ee1e48df4353a064d6edde6166de70ff064891f7fd1435ce653f4ceaae92b1b579de7f8a39ce1bebd83f007fb2ca69e0d1465288b17faaa91c692bb2bb509f9
@@ -1,47 +1,58 @@
1
- Lita::CommanderMiddleware = lambda do |env|
2
-
3
- if Faye::WebSocket.websocket?(env)
4
- ws = Faye::WebSocket.new(env)
5
-
6
- Lita::Handlers::SonosCommander.add_socket(ws)
1
+ class Lita::CommanderMiddleware
2
+ def self.build(open_sockets:)
3
+ new.build open_sockets: open_sockets
4
+ end
7
5
 
8
- ws.on :open do |event|
6
+ attr_reader :env, :open_sockets
7
+
8
+ def build(open_sockets:)
9
+ @open_sockets = open_sockets
10
+
11
+ return lambda do |env|
12
+ if Faye::WebSocket.websocket?(env)
13
+ @env = env
14
+ handle_env_has_socket
15
+ else
16
+ [
17
+ 200,
18
+ { 'Content-Type' => 'text/plain' },
19
+ ['Hello from a Lita chatbot! Feed me a websocket connection!']
20
+ ]
21
+ end
9
22
  end
23
+ end
10
24
 
11
- ws.on :connect do |event|
12
- end
25
+ def build_socket(env)
26
+ ws = Faye::WebSocket.new(env)
27
+ open_sockets << ws
28
+ Lita.logger.debug "Sonos client count: #{open_sockets.count}"
29
+ ws
30
+ end
13
31
 
14
- ws.on :message do |event|
15
- ws.send({ message: event.data }.to_json)
32
+ def close_socket(ws)
33
+ open_sockets.delete_if { |s| s == ws }
34
+ Lita.logger.debug "Sonos client count: #{open_sockets.count}"
35
+ p [:close, event.code, event.reason]
36
+ ws = nil
37
+ end
16
38
 
17
- sleep 0.5
18
- ws.send({ message: 'WE DID IT TWITCH', command: 'echo' }.to_json)
19
- end
39
+ def handle_message(ws, event)
40
+ ws.send({ message: event.data }.to_json)
41
+ maybe_send_debug_message(ws)
42
+ end
20
43
 
21
- ws.on :close do |event|
22
- Lita::Handlers::SonosCommander.drop_socket(ws)
44
+ def maybe_send_debug_message(ws)
45
+ sleep 0.5
46
+ ws.send({ message: 'WE DID IT TWITCH', command: 'echo' }.to_json)
47
+ end
23
48
 
24
- p [:close, event.code, event.reason]
25
- ws = nil
26
- end
49
+ def handle_env_has_socket
50
+ ws = build_socket(env)
51
+
52
+ ws.on(:message) { |event| handle_message(ws, event) }
53
+ ws.on(:close) { |event| close_socket(ws) }
27
54
 
28
55
  # Return async Rack response
29
56
  ws.rack_response
30
-
31
- else
32
- # Normal HTTP request
33
- [200, {'Content-Type' => 'text/plain'}, ['Hello']]
34
- end
35
- end
36
-
37
- def register_faye(arg)
38
- @@_sockets ||= []
39
- middleware = robot.registry.config.http.middleware
40
- result = middleware.use App
41
- end
42
-
43
- def sonos_connector(request, response)
44
- middlewares.each do |mw|
45
- mw.middleware.call(request.env)
46
57
  end
47
58
  end
@@ -1,3 +1,5 @@
1
+ require 'pry'
2
+
1
3
  require 'json'
2
4
  require 'faye/websocket'
3
5
  require 'uri'
@@ -6,70 +8,93 @@ module Lita
6
8
  module Handlers
7
9
  class SonosCommander < Handler
8
10
 
9
- http.get '/sonos/listen', :sonos_connector
11
+ # START:socket_registry
12
+ @_sockets ||= []
13
+
14
+ def self.sockets
15
+ @_sockets
16
+ end
17
+
18
+ def sockets
19
+ self.class.sockets
20
+ end
21
+ # END:socket_registry
22
+
23
+ # START:routing
24
+ http.get '/sonos/listen', :websocket_creator
10
25
 
11
- route /^play_url (.+)/, :sonos_play_url
12
- route /^say_text (.+)/, :sonos_say_text
26
+ route(/^play url (http.+)/i, :handle_sonos_play_url)
27
+ route(/^speak words (.+)/i, :handle_sonos_say_text)
13
28
 
14
29
  on :loaded, :register_faye
30
+ # END:routing
15
31
 
16
- def sonos_play_url(message)
17
- text = message.matches.last.last
18
- emit_message command: 'play_url', data: URI.escape(text)
32
+ # START:create_sockets
33
+ def websocket_creator(request, _response)
34
+ # could probably skip straight to the Commander middleware
35
+ # but it's cleaner to leave them all in play.
36
+
37
+ middleware_registry.each do |mw|
38
+ mw.middleware.call(request.env)
39
+ end
19
40
  end
41
+ # END:create_sockets
42
+
43
+ # START:chat_handlers
44
+ def handle_sonos_play_url(message)
45
+ return message.reply('No clients found!') unless sockets.any?
20
46
 
21
- def sonos_say_text(message)
22
47
  text = message.matches.last.last
23
- emit_message command: 'play_text', data: text
24
- end
48
+ play_url message.matches.last.last
25
49
 
26
- def emit_message(command:, data:)
27
- puts "emitting #{command} \t #{data}"
28
- sockets.each do |ws|
29
- ws.send(
30
- { command: command, data: { text: data, volume: 20 } }.to_json
31
- )
32
- end
50
+ message.reply "Command sent: [play_url] #{text}!"
33
51
  end
34
52
 
35
- def middlewares
36
- robot.registry.config.http.middleware
53
+ def play_url(url)
54
+ emit_message command: 'play_url', data: URI.escape(url)
37
55
  end
38
56
 
39
- def sockets
40
- self.class.sockets
41
- end
57
+ def handle_sonos_say_text(message)
58
+ return message.reply('No clients found!') unless sockets.any?
42
59
 
43
- def self.sockets
44
- @@_sockets ||= []
45
- end
60
+ text = message.matches.last.last
61
+ say_text text
46
62
 
47
- def self.add_socket(socket)
48
- puts "Tracking socket #{socket}"
49
- @@_sockets ||= []
50
- @@_sockets << socket
63
+ message.reply "Command sent: [play_text] #{text}!"
51
64
  end
52
65
 
53
- def self.drop_socket(socket)
54
- puts "Forgetting socket #{socket}"
55
- sockets.delete_if { |s| s == socket }
66
+ def say_text(text)
67
+ emit_message command: 'play_text', data: text
56
68
  end
69
+ # END:chat_handlers
57
70
 
58
- def self.serialize(message)
71
+ # START:faye_hookup
72
+ def register_faye(_arg)
73
+ socket_manager = Lita::CommanderMiddleware.build(open_sockets: sockets)
74
+ middleware_registry.use socket_manager
59
75
  end
60
76
 
61
- def register_faye(arg)
62
- @@_sockets ||= []
63
- middleware = robot.registry.config.http.middleware
64
- result = middleware.use Lita::CommanderMiddleware
77
+ def middleware_registry
78
+ robot.registry.config.http.middleware
65
79
  end
80
+ # END:faye_hookup
66
81
 
67
- def sonos_connector(request, response)
68
- middlewares.each do |mw|
69
- mw.middleware.call(request.env)
82
+ # START:message_emission
83
+ def emit_message(command:, data:)
84
+ puts "emitting #{command} \t #{data}"
85
+
86
+ sockets.each do |ws|
87
+ ws.send serialize(command: command, text: data)
70
88
  end
71
89
  end
72
90
 
91
+ def serialize(command:, text:)
92
+ {
93
+ command: command,
94
+ data: { text: text, volume: 20 }
95
+ }.to_json
96
+ end
97
+ # END:message_emission
73
98
 
74
99
  Lita.register_handler(self)
75
100
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = 'lita-sonos-commander'
3
- spec.version = '1.0.0'
3
+ spec.version = '1.1.0'
4
4
  spec.authors = ['Daniel J. Pritchett']
5
5
  spec.email = ['dpritchett@gmail.com']
6
6
  spec.description = 'Control your Sonos with Lita chatbot commands'
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'date'
3
+
4
+ describe Lita::CommanderMiddleware do
5
+ let(:handler) { double 'handler' }
6
+ let(:result) { subject.build({}) }
7
+
8
+ it 'returns a lambda' do
9
+ result = subject.class.build(open_sockets: [])
10
+ expect(result.is_a?(Proc)).to be_truthy
11
+ end
12
+ end
@@ -11,51 +11,104 @@ describe Lita::Handlers::SonosCommander, lita_handler: true do
11
11
  describe 'routes' do
12
12
  it {
13
13
  is_expected.to route_http(:get, '/sonos/listen')
14
- .to(:sonos_connector)
14
+ .to(:websocket_creator)
15
15
  }
16
16
 
17
- it { is_expected.to route('Lita sonos alpha bravo')
18
- .to(:send_to_sonos) }
17
+ it {
18
+ is_expected.to(route('Lita play url http://zombo.com')
19
+ .to(:handle_sonos_play_url))
20
+ }
21
+ it {
22
+ is_expected.to(route('Lita play url https://www.youtube.com/watch?v=dQw4w9WgXcQ')
23
+ .to(:handle_sonos_play_url))
24
+ }
25
+ it {
26
+ is_expected.to(route('Lita speak words i like turtles')
27
+ .to(:handle_sonos_say_text))
28
+ }
29
+
30
+ it { is_expected.to route_event(:loaded).to(:register_faye) }
19
31
  end
20
32
  # END:routes
21
33
 
22
- describe 'exploratory' do
23
- it "let's play" do
24
- subject.explorer
34
+ describe 'sending messages to clients' do
35
+ let(:client) { double('socket client') }
36
+ before { subject.stub(:sockets).and_return [client] }
37
+
38
+ it 'should :send some commanding json to the client' do
39
+ expect(client).to receive(:send).with(/play_url.+http.+volume/)
40
+
41
+ subject.play_url 'http://bana.nas'
25
42
  end
26
- end
27
- # START:save_message
28
- describe ':save_message' do
29
- let(:body) { 'hello, alexa!' }
30
- it 'saves a message and acknowledges' do
31
- result = subject.save_message(username: 'dpritchett', message: body)
32
43
 
33
- expect(result.fetch(:message)).to eq body
44
+ it 'should :send some commanding json to the client' do
45
+ expect(client).to receive(:send).with(/play_text.+turtles.+volume/)
46
+
47
+ subject.say_text 'i like turtles'
34
48
  end
35
49
 
36
- it { is_expected.to route_event(:save_alexa_message).to(:save_message) }
50
+ it 'can grab a message from chat and store it' do
51
+ send_message 'lita speak words i like turtles'
52
+ response = replies.last
53
+
54
+ expect(response).to match(/No clients found/i)
55
+ end
37
56
  end
38
- # END:save_message
39
57
 
40
- # START:alexify
41
- describe ':alexify' do
42
- let(:message) do
43
- subject.save_message username: 'daniel', message: 'test message'
58
+ describe 'socket registry' do
59
+ let(:sockets) { subject.sockets }
60
+ it 'has a socket registry shared amongst instances of the handler' do
61
+ sockets << 'banana'
62
+ another_one = Lita::Handlers::SonosCommander.new({})
63
+
64
+ expect(another_one.sockets).to include('banana')
44
65
  end
45
66
 
46
- it 'should return a hash with an alexa-specific shape' do
47
- result = subject.alexify(message)
48
- expect(result.fetch(:mainText)).to eq('test message')
67
+ it 'allows addition and deletion to the socket registry via array methods' do
68
+ canary = rand(10_000).to_s
69
+ sockets << canary
70
+ expect(sockets).to include(canary)
71
+
72
+ sockets.delete_if { |socket| socket.eql? canary }
73
+ expect(sockets).to_not include(canary)
49
74
  end
50
75
  end
51
- # END:alexify
52
76
 
53
- it 'can grab a message from chat and store it' do
54
- send_message "lita newsfeed hello there #{DateTime.now}"
55
- response = replies.last
56
- expect(response =~ /hello there/i).to be_truthy
57
- expect(response =~ /Saved message for Alexa/).to be_truthy
77
+ describe 'text serializer' do
78
+ it 'creates a json payload' do
79
+ result = subject.serialize(command: 'phone_call', text: 'call your mother')
80
+
81
+ deserialized = JSON.parse(result)
82
+
83
+ expect(deserialized.fetch('command')).to eq 'phone_call'
84
+ expect(deserialized.dig('data', 'volume')).to_not be_falsey
85
+ end
58
86
  end
59
87
 
60
- end
88
+ describe 'socket middleware registration' do
89
+ let(:middlewares) { double 'middlewares' }
90
+ before { subject.stub(:middleware_registry).and_return(middlewares) }
91
+
92
+ let(:commander) { double 'commander' }
93
+ before { Lita::CommanderMiddleware.stub(:build).and_return(commander) }
94
+
95
+ it 'should register the commander middleware' do
96
+ expect(middlewares).to receive(:use).with(commander)
97
+
98
+ subject.register_faye(nil)
99
+ end
100
+ end
101
+
102
+ describe ':websocket_creator' do
103
+ let(:request) { double 'request' }
104
+ let(:request_env) { double 'request' }
105
+ let(:middleware) { double 'middleware' }
106
+
107
+ before { subject.stub_chain(:middleware_registry, :each).and_return [middleware] }
108
+ before { request.stub(:env).and_return(request_env) }
61
109
 
110
+ it 'passes incoming any request environments to registered middlewares' do
111
+ subject.websocket_creator(request, nil)
112
+ end
113
+ end
114
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lita-sonos-commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Pritchett
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-26 00:00:00.000000000 Z
11
+ date: 2018-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lita
@@ -156,6 +156,7 @@ files:
156
156
  - lib/lita/handlers/sonos_commander.rb
157
157
  - lita-sonos-commander.gemspec
158
158
  - locales/en.yml
159
+ - spec/lita/commander_middleware_spec.rb
159
160
  - spec/lita/handlers/sonos_commander_spec.rb
160
161
  - spec/spec_helper.rb
161
162
  homepage: https://github.com/dpritchett/lita-sonos-commander
@@ -184,5 +185,6 @@ signing_key:
184
185
  specification_version: 4
185
186
  summary: Control your Sonos with Lita chatbot commands
186
187
  test_files:
188
+ - spec/lita/commander_middleware_spec.rb
187
189
  - spec/lita/handlers/sonos_commander_spec.rb
188
190
  - spec/spec_helper.rb