connfu-client 0.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.
Files changed (94) hide show
  1. data/.rspec +3 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +120 -0
  5. data/LICENSE.txt +661 -0
  6. data/README.rdoc +398 -0
  7. data/Rakefile +38 -0
  8. data/bin/.DS_Store +0 -0
  9. data/bin/connfu-client +94 -0
  10. data/connfu-client.gemspec +41 -0
  11. data/examples/conference/application.rb +68 -0
  12. data/examples/conference/conference.rb +33 -0
  13. data/examples/conference/conference_app.rb +25 -0
  14. data/examples/conference/connfu.log +5 -0
  15. data/examples/conference/wall.rb +11 -0
  16. data/examples/provisioning/app/get.rb +29 -0
  17. data/examples/provisioning/channels/get.rb +34 -0
  18. data/examples/provisioning/rss/create.rb +28 -0
  19. data/examples/provisioning/rss/delete.rb +27 -0
  20. data/examples/provisioning/rss/get.rb +32 -0
  21. data/examples/provisioning/rss/put.rb +29 -0
  22. data/examples/provisioning/setup.rb +2 -0
  23. data/examples/provisioning/twitter/create.rb +31 -0
  24. data/examples/provisioning/twitter/delete.rb +27 -0
  25. data/examples/provisioning/twitter/get.rb +32 -0
  26. data/examples/provisioning/twitter/put.rb +29 -0
  27. data/examples/provisioning/voice/create.rb +26 -0
  28. data/examples/provisioning/voice/delete.rb +27 -0
  29. data/examples/provisioning/voice/get.rb +36 -0
  30. data/examples/provisioning/voice/phones/create.rb +26 -0
  31. data/examples/provisioning/voice/phones/delete.rb +28 -0
  32. data/examples/provisioning/voice/phones/get.rb +38 -0
  33. data/examples/provisioning/voice/put.rb +38 -0
  34. data/examples/provisioning/voice/whitelist/create.rb +26 -0
  35. data/examples/provisioning/voice/whitelist/delete.rb +27 -0
  36. data/examples/provisioning/voice/whitelist/get.rb +36 -0
  37. data/examples/provisioning/voice/whitelist/put.rb +27 -0
  38. data/lib/connfu.rb +134 -0
  39. data/lib/connfu/cli/generator.rb +71 -0
  40. data/lib/connfu/connfu_logger.rb +88 -0
  41. data/lib/connfu/connfu_message_formatter.rb +134 -0
  42. data/lib/connfu/connfu_stream.rb +182 -0
  43. data/lib/connfu/dispatcher.rb +164 -0
  44. data/lib/connfu/dsl.rb +84 -0
  45. data/lib/connfu/events.rb +32 -0
  46. data/lib/connfu/listener.rb +85 -0
  47. data/lib/connfu/listener_channel.rb +100 -0
  48. data/lib/connfu/message.rb +74 -0
  49. data/lib/connfu/provisioning.rb +12 -0
  50. data/lib/connfu/provisioning/application.rb +374 -0
  51. data/lib/connfu/provisioning/base.rb +95 -0
  52. data/lib/connfu/provisioning/channel.rb +79 -0
  53. data/lib/connfu/provisioning/phone.rb +55 -0
  54. data/lib/connfu/provisioning/rss.rb +21 -0
  55. data/lib/connfu/provisioning/twitter.rb +28 -0
  56. data/lib/connfu/provisioning/voice.rb +89 -0
  57. data/lib/connfu/provisioning/whitelist.rb +62 -0
  58. data/lib/connfu/version.rb +6 -0
  59. data/lib/rdoc/generator/template/connfu/_context.rhtml +209 -0
  60. data/lib/rdoc/generator/template/connfu/_head.rhtml +7 -0
  61. data/lib/rdoc/generator/template/connfu/class.rhtml +38 -0
  62. data/lib/rdoc/generator/template/connfu/file.rhtml +36 -0
  63. data/lib/rdoc/generator/template/connfu/index.rhtml +13 -0
  64. data/lib/rdoc/generator/template/connfu/resources/apple-touch-icon.png +0 -0
  65. data/lib/rdoc/generator/template/connfu/resources/css/github.css +129 -0
  66. data/lib/rdoc/generator/template/connfu/resources/css/main.css +339 -0
  67. data/lib/rdoc/generator/template/connfu/resources/css/panel.css +389 -0
  68. data/lib/rdoc/generator/template/connfu/resources/css/reset.css +53 -0
  69. data/lib/rdoc/generator/template/connfu/resources/favicon.ico +0 -0
  70. data/lib/rdoc/generator/template/connfu/resources/i/arrows.png +0 -0
  71. data/lib/rdoc/generator/template/connfu/resources/i/results_bg.png +0 -0
  72. data/lib/rdoc/generator/template/connfu/resources/i/tree_bg.png +0 -0
  73. data/lib/rdoc/generator/template/connfu/resources/js/highlight.pack.js +1 -0
  74. data/lib/rdoc/generator/template/connfu/resources/js/jquery-1.3.2.min.js +19 -0
  75. data/lib/rdoc/generator/template/connfu/resources/js/jquery-effect.js +593 -0
  76. data/lib/rdoc/generator/template/connfu/resources/js/main.js +20 -0
  77. data/lib/rdoc/generator/template/connfu/resources/js/searchdoc.js +628 -0
  78. data/lib/rdoc/generator/template/connfu/resources/panel/index.html +72 -0
  79. data/lib/rdoc/generator/template/connfu/se_index.rhtml +8 -0
  80. data/spec/connfu_message_formatter_spec.rb +88 -0
  81. data/spec/connfu_spec.rb +51 -0
  82. data/spec/connfu_stream_spec.rb +84 -0
  83. data/spec/dispatcher_spec.rb +227 -0
  84. data/spec/dsl_spec.rb +159 -0
  85. data/spec/listener_channel_spec.rb +130 -0
  86. data/spec/listener_spec.rb +67 -0
  87. data/spec/provisioning/application_spec.rb +47 -0
  88. data/spec/provisioning/channel_shared_examples.rb +52 -0
  89. data/spec/provisioning/channel_spec.rb +13 -0
  90. data/spec/provisioning/phone_spec.rb +88 -0
  91. data/spec/provisioning/voice_spec.rb +138 -0
  92. data/spec/provisioning_spec.rb +500 -0
  93. data/spec/spec_helper.rb +51 -0
  94. metadata +298 -0
