livefyre-mashable 0.2.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.
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDdDCCAlygAwIBAgIBATANBgkqhkiG9w0BAQUFADBAMQ8wDQYDVQQDDAZjaGVh
3
+ bGQxGDAWBgoJkiaJk/IsZAEZFghtYXNoYWJsZTETMBEGCgmSJomT8ixkARkWA2Nv
4
+ bTAeFw0xNDAzMjAyMTUyMzNaFw0xNTAzMjAyMTUyMzNaMEAxDzANBgNVBAMMBmNo
5
+ ZWFsZDEYMBYGCgmSJomT8ixkARkWCG1hc2hhYmxlMRMwEQYKCZImiZPyLGQBGRYD
6
+ Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4AMfgtORekddgqRx
7
+ mHdKSRuQ7Pks2LN96uINU9lWhmppM/mrbHUvkW28wdfC27MTtR1cQUZWAzS8eWsH
8
+ Fq0hdm5R5tsrXLM8Y7tUcoKu5qTJEmDyN2TH3lx9hH/fzwhU2rhb/DrIhtOJNBsg
9
+ 5AERbX/LA6nmQLEQlbVoVHH5tmIGnAYY96WfCNeJyexLfuOeAn1NFyBJqsU7VhFf
10
+ JBhyHKdMDRY3HkQD6LHVVDidwEOyi+vKb13GlHwproUPutp3qTEG24IhuQGARV+f
11
+ zSwQ1Ay6L7CmMZmznwPaPBd/zOMbe1RGg5ofCvNAgxspbNGQPJvNp+DxRENdY06N
12
+ qeO/GQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
13
+ apPfXB2HQZ7X5EmDpGX1yXG2kwIwHgYDVR0RBBcwFYETY2hlYWxkQG1hc2hhYmxl
14
+ LmNvbTAeBgNVHRIEFzAVgRNjaGVhbGRAbWFzaGFibGUuY29tMA0GCSqGSIb3DQEB
15
+ BQUAA4IBAQDOxCvINYMhcPL9S0+QYUiNU3fyRGnlWmtGtdSMWVeHvB+fp4YlkdLF
16
+ 0kmAyz4RIesM0lERf1VqBKoNbHtKFZS/xhIOWL2fAu8THLpP5a9eVA2rdYfhP/ci
17
+ ka+Vu7ZE8592V3oc58mrVM23aSbXcayMAhxisdl861ivx4HOx2uUa1uXRDKQeUu6
18
+ b+eVzkQQqG4wHTlqW9E3ZtQP5tzg/ysTziZPMxwdO2jPq6TnN/eVHSXNUj8KFvCM
19
+ 3SGzoShdNQygjL3WAVzfODhd9S5immxeoFdoNy2bMaQ56xcnhaQo+oObjGfUHXfG
20
+ r/Yw+hd3zY0RL8eCvGhd6dDlhmPPIBMc
21
+ -----END CERTIFICATE-----
data/lib/livefyre.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'jwt'
2
+ require 'faraday'
3
+ require 'forwardable'
4
+ require 'hmac-sha1'
5
+
6
+ # Public: Toplevel Livefyre namespace
7
+ module Livefyre
8
+ # Public: Exception thrown when the Livefyre API does not return a success value
9
+ # #message will be the response body from the Livefyre API.
10
+ class APIException < ::Exception; end
11
+
12
+ # Public: Set the default configuration object for Livefyre clients
13
+ #
14
+ # Returns [nil]
15
+ def self.config=(config)
16
+ config.keys.each do |key|
17
+ config[(key.to_sym rescue key) || key] = config.delete(key)
18
+ end if config.is_a? Hash
19
+ @@config = config
20
+ @@client = nil
21
+ end
22
+
23
+ # Public: Get the configuration object for default clients
24
+ #
25
+ # Returns [Hash] configuration hash
26
+ def self.config
27
+ @@config
28
+ end
29
+
30
+ # Public: Retreive a singleton instance of the Livefyre client
31
+ #
32
+ # Returns [Livefyre::Client] instance configured with the default settings
33
+ # Raises Exception if #config is nil
34
+ def self.client
35
+ raise "Invalid configuration" if @@config.nil?
36
+ @@client ||= Livefyre::Client.new(@@config)
37
+ end
38
+ end
39
+
40
+ require File.expand_path("livefyre/client", File.dirname(__FILE__))
41
+ require File.expand_path("livefyre/user", File.dirname(__FILE__))
42
+ require File.expand_path("livefyre/domain", File.dirname(__FILE__))
43
+ require File.expand_path("livefyre/site", File.dirname(__FILE__))
44
+ require File.expand_path("livefyre/activity", File.dirname(__FILE__))
45
+ require File.expand_path("livefyre/conversation", File.dirname(__FILE__))
46
+ require File.expand_path("livefyre/comment", File.dirname(__FILE__))
47
+
48
+ if defined?(Rails)
49
+ require File.expand_path("livefyre/controller_extensions", File.dirname(__FILE__))
50
+ require File.expand_path("livefyre/helpers", File.dirname(__FILE__))
51
+ require File.expand_path("livefyre/model_extensions", File.dirname(__FILE__))
52
+ require File.expand_path("../railties/railtie", File.dirname(__FILE__))
53
+ require File.expand_path("livefyre/engine", File.dirname(__FILE__))
54
+ end
@@ -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
@@ -0,0 +1,141 @@
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
@@ -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