livechat_client 0.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 (44) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +39 -0
  4. data/README.md +327 -0
  5. data/Rakefile +13 -0
  6. data/conf/cacert.pem +3376 -0
  7. data/lib/livechat.rb +23 -0
  8. data/lib/livechat/rest/agents.rb +19 -0
  9. data/lib/livechat/rest/canned_responses.rb +9 -0
  10. data/lib/livechat/rest/chats.rb +18 -0
  11. data/lib/livechat/rest/client.rb +142 -0
  12. data/lib/livechat/rest/errors.rb +14 -0
  13. data/lib/livechat/rest/goals.rb +14 -0
  14. data/lib/livechat/rest/groups.rb +9 -0
  15. data/lib/livechat/rest/instance_resource.rb +115 -0
  16. data/lib/livechat/rest/list_resource.rb +115 -0
  17. data/lib/livechat/rest/reports.rb +116 -0
  18. data/lib/livechat/rest/status.rb +13 -0
  19. data/lib/livechat/rest/utils.rb +25 -0
  20. data/lib/livechat/rest/visitors.rb +17 -0
  21. data/lib/livechat/util.rb +7 -0
  22. data/lib/livechat/version.rb +3 -0
  23. data/livechat_client.gemspec +33 -0
  24. data/spec/fixtures/agent.json +30 -0
  25. data/spec/fixtures/agents.json +16 -0
  26. data/spec/fixtures/canned_response.json +12 -0
  27. data/spec/fixtures/canned_responses.json +37 -0
  28. data/spec/fixtures/chat.json +129 -0
  29. data/spec/fixtures/chats.json +135 -0
  30. data/spec/fixtures/goal.json +8 -0
  31. data/spec/fixtures/goals.json +12 -0
  32. data/spec/fixtures/group.json +10 -0
  33. data/spec/fixtures/groups.json +32 -0
  34. data/spec/fixtures/visitors.json +91 -0
  35. data/spec/livechat/rest/agents_spec.rb +56 -0
  36. data/spec/livechat/rest/canned_responses_spec.rb +43 -0
  37. data/spec/livechat/rest/chats_spec.rb +37 -0
  38. data/spec/livechat/rest/goals_spec.rb +50 -0
  39. data/spec/livechat/rest/groups_spec.rb +45 -0
  40. data/spec/livechat/rest/reports_spec.rb +67 -0
  41. data/spec/livechat/rest/status_spec.rb +22 -0
  42. data/spec/livechat/rest/visitors_spec.rb +44 -0
  43. data/spec/spec_helper.rb +38 -0
  44. metadata +185 -0