@@ -0,0 +1,71 @@
1
+ module Connfu
2
+ ##
3
+ # This module helps a client to manage connFu applications
4
+ module Cli
5
+ module Generator
6
+
7
+ APPLICATION_TEMPLATE=<<END
8
+
9
+ require 'connfu'
10
+
11
+ ##
12
+ # This application is an example of how to create a connFu application
13
+
14
+ token = "YOUR-VALID-CONNFU-TOKEN"
15
+
16
+ Connfu.logger = STDOUT
17
+ Connfu.log_level = Logger::DEBUG
18
+
19
+ Connfu.application(token) {
20
+
21
+ listen(:voice) do |conference|
22
+ conference.on(:join) do |call|
23
+ puts "New inbound call from \#{call[:from]} on number \#{call[:to]}"
24
+ end
25
+
26
+ conference.on(:leave) do |call|
27
+ puts "\#{call[:from]} has left the conference \#{call[:channel_name]}"
28
+ end
29
+
30
+ conference.on(:new_topic) do |topic|
31
+ puts "New topic in the conference \#{topic[:channel_name]}: \#{topic[:content]}"
32
+ end
33
+ end
34
+
35
+ listen(:twitter) do |twitter|
36
+ twitter.on(:new) do |tweet|
37
+ puts "\#{tweet[:channel_name]} just posted a new tweet in the conference room: \#{tweet.content}"
38
+ end
39
+ end
40
+
41
+ listen(:sms) do |sms|
42
+ sms.on(:new) do |message|
43
+ puts "New inbound sms from \#{message[:from]}: \#{message[:content]}"
44
+ end
45
+ end
46
+
47
+ }
48
+
49
+ END
50
+
51
+ class << self
52
+ ##
53
+ #
54
+ # ==== Parameters
55
+ # * **name** application name
56
+ # * **channels** channels the application should listen to
57
+ # * **file_name** main file that will hold the application logic
58
+ #
59
+ # ==== Return
60
+ def run(name, channels = nil, file_name = "application.rb")
61
+ Dir.mkdir(name)
62
+ Dir.chdir(name) do
63
+ File.open(file_name, 'w') do |f|
64
+ f.write(APPLICATION_TEMPLATE)
65
+ end
66
+ end
67
+ end # end:run
68
+ end # end class level
69
+ end # end module Generator
70
+ end # end module Cli
71
+ end
@@ -0,0 +1,88 @@
1
+ require 'logger'
2
+
3
+ module Connfu
4
+ ##
5
+ # This module defines a mixin to be used in any class that needs to log messages.
6
+ #
7
+ module ConnfuLogger
8
+
9
+ ##
10
+ # Nice way to include the Module methods when including it in a class/module
11
+ #
12
+ # ==== Parameters
13
+ # * +base+ class that includes the mixin
14
+ def self.included(base)
15
+ base.extend(ClassMethods)
16
+ end
17
+
18
+ ##
19
+ # This internal module acts as a wrapper to include the class/module level methods
20
+ module ClassMethods
21
+
22
+ ##
23
+ # logger setter
24
+ #
25
+ # ==== Parameters
26
+ # * +value+ should be:
27
+ # * a valid IO object (STDOUT, string representing a valid filename, File object)
28
+ # * a ::Logger instance
29
+ #
30
+ # ==== Return
31
+ # new ::Logger object created
32
+ def logger=(value)
33
+ # _logger must be static var and not class var to be shared between objects/classes
34
+ if value.is_a?(String) or value.is_a?(IO)
35
+ @@_logger = Logger.new(value)
36
+ else
37
+ @@_logger = value
38
+ end
39
+ end
40
+
41
+ ##
42
+ # logger getter
43
+ #
44
+ # ==== Return
45
+ # ::Logger object
46
+ def logger
47
+ @@_logger ||= create_logger
48
+ end
49
+
50
+ ##
51
+ # Change logger level
52
+ #
53
+ # ==== Parameters
54
+ # * +level+ valid Logger level constant (::Logger::DEBUG, etc)
55
+ def log_level=(level)
56
+ logger.level = level
57
+ end
58
+
59
+ ##
60
+ # Creates a new Logger object and defines the level and format
61
+ #
62
+ # ==== Parameters
63
+ # * +output+ valid IO object
64
+ # ==== Return
65
+ # Logger object
66
+ def create_logger(output = nil)
67
+ output.nil? and output = STDOUT
68
+ logger = Logger.new(output)
69
+ logger.level = Logger::ERROR
70
+ #logger.formatter = proc { |severity, datetime, progname, msg|
71
+ # "#{severity} on #{datetime} at #{progname}: #{msg}\n"
72
+ #}
73
+ logger.datetime_format = "%Y-%m-%d %H:%M:%S"
74
+ logger
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Instance method that wraps the class method
80
+ #
81
+ # ==== Return
82
+ # * see ClassMethods.logger
83
+ def logger
84
+ self.class.logger
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,134 @@
1
+ require 'active_support'
2
+ require 'connfu/connfu_logger'
3
+ require 'connfu/message'
4
+ require 'uri'
5
+
6
+ module Connfu
7
+
8
+ ##
9
+ # This class is in charge of formatting the incoming messages
10
+ # It's used by any connFu listener to format a raw message got from the streaming API to a
11
+ # Connfu::Message instance
12
+ #
13
+ class ConnfuMessageFormatter
14
+ include Connfu::ConnfuLogger # application logger
15
+
16
+ class << self
17
+
18
+ ##
19
+ # Convert an inbound twitter/RSS event to a Message instance.
20
+ #
21
+ # ==== Parameters
22
+ #
23
+ # * +message+ raw JSON decoded message
24
+ # ==== Return
25
+ # Array of Connfu::Message instances
26
+ def format_message(message)
27
+ message.is_a?(String) and message = ActiveSupport::JSON.decode(message)
28
+ values = []
29
+
30
+ # internal method that process the message
31
+ fetch_values = lambda { |msg|
32
+ begin
33
+ if msg.is_a?(Hash)
34
+ sender = msg["actor"]["id"]
35
+ recipients = nil
36
+ user_mentions = msg["object"]["entities"]["user_mentions"]
37
+ logger.debug(user_mentions)
38
+ if user_mentions.is_a?(Array) && user_mentions.length > 0
39
+ recipients = []
40
+ user_mentions.each { |recipient|
41
+ recipients << recipient
42
+ }
43
+ if recipients.length.eql?(1)
44
+ recipients = recipients[0]
45
+ end
46
+ end
47
+ channel_info = msg["backchat"]["bare_uri"].split(":")
48
+ logger.debug(channel_info)
49
+ params = {
50
+ :id => msg["object"]["id"],
51
+ :content => msg["object"]["content"],
52
+ :message_type => "new",
53
+ :channel_type => channel_info[0],
54
+ :channel_name => channel_info[1][2..-2],
55
+ :from => sender,
56
+ :to => recipients
57
+ }
58
+ params
59
+ else
60
+ logger.error("Invalid message format: #{msg}")
61
+ nil
62
+ end
63
+ rescue => ex
64
+ logger.error("Unable to fetch values from message #{msg}. Caught exception #{ex}")
65
+ nil
66
+ end
67
+ }
68
+
69
+ if message.is_a?(Array)
70
+ message.each { |msg|
71
+ params = fetch_values.call(msg)
72
+ # include the message if valid params
73
+ params.nil? or values << Connfu::Message.new(params)
74
+ }
75
+ else
76
+ params = fetch_values.call(message)
77
+ # include the message if valid params
78
+ params.nil? or values << Connfu::Message.new(params)
79
+ end
80
+ values
81
+ end
82
+
83
+ ##
84
+ # Convert the inbound voice/sms event to a Message instance
85
+ # ==== Parameters
86
+ # * +message+ raw JSON decoded message
87
+ #
88
+ # ==== Return
89
+ # * Array of one Connfu::Message instance
90
+ # * Empty array if the message is invalid
91
+ def format_voice_sms(message)
92
+ if message.is_a?(Array) and message.length.eql?(2)
93
+ if Connfu::Message.is_voice?(message[0])
94
+ logger.debug("Format the new voice message")
95
+ conference_id = URI.parse(message[1]["conferenceId"]).host
96
+ params = {
97
+ :id => 1234,
98
+ :content => message[1]["newTopic"],
99
+ :from => message[1]["from"],
100
+ :to => message[1]["to"],
101
+ :message_type => message[0],
102
+ :channel_type => "voice",
103
+ :channel_name => conference_id
104
+ }
105
+ logger.debug(params)
106
+
107
+ [Connfu::Message.new(params)]
108
+ elsif Connfu::Message.is_sms?(message[0])
109
+ logger.debug("Format the new sms message")
110
+ params = {
111
+ :id => 1234,
112
+ :content => message[1]["message"],
113
+ :from => message[1]["from"],
114
+ :to => message[1]["to"],
115
+ :message_type => "new",
116
+ :channel_type => "sms",
117
+ :channel_name => message[1]["appId"]
118
+ }
119
+ [Connfu::Message.new(params)]
120
+ else
121
+ logger.error("Unexpected message type")
122
+ logger.error(message)
123
+ []
124
+ end
125
+ else
126
+ logger.error("Unexpected message format")
127
+ logger.error(message)
128
+ []
129
+ end
130
+ end
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,182 @@
1
+ require 'active_support'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'thread'
6
+ require 'connfu/connfu_logger'
7
+ require 'connfu/message'
8
+ require 'connfu/connfu_message_formatter'
9
+
10
+ module Connfu
11
+
12
+ ##
13
+ # Open an HTTP connection to connFu and start listening to any incoming event
14
+ # This is the entry point to execute any proc in a connFu application
15
+ # based on external events.
16
+ class ConnfuStream
17
+ include Connfu::ConnfuLogger # application logger
18
+
19
+ ##
20
+ # ConnfuStream initializer.
21
+ # ==== Parameters
22
+ #
23
+ # * +app_stream+ valid HTTP stream url to connect and listen events
24
+ # * +api_key+ valid Token to get access to a Connfu Stream
25
+ # * +uri+ HTTP endpoint to open the connection
26
+ def initialize(app_stream, api_key, uri)
27
+
28
+ @app_stream = app_stream
29
+ @api_key = api_key
30
+
31
+ _uri = URI.parse(uri)
32
+ @port = _uri.port
33
+ @host = _uri.host
34
+ @path = _uri.path.concat(app_stream)
35
+ @scheme = _uri.scheme
36
+
37
+ @formatter = Connfu::ConnfuMessageFormatter
38
+ end
39
+
40
+ ##
41
+ # Open a HTTP connection to connFu and start listening new events
42
+ def start_listening
43
+
44
+ begin
45
+ # http client instantiation and configuration
46
+ http_client = Net::HTTP.new(@host, @port)
47
+ logger.debug("#{self.class} opening connection to #{@host}:#{@port}")
48
+ http_client.use_ssl = @scheme.eql?("https")
49
+ if @scheme.eql?("https")
50
+ http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
51
+ end
52
+
53
+ logger.debug("#{self.class} start listening to stream #{@path}")
54
+
55
+ http_client.read_timeout = 60*6 # double the timeout connFu is configured
56
+
57
+ # open connection
58
+ http_client.start do |http|
59
+ req = Net::HTTP::Get.new(
60
+ @path,
61
+ headers)
62
+
63
+ # send GET request
64
+ http.request(req) do |res|
65
+ logger.debug "#{self.class} Request to the endpoint...."
66
+ # read chunk data
67
+ logger.debug "#{self.class} Waiting for a new event...."
68
+ res.read_body do |chunk|
69
+ unless chunk.chomp.strip.empty?
70
+ # format data retrieved
71
+ events = handle_data(chunk)
72
+ # Insert message(s) in the queue
73
+ events.nil? or message(events)
74
+ else
75
+ logger.debug "#{self.class} got an empty data"
76
+ end
77
+ logger.debug "#{self.class} Waiting for a new event...."
78
+ end
79
+ end
80
+ end
81
+ rescue Exception => ex
82
+ logger.error "[#{Time.now} | #{ex.class}] #{ex.message}\n#{ex.backtrace.join("\n")}"
83
+ # loop again
84
+ start_listening
85
+ end
86
+ end
87
+
88
+ ##
89
+ # This method should be called by the class that instantiates the ConnfuStream to fetch inbound events.
90
+ # It stops the execution waiting for an inbound event.
91
+ #
92
+ # ==== Return
93
+ # Connfu::Message instance
94
+ def get
95
+ queue.pop
96
+ end
97
+
98
+ private
99
+
100
+ ##
101
+ # Internal Queue to send Message instances to the listener
102
+ #
103
+ # ==== Return
104
+ # Queue instance
105
+ def queue
106
+ @queue ||= Queue.new
107
+ end
108
+
109
+ ##
110
+ # Process the data retrieved, formatting the raw data into one or more Connfu::Message instances
111
+ #
112
+ # ==== Parameters
113
+ # * +chunk+ data got from the HTTP stream
114
+ #
115
+ # ==== Return
116
+ # * Array of Connfu::Message instances
117
+ # * nil if invalid data
118
+ def handle_data(chunk)
119
+ if chunk.nil?
120
+ logger.info("Unable to process nil data")
121
+ return nil
122
+ end
123
+ logger.debug("raw data #{chunk}")
124
+ chunk = chunk.split("\n") # more than one event can be retrieved separated by '\n'
125
+ if chunk.is_a?(Array)
126
+ events = []
127
+ temp_events = []
128
+ chunk.each { |value|
129
+ json = ActiveSupport::JSON.decode(value)
130
+ if json
131
+ unless json.is_a?(Array) # Twitter - RSS message
132
+ unless json.nil?
133
+ logger.debug("#{self.class} Got a twitter message")
134
+ temp_events << @formatter.format_message(json)
135
+ temp_events.nil? or events << temp_events.flatten
136
+ else
137
+ logger.debug("#{self.class} Invalid data received")
138
+ events = nil
139
+ end
140
+ else # Voice - SMS message
141
+ logger.debug("#{self.class} Got a voice/sms message")
142
+ logger.debug(json)
143
+ temp_events = @formatter.format_voice_sms(json)
144
+ temp_events.nil? or events << temp_events.flatten
145
+ end
146
+ end
147
+ }
148
+ events.flatten
149
+ else
150
+ logger.info("#{self.class} Invalid data received #{chunk}")
151
+ nil
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Insert an array of messages in the queue
157
+ # ==== Parameters
158
+ # * +events+ array of incoming events
159
+ def message(events)
160
+ if events.is_a?(Array)
161
+ events.each { |msg|
162
+ if msg.is_a?(Connfu::Message)
163
+ logger.debug("#{self.class} Inserting message in the queue")
164
+ logger.debug msg.to_s
165
+ queue << msg
166
+ else
167
+ logger.info("Invalid message type #{msg.class}")
168
+ end
169
+ }
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Headers to send to authenticate
175
+ def headers
176
+ {"accept" => "application/json",
177
+ "authorization" => "Backchat #{@api_key}",
178
+ "Connection" => "Keep-Alive"}
179
+ end
180
+
181
+ end
182
+ end