livefyre 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -7,7 +7,7 @@ Gemfile.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
10
- doc/
10
+ doc
11
11
  lib/bundler/man
12
12
  pkg
13
13
  rdoc
data/README.md CHANGED
@@ -91,16 +91,38 @@ Or you can have the gem do it automatically from the model:
91
91
  livefyre_user :update_on => [:email, :display_name]
92
92
  end
93
93
 
94
- You can even have the gem do your ping updated asynchronously with Resque
94
+ You can provide your own update callback, in case you want to use something like Sidekiq to do the updates:
95
95
 
96
96
  class User < ActiveRecord::Base
97
97
  # ...
98
98
 
99
- livefyre_user :update_on => [:email, :display_name], :defer => true
99
+ livefyre_user :update_on => [:email, :display_name] do |user, livefyre_id|
100
+ Livefyre::User.delay.refresh livefyre_id
101
+ end
100
102
  end
101
103
 
102
104
  This will enqueue a ping-to-pull job in the "livefyre" queue. Make sure you have a worker running that'll handle that queue!
103
105
 
106
+ ### Handling Postbacks
107
+
108
+ To handle postbacks, you'll need to set up a postback route:
109
+
110
+ match '/livefyre/postback', to: 'comments#postback'
111
+
112
+ You'll also need to tell Livefyre about this URL (similar to the ping-to-pull URL, via a console or elsewhere)
113
+
114
+ Livefyre::Site.new.set_postback_url "http://foo.com/livefyre/postback"
115
+
116
+ Finally, the gem provides a helper for validating Livefyre postback requests.
117
+
118
+ class CommentsController < ApplicationController
119
+ validate_postback_signature :only => [:postback], :key => "your_site_key"
120
+
121
+ def postback
122
+ # Handle the postback
123
+ end
124
+ end
125
+
104
126
  ### View integration
105
127
 
106
128
 
data/Rakefile CHANGED
@@ -3,8 +3,10 @@ require "bundler/gem_tasks"
3
3
  begin
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new('spec')
6
- rescue LoadError => e
6
+ rescue LoadError
7
7
  # Pass
8
8
  end
9
9
 