@@ -0,0 +1,23 @@
1
+
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'multi_json'
5
+ require 'cgi'
6
+ require 'openssl'
7
+
8
+
9
+ require "livechat/version"
10
+ require 'livechat/util'
11
+ require 'livechat/rest/errors'
12
+ require 'livechat/rest/utils'
13
+ require 'livechat/rest/list_resource'
14
+ require 'livechat/rest/instance_resource'
15
+ require 'livechat/rest/agents'
16
+ require 'livechat/rest/canned_responses'
17
+ require 'livechat/rest/chats'
18
+ require 'livechat/rest/goals'
19
+ require 'livechat/rest/groups'
20
+ require 'livechat/rest/reports'
21
+ require 'livechat/rest/status'
22
+ require 'livechat/rest/visitors'
23
+ require 'livechat/rest/client'
@@ -0,0 +1,19 @@
1
+ module LiveChat
2
+ module REST
3
+ class Agents < ListResource
4
+ def initialize(path, client)
5
+ super
6
+ #hard-coded keys since agents are special
7
+ @instance_id_key = 'login'
8
+ end
9
+ end
10
+
11
+ class Agent < InstanceResource
12
+ def reset_api_key
13
+ raise "Can't execute without a REST Client" unless @client
14
+ set_up_properties_from(@client.put("#{@path}/reset_api_key", {}))
15
+ self
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module LiveChat
2
+ module REST
3
+ class CannedResponses < ListResource
4
+ end
5
+
6
+ class CannedResponse < InstanceResource
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module LiveChat
2
+ module REST
3
+ class Chats < ListResource
4
+ def initialize(path, client)
5
+ super
6
+ #chats is different than the other resources
7
+ @list_key = 'chats'
8
+ end
9
+ end
10
+
11
+ class Chat < InstanceResource
12
+ def send_transcript(*args)
13
+ @client.post "#{@path}/send_transcript", Hash[*args]
14
+ self
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,142 @@
1
+ module LiveChat
2
+ module REST
3
+ class Client
4
+ include LiveChat::Util
5
+ include LiveChat::REST::Utils
6
+
7
+ API_VERSION = '2'
8
+
9
+ HTTP_HEADERS = {
10
+ 'Accept' => 'application/json',
11
+ 'Accept-Charset' => 'utf-8',
12
+ 'User-Agent' => "livechat-ruby/#{LiveChat::VERSION}",
13
+ 'X-API-Version' => API_VERSION
14
+ }
15
+
16
+ DEFAULTS = {
17
+ :host => 'api.livechatinc.com',
18
+ :port => 443,
19
+ :use_ssl => true,
20
+ :ssl_verify_peer => true,
21
+ :ssl_ca_file => File.dirname(__FILE__) + '/../../../conf/cacert.pem',
22
+ :timeout => 30,
23
+ :proxy_addr => nil,
24
+ :proxy_port => nil,
25
+ :proxy_user => nil,
26
+ :proxy_pass => nil,
27
+ :retry_limit => 1,
28
+ }
29
+
30
+ attr_reader :login, :last_request, :last_response
31
+
32
+ %w(agents canned_responses chats goals groups reports status visitors).each do |r|
33
+ define_method(r.to_sym) do |*args|
34
+ klass = LiveChat::REST.const_get restify(r.capitalize)
35
+ n = klass.new("/#{r}", self)
36
+ if args.length > 0
37
+ n.get(args[0])
38
+ else
39
+ n
40
+ end
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Instantiate a new HTTP client to talk to LiveChat. The parameters
46
+ # +login+ and +api_key+ are required and used to generate the
47
+ # HTTP basic auth header in each request.
48
+ #
49
+ def initialize(options={})
50
+ yield options if block_given?
51
+ @config = DEFAULTS.merge! options
52
+ @login = @config[:login].strip
53
+ @api_key = @config[:api_key].strip
54
+ raise ArgumentError, "Login and API key are required!" unless @login and @api_key
55
+ set_up_connection
56
+ end
57
+
58
+ def inspect # :nodoc:
59
+ "<LiveChat::REST::Client @login=#{@login}>"
60
+ end
61
+
62
+ ##
63
+ # Define #get, #put, #post and #delete helper methods for sending HTTP
64
+ # requests to LiveChat. You shouldn't need to use these methods directly,
65
+ # but they can be useful for debugging. Each method returns a hash
66
+ # obtained from parsing the JSON object in the response body.
67
+ [:get, :put, :post, :delete].each do |method|
68
+ method_class = Net::HTTP.const_get method.to_s.capitalize
69
+ define_method method do |path, *args|
70
+ params ||= {}
71
+ params = args[0] if args[0]
72
+ unless args[1] # build the full path unless already given
73
+ path = "#{path}"
74
+ if method == :get
75
+ path << "?#{url_encode(params)}" if method == :get && !params.empty?
76
+ end
77
+ end
78
+ request = method_class.new path, HTTP_HEADERS
79
+ request.basic_auth @login, @api_key
80
+ request.form_data = params if [:post, :put].include? method
81
+ connect_and_send request
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ ##
88
+ # Set up and cache a Net::HTTP object to use when making requests. This is
89
+ # a private method documented for completeness.
90
+ def set_up_connection # :doc:
91
+ connection_class = Net::HTTP::Proxy @config[:proxy_addr],
92
+ @config[:proxy_port], @config[:proxy_user], @config[:proxy_pass]
93
+ @connection = connection_class.new @config[:host], @config[:port]
94
+ set_up_ssl
95
+ @connection.open_timeout = @config[:timeout]
96
+ @connection.read_timeout = @config[:timeout]
97
+ end
98
+
99
+ ##
100
+ # Set up the ssl properties of the <tt>@connection</tt> Net::HTTP object.
101
+ # This is a private method documented for completeness.
102
+ def set_up_ssl # :doc:
103
+ @connection.use_ssl = @config[:use_ssl]
104
+ if @config[:ssl_verify_peer]
105
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
106
+ @connection.ca_file = @config[:ssl_ca_file]
107
+ else
108
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
109
+ end
110
+ end
111
+
112
+
113
+ ##
114
+ # Send an HTTP request using the cached <tt>@connection</tt> object and
115
+ # return the JSON response body parsed into a hash. Also save the raw
116
+ # Net::HTTP::Request and Net::HTTP::Response objects as
117
+ # <tt>@last_request</tt> and <tt>@last_response</tt> to allow for
118
+ # inspection later.
119
+ def connect_and_send(request) # :doc:
120
+ @last_request = request
121
+ retries_left = @config[:retry_limit]
122
+ begin
123
+ response = @connection.request request
124
+ @last_response = response
125
+ if response.kind_of? Net::HTTPServerError
126
+ raise LiveChat::REST::ServerError
127
+ end
128
+ rescue Exception
129
+ raise if request.class == Net::HTTP::Post
130
+ if retries_left > 0 then retries_left -= 1; retry else raise end
131
+ end
132
+ if response.body and !response.body.empty?
133
+ object = MultiJson.load response.body
134
+ end
135
+ if response.kind_of? Net::HTTPClientError
136
+ raise LiveChat::REST::RequestError.new "#{object['message']}: #{response.body}", object['code']
137
+ end
138
+ object
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,14 @@
1
+ module LiveChat
2
+ module REST
3
+ class ServerError < StandardError; end
4
+
5
+ class RequestError < StandardError
6
+ attr_reader :code
7
+
8
+ def initialize(message, code=nil)
9
+ super message
10
+ @code = code
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module LiveChat
2
+ module REST
3
+ class Goals < ListResource
4
+ end
5
+
6
+ class Goal < InstanceResource
7
+ def mark_as_successful(*args)
8
+ raise "Can't execute without a REST Client" unless @client
9
+ @client.post "#{@path}/mark_as_successful", Hash[*args]
10
+ self
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module LiveChat
2
+ module REST
3
+ class Groups < ListResource
4
+ end
5
+
6
+ class Group < InstanceResource
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,115 @@
1
+ #This file is based on code from https://github.com/twilio/twilio-ruby
2
+ #
3
+ #Copyright (c) 2010 Andrew Benton.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ #of this software and associated documentation files (the "Software"), to deal
7
+ #in the Software without restriction, including without limitation the rights
8
+ #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ #copies of the Software, and to permit persons to whom the Software is
10
+ #furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ #all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ #THE SOFTWARE.
22
+
23
+
24
+ module LiveChat
25
+ module REST
26
+ ##
27
+ # A class to wrap an instance resource (like a call or application) within
28
+ # the LiveChat API.
29
+ class InstanceResource
30
+ include Utils
31
+
32
+ ##
33
+ # Instantiate a new instance resource object. You must pass the +path+ of
34
+ # the instance (e.g. /chats/MH022RD0K5) as well as a
35
+ # +client+ object that responds to #get #post and #delete. This client
36
+ # is meant to be an instance of LiveChat::REST::Client but could just as
37
+ # well be a mock object if you want to test the interface. The optional
38
+ # +params+ hash will be converted into attributes on the instantiated
39
+ # object.
40
+ def initialize(path, client, params = {})
41
+ @path, @client = path, client
42
+ set_up_properties_from params
43
+ end
44
+
45
+ def inspect # :nodoc:
46
+ "<#{self.class} @path=#{@path}>"
47
+ end
48
+
49
+ ##
50
+ # Update the properties of this instance resource using the key/value
51
+ # pairs in +params+. This makes an HTTP POST request to <tt>@path</tt>
52
+ # to handle the update.
53
+ #
54
+ # After returning, the object will contain the most recent state of the
55
+ # instance resource, including the newly updated properties.
56
+ def update(params = {})
57
+ raise "Can't update a resource without a REST Client" unless @client
58
+ yield params if block_given?
59
+ set_up_properties_from(@client.put(@path, params))
60
+ self
61
+ end
62
+
63
+ ##
64
+ # Refresh the attributes of this instance resource object by fetching it
65
+ # from LiveChat. Calling this makes an HTTP GET request to <tt>@path</tt>.
66
+ def refresh
67
+ raise "Can't refresh a resource without a REST Client" unless @client
68
+ @updated = false
69
+ set_up_properties_from(@client.get(@path))
70
+ self
71
+ end
72
+
73
+ ##
74
+ # Delete an instance resource from LiveChat. This operation isn't always
75
+ # supported. For instance, you can't delete an SMS. Calling this method
76
+ # makes an HTTP DELETE request to <tt>@path</tt>.
77
+ def delete
78
+ raise "Can't delete a resource without a REST Client" unless @client
79
+ @client.delete @path
80
+ end
81
+
82
+ ##
83
+ # Lazily load attributes of the instance resource by waiting to fetch it
84
+ # until an attempt is made to access an unknown attribute.
85
+ def method_missing(method, *args)
86
+ super if @updated
87
+ set_up_properties_from(@client.get(@path))
88
+ self.send method, *args
89
+ end
90
+
91
+ protected
92
+
93
+ def set_up_properties_from(hash)
94
+ eigenclass = class << self; self; end
95
+ hash.each do |p,v|
96
+ property = unrestify p
97
+ eigenclass.send :define_method, property.to_sym, &lambda {v}
98
+ end
99
+ @updated = !hash.keys.empty?
100
+ end
101
+
102
+ def resource(*resources)
103
+ resources.each do |r|
104
+ resource = restify r
105
+ relative_path = resource
106
+ path = "#{@path}/#{relative_path}"
107
+ resource_class = LiveChat::REST.const_get resource
108
+ instance_variable_set("@#{r}", resource_class.new(path, @client))
109
+ end
110
+ self.class.instance_eval {attr_reader *resources}
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,115 @@
1
+ #This file is based on code from https://github.com/twilio/twilio-ruby
2
+ #
3
+ #Copyright (c) 2010 Andrew Benton.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ #of this software and associated documentation files (the "Software"), to deal
7
+ #in the Software without restriction, including without limitation the rights
8
+ #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ #copies of the Software, and to permit persons to whom the Software is
10
+ #furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ #all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ #THE SOFTWARE.
22
+
23
+ module LiveChat
24
+ module REST
25
+ class ListResource
26
+ include Utils
27
+
28
+ def initialize(path, client)
29
+ @path, @client = path, client
30
+ resource_name = self.class.name.split('::')[-1]
31
+ @instance_class = LiveChat::REST.const_get resource_name.chop
32
+ @instance_id_key = 'id'
33
+ end
34
+
35
+ def inspect # :nodoc:
36
+ "<#{self.class} @path=#{@path}>"
37
+ end
38
+
39
+ ##
40
+ # Grab a list of this kind of resource and return it as an array. The
41
+ # array includes a special attribute named +total+ which will return the
42
+ # total number of items in the list on LiveChat's server. This may differ
43
+ # from the +size+ and +length+ attributes of the returned array since
44
+ # by default LiveChat will only return 50 resources, and the maximum number
45
+ # of resources you can request is 1000.
46
+ #
47
+ # The optional +params+ hash allows you to filter the list returned. The
48
+ # filters for each list resource type are defined by LiveChat.
49
+ def list(params={}, full_path=false)
50
+ raise "Can't get a resource list without a REST Client" unless @client
51
+ response = @client.get @path, params, full_path
52
+ resources = @list_key ? response[@list_key]: response
53
+ path = full_path ? @path.split('.')[0] : @path
54
+ resource_list = resources.map do |resource|
55
+ @instance_class.new "#{path}/#{resource[@instance_id_key]}", @client,
56
+ resource
57
+ end
58
+ # set the +total+ and +next_page+ properties on the array
59
+ #client, list_class = @client, self.class
60
+ #resource_list.instance_eval do
61
+ #eigenclass = class << self; self; end
62
+ #eigenclass.send :define_method, :total, &lambda {response['total']}
63
+ #eigenclass.send :define_method, :next_page, &lambda {
64
+ # if response['next_page_uri']
65
+ # list_class.new(response['next_page_uri'], client).list({}, true)
66
+ # else
67
+ # []
68
+ # end
69
+ #}
70
+ #end
71
+ resource_list
72
+ end
73
+
74
+ ##
75
+ # Ask LiveChat for the total number of items in the list.
76
+ # Calling this method makes an HTTP GET request to <tt>@path</tt> with a
77
+ # page size parameter of 1 to minimize data over the wire while still
78
+ # obtaining the total. Don't use this if you are planning to
79
+ # call #list anyway, since the array returned from #list will have a
80
+ # +total+ attribute as well.
81
+ #def total
82
+ # raise "Can't get a resource total without a REST Client" unless @client
83
+ # @client.get(@path, :page_size => 1)['total']
84
+ #end
85
+
86
+ ##
87
+ # Return an empty instance resource object with the proper path. Note that
88
+ # this will never raise a LiveChat::REST::RequestError on 404 since no HTTP
89
+ # request is made. The HTTP request is made when attempting to access an
90
+ # attribute of the returned instance resource object, such as
91
+ # its #date_created or #voice_url attributes.
92
+ def get(id)
93
+ @instance_class.new "#{@path}/#{id}", @client
94
+ end
95
+ alias :find :get # for the ActiveRecord lovers
96
+
97
+ ##
98
+ # Return a newly created resource. Some +params+ may be required. Consult
99
+ # the LiveChat REST API documentation related to the kind of resource you
100
+ # are attempting to create for details. Calling this method makes an HTTP
101
+ # POST request to <tt>@path</tt> with the given params
102
+ def create(params={})
103
+ raise "Can't create a resource without a REST Client" unless @client
104
+ yield params if block_given?
105
+ response = @client.post @path, params
106
+ @instance_class.new "#{@path}/#{response[@instance_id_key]}", @client, response
107
+ end
108
+
109
+ def each
110
+ list.each { |result| yield result }
111
+ end
112
+
113
+ end
114
+ end
115
+ end