livefyre 0.0.1 → 0.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.
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