10
- task :default => :spec
10
+ task :doc do
11
+ sh %{yard --plugin yard-tomdoc -o doc}
12
+ end
@@ -0,0 +1,177 @@
1
+ // Generated by CoffeeScript 1.3.3
2
+ (function() {
3
+ var cookie, defaultDelegate, load, utils, _initialized;
4
+
5
+ defaultDelegate = function(options) {
6
+ var authDelegate, k, v, _ref;
7
+ authDelegate = new fyre.conv.RemoteAuthDelegate();
8
+ _ref = options.auth;
9
+ for (k in _ref) {
10
+ v = _ref[k];
11
+ authDelegate[k] = v;
12
+ }
13
+ return authDelegate;
14
+ };
15
+
16
+ load = null;
17
+
18
+ (function() {
19
+ var head, __loadedScripts;
20
+ __loadedScripts = [];
21
+ head = null;
22
+ return load = function(source, id, content, options) {
23
+ var js, k, v;
24
+ if (!content) {
25
+ content = null;
26
+ }
27
+ if (document.getElementById(id)) {
28
+ return;
29
+ }
30
+ if (__loadedScripts[id]) {
31
+ return;
32
+ }
33
+ __loadedScripts[id] = true;
34
+ if (!head) {
35
+ head = document.getElementsByTagName('head')[0];
36
+ }
37
+ js = document.createElement("script");
38
+ js.id = id;
39
+ js.async = true;
40
+ js.src = source;
41
+ js.innerHTML = content;
42
+ if (options) {
43
+ for (k in options) {
44
+ v = options[k];
45
+ js[k] = v;
46
+ }
47
+ }
48
+ head.appendChild(js);
49
+ return js;
50
+ };
51
+ })();
52
+
53
+ cookie = function(token) {
54
+ var m;
55
+ m = document.cookie.match(new RegExp(token + "=([^;]+)"));
56
+ if (m) {
57
+ return m[1];
58
+ } else {
59
+ return null;
60
+ }
61
+ };
62
+
63
+ utils = function(options) {
64
+ var obj;
65
+ return obj = {
66
+ load: load,
67
+ startLogin: function(url, width, height, callback) {
68
+ var left, popup, top;
69
+ if (width == null) {
70
+ width = 600;
71
+ }
72
+ if (height == null) {
73
+ height = 400;
74
+ }
75
+ if (callback == null) {
76
+ callback = null;
77
+ }
78
+ left = (screen.width / 2) - (width / 2);
79
+ top = (screen.height / 2) - (height / 2);
80
+ popup = window.open(url, name, "menubar=no,toolbar=no,status=no,width=" + width + ",height=" + height + ",toolbar=no,left=" + left + ",top=" + top);
81
+ this.finishCallback = callback;
82
+ return this.startLoginPopup(popup);
83
+ },
84
+ startLoginPopup: function(popup) {
85
+ var _this = this;
86
+ this.tries = 0;
87
+ this.popup = popup;
88
+ return this.timer = setInterval(function() {
89
+ _this.tries += 1;
90
+ return _this.__checkLogin();
91
+ }, 100);
92
+ },
93
+ __checkLogin: function() {
94
+ var token;
95
+ token = cookie(options.cookie_name || "livefyre_utoken");
96
+ if (this.popup) {
97
+ try {
98
+ if (this.popup.closed === false && this.tries > 30) {
99
+ clearInterval(this.timer);
100
+ this.timer = null;
101
+ this.popup = null;
102
+ }
103
+ } catch (err) {
104
+
105
+ }
106
+ }
107
+ if (token) {
108
+ clearInterval(this.timer);
109
+ this.popup.close();
110
+ this.popup = null;
111
+ this.timer = null;
112
+ if (this.finishCallback) {
113
+ this.finishCallback();
114
+ }
115
+ return window.fyre.conv.login(token);
116
+ }
117
+ }
118
+ };
119
+ };
120
+
121
+ _initialized = false;
122
+
123
+ this.initLivefyre = function(options) {
124
+ var e, element, returnable;
125
+ if (_initialized && !options.force) {
126
+ throw "Livefyre has already been initialized";
127
+ }
128
+ _initialized = true;
129
+ e = document.getElementById(options.element_id || "livefyre_comments");
130
+ if (e) {
131
+ options.config || (options.config = {
132
+ checksum: e.getAttribute("data-checksum"),
133
+ collectionMeta: e.getAttribute("data-collection-meta"),
134
+ articleId: e.getAttribute("data-article-id"),
135
+ siteId: e.getAttribute("data-site-id"),
136
+ el: e.id
137
+ });
138
+ options.network || (options.network = e.getAttribute("data-network"));
139
+ options.domain || (options.domain = e.getAttribute("data-domain"));
140
+ options.root || (options.root = e.getAttribute("data-root"));
141
+ returnable = utils(options);
142
+ this.FYRE_LOADED_CB = function() {
143
+ var opts;
144
+ if (options.preLoad) {
145
+ options.preLoad(fyre);
146
+ }
147
+ opts = {
148
+ network: options.network,
149
+ authDelegate: options.delegate || defaultDelegate(options)
150
+ };
151
+ return fyre.conv.load(opts, [options.config], function(widget) {
152
+ var token;
153
+ returnable.widget = widget;
154
+ token = cookie(options.cookie_name || "livefyre_utoken");
155
+ if (token) {
156
+ try {
157
+ return fyre.conv.login(token);
158
+ } catch (error) {
159
+ if (window.console) {
160
+ return window.console.log("Error logging in:", e);
161
+ }
162
+ }
163
+ }
164
+ });
165
+ };
166
+ if (!options.manualLoad) {
167
+ element = load("http://" + options.root + "/wjs/v3.0/javascripts/livefyre.js", null, null, {
168
+ "data-lf-domain": options.network
169
+ });
170
+ }
171
+ return returnable;
172
+ } else {
173
+ return null;
174
+ }
175
+ };
176
+
177
+ }).call(this);
@@ -1,28 +1,13 @@
1
1
  defaultDelegate = (options) ->
2
2
  authDelegate = new fyre.conv.RemoteAuthDelegate()
3
- authDelegate.login = (handlers) ->
4
- if options.login
5
- options.login(handlers)
3
+ authDelegate[k] = v for k, v of options.auth
4
+ authDelegate
6
5
 
