livefyre 0.1.2 → 1.0.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.
@@ -1,43 +0,0 @@
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
@@ -1,141 +0,0 @@
1
- module Livefyre
2
- # Public: Primary interface to the Livefyre API
3
- class Client
4
- extend Forwardable
5
- # Public: Valid roles for #set_user_role
6
- ROLES = %w(admin member none outcast owner)
7
-
8
- # Public: Valid scopes for #set_user_role
9
- SCOPES = %w(domain site conv)
10
-
11
- attr_accessor :host, :key, :options, :system_token, :http_client, :site_key, :quill, :stream, :bootstrap, :search
12
-
13
- def_delegators :http_client, :get, :post, :delete, :put
14
-
15
- # Public: Create a new Livefyre client.
16
- #
17
- # options - [Hash] array of options to pass to the client for initialization
18
- # :host - your Livefyre network_host
19
- # :key - your Livefyre network_key
20
- # :system_token - your Livefyre long-lived system user key
21
- def initialize(options = {})
22
- @options = options.clone
23
- @host = options.delete(:network) || options.delete(:host)
24
- raise "Invalid host" if @host.nil?
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]
31
-
32
- @key = options.delete(:secret) || options.delete(:key) || options.delete(:network_key)
33
- raise "Invalid secret key" if @key.nil?
34
-
35
- @system_token = options.delete(:system_token)
36
- raise "Invalid system token" if @system_token.nil?
37
- end
38
-
39
- # Public: Sign a data structure with this client's network key.
40
- #
41
- # Returns [String] A signed JWT token
42
- def sign(data)
43
- JWT.encode(data, @key)
44
- end
45
-
46
- # Public: Validates and decodes a JWT token
47
- #
48
- # Returns [Hash] A hash of data passed from the token
49
- # Raises [JWT::DecodeError] if invalid token contents or signature
50
- def validate(data)
51
- JWT.decode(data, @key)
52
- end
53
-
54
- # Public: Create a {Livefyre::User} with this client's credentials.
55
- #
56
- # uid - the user ID to create a Livefyre user for. This should be the ID used to reference this user in Livefyre's system.
57
- # display_name - the displayed name for this user. Optional.
58
- #
59
- # Returns [Livefyre::User]
60
- def user(uid, display_name = nil)
61
- User.new(uid, self, display_name)
62
- end
63
-
64
- # Public: Sets a user's role (affiliation) in a given scope.
65
- #
66
- # user_id - The user ID (without the host) to set roles for
67
- # role - The {ROLES role} to set.
68
- # scope - The {SCOPES scope} for which to set this role.
69
- # scope_id - In the case that the given scope requires identification, specifies which scope to operate on.
70
- #
71
- # Examples
72
- #
73
- # set_user_role(1234, "owner", "domain")
74
- # set_user_role(1234, "moderator", "site", site_id)
75
- # set_user_role(1234, "moderator", "conv", conversation_id)
76
- #
77
- #
78
- # Returns [Bool] true on success
79
- # Raises APIException if the request failed
80
- def set_user_role(user_id, role, scope = 'domain', scope_id = nil)
81
- raise "Invalid scope" unless SCOPES.include? scope
82
- raise "Invalid role" unless ROLES.include? role
83
-
84
- post_data = {
85
- :affiliation => role,
86
- :lftoken => system_token,
87
- }
88
- case scope
89
- when "domain"
90
- post_data[:domain_wide] = 1
91
- when "conv"
92
- raise "Invalid scope_id" if scope_id.nil?
93
- post_data[:conv_id] = scope_id
94
- when "site"
95
- raise "Invalid scope_id" if scope_id.nil?
96
- post_data[:site_id] = scope_id
97
- end
98
- result = post "/api/v1.1/private/management/user/#{jid(user_id)}/role/", post_data
99
- if result.success?
100
- true
101
- else
102
- raise APIException.new(result.body)
103
- end
104
- end
105
-
106
- # Public: Transform the given ID into a jid
107
- #
108
- # id - a string value to compose the JID with
109
- #
110
- # Returns [String] JID
111
- def jid(id)
112
- "%s@%s" % [id, host]
113
- end
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
-
122
- # Internal: Returns a cleaner string representation of this object
123
- #
124
- # Returns [String] representation of this class
125
- def to_s
126
- "#<#{self.class.name}:0x#{object_id.to_s(16).rjust(14, "0")} host='#{host}' key='#{key}'>"
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
140
- end
141
- end
@@ -1,190 +0,0 @@
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
@@ -1,82 +0,0 @@
1
- module Livefyre
2
- # Public: Controller extensions for Rails. Adds methods to be called from your controller to integrate with Livefyre.
3
- module Controller
4
- extend ActiveSupport::Concern
5
-
6
- # Public: Creates the Livefyre session cookies. Should be called when the user logs in.
7
- def livefyre_login(id, display_name)
8
- cookie = (Livefyre.config[:cookie_options] || {}).clone || {:path => "/", :expires => Time.now + 1.year}
9
- expiry = cookie.delete(:expires) || (Time.now + 1.year)
10
-
11
- token = {
12
- :domain => Livefyre.client.host,
13
- :user_id => id,
14
- :expires => expiry.to_i,
15
- :display_name => display_name
16
- }
17
-
18
- name = cookie.delete(:name) || "livefyre_utoken"
19
- cookies[name] = cookie.merge(:value => JWT.encode(token, Livefyre.client.key), :expires => expiry)
20
- end
21
-
22
- # Public: Destroys the Livefyre session cookies. Should be called when the user logs out
23
- def livefyre_logout
24
- name = (Livefyre.config[:cookie_options] || {})[:name] || "livefyre_utoken"
25
- cookies.delete(name)
26
- end
27
-
28
- # Public: Attempt to generate valid Livefire profile dump from the passed user record by guessing at field names.
29
- #
30
- # user - The user record to generate data from. Assumes it's ActiveModel-ish.
31
- # values - [Hash] of values to force values for, rather than guessing at.
32
- #
33
- # Returns [Hash] suitable for conversion to JSON
34
- def livefire_profile(user, values = {})
35
- {
36
- :id => user.id,
37
- :display_name => user.try(:display_name) || user.try(:name) || user.try(:username),
38
- :email => user.try(:email),
39
- :profile => url_for(user),
40
- :settings_url => url_for(:edit, user),
41
- :bio => user.try(:bio) || user.try(:about),
42
- :name => {
43
- :first_name => user.try(:first_name),
44
- :last_name => user.try(:last_name),
45
- }
46
- }.merge defaults
47
- end
48
-
49
- # Public: Check the validity of the JWT that Livefyre sends with pull requests.
50
- #
51
- # Raises [JWT::DecodeError] if the token is invalid or missing.
52
- def validate_livefyre_request!
53
- JWT::DecodeError if params[:lftoken].nil?
54
- token = JWT.decode params[:lftoken], Livefyre.client.key
55
- raise JWT::DecodeError unless token["domain"] == Livefyre.client.host
56
- return true
57
- end
58
-
59
- # Public: Used in your postback handler to parse the Livefyre postback body into an Activity stream for processing
60
- #
61
- # Returns [Array<Activity>] List of activities included in this postback.
62
- def parse_livefyre_postback
63
- JSON.parse(request.body).map {|item| Activity.new(client, item) }
64
- end
65
-
66
- module ClassMethods
67
- # Public: filter helper to validate postback signatures for Livefyre requests.
68
- #
69
- # options - valid before_filter options, plus :key
70
- #
71
- # Example:
72
- #
73
- # validate_postback_signature :only => [:postback], :key => "your_site_key"
74
- def validate_postback_signature(options = {})
75
- key = options.delete :key
76
- before_filter Proc.new {|c|
77
- Livefyre::Site.validate_signature(c.params, key)
78
- }, options
79
- end
80
- end
81
- end
82
- end
@@ -1,133 +0,0 @@
1
- module Livefyre
2
- # Public: Proxy object for a Livefyre [Conversation] (also called a Collection)
3
- class Conversation
4
- attr_accessor :id, :article_id
5
- def initialize(id, article_id)
6
- @id = id
7
- @article_id = article_id
8
- @client = Livefyre.client
9
- end
10
-
11
- # Public: Fetch a list of comments from a conversation
12
- # TODO: Not currently working.
13
- def comments
14
- response = @client.bootstrap.get "/bs3/#{@client.options[:domain]}/#{@client.options[:network]}/#{@client.options[:site_id]}/#{Base64.encode64 @article_id}/init"
15
- if response.success?
16
- JSON.parse response.body
17
- else
18
- raise APIException.new(response.body)
19
- end
20
- end
21
-
22
- # Public: Update this collection with new metadata
23
- #
24
- # Returns [Bool] true on success
25
- # Raises [APIException] on failure
26
- def update(title, link, tags = nil)
27
- meta = self.class.collectionMeta(@client, @article_id, title, link, tags)
28
- response = @client.quill.post "/api/v3.0/site/#{@client.options[:site_id]}/collection/update/", {:collectionMeta => meta, :articleId => @article_id}.to_json
29
- if response.success?
30
- true
31
- else
32
- raise APIException.new(response.body)
33
- end
34
- end
35
-
36
- # Public: Create a comment on this conversation
37
- #
38
- # user - [User] to create the comment as
39
- # body - [String] body of the content
40
- #
41
- # Returns [Comment]
42
- def create_comment(user, body)
43
- Comment.create(@client, user, self, body)
44
- end
45
-
46
- # Public: Follow this conversation as the passed user
47
- #
48
- # user - [User] to follow the conversation as
49
- #
50
- # Returns [Boolean] true on success
51
- # Raises [APIException] on failure
52
- def follow_as(user)
53
- response = @client.quill.post "/api/v3.0/collection/10584292/follow/", :lftoken => user.token, :collectionId => @id
54
- if response.success?
55
- true
56
- else
57
- raise APIException.new(response.body)
58
- end
59
- end
60
-
61
- # Public: Unfollow this conversation as the passed user
62
- #
63
- # user - [User] to unfollow the conversation as
64
- #
65
- # Returns [Boolean] true on success
66
- # Raises [APIException] on failure
67
- def unfollow_as(user)
68
- response = @client.quill.post "/api/v3.0/collection/10584292/unfollow/", :lftoken => user.token, :collectionId => @id
69
- if response.success?
70
- true
71
- else
72
- raise APIException.new(response.body)
73
- end
74
- end
75
-
76
- # Public: Create a new collection
77
- #
78
- # client - [Client] identifying the site to create the collection on
79
- # article_id - [String] ID to use to identify this article
80
- # title - [String] Article title
81
- # link - [String] Article link
82
- # tags - [String, Array] Article tags
83
- #
84
- # Returns [Conversation]
85
- def self.create(client, article_id, title, link, tags = nil)
86
- meta = collectionMeta(client, article_id, title, link, tags)
87
- response = client.quill.post "/api/v3.0/site/#{client.options[:site_id]}/collection/create", {:collectionMeta => meta, :articleId => article_id}.to_json
88
- if response.success?
89
- body = JSON.parse(response.body)
90
- Conversation.new(body["data"]["collectionId"], article_id)
91
- else
92
- error = JSON.parse(response.body)
93
- raise APIException.new(error["msg"])
94
- end
95
- end
96
-
97
- # Internal: Generate a signed collectionMeta
98
- #
99
- # client - [Client] identifying the site to create the collection on
100
- # title - [String] Article title
101
- # link - [String] Article link
102
- # tags - [String, Array] Article tags
103
- #
104
- # Returns [String] signed token
105
- def self.collectionMeta(client, article_id, title, link, tags)
106
- tag_str = case tags
107
- when Array
108
- tags.join ","
109
- when String
110
- tags
111
- when nil
112
- nil
113
- else
114
- raise "Invalid value given for tags: must be Array, String, or nil"
115
- end
116
-
117
- begin
118
- URI.parse(link)
119
- rescue URI::InvalidURIError => e
120
- raise "Invalid value for link: #{e.message}"
121
- end
122
-
123
- metadata = {
124
- :title => title,
125
- :url => link,
126
- :articleId => article_id,
127
- :tags => tag_str || "",
128
- }
129
-
130
- JWT.encode(metadata, client.site_key)
131
- end
132
- end
133
- end