siriproxy 0.4.1

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,201 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+ require 'ostruct'
4
+
5
+ # @todo want to make SiriProxy::Commandline without having to
6
+ # require 'siriproxy'. Im sure theres a better way.
7
+ class SiriProxy
8
+
9
+ end
10
+
11
+ class SiriProxy::CommandLine
12
+ BANNER = <<-EOS
13
+ Siri Proxy is a proxy server for Apple's Siri "assistant." The idea is to allow for the creation of custom handlers for different actions. This can allow developers to easily add functionality to Siri.
14
+
15
+ See: http://github.com/plamoni/SiriProxy/
16
+
17
+ Usage: siriproxy COMMAND OPTIONS
18
+
19
+ Commands:
20
+ server Start up the Siri proxy server
21
+ gencerts Generate a the certificates needed for SiriProxy
22
+ bundle Install any dependancies needed by plugins
23
+ console Launch the plugin test console
24
+ update [dir] Updates to the latest code from GitHub or from a provided directory
25
+ help Show this usage information
26
+
27
+ Options:
28
+ Option Command Description
29
+ EOS
30
+
31
+ def initialize
32
+ @branch = nil
33
+ parse_options
34
+ command = ARGV.shift
35
+ subcommand = ARGV.shift
36
+ case command
37
+ when 'server' then run_server(subcommand)
38
+ when 'gencerts' then gen_certs
39
+ when 'bundle' then run_bundle(subcommand)
40
+ when 'console' then run_console
41
+ when 'update' then update(subcommand)
42
+ when 'help' then usage
43
+ else usage
44
+ end
45
+ end
46
+
47
+ def run_console
48
+ load_code
49
+ $LOG_LEVEL = 0
50
+ # this is ugly, but works for now
51
+ SiriProxy::PluginManager.class_eval do
52
+ def respond(text, options={})
53
+ puts "=> #{text}"
54
+ end
55
+ def process(text)
56
+ super(text)
57
+ end
58
+ def send_request_complete_to_iphone
59
+ end
60
+ def no_matches
61
+ puts "No plugin responded"
62
+ end
63
+ end
64
+ SiriProxy::Plugin.class_eval do
65
+ def last_ref_id
66
+ 0
67
+ end
68
+ def send_object(object, options={:target => :iphone})
69
+ puts "=> #{object}"
70
+ end
71
+ end
72
+
73
+ cora = SiriProxy::PluginManager.new
74
+ repl = -> prompt { print prompt; cora.process(gets.chomp!) }
75
+ loop { repl[">> "] }
76
+ end
77
+
78
+ def run_bundle(subcommand='')
79
+ setup_bundler_path
80
+ puts `bundle #{subcommand} #{ARGV.join(' ')}`
81
+ end
82
+
83
+ def run_server(subcommand='start')
84
+ load_code
85
+ start_server
86
+ # @todo: support for forking server into bg and start/stop/restart
87
+ # subcommand ||= 'start'
88
+ # case subcommand
89
+ # when 'start' then start_server
90
+ # when 'stop' then stop_server
91
+ # when 'restart' then restart_server
92
+ # end
93
+ end
94
+
95
+ def start_server
96
+ proxy = SiriProxy.new
97
+ proxy.start()
98
+ end
99
+
100
+ def gen_certs
101
+ ca_name = @ca_name ||= ""
102
+ command = File.join(File.dirname(__FILE__), '..', "..", "scripts", 'gen_certs.sh')
103
+ sp_root = File.join(File.dirname(__FILE__), '..', "..")
104
+ puts `#{command} "#{sp_root}" "#{ca_name}"`
105
+ end
106
+
107
+ def update(directory=nil)
108
+ if(directory)
109
+ puts "=== Installing from '#{directory}' ==="
110
+ puts `cd #{directory} && rake install`
111
+ puts "=== Bundling ===" if $?.exitstatus == 0
112
+ puts `siriproxy bundle` if $?.exitstatus == 0
113
+ puts "=== SUCCESS ===" if $?.exitstatus == 0
114
+
115
+ exit $?.exitstatus
116
+ else
117
+ branch_opt = @branch ? "-b #{@branch}" : ""
118
+ @branch = "master" if @branch == nil
119
+ puts "=== Installing latest code from git://github.com/plamoni/SiriProxy.git [#{@branch}] ==="
120
+
121
+ tmp_dir = "/tmp/SiriProxy.install." + (rand 9999).to_s.rjust(4, "0")
122
+
123
+ `mkdir -p #{tmp_dir}`
124
+ puts `git clone #{branch_opt} git://github.com/plamoni/SiriProxy.git #{tmp_dir}` if $?.exitstatus == 0
125
+ puts "=== Performing Rake Install ===" if $?.exitstatus == 0
126
+ puts `cd #{tmp_dir} && rake install` if $?.exitstatus == 0
127
+ puts "=== Bundling ===" if $?.exitstatus == 0
128
+ puts `siriproxy bundle` if $?.exitstatus == 0
129
+ puts "=== Cleaning Up ===" and puts `rm -rf #{tmp_dir}` if $?.exitstatus == 0
130
+ puts "=== SUCCESS ===" if $?.exitstatus == 0
131
+
132
+ exit $?.exitstatus
133
+ end
134
+ end
135
+
136
+ def usage
137
+ puts "\n#{@option_parser}\n"
138
+ end
139
+
140
+ private
141
+
142
+ def parse_options
143
+ $APP_CONFIG = OpenStruct.new(YAML.load_file(File.expand_path('~/.siriproxy/config.yml')))
144
+
145
+ # Google Public DNS servers
146
+ $APP_CONFIG.upstream_dns ||= %w[8.8.8.8 8.8.4.4]
147
+
148
+ @branch = nil
149
+ @option_parser = OptionParser.new do |opts|
150
+ opts.on('-L', '--listen ADDRESS', '[server] address to listen on (central or node)') do |listen|
151
+ $APP_CONFIG.listen = listen
152
+ end
153
+ opts.on('-p', '--port PORT', '[server] port number for server (central or node)') do |port_num|
154
+ $APP_CONFIG.port = port_num
155
+ end
156
+ opts.on('-l', '--log LOG_LEVEL', '[server] The level of debug information displayed (higher is more)') do |log_level|
157
+ $APP_CONFIG.log_level = log_level
158
+ end
159
+ opts.on( '--upstream-dns SERVERS', Array, '[server] List of upstream DNS servers to query for the real guzzoni.apple.com. Defaults to Google DNS servers') do |servers|
160
+ $APP_CONFIG.upstream_dns = servers
161
+ end
162
+ opts.on('-u', '--user USER', '[server] The user to run as after launch') do |user|
163
+ $APP_CONFIG.user = user
164
+ end
165
+ opts.on('-b', '--branch BRANCH', '[update] Choose the branch to update from (default: master)') do |branch|
166
+ @branch = branch
167
+ end
168
+ opts.on('-n', '--name CA_NAME', '[gencerts] Define a common name for the CA (default: "SiriProxyCA")') do |ca_name|
169
+ @ca_name = ca_name
170
+ end
171
+ opts.on_tail('-v', '--version', ' show version') do
172
+ require "siriproxy/version"
173
+ puts "SiriProxy version #{SiriProxy::VERSION}"
174
+ exit
175
+ end
176
+ end
177
+ @option_parser.banner = BANNER
178
+ @option_parser.parse!(ARGV)
179
+ end
180
+
181
+ def setup_bundler_path
182
+ require 'pathname'
183
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../../Gemfile",
184
+ Pathname.new(__FILE__).realpath)
185
+ end
186
+
187
+ def load_code
188
+ setup_bundler_path
189
+
190
+ require 'bundler'
191
+ require 'bundler/setup'
192
+
193
+ require 'siriproxy'
194
+ require 'siriproxy/connection'
195
+ require 'siriproxy/connection/iphone'
196
+ require 'siriproxy/connection/guzzoni'
197
+
198
+ require 'siriproxy/plugin'
199
+ require 'siriproxy/plugin_manager'
200
+ end
201
+ end
@@ -0,0 +1,24 @@
1
+ #####
2
+ # This is the connection to the Guzzoni (the Siri server backend)
3
+ #####
4
+ class SiriProxy::Connection::Guzzoni < SiriProxy::Connection
5
+ def initialize
6
+ super
7
+ self.name = "Guzzoni"
8
+ end
9
+
10
+ def connection_completed
11
+ super
12
+ start_tls(:verify_peer => false)
13
+ end
14
+
15
+ def received_object(object)
16
+ return plugin_manager.process_filters(object, :from_guzzoni)
17
+
18
+ #plugin_manager.object_from_guzzoni(object, self)
19
+ end
20
+
21
+ def block_rest_of_session
22
+ @block_rest_of_session = true
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ require 'resolv'
2
+
3
+ #####
4
+ # This is the connection to the iPhone
5
+ #####
6
+ class SiriProxy::Connection::Iphone < SiriProxy::Connection
7
+ def initialize upstream_dns
8
+ puts "Create server for iPhone connection"
9
+ super()
10
+ self.name = "iPhone"
11
+ @upstream_dns = upstream_dns
12
+ end
13
+
14
+ def post_init
15
+ super
16
+ start_tls(:cert_chain_file => File.expand_path("~/.siriproxy/server.passless.crt"),
17
+ :private_key_file => File.expand_path("~/.siriproxy/server.passless.key"),
18
+ :verify_peer => false)
19
+ end
20
+
21
+ # Resolves guzzoni.apple.com using the Google DNS servers. This allows the
22
+ # machine running siriproxy to use the DNS server returning fake records for
23
+ # guzzoni.apple.com.
24
+
25
+ def resolve_guzzoni
26
+ addresses = Resolv::DNS.open(nameserver: @upstream_dns) do |dns|
27
+ res = dns.getresources('guzzoni.apple.com', Resolv::DNS::Resource::IN::A)
28
+
29
+ res.map { |r| r.address }
30
+ end
31
+
32
+ addresses.map do |address|
33
+ address.address.unpack('C*').join('.')
34
+ end.sample
35
+ end
36
+
37
+ def ssl_handshake_completed
38
+ super
39
+ self.other_connection = EventMachine.connect(resolve_guzzoni, 443, SiriProxy::Connection::Guzzoni)
40
+ self.plugin_manager.guzzoni_conn = self.other_connection
41
+ other_connection.other_connection = self #hehe
42
+ other_connection.plugin_manager = plugin_manager
43
+ end
44
+
45
+ def received_object(object)
46
+ return plugin_manager.process_filters(object, :from_iphone)
47
+
48
+ #plugin_manager.object_from_client(object, self)
49
+ end
50
+ end
@@ -0,0 +1,212 @@
1
+ require 'cfpropertylist'
2
+ require 'siriproxy/interpret_siri'
3
+
4
+ class SiriProxy::Connection < EventMachine::Connection
5
+ include EventMachine::Protocols::LineText2
6
+
7
+ attr_accessor :other_connection, :name, :ssled, :output_buffer, :input_buffer, :processed_headers, :unzip_stream, :zip_stream, :consumed_ace, :unzipped_input, :unzipped_output, :last_ref_id, :plugin_manager
8
+
9
+ def last_ref_id=(ref_id)
10
+ @last_ref_id = ref_id
11
+ self.other_connection.last_ref_id = ref_id if other_connection.last_ref_id != ref_id
12
+ end
13
+
14
+ def initialize
15
+ super
16
+ self.processed_headers = false
17
+ self.output_buffer = ""
18
+ self.input_buffer = ""
19
+ self.unzipped_input = ""
20
+ self.unzipped_output = ""
21
+ self.unzip_stream = Zlib::Inflate.new
22
+ self.zip_stream = Zlib::Deflate.new
23
+ self.consumed_ace = false
24
+ end
25
+
26
+ def post_init
27
+ self.ssled = false
28
+ end
29
+
30
+ def ssl_handshake_completed
31
+ self.ssled = true
32
+
33
+ puts "[Info - #{self.name}] SSL completed for #{self.name}" if $LOG_LEVEL > 1
34
+ end
35
+
36
+ def receive_line(line) #Process header
37
+ puts "[Header - #{self.name}] #{line}" if $LOG_LEVEL > 2
38
+ if(line == "") #empty line indicates end of headers
39
+ puts "[Debug - #{self.name}] Found end of headers" if $LOG_LEVEL > 3
40
+ set_binary_mode
41
+ self.processed_headers = true
42
+ end
43
+ self.output_buffer << (line + "\x0d\x0a") #Restore the CR-LF to the end of the line
44
+
45
+ flush_output_buffer()
46
+ end
47
+
48
+ def receive_binary_data(data)
49
+ self.input_buffer << data
50
+
51
+ ##Consume the "0xAACCEE02" data at the start of the stream if necessary (by forwarding it to the output buffer)
52
+ if(self.consumed_ace == false)
53
+ self.output_buffer << input_buffer[0..3]
54
+ self.input_buffer = input_buffer[4..-1]
55
+ self.consumed_ace = true;
56
+ end
57
+
58
+ process_compressed_data()
59
+
60
+ flush_output_buffer()
61
+ end
62
+
63
+ def flush_output_buffer
64
+ return if output_buffer.empty?
65
+
66
+ if other_connection.ssled
67
+ puts "[Debug - #{self.name}] Forwarding #{self.output_buffer.length} bytes of data to #{other_connection.name}" if $LOG_LEVEL > 5
68
+ #puts self.output_buffer.to_hex if $LOG_LEVEL > 5
69
+ other_connection.send_data(output_buffer)
70
+ self.output_buffer = ""
71
+ else
72
+ puts "[Debug - #{self.name}] Buffering some data for later (#{self.output_buffer.length} bytes buffered)" if $LOG_LEVEL > 5
73
+ #puts self.output_buffer.to_hex if $LOG_LEVEL > 5
74
+ end
75
+ end
76
+
77
+ def process_compressed_data
78
+ self.unzipped_input << unzip_stream.inflate(self.input_buffer)
79
+ self.input_buffer = ""
80
+ puts "========UNZIPPED DATA (from #{self.name} =========" if $LOG_LEVEL > 5
81
+ puts unzipped_input.to_hex if $LOG_LEVEL > 5
82
+ puts "==================================================" if $LOG_LEVEL > 5
83
+
84
+ while(self.has_next_object?)
85
+ object = read_next_object_from_unzipped()
86
+
87
+ if(object != nil) #will be nil if the next object is a ping/pong
88
+ new_object = prep_received_object(object) #give the world a chance to mess with folks
89
+
90
+ inject_object_to_output_stream(new_object) if new_object != nil #might be nil if "the world" decides to rid us of the object
91
+ end
92
+ end
93
+ end
94
+
95
+ def has_next_object?
96
+ return false if unzipped_input.empty? #empty
97
+ unpacked = unzipped_input[0...5].unpack('H*').first
98
+ return true if(unpacked.match(/^0[34]/)) #Ping or pong
99
+ return true if(unpacked.match(/^ff/)) #clear context
100
+
101
+ if unpacked.match(/^[0-9][15-9]/)
102
+ puts "ROGUE PACKET!!! WHAT IS IT?! TELL US!!! IN IRC!! COPY THE STUFF FROM BELOW"
103
+ puts unpacked.to_hex
104
+ end
105
+
106
+ objectLength = unpacked.match(/^0200(.{6})/)[1].to_i(16)
107
+ return ((objectLength + 5) < unzipped_input.length) #determine if the length of the next object (plus its prefix) is less than the input buffer
108
+ end
109
+
110
+ def read_next_object_from_unzipped
111
+ unpacked = unzipped_input[0...5].unpack('H*').first
112
+ info = unpacked.match(/^(..)(.{8})$/)
113
+
114
+ if(info[1] == "03" || info[1] == "04" || info[1] == "ff") #Ping or pong -- just get these out of the way (and log them for good measure)
115
+ object = unzipped_input[0...5]
116
+ self.unzipped_output << object
117
+
118
+ type = (info[1] == "03") ? "Ping" : ((info[1] == "04") ? "Pong" : "Clear Context")
119
+ puts "[#{type} - #{self.name}] (#{info[2].to_i(16)})" if $LOG_LEVEL > 3
120
+ self.unzipped_input = unzipped_input[5..-1]
121
+
122
+ flush_unzipped_output()
123
+ return nil
124
+ end
125
+
126
+ object_size = info[2].to_i(16)
127
+ prefix = unzipped_input[0...5]
128
+ object_data = unzipped_input[5...object_size+5]
129
+ self.unzipped_input = unzipped_input[object_size+5..-1]
130
+
131
+ parse_object(object_data)
132
+ end
133
+
134
+
135
+ def parse_object(object_data)
136
+ plist = CFPropertyList::List.new(:data => object_data)
137
+ object = CFPropertyList.native_types(plist.value)
138
+
139
+ object
140
+ end
141
+
142
+ def inject_object_to_output_stream(object)
143
+ if object["refId"] != nil && !object["refId"].empty?
144
+ @block_rest_of_session = false if @block_rest_of_session && self.last_ref_id != object["refId"] #new session
145
+ self.last_ref_id = object["refId"]
146
+ end
147
+
148
+ puts "[Info - Forwarding object to #{self.other_connection.name}] #{object["class"]}" if $LOG_LEVEL > 1
149
+
150
+ object_data = object.to_plist(:plist_format => CFPropertyList::List::FORMAT_BINARY)
151
+
152
+ #Recalculate the size in case the object gets modified. If new size is 0, then remove the object from the stream entirely
153
+ obj_len = object_data.length
154
+
155
+ if(obj_len > 0)
156
+ prefix = [(0x0200000000 + obj_len).to_s(16).rjust(10, '0')].pack('H*')
157
+ self.unzipped_output << prefix + object_data
158
+ end
159
+
160
+ flush_unzipped_output()
161
+ end
162
+
163
+ def flush_unzipped_output
164
+ self.zip_stream << self.unzipped_output
165
+ self.unzipped_output = ""
166
+ self.output_buffer << zip_stream.flush
167
+
168
+ flush_output_buffer()
169
+ end
170
+
171
+ def prep_received_object(object)
172
+ if object["refId"] == self.last_ref_id && @block_rest_of_session
173
+ puts "[Info - Dropping Object from Guzzoni] #{object["class"]}" if $LOG_LEVEL > 1
174
+ pp object if $LOG_LEVEL > 3
175
+ return nil
176
+ end
177
+
178
+ puts "[Info - #{self.name}] Received Object: #{object["class"]}" if $LOG_LEVEL == 1
179
+ puts "[Info - #{self.name}] Received Object: #{object["class"]} (group: #{object["group"]})" if $LOG_LEVEL == 2
180
+ puts "[Info - #{self.name}] Received Object: #{object["class"]} (group: #{object["group"]}, ref_id: #{object["refId"]}, ace_id: #{object["aceId"]})" if $LOG_LEVEL > 2
181
+ pp object if $LOG_LEVEL > 3
182
+
183
+ #keeping this for filters
184
+ new_obj = received_object(object)
185
+ if new_obj == nil
186
+ puts "[Info - Dropping Object from #{self.name}] #{object["class"]}" if $LOG_LEVEL > 1
187
+ pp object if $LOG_LEVEL > 3
188
+ return nil
189
+ end
190
+
191
+ #block the rest of the session if a plugin claims ownership
192
+ speech = SiriProxy::Interpret.speech_recognized(object)
193
+ if speech != nil
194
+ inject_object_to_output_stream(object)
195
+ block_rest_of_session if plugin_manager.process(speech)
196
+ return nil
197
+ end
198
+
199
+
200
+ #object = new_obj if ((new_obj = SiriProxy::Interpret.unknown_intent(object, self, plugin_manager.method(:unknown_command))) != false)
201
+ #object = new_obj if ((new_obj = SiriProxy::Interpret.speech_recognized(object, self, plugin_manager.method(:speech_recognized))) != false)
202
+
203
+ object
204
+ end
205
+
206
+ #Stub -- override in subclass
207
+ def received_object(object)
208
+
209
+ object
210
+ end
211
+
212
+ end
@@ -0,0 +1,50 @@
1
+ ######
2
+ # The idea behind this class is that you can call the different
3
+ # methods to get different interpretations of a Siri object.
4
+ # For instance, you can "unknown_intent" and it will check
5
+ # to see if an object is a "Common#unknownIntent" response and
6
+ # call the provided processor method with the appropriate info.
7
+ # processor method signatures are provided in comments above each
8
+ # method.
9
+ #
10
+ # each method will return "nil" if the object is not the valid
11
+ # type. If it is, it will return the result of the processor.
12
+ #####
13
+ class SiriProxy::Interpret
14
+ class << self
15
+ #Checks if the object is Guzzoni responding that it can't
16
+ #determine the intent of the query
17
+ #processor(object, connection, unknown_text)
18
+ def unknown_intent(object, connection, processor)
19
+ return false if object == nil
20
+ return false if (!(object["properties"]["views"][0]["properties"]["dialogIdentifier"] == "Common#unknownIntent") rescue true)
21
+
22
+ searchUtterance = object["properties"]["views"][1]["properties"]["commands"][0]["properties"]["commands"][0]["properties"]["utterance"]
23
+ searchText = searchUtterance.split("^")[3]
24
+ return processor.call(object, connection, searchText)
25
+
26
+ return false
27
+ end
28
+
29
+ #Checks if the object is Guzzoni responding that it recognized
30
+ #speech. Sends "best interpretation" phrase to processor
31
+ #processor(object, connection, phrase)
32
+ def speech_recognized(object)
33
+ return nil if object == nil
34
+ return nil if (!(object["class"] == "SpeechRecognized") rescue true)
35
+ phrase = ""
36
+
37
+ object["properties"]["recognition"]["properties"]["phrases"].map { |phraseObj|
38
+ phraseObj["properties"]["interpretations"].first["properties"]["tokens"].map { |token|
39
+ tokenProps = token["properties"]
40
+
41
+ phrase = phrase[0..-2] if tokenProps["removeSpaceBefore"] and phrase[-1] == " "
42
+ phrase << tokenProps["text"]
43
+ phrase << " " if !tokenProps["removeSpaceAfter"]
44
+ }
45
+ }
46
+
47
+ phrase
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ require 'cora'
2
+
3
+ class SiriProxy::Plugin < Cora::Plugin
4
+ def initialize(config)
5
+
6
+ end
7
+
8
+ def request_completed
9
+ self.manager.send_request_complete_to_iphone
10
+ end
11
+
12
+ #use send_object(object, target: :guzzoni) to send to guzzoni
13
+ def send_object(object, options={})
14
+ (object = object.to_hash) rescue nil #convert SiriObjects to a hash
15
+ options[:target] = options[:target] ||= :iphone
16
+
17
+ if(options[:target] == :iphone)
18
+ self.manager.guzzoni_conn.inject_object_to_output_stream(object)
19
+ elsif(options[:target] == :guzzoni)
20
+ self.manager.iphone_conn.inject_object_to_output_stream(object)
21
+ end
22
+ end
23
+
24
+ def last_ref_id
25
+ self.manager.iphone_conn.last_ref_id
26
+ end
27
+
28
+ #direction should be :from_iphone, or :from_guzzoni
29
+ def process_filters(object, direction)
30
+ return nil if object == nil
31
+ f = filters[object["class"]]
32
+ if(f != nil && (f[:direction] == :both || f[:direction] == direction))
33
+ object = instance_exec(object, &f[:block])
34
+ end
35
+
36
+ object
37
+ end
38
+
39
+ class << self
40
+ def filter(class_names, options={}, &block)
41
+ [class_names].flatten.each do |class_name|
42
+ filters[class_name] = {
43
+ direction: (options[:direction] ||= :both),
44
+ block: block
45
+ }
46
+ end
47
+ end
48
+
49
+ def filters
50
+ @filters ||= {}
51
+ end
52
+ end
53
+
54
+ def filters
55
+ self.class.filters
56
+ end
57
+
58
+ end
@@ -0,0 +1,78 @@
1
+ require 'cora'
2
+ require 'pp'
3
+
4
+ class SiriProxy::PluginManager < Cora
5
+ attr_accessor :plugins, :iphone_conn, :guzzoni_conn
6
+
7
+ def initialize()
8
+ load_plugins()
9
+ end
10
+
11
+ def load_plugins()
12
+ @plugins = []
13
+ if $APP_CONFIG.plugins
14
+ $APP_CONFIG.plugins.each do |pluginConfig|
15
+ if pluginConfig.is_a? String
16
+ className = pluginConfig
17
+ requireName = "siriproxy-#{className.downcase}"
18
+ else
19
+ className = pluginConfig['name']
20
+ requireName = pluginConfig['require'] || "siriproxy-#{className.downcase}"
21
+ end
22
+ require requireName
23
+ plugin = SiriProxy::Plugin.const_get(className).new(pluginConfig)
24
+ plugin.manager = self
25
+ @plugins << plugin
26
+ end
27
+ end
28
+ log "Plugins loaded: #{@plugins}"
29
+ end
30
+
31
+ def process_filters(object, direction)
32
+ object_class = object.class #This way, if we change the object class we won't need to modify this code.
33
+
34
+ if object['class'] == 'SetRequestOrigin'
35
+ properties = object['properties']
36
+ set_location(properties['latitude'], properties['longitude'], properties)
37
+ end
38
+
39
+ plugins.each do |plugin|
40
+ #log "Processing filters on #{plugin} for '#{object["class"]}'"
41
+ new_obj = plugin.process_filters(object, direction)
42
+ object = new_obj if(new_obj == false || new_obj.class == object_class) #prevent accidental poorly formed returns
43
+ return nil if object == false #if any filter returns "false," then the object should be dropped
44
+ end
45
+
46
+ return object
47
+ end
48
+
49
+ def process(text)
50
+ begin
51
+ result = super(text)
52
+ self.guzzoni_conn.block_rest_of_session if result
53
+ return result
54
+ rescue Exception=>e
55
+ log "Plugin Crashed: #{e}"
56
+ respond e.to_s, spoken: "a plugin crashed"
57
+ return true
58
+ end
59
+ end
60
+
61
+ def send_request_complete_to_iphone
62
+ log "Sending Request Completed"
63
+ object = generate_request_completed(self.guzzoni_conn.last_ref_id)
64
+ self.guzzoni_conn.inject_object_to_output_stream(object)
65
+ end
66
+
67
+ def respond(text, options={})
68
+ self.guzzoni_conn.inject_object_to_output_stream(generate_siri_utterance(self.guzzoni_conn.last_ref_id, text, (options[:spoken] or text), options[:prompt_for_response] == true))
69
+ end
70
+
71
+ def no_matches
72
+ return false
73
+ end
74
+
75
+ def log(text)
76
+ puts "[Info - Plugin Manager] #{text}" if $LOG_LEVEL >= 1
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ class SiriProxy
2
+ VERSION = "0.4.1"
3
+ end