7
- authDelegate.logout = (handlers) ->
8
- if options.logout
9
- options.logout(handlers)
10
- authDelegate.viewProfile = (handlers, author) ->
11
- if options.viewProfile
12
- if options.viewProfile(handlers, author)
13
- handlers.success()
14
-
15
- authDelegate.editProfile = (handlers, author) ->
16
- if options.editProfile
17
- if options.editProfile(handlers, author)
18
- handlers.success()
19
-
20
- loadScriptAsync = null
6
+ load = null
21
7
  (->
22
8
  __loadedScripts = []
23
9
  fjs = null
24
- loadScriptAsync = (source, id, content, options) ->
25
- content = null if !content
10
+ load = (source, id, options) ->
26
11
  return if (document.getElementById(id))
27
12
  return if __loadedScripts[id]
28
13
  __loadedScripts[id] = true
@@ -31,18 +16,57 @@ loadScriptAsync = null
31
16
  js.id = id
32
17
  js.async = true
33
18
  js.src = source
34
- js.innerHTML = content
35
19
  js[k] = v for k, v of options if options
36
20
 
37
21
  fjs.parentNode.insertBefore(js, fjs)
38
22
  js
39
23
  )()
40
24
 
41
- livefyreInitialized = false
25
+ cookie = (token) ->
26
+ m = document.cookie.match(new RegExp(token + "=([^;]+)"))
27
+ if m then m[1] else null
28
+
29
+ utils = (options) ->
30
+ obj =
31
+ load: load
32
+ startLogin: (url, width = 600, height = 400, callback = null) ->
33
+ left = (screen.width / 2) - (width / 2)
34
+ top = (screen.height / 2) - (height / 2)
35
+ popup = window.open url, name, "menubar=no,toolbar=no,status=no,width=#{width},height=#{height},toolbar=no,left=#{left},top=#{top}"
36
+ @finishCallback = callback
37
+ @startLoginPopup(popup)
38
+
39
+ startLoginPopup: (popup) ->
40
+ @tries = 0
41
+ @popup = popup
42
+ @timer = setInterval(=>
43
+ @tries += 1
44
+ @__checkLogin()
45
+ , 100)
46
+
47
+ __checkLogin: ->
48
+ token = cookie(options.cookie_name || "livefyre_utoken")
49
+ if @popup
50
+ try
51
+ if @popup.closed == false and @tries > 30 # 3 seconds
52
+ clearInterval(@timer)
53
+ @timer = null
54
+ @popup = null
55
+ catch err
56
+
57
+ if token
58
+ clearInterval(@timer)
59
+ @popup.close()
60
+ @popup = null
61
+ @timer = null
62
+ @finishCallback() if @finishCallback
63
+ window.fyre.conv.login(token)
64
+
65
+ _initialized = false
42
66
  @initLivefyre = (options) ->
43
- if livefyreInitialized
67
+ if _initialized and !options.force
44
68
  throw "Livefyre has already been initialized"
45
- livefyreInitialized = true
69
+ _initialized = true
46
70
  e = document.getElementById(options.element_id || "livefyre_comments")
47
71
  if e
48
72
  options.config ||=
@@ -56,17 +80,26 @@ livefyreInitialized = false
56
80
  options.domain ||= e.getAttribute("data-domain")
57
81
  options.root ||= e.getAttribute("data-root")
58
82
 
83
+ returnable = utils(options)
84
+
59
85
  @FYRE_LOADED_CB = ->
86
+ options.preLoad(fyre) if options.preLoad
60
87
  opts =
61
88
  network: options.network
62
89
  authDelegate: options.delegate || defaultDelegate(options)
63
90
 
64
- fyre.conv.load opts, [options.config], ->
65
- token = $.cookie(options.cookie_name || "livefyre_utoken")
91
+ fyre.conv.load opts, [options.config], (widget) ->
92
+ returnable.widget = widget
93
+ token = cookie(options.cookie_name || "livefyre_utoken")
66
94
  if token
67
95
  try
68
96
  fyre.conv.login(token)
69
97
  catch error
70
98
  window.console.log "Error logging in:", e if window.console
71
99
 
72
- element = loadScriptAsync "http://#{options.root}/wjs/v3.0/javascripts/livefyre.js", null, null, {"data-lf-domain": options.network}
100
+ unless options.manualLoad
101
+ element = load "http://#{options.root}/wjs/v3.0/javascripts/livefyre.js", null, {"data-lf-domain": options.network}
102
+ returnable
103
+
104
+ else
105
+ null
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDNDCCAhygAwIBAgIBADANBgkqhkiG9w0BAQUFADBAMQ8wDQYDVQQDDAZjaGVh
3
+ bGQxGDAWBgoJkiaJk/IsZAEZFghtYXNoYWJsZTETMBEGCgmSJomT8ixkARkWA2Nv
4
+ bTAeFw0xMzAxMzExNzI2NDBaFw0xNDAxMzExNzI2NDBaMEAxDzANBgNVBAMMBmNo
5
+ ZWFsZDEYMBYGCgmSJomT8ixkARkWCG1hc2hhYmxlMRMwEQYKCZImiZPyLGQBGRYD
6
+ Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4AMfgtORekddgqRx
7
+ mHdKSRuQ7Pks2LN96uINU9lWhmppM/mrbHUvkW28wdfC27MTtR1cQUZWAzS8eWsH
8
+ Fq0hdm5R5tsrXLM8Y7tUcoKu5qTJEmDyN2TH3lx9hH/fzwhU2rhb/DrIhtOJNBsg
9
+ 5AERbX/LA6nmQLEQlbVoVHH5tmIGnAYY96WfCNeJyexLfuOeAn1NFyBJqsU7VhFf
10
+ JBhyHKdMDRY3HkQD6LHVVDidwEOyi+vKb13GlHwproUPutp3qTEG24IhuQGARV+f
11
+ zSwQ1Ay6L7CmMZmznwPaPBd/zOMbe1RGg5ofCvNAgxspbNGQPJvNp+DxRENdY06N
12
+ qeO/GQIDAQABozkwNzAJBgNVHRMEAjAAMB0GA1UdDgQWBBRqk99cHYdBntfkSYOk
13
+ ZfXJcbaTAjALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQADggEBAHaKNSM783Da
14
+ /BGQv6DRjxi3wYmnn4RHJIQK4DiJjR6umtCtlBuMbhQbUBbu8sIFnqtdsKHaC097
15
+ ygk9anqygAOYtYk2+hfn2fBFYZTflvgSuo6/IzMWucOpGrbIWczDMnH8p4biJNp+
16
+ 24du/MH+9ODiInly0UwcDyE4TW2chmvOCjLowa7bvJY3LXCDpSUMXjIO/4Cysfwk
17
+ F5adOPFD5KcabVlHmRpprAw2GYYbWwuuV11tA+4Fhkz0ClB25/gEkFZWevs5wsEC
18
+ miAJUwubyGI/SoFFe9MYI4i01qPX/Hqa9wqLbbEaUSCUQfcHNzgK3o2SYjmQe6Nm
19
+ yld6FGg9zOY=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,43 @@
1
+ module Livefyre
2
+ # Public: Proxy object for an item from a Conversation activity feed
3
+ class Activity
4
+ attr_accessor :id, :user, :conversation, :body, :state, :created_at
5
+ def initialize(client, params = {})
6
+ @client = Livefyre.client
7
+ @params = params
8
+ @id = params["activity_id"]
9
+ @conversation = Conversation.new(@params["lf_conv_id"], @params["article_identifier"])
10
+ @created_at = Time.at(@params["created"]) - Time.at(0).utc_offset
11
+ end
12
+
13
+ # Public: Cast this activity to a Comment
14
+ #
15
+ # Return [Comment]
16
+ def comment
17
+ Comment.new(@params["lf_comment_id"], conversation,
18
+ :body => @params["body_text"],
19
+ :user => user,
20
+ :parent_id => @params["lf_parent_comment_id"],
21
+ :author_ip => @params["author_ip"],
22
+ :state => @params["state"]
23
+ )
24
+ end
25
+
26
+ # Public: Fetch the user that created this record
27
+ #
28
+ # Returns [User]
29
+ def user
30
+ User.new((@params["lf_jid"] || "").split("@", 2).first, @client, @params["author"],
31
+ :email => @params["author_email"],
32
+ :url => @params["author_url"]
33
+ )
34
+ end
35
+
36
+ # Internal: Test if this activity represented a comment
37
+ #
38
+ # Returns [Boolean]
39
+ def comment?
40
+ @params["activity_type"] == "comment-add"
41
+ end
42
+ end
43
+ end
@@ -8,7 +8,7 @@ module Livefyre
8
8
  # Public: Valid scopes for #set_user_role
9
9
  SCOPES = %w(domain site conv)
10
10
 
11
- attr_accessor :host, :key, :options, :system_token, :http_client
11
+ attr_accessor :host, :key, :options, :system_token, :http_client, :site_key, :quill, :stream, :bootstrap, :search
12
12
 
13
13
  def_delegators :http_client, :get, :post, :delete, :put
14
14
 
@@ -23,6 +23,11 @@ module Livefyre
23
23
  @host = options.delete(:network) || options.delete(:host)
24
24
  raise "Invalid host" if @host.nil?
25
25
  @http_client = Faraday.new(:url => "http://#{@host}")
26
+ @quill = Faraday.new(:url => "http://quill.#{@host}")
27
+ @stream = Faraday.new(:url => "http://stream.#{@host}")
28
+ @search = Faraday.new(:url => "http://search.#{@host}")
29
+ @bootstrap = Faraday.new(:url => "http://bootstrap.#{@host}")
30
+ @site_key = options[:site_key]
26
31
 
27
32
  @key = options.delete(:secret) || options.delete(:key) || options.delete(:network_key)
28
33
  raise "Invalid secret key" if @key.nil?
@@ -107,11 +112,30 @@ module Livefyre
107
112
  "%s@%s" % [id, host]
108
113
  end
109
114
 
115
+ # Internal: Identifier to use to uniquely identify this client.
116
+ #
117
+ # Returns string ID
118
+ def identifier
119
+ @identifier ||= "RubyLib-#{Process.pid}-#{local_ip}-#{object_id}"
120
+ end
121
+
110
122
  # Internal: Returns a cleaner string representation of this object
111
123
  #
112
124
  # Returns [String] representation of this class
113
125
  def to_s
114
126
  "#<#{self.class.name}:0x#{object_id.to_s(16).rjust(14, "0")} host='#{host}' key='#{key}'>"
115
127
  end
128
+
129
+ private
130
+
131
+ def local_ip
132
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
133
+ UDPSocket.open do |s|
134
+ s.connect '64.233.187.99', 1
135
+ s.addr.last
136
+ end
137
+ ensure
138
+ Socket.do_not_reverse_lookup = orig
139
+ end
116
140
  end
117
141
  end
@@ -0,0 +1,190 @@
1
+ module Livefyre
2
+ # Public: Proxy object for a [Comment] on a [Livefyre::Conversation]
3
+ class Comment
4
+ private
5
+ SOURCES = %w(Livefyre Twitter Twitter Facebook Livefyre Livefyre Facebook Twitter Livefyre)
6
+ VISIBILITIES = %w(None Everyone Owner Group)
7
+ CONTENT_TYPES = %w(Message Opinion)
8
+ PERMISSIONS = %w(Global Network Site Collection CollectionRule)
9
+ REASONS = %w(disagree spam offensive off-topic)
10
+
11
+ public
12
+
13
+ attr_accessor :id, :body, :user, :parent_id, :ip, :conversation, :created_at
14
+ def initialize(id, conversation, options = {})
15
+ @id = id
16
+ @body = options[:body]
17
+ @user = options[:user]
18
+ @parent_id = options[:parent_id]
19
+ @ip = options[:author_ip]
20
+ @conversation = conversation
21
+ @created_at = options[:created_at]
22
+ @client = options[:client] || Livefyre.client
23
+ @options = options
24
+ end
25
+
26
+ # Public: Flag a comment
27
+ #
28
+ # reason - one of [disagree, spam, offensive, off-topic]
29
+ # notes - String containing the reason for the flag
30
+ # email - email address of the flagger
31
+ # user - [User] If set, will include the user token for validation of the flag
32
+ def flag(reason, notes, email, user = nil)
33
+ raise "invalid reason" unless REASONS.include? reason
34
+ payload = {
35
+ :message_id => @id,
36
+ :collection_id => @conversation.id,
37
+ :flag => reason,
38
+ :notes => notes,
39
+ :email => email
40
+ }
41
+ payload[:lftoken] = user.token if user
42
+ response = client.quill.post "/api/v3.0/message/25818122/flag/#{reason}/", payload.to_json
43
+ if response.success?
44
+ true
45
+ else
46
+ raise APIException.new(response.body)
47
+ end
48
+ end
49
+
50
+ # Public: Delete this comment
51
+ #
52
+ # Returns [Boolean] true on success
53
+ # Raises [APIException] on failure
54
+ def delete!
55
+ response = client.quill.post "/api/v3.0/message/#{id}/delete", {:lftoken => @client.system_token}
56
+ if response.success?
57
+ true
58
+ else
59
+ raise APIException.new(response.body)
60
+ end
61
+ end
62
+
63
+ # Public: Update this comment's content
64
+ #
65
+ # Returns [Boolean] true on success
66
+ # Raises [APIException] on failure
67
+ def update(body)
68
+ response = client.quill.post "/api/v3.0/message/#{id}/edit", {:lftoken => @client.system_token, :body => body}
69
+ if response.success?
70
+ true
71
+ else
72
+ raise APIException.new(response.body)
73
+ end
74
+ end
75
+
76
+ # Public: Get the comment source as a string.
77
+ # Currently only populated when created via ::create
78
+ #
79
+ # Returns [Enum<SOURCES>]
80
+ def source
81
+ source_id ? SOURCES[source_id] : nil
82
+ end
83
+
84
+ # Public: Get the comment source as an integer.
85
+ # Currently only populated when created via ::create
86
+ #
87
+ # Returns [Integer]
88
+ def source_id
89
+ @options[:source]
90
+ end
91
+
92
+ # Public: Get the comment visibility as a string.
93
+ # Currently only populated when created via ::create
94
+ #
95
+ # Returns [Enum<VISIBILITIES>]
96
+ def visibility
97
+ visibility_id ? VISIBILITIES[visibility_id] : nil
98
+ end
99
+
100
+ # Public: Get the comment visibility as an integer.
101
+ # Currently only populated when created via ::create
102
+ #
103
+ # Returns [Integer]
104
+ def visibility_id
105
+ @options[:visibility]
106
+ end
107
+
108
+ # Public: Get the comment content type as a string.
109
+ # Currently only populated when created via ::create
110
+ #
111
+ # Returns [Enum<CONTENT_TYPES>]
112
+ def content_type
113
+ content_type_id ? CONTENT_TYPES[content_type_id] : nil
114
+ end
115
+
116
+ # Public: Get the comment visibility as an integer.
117
+ # Currently only populated when created via ::create
118
+ #
119
+ # Returns [Integer]
120
+ def content_type_id
121
+ @options[:type]
122
+ end
123
+
124
+ # Public: Likes a comment as the passed user
125
+ #
126
+ # Returns [Boolean] true on success
127
+ # Raises [APIException] on failure
128
+ def like!(user)
129
+ response = @client.quill.post "/api/v3.0/message/#{id}/like/", {:collection_id => conversation.id, :lftoken => user.token}
130
+ if response.success?
131
+ true
132
+ else
133
+ raise APIException.new(response.body)
134
+ end
135
+ end
136
+
137
+ # Public: Unlikes a comment as the passed user
138
+ #
139
+ # Returns [Boolean] true on success
140
+ # Raises [APIException] on failure
141
+ def unlike!(user)
142
+ response = @client.quill.post "/api/v3.0/message/#{id}/unlike/", {:collection_id => conversation.id, :lftoken => user.token}
143
+ if response.success?
144
+ true
145
+ else
146
+ raise APIException.new(response.body)
147
+ end
148
+ end
149
+
150
+ # Public: create a new comment on a conversation
151
+ #
152
+ # client - [Client] representing the site to use when creating the conversation
153
+ # user - [User] to create the comment as
154
+ # conversation - [Conversation] to create
155
+ # body - [String] Comment body
156
+ #
157
+ # Returns [Comment]
158
+ # Raises [APIException] when the API call fails
159
+ def self.create(client, user, conversation, body, reply_to = nil)
160
+ response = client.quill.post "/api/v3.0/collection/#{conversation.id}/post/", {:lftoken => user.token, :body => body, :_bi => client.identifier, :parent_id => reply_to}
161
+ if response.success?
162
+ puts JSON.parse(response.body).inspect
163
+ data = JSON.parse(response.body)["data"]
164
+
165
+ data["messages"].map do |entry|
166
+ c = entry["content"]
167
+ Comment.new(c["id"], conversation, {
168
+ :body => c["bodyHtml"],
169
+ :parent_id => c["parentId"],
170
+ :user => User.new(c["authorId"], data["authors"].first.last["displayName"], data["authors"].first.last),
171
+ :created_at => Time.at(c["createdAt"]),
172
+ :source => entry["source"],
173
+ :visibility => entry["vis"],
174
+ :client => client,
175
+ :type => entry["type"]
176
+ })
177
+ end.first
178
+ else
179
+ raise APIException.new(response.body)
180
+ end
181
+ end
182
+
183
+ # Internal: Returns a cleaner string representation of this object
184
+ #
185
+ # Returns [String] representation of this class
186
+ def to_s
187
+ "#<#{self.class.name}:0x#{object_id.to_s(16).rjust(14, "0")} id='#{id}' options=#{@options.inspect}>"
188
+ end
189
+ end
190
+ end