livefyre 1.1.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YWNlMjA0YzI2MmY2MTE4ZDVjODFhMmIyOWU4NGQ4MjY5NDBlMGQ2ZQ==
4
+ ZGUyOTkxOWQwN2MzZWRmYTZiMjhlMjhiNTliNTU4NTg2N2M5YjVkYg==
5
5
  data.tar.gz: !binary |-
6
- YmNkMGRkZWE5MDY3N2Y1NmM2MDI2NzFiNjg0ZjcwMTkxMTA0NDAyOQ==
6
+ ZTlmOWRlMWU4NTI0NzI5ODM1YmJlN2U1NzkzODA2M2MxMzExOGJmYQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZmY0ODM1ZmZiMzk1Y2EwNzJmMmFjY2MwMTc2ZDc2NTY2MjlhNTFjZDI3YmE4
10
- ZjcwNDI2NWQxMzkwZjZmNDJlOTk0YWJhMzZiMTEyMjhjZDUwZjc0MGMwN2Mz
11
- NDNiMmJlOTFlZmVlYWVhYjE0MWFmNzYyMmMwOWY3M2E0ZDdkMDE=
9
+ YjM4ZWIyMWM2YjdhMzVhMjRiZTI4YTZjMzMwNzNjNjFhZjIwMTFkZDE3OTkx
10
+ YmRkZDIzZTY3MjlmOGM4NmY0ZDEzNmMxZDA2YWYyZDBlMmQxZDU0YTg1MzZi
11
+ ZGQxNmJlOTA0ZGEwMDBkZGQ0MjNmYzFiOTA4MTg0N2UwNzhmZjE=
12
12
  data.tar.gz: !binary |-
13
- MDRlYmJjN2U5MWExNmUyZWI4ZDUzYmYyYmU4ZDliMGIxMzdhM2Y4YWIwZjg1
14
- NjkyNzZmYWRkM2E0ZTg0ZGRhNWM2NTQ5MDVkZTU3ZjM0ZDQ4NjkyYTQ5ZGJm
15
- OTRhM2ZlZTUzZDg0MjBmZWU4NzQ3OThjZDBmNzhhZjNkNDFjYjg=
13
+ MmY1NjUwNjk3OTE4Nzc2ZTI4ZWI3MWFkMzFjYmFkZmIwNzJjZGMyMjJiODZh
14
+ OGRmNjc2NDlmNDRhODM4N2NhNzgzYmZkZGI5NWQyODdmMGU3ZDQ3YWJiNTcy
15
+ OTAxOTZhMTgxYzA3MzA5ODM5N2E5MmI3NmEzNzI4ZmVjODc4MjQ=
data/.gitignore CHANGED
@@ -15,3 +15,9 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+
19
+ .idea/livefyre-ruby-utils.iml
20
+
21
+ .idea/workspace.xml
22
+
23
+ .idea/livefyre-ruby-utils.iml
data/.idea/.name ADDED
@@ -0,0 +1 @@
1
+ livefyre-ruby-utils
data/.idea/.rakeTasks ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Settings><!--This file was automatically generated by Ruby plugin.
3
+ You are allowed to:
4
+ 1. Remove rake task
5
+ 2. Add existing rake tasks
6
+ To add existing rake tasks automatically delete this file and reload the project.
7
+ --><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Build livefyre-1.1.4.gem into the pkg directory" fullCmd="build" taksId="build" /><RakeTask description="Build and install livefyre-1.1.4.gem into system gems" fullCmd="install" taksId="install" /><RakeTask description="Create tag v1.1.4 and build and push livefyre-1.1.4.gem to Rubygems" fullCmd="release" taksId="release" /><RakeTask description="" fullCmd="run_tests" taksId="run_tests" /></RakeGroup></Settings>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
4
+ </project>
5
+
data/.idea/misc.xml ADDED
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-1.9.3-p484 [global]" project-jdk-type="RUBY_SDK" />
4
+ </project>
5
+
data/.idea/modules.xml ADDED
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/livefyre-ruby-utils.iml" filepath="$PROJECT_DIR$/.idea/livefyre-ruby-utils.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
9
+
@@ -0,0 +1,5 @@
1
+ <component name="DependencyValidationManager">
2
+ <state>
3
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
4
+ </state>
5
+ </component>
data/.idea/vagrant.xml ADDED
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VagrantProjectSettings">
4
+ <option name="instanceFolder" value="" />
5
+ <option name="provider" value="" />
6
+ </component>
7
+ </project>
8
+
data/.idea/vcs.xml ADDED
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
7
+
data/.project ADDED
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>livefyre-ruby-utils</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ </buildSpec>
9
+ <natures>
10
+ </natures>
11
+ </projectDescription>
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ === 1.3.0 2014-07-30
2
+
3
+ *** CAUTION: THIS VERSION HAS UPDATED METHODS THAT MAKE PREVIOUS FUNCTION CALLS OBSOLETE ***
4
+
5
+ * Jumped from 1.1.0 to 1.3.0 to match other libraries.
6
+ * Added personal stream and timeline support.
7
+ * Updated most API to use SSL.
8
+ * Added create_collection method.
9
+ * Updated checksum formula to match other libraries.
10
+ * Refactored core objects.
11
+ * Updated create_collection_meta to take in a dict of optional params.
12
+
1
13
  === 1.1.4 2014-05-07
2
14
 
3
15
  * Added changelog.
data/README.md CHANGED
@@ -53,10 +53,10 @@ site = network.get_site('site_id', 'site_key')
53
53
  ```
54
54
 
55
55
  Building a collection meta token:
56
- *The 'tags' and type' arguments are optional.*
56
+ *The {options} argument is optional.*
57
57
 
58
58
  ```ruby
59
- site.build_collection_meta_token('title', 'article_id', 'url', 'tags', 'type')
59
+ site.build_collection_meta_token('title', 'article_id', 'url', {options})
60
60
  ```
61
61
 
62
62
  Building a checksum:
@@ -78,7 +78,7 @@ To get a content collection's id:
78
78
  site.get_collection_id('article_id')
79
79
  ```
80
80
 
81
- ## Documentation
81
+ ## Additional Documentation
82
82
 
83
83
  Located [here](http://answers.livefyre.com/developers/libraries).
84
84
 
data/lib/livefyre.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "livefyre/core"
1
+ require 'livefyre/core/network'
2
2
 
3
3
  module Livefyre
4
4
  def self.get_network(network_name, network_key)
@@ -0,0 +1,271 @@
1
+ require 'json'
2
+ require 'jwt'
3
+ require 'rest-client'
4
+ require 'addressable/uri'
5
+
6
+ require 'livefyre/entity/topic'
7
+ require 'livefyre/entity/subscription'
8
+
9
+ module Livefyre
10
+ class PersonalizedStream
11
+ # Topic API
12
+ def self.get_topic(core, topic_id)
13
+ url = self.base_url(core) + self.topic_path(core, topic_id)
14
+
15
+ response = RestClient.get(url, self.get_headers(core))
16
+ data = JSON.parse(response)['data']
17
+
18
+ Topic::serialize_from_json(data['topic'])
19
+ end
20
+
21
+ def self.create_or_update_topic(core, topic_id, label)
22
+ PersonalizedStream::create_or_update_topics(core, { "#{topic_id}" => label })[0]
23
+ end
24
+
25
+ def self.delete_topic(core, topic)
26
+ PersonalizedStream::delete_topics(core, [topic]) == 1
27
+ end
28
+
29
+ # Multiple Topic API
30
+ def self.get_topics(core, limit=100, offset=0)
31
+ url = self.base_url(core) + self.multiple_topic_path(core)
32
+ url += "?limit=#{limit}&offset=#{offset}"
33
+
34
+ response = RestClient.get(url, self.get_headers(core))
35
+ data = JSON.parse(response)['data']
36
+
37
+ topics = []
38
+ data['topics'].each do |topic|
39
+ topics << Topic::serialize_from_json(topic)
40
+ end
41
+
42
+ topics
43
+ end
44
+
45
+ def self.create_or_update_topics(core, topic_map)
46
+ topics = []
47
+
48
+ topic_map.each do |key, value|
49
+ if not value or value.length > 128
50
+ raise ArgumentError, 'label cannot be longer than 128 chars.'
51
+ end
52
+ topics << Topic::create(core, key, value)
53
+ end
54
+
55
+ url = self.base_url(core) + self.multiple_topic_path(core)
56
+ headers = self.get_headers(core)
57
+ headers[:content_type] = :json
58
+
59
+ topics_json = []
60
+ topics.each do |topic|
61
+ topics_json << topic.to_dict
62
+ end
63
+
64
+ response = RestClient.post(url, {topics: topics_json}.to_json, headers)
65
+ JSON.parse(response)['data']
66
+
67
+ return topics
68
+ end
69
+
70
+ def self.delete_topics(core, topics)
71
+ url = self.base_url(core) + self.multiple_topic_path(core)
72
+ headers = self.get_headers(core)
73
+ headers[:content_type] = :json
74
+ form = {delete: self.get_ids(topics)}
75
+
76
+ response = RestClient.patch(url, form.to_json, headers)
77
+ data = JSON.parse(response)['data']
78
+
79
+ data.has_key?('deleted') ? data['deleted'] : 0
80
+ end
81
+
82
+ # Collection Topic API
83
+ def self.get_collection_topics(site, collection_id)
84
+ url = self.base_url(site) + self.collection_topics_path(site, collection_id)
85
+
86
+ response = RestClient.get(url, self.get_headers(site))
87
+ data = JSON.parse(response)['data']
88
+
89
+ data.has_key?('topicIds') ? data['topicIds'] : []
90
+ end
91
+
92
+ def self.add_collection_topics(site, collection_id, topics)
93
+ url = self.base_url(site) + self.collection_topics_path(site, collection_id)
94
+ headers = self.get_headers(site)
95
+ headers[:content_type] = :json
96
+ form = {topicIds: self.get_ids(topics)}
97
+
98
+ response = RestClient.post(url, form.to_json, headers)
99
+ data = JSON.parse(response)['data']
100
+
101
+ data.has_key?('added') ? data['added'] : 0
102
+ end
103
+
104
+ def self.replace_collection_topics(site, collection_id, topics)
105
+ url = self.base_url(site) + self.collection_topics_path(site, collection_id)
106
+ headers = self.get_headers(site)
107
+ headers[:content_type] = :json
108
+ form = {topicIds: self.get_ids(topics)}
109
+
110
+ response = RestClient.put(url, form.to_json, headers)
111
+ data = JSON.parse(response)['data']
112
+
113
+ return data.has_key?('added') ? data['added'] : 0, data.has_key?('removed') ? data['removed'] : 0
114
+ end
115
+
116
+ def self.remove_collection_topics(site, collection_id, topics)
117
+ url = self.base_url(site) + self.collection_topics_path(site, collection_id)
118
+ headers = self.get_headers(site)
119
+ headers[:content_type] = :json
120
+ form = {delete: self.get_ids(topics)}
121
+
122
+ response = RestClient.patch(url, form.to_json, headers)
123
+ data = JSON.parse(response)['data']
124
+
125
+ data.has_key?('removed') ? data['removed'] : 0
126
+ end
127
+
128
+ # Subscription API
129
+ def self.get_subscriptions(network, user_id)
130
+ url = self.base_url(network) + self.user_subscription_path(network.get_user_urn(user_id))
131
+
132
+ response = RestClient.get(url, self.get_headers(network))
133
+ data = JSON.parse(response)['data']
134
+
135
+ subscriptions = []
136
+ if data.has_key?('subscriptions')
137
+ data['subscriptions'].each do |sub_json|
138
+ subscriptions << Subscription::serialize_from_json(sub_json)
139
+ end
140
+ end
141
+
142
+ subscriptions
143
+ end
144
+
145
+ def self.add_subscriptions(network, user_token, topics)
146
+ user_id = JWT.decode(user_token, network.key)['user_id']
147
+ user_urn = network.get_user_urn(user_id)
148
+ url = self.base_url(network) + self.user_subscription_path(user_urn)
149
+ headers = self.get_headers(network, user_token)
150
+ headers[:content_type] = :json
151
+ form = {subscriptions: self.to_subscriptions(topics, user_urn)}
152
+
153
+ response = RestClient.post(url, form.to_json, headers)
154
+ data = JSON.parse(response)['data']
155
+
156
+ data.has_key?('added') ? data['added'] : 0
157
+ end
158
+
159
+ def self.replace_subscriptions(network, user_token, topics)
160
+ user_id = JWT.decode(user_token, network.key)['user_id']
161
+ user_urn = network.get_user_urn(user_id)
162
+ url = self.base_url(network) + self.user_subscription_path(user_urn)
163
+ headers = self.get_headers(network, user_token)
164
+ headers[:content_type] = :json
165
+ form = {subscriptions: self.to_subscriptions(topics, user_urn)}
166
+
167
+ response = RestClient.put(url, form.to_json, headers)
168
+ data = JSON.parse(response)['data']
169
+
170
+ return data.has_key?('added') ? data['added'] : 0, data.has_key?('removed') ? data['removed'] : 0
171
+ end
172
+
173
+ def self.remove_subscriptions(network, user_token, topics)
174
+ user_id = JWT.decode(user_token, network.key)['user_id']
175
+ user_urn = network.get_user_urn(user_id)
176
+ url = self.base_url(network) + self.user_subscription_path(user_urn)
177
+ headers = self.get_headers(network, user_token)
178
+ headers[:content_type] = :json
179
+ form = {delete: self.to_subscriptions(topics, user_urn)}
180
+
181
+ response = RestClient.patch(url, form.to_json, headers)
182
+ data = JSON.parse(response)['data']
183
+
184
+ data.has_key?('removed') ? data['removed'] : 0
185
+ end
186
+
187
+ def self.get_subscribers(network, topic, limit=100, offset=0)
188
+ url = self.base_url(network) + self.topic_subscription_path(topic)
189
+ url += "?limit=#{limit}&offset=#{offset}"
190
+
191
+ response = RestClient.get(url, self.get_headers(network))
192
+ data = JSON.parse(response)['data']
193
+
194
+ subscriptions = []
195
+ if data.has_key?('subscriptions')
196
+ data['subscriptions'].each do |sub_json|
197
+ subscriptions << Subscription::serialize_from_json(sub_json)
198
+ end
199
+ end
200
+
201
+ subscriptions
202
+ end
203
+
204
+ # Stream API
205
+ def self.get_timeline_stream(core, resource, limit=50, t_until=nil, t_since=nil)
206
+ url = STREAM_BASE_URL + TIMELINE_PATH
207
+ url += "?resource=#{resource}&limit=#{limit}"
208
+
209
+ if t_until != nil
210
+ url += "&until=#{t_until}"
211
+ elsif t_since != nil
212
+ url += "&since=#{t_since}"
213
+ end
214
+
215
+ response = RestClient.get(url, self.get_headers(core))
216
+
217
+ JSON.parse(response)
218
+ end
219
+
220
+ private
221
+
222
+ def self.base_url(core)
223
+ "https://#{core.network_name}.quill.fyre.co/api/v4"
224
+ end
225
+
226
+ STREAM_BASE_URL = 'https://bootstrap.livefyre.com/api/v4'
227
+
228
+ def self.topic_path(core, topic_id)
229
+ "/#{Topic.generate_urn(core, topic_id)}/"
230
+ end
231
+
232
+ def self.multiple_topic_path(core)
233
+ "/#{core.get_urn}:topics/"
234
+ end
235
+
236
+ def self.collection_topics_path(site, collection_id)
237
+ "/#{site.get_urn}:collection=#{collection_id}:topics/"
238
+ end
239
+
240
+ def self.user_subscription_path(user_urn)
241
+ "/#{user_urn}:subscriptions/"
242
+ end
243
+
244
+ def self.topic_subscription_path(topic)
245
+ "/#{topic.id}:subscribers/"
246
+ end
247
+
248
+ TIMELINE_PATH = '/timeline/'
249
+
250
+ def self.get_headers(core, user_token=nil)
251
+ {:accepts => :json, :authorization => 'lftoken ' + (user_token == nil ? core.build_livefyre_token : user_token)}
252
+ end
253
+
254
+ def self.get_ids(topics)
255
+ ids = []
256
+ topics.each do |topic|
257
+ ids << topic.id
258
+ end
259
+
260
+ ids
261
+ end
262
+
263
+ def self.to_subscriptions(topics, user)
264
+ subscriptions = []
265
+ topics.each do |topic|
266
+ subscriptions << Subscription.new(topic.id, user, SubscriptionType::PERSONAL_STREAM).to_dict
267
+ end
268
+ subscriptions
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,75 @@
1
+ require 'addressable/uri'
2
+ require 'jwt'
3
+ require 'rest-client'
4
+
5
+ require 'livefyre/core/site'
6
+
7
+ module Livefyre
8
+ class Network
9
+ DEFAULT_USER = 'system'
10
+ DEFAULT_EXPIRES = 86400
11
+
12
+ def initialize(name, key)
13
+ @name = name
14
+ @key = key
15
+ @network_name = name.split('.')[0]
16
+ end
17
+
18
+ attr_reader :name
19
+ attr_reader :key
20
+ attr_reader :network_name
21
+
22
+ def set_user_sync_url(url_template)
23
+ raise ArgumentError, 'url_template should contain {id}' if !url_template.include?('{id}')
24
+
25
+ response = RestClient.post(
26
+ "http://#{@name}",
27
+ { actor_token: build_livefyre_token, pull_profile_url: url_template }
28
+ )
29
+ response.code == 204
30
+ end
31
+
32
+ def sync_user(user_id)
33
+ response = RestClient.post(
34
+ "http://#{@name}/api/v3_0/user/#{user_id}/refresh",
35
+ { lftoken: build_livefyre_token }
36
+ )
37
+ response.code == 200
38
+ end
39
+
40
+ def build_livefyre_token
41
+ build_user_auth_token(DEFAULT_USER, DEFAULT_USER, DEFAULT_EXPIRES)
42
+ end
43
+
44
+ def build_user_auth_token(user_id, display_name, expires)
45
+ raise ArgumentError, 'user_id must be alphanumeric' if !(user_id =~ /\A\p{Alnum}+\z/)
46
+
47
+ JWT.encode({
48
+ domain: @name,
49
+ user_id: user_id,
50
+ display_name: display_name,
51
+ expires: Time.new.to_i + expires},
52
+ @key)
53
+ end
54
+
55
+ def validate_livefyre_token(lf_token)
56
+ token_attributes = JWT.decode(lf_token, @key)
57
+
58
+ token_attributes['domain'] == @name \
59
+ && token_attributes['user_id'] == DEFAULT_USER \
60
+ && token_attributes['expires'] >= Time.new.to_i
61
+ end
62
+
63
+ def get_site(site_id, site_key)
64
+ Site.new(self, site_id, site_key)
65
+ end
66
+
67
+ def get_urn
68
+ "urn:livefyre:#{@name}"
69
+ end
70
+
71
+ def get_user_urn(user)
72
+ "#{get_urn}:user=#{user}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,102 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ require 'json'
4
+ require 'jwt'
5
+ require 'rest-client'
6
+ require 'addressable/uri'
7
+
8
+ module Livefyre
9
+ class Site
10
+ TYPE = %w(reviews sidenotes ratings counting liveblog livechat livecomments)
11
+
12
+ def initialize(network, id, key)
13
+ @network = network
14
+ @id = id
15
+ @key = key
16
+ end
17
+
18
+ attr_reader :network
19
+ attr_reader :id
20
+ attr_reader :key
21
+
22
+ def build_collection_meta_token(title, article_id, url, options={})
23
+ raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
24
+ raise ArgumentError, 'title length should be under 255 char' if title.length > 255
25
+
26
+ collection_meta = {
27
+ url: url,
28
+ title: title,
29
+ articleId: article_id
30
+ }
31
+
32
+ if options.has_key?(:type) && !TYPE.include?(options[:type])
33
+ raise ArgumentError, 'type is not a recognized type. should be liveblog, livechat, livecomments, reviews, sidenotes, or an empty string'
34
+ end
35
+
36
+ JWT.encode(collection_meta.merge(options), @key)
37
+ end
38
+
39
+ def build_checksum(title, url, tags='')
40
+ raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
41
+ raise ArgumentError, 'title length should be under 255 char' if title.length > 255
42
+
43
+ collection_meta = { tags: tags, title: title, url: url }
44
+ Digest::MD5.new.update(collection_meta.to_json).hexdigest
45
+ end
46
+
47
+
48
+ def create_collection(title, article_id, url, options={})
49
+ uri = "https://#{network_name}.quill.fyre.co/api/v3.0/site/#{@id}/collection/create/?sync=1"
50
+ data = {
51
+ articleId: article_id,
52
+ collectionMeta: build_collection_meta_token(title, article_id, url, options),
53
+ checksum: build_checksum(title, url, options.has_key?(:tags) ? options[:tags] : '')
54
+ }
55
+ headers = {:accepts => :json, :content_type => :json}
56
+
57
+ response = RestClient.post(uri, data.to_json, headers)
58
+
59
+ if response.code == 200
60
+ return JSON.parse(response)['data']['collectionId']
61
+ end
62
+
63
+ nil
64
+ end
65
+
66
+ def get_collection_content(article_id)
67
+ response = RestClient.get(
68
+ "https://bootstrap.livefyre.com/bs3/#{@network.name}/#{@id}/#{Base64.encode64(article_id.to_s).chomp}/init",
69
+ :accepts => :json
70
+ )
71
+ response.code == 200 ? JSON.parse(response) : nil
72
+ end
73
+
74
+ def get_collection_id(article_id)
75
+ content = get_collection_content(article_id)
76
+ if content
77
+ content['collectionSettings']['collectionId']
78
+ end
79
+ end
80
+
81
+ def network_name
82
+ @network.network_name
83
+ end
84
+
85
+ def build_livefyre_token
86
+ @network.build_livefyre_token
87
+ end
88
+
89
+ def get_urn
90
+ "#{@network.get_urn}:site=#{@id}"
91
+ end
92
+
93
+ def uri?(string)
94
+ uri = Addressable::URI.parse(string)
95
+ %w( ftp ftps http https ).include?(uri.scheme)
96
+ rescue Addressable::URI::BadURIError
97
+ false
98
+ rescue Addressable::URI::InvalidURIError
99
+ false
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,32 @@
1
+ module Livefyre
2
+ class Subscription
3
+ def initialize(to, by, type, created_at=nil)
4
+ @to = to
5
+ @by = by
6
+ @type = type
7
+ @created_at = created_at
8
+ end
9
+
10
+ attr_reader :to
11
+ attr_reader :by
12
+ attr_reader :type
13
+ attr_reader :created_at
14
+
15
+ def self.serialize_from_json(json)
16
+ new(json['to'], json['by'], json['type'], json['createdAt'])
17
+ end
18
+
19
+ def to_dict
20
+ dict = { :to => @to, :by => @by, :type => @type }
21
+ if @created_at != nil
22
+ dict[:createdAt] = @created_at
23
+ end
24
+ dict
25
+ end
26
+
27
+ end
28
+
29
+ module SubscriptionType
30
+ PERSONAL_STREAM = 1
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ require 'livefyre/api/personalized_stream'
2
+
3
+ module Livefyre
4
+ class TimelineCursor
5
+ def initialize(core, resource, limit, date)
6
+ @core = core
7
+ @resource = resource
8
+ @limit = limit
9
+ @next = false
10
+ @previous = false
11
+
12
+ @cursor_time = date.utc.iso8601(3)
13
+ end
14
+
15
+ def next(limit=@limit)
16
+ data = PersonalizedStream::get_timeline_stream(@core, @resource, limit, nil, @cursor_time)
17
+ cursor = data['meta']['cursor']
18
+
19
+ @next = cursor['hasNext']
20
+ @previous = cursor['next'] != nil
21
+ @cursor_time = cursor['next']
22
+
23
+ data
24
+ end
25
+
26
+ def previous(limit=@limit)
27
+ data = PersonalizedStream::get_timeline_stream(@core, @resource, limit, @cursor_time, nil)
28
+ cursor = data['meta']['cursor']
29
+
30
+ @previous = cursor['hasPrev']
31
+ @next = cursor['prev'] != nil
32
+ @cursor_time = cursor['prev']
33
+
34
+ data
35
+ end
36
+
37
+ def set_cursor_time(time)
38
+ @cursor_time = time.utc.iso8601(3)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ module Livefyre
2
+ class Topic
3
+ TOPIC_IDENTIFIER = ':topic='
4
+
5
+ def initialize(id, label, created_at=nil, modified_at=nil)
6
+ @id = id
7
+ @label = label
8
+ @created_at = created_at
9
+ @modified_at = modified_at
10
+ end
11
+
12
+ attr_reader :id
13
+ attr_reader :label
14
+ attr_reader :created_at
15
+ attr_reader :modified_at
16
+
17
+ def self.create(core, id, label)
18
+ new(Topic::generate_urn(core, id), label)
19
+ end
20
+
21
+ def self.serialize_from_json(json)
22
+ new(json['id'], json['label'], json['createdAt'], json['modifiedAt'])
23
+ end
24
+
25
+ def to_dict
26
+ json = { :id => @id, :label => @label }
27
+ if @created_at != nil
28
+ json[:createdAt] = @created_at
29
+ end
30
+
31
+ if @modified_at != nil
32
+ json[:modifiedAt] = @modified_at
33
+ end
34
+
35
+ json
36
+ end
37
+
38
+ def self.generate_urn(core, id)
39
+ "#{core.get_urn}#{TOPIC_IDENTIFIER}#{id}"
40
+ end
41
+
42
+ def get_truncated_id
43
+ @id[@id.index(TOPIC_IDENTIFIER) + TOPIC_IDENTIFIER.length]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ require 'livefyre/entity/timeline_cursor'
2
+
3
+ module Livefyre
4
+ class CursorFactory
5
+ def self.get_topic_stream_cursor(core, topic, limit=50, date=Time.new)
6
+ resource = "#{topic.id}:topicStream"
7
+ TimelineCursor.new(core, resource, limit, date)
8
+ end
9
+
10
+ def self.get_personal_stream_cursor(network, user, limit=50, date=Time.new)
11
+ resource = "#{network.get_user_urn(user)}:personalStream"
12
+ TimelineCursor.new(network, resource, limit, date)
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module Livefyre
2
- VERSION = "1.1.4"
2
+ VERSION = '1.3.0'
3
3
  end
@@ -0,0 +1,65 @@
1
+ require 'livefyre'
2
+ require 'jwt'
3
+ require 'livefyre/api/personalized_stream'
4
+ require 'livefyre/factory/cursor_factory'
5
+
6
+ RSpec.configure do |c|
7
+ c.filter_run_excluding :broken => true
8
+ end
9
+
10
+ describe Livefyre::Network, :broken => true do
11
+ before(:each) do
12
+ @network = Livefyre.get_network(NETWORK_NAME, NETWORK_KEY)
13
+ @site = @network.get_site(SITE_ID, SITE_KEY)
14
+ end
15
+
16
+ it 'should test that personalized streams api work for topics' do
17
+ Livefyre::PersonalizedStream::create_or_update_topic(@network, 1, 'EINS')
18
+ topic = Livefyre::PersonalizedStream::get_topic(@network, 1)
19
+ Livefyre::PersonalizedStream::delete_topic(@network, topic).should == true
20
+
21
+ Livefyre::PersonalizedStream::create_or_update_topics(@network, {1 => 'EINS', 2 => 'ZWEI'})
22
+ topics = Livefyre::PersonalizedStream::get_topics(@network)
23
+ Livefyre::PersonalizedStream::delete_topics(@network, topics)
24
+ end
25
+
26
+ it 'should test that personalized streams api work for subscriptions' do
27
+ topics = Livefyre::PersonalizedStream::create_or_update_topics(@network, {1 => 'EINS', 2 => 'ZWEI'})
28
+ user_token = @network.build_user_auth_token(USER_ID, "#{USER_ID}@#{NETWORK_NAME}", Livefyre::Network::DEFAULT_EXPIRES)
29
+
30
+ Livefyre::PersonalizedStream::add_subscriptions(@network, user_token, topics)
31
+ Livefyre::PersonalizedStream::get_subscriptions(@network, USER_ID)
32
+ Livefyre::PersonalizedStream::replace_subscriptions(@network, user_token, [topics[1]])
33
+ Livefyre::PersonalizedStream::get_subscribers(@network, topics[1])
34
+ Livefyre::PersonalizedStream::remove_subscriptions(@network, user_token, [topics[1]])
35
+ end
36
+
37
+ it 'should test that personalized streams api work for timelines and cursors' do
38
+ topic = Livefyre::PersonalizedStream::create_or_update_topic(@network, 1, 'EINS')
39
+ cursor = Livefyre::CursorFactory::get_topic_stream_cursor(@network, topic)
40
+
41
+ cursor.next
42
+ cursor.previous
43
+
44
+ Livefyre::PersonalizedStream::delete_topic(@network, topic)
45
+ end
46
+
47
+ it 'should test that personalized streams api work for topics' do
48
+ Livefyre::PersonalizedStream::create_or_update_topic(@site, 1, 'EINS')
49
+ topic = Livefyre::PersonalizedStream::get_topic(@site, 1)
50
+ Livefyre::PersonalizedStream::delete_topic(@site, topic).should == true
51
+
52
+ Livefyre::PersonalizedStream::create_or_update_topics(@site, {1 => 'EINS', 2 => 'ZWEI'})
53
+ topics = Livefyre::PersonalizedStream::get_topics(@site)
54
+ Livefyre::PersonalizedStream::delete_topics(@site, topics)
55
+ end
56
+
57
+ it 'should test that personalized streams api work for collections' do
58
+ topics = Livefyre::PersonalizedStream::create_or_update_topics(@site, {1 => 'EINS', 2 => 'ZWEI'})
59
+
60
+ Livefyre::PersonalizedStream::add_collection_topics(@site, COLLECTION_ID, topics)
61
+ Livefyre::PersonalizedStream::get_collection_topics(@site, COLLECTION_ID)
62
+ Livefyre::PersonalizedStream::replace_collection_topics(@site, COLLECTION_ID, [topics[1]])
63
+ Livefyre::PersonalizedStream::remove_collection_topics(@site, COLLECTION_ID, [topics[1]])
64
+ end
65
+ end
@@ -0,0 +1,24 @@
1
+ require 'livefyre'
2
+ require 'jwt'
3
+
4
+ RSpec.configure do |c|
5
+ c.filter_run_excluding :broken => true
6
+ end
7
+
8
+ describe Livefyre::Network do
9
+ before(:each) do
10
+ @network = Livefyre.get_network(NETWORK_NAME, NETWORK_KEY)
11
+ end
12
+
13
+ it 'should raise ArgumentError if url_template does not contain {id}' do
14
+ expect{ @network.set_user_sync_url('blah.com/') }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'should raise ArgumentError if user_id is not alphanumeric' do
18
+ expect{ @network.build_user_auth_token('fjoiwje.1fj', 'test', 100) }.to raise_error(ArgumentError)
19
+ end
20
+
21
+ it 'should validate a livefyre token' do
22
+ @network.validate_livefyre_token(@network.build_livefyre_token).should == true
23
+ end
24
+ end
@@ -0,0 +1,73 @@
1
+ # coding: utf-8
2
+
3
+ require 'livefyre'
4
+ require 'jwt'
5
+
6
+ RSpec.configure do |c|
7
+ c.filter_run_excluding :broken => true
8
+ end
9
+
10
+ describe Livefyre::Site do
11
+ before(:each) do
12
+ @site = Livefyre.get_network(NETWORK_NAME, NETWORK_KEY).get_site(SITE_ID, SITE_KEY)
13
+ end
14
+
15
+ it 'should raise ArgumentError if url is not a valid url for cmt' do
16
+ expect{ @site.build_collection_meta_token('test', 'test', 'blah.com/', 'test') }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'should raise ArgumentError if title is more than 255 characters for cmt' do
20
+ expect{ @site.build_collection_meta_token('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456', 'test', 'http://test.com', 'test') }.to raise_error(ArgumentError)
21
+ end
22
+
23
+ it 'should raise ArgumentError if not a valid type is passed in when building a collection meta token' do
24
+ expect{ @site.build_collection_meta_token('', '', 'http://livefyre.com', {type: 'bad type'}) }.to raise_error(ArgumentError)
25
+ end
26
+
27
+ it 'should check type and assign them to the correct field in the collection meta token' do
28
+ @token = @site.build_collection_meta_token('', '', 'http://livefyre.com', {tags: '', type: 'reviews'})
29
+ @decoded = JWT.decode(@token, SITE_KEY)
30
+
31
+ expect(@decoded['type']).to eq('reviews')
32
+
33
+ @token = @site.build_collection_meta_token('', '', 'http://livefyre.com', {type: 'liveblog'})
34
+ @decoded = JWT.decode(@token, SITE_KEY)
35
+
36
+ expect(@decoded['type']).to eq('liveblog')
37
+ end
38
+
39
+ it 'should return a collection meta token' do
40
+ expect{ @site.build_collection_meta_token('title', 'article_id', 'https://www.url.com', 'tags') }.to be_true
41
+ end
42
+
43
+ it 'should raise ArgumentError if url is not a valid url for checksum' do
44
+ expect{ @site.build_checksum('test', 'blah.com/', 'test') }.to raise_error(ArgumentError)
45
+ end
46
+
47
+ it 'should raise ArgumentError if title is more than 255 characters for checksum' do
48
+ expect{ @site.build_checksum('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456', 'http://test.com', 'test') }.to raise_error(ArgumentError)
49
+ end
50
+
51
+ it 'should return a valid checksum' do
52
+ expect(@site.build_checksum('title', 'https://www.url.com', 'tags')).to eq('4464458a10c305693b5bf4d43a384be7')
53
+ end
54
+
55
+ it 'should check for valid and invalid urls' do
56
+ expect{ @site.build_checksum('', 'test.com', '') }.to raise_error(ArgumentError)
57
+
58
+ @site.build_checksum('', 'http://localhost:8000', '')
59
+ @site.build_checksum('', 'http://清华大学.cn', '')
60
+ @site.build_checksum('', 'http://www.mysite.com/myresumé.html', '')
61
+ @site.build_checksum('', 'https://test.com/', '')
62
+ @site.build_checksum('', 'ftp://test.com/', '')
63
+ @site.build_checksum('', "https://test.com/path/test.-_~!$&'()*+,;=:@/dash", '')
64
+ end
65
+
66
+ it 'should test basic site api', :broken => true do
67
+ @site.get_collection_content(ARTICLE_ID)
68
+
69
+ name = "RubyCreateCollection#{Time.new}"
70
+ id = @site.create_collection(name, name, 'http://answers.livefyre.com/RUBY')
71
+ expect(@site.get_collection_id(name)).to eq(id)
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ NETWORK_NAME = '<NETWORK-NAME>'
2
+ NETWORK_KEY = '<NETWORK-KEY>'
3
+ SITE_ID = '<SITE-ID>'
4
+ SITE_KEY = '<SITE-KEY>'
5
+ COLLECTION_ID = '<COLLECTION-ID>'
6
+ USER = '<USER-ID>'
7
+ ARTICLE_ID = '<ARTICLE-ID>'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: livefyre
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Livefyre
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-07 00:00:00.000000000 Z
11
+ date: 2014-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -126,16 +126,34 @@ extensions: []
126
126
  extra_rdoc_files: []
127
127
  files:
128
128
  - .gitignore
129
+ - .idea/.name
130
+ - .idea/.rakeTasks
131
+ - .idea/encodings.xml
132
+ - .idea/misc.xml
133
+ - .idea/modules.xml
134
+ - .idea/scopes/scope_settings.xml
135
+ - .idea/vagrant.xml
136
+ - .idea/vcs.xml
137
+ - .project
129
138
  - CHANGELOG
130
139
  - Gemfile
131
140
  - LICENSE.txt
132
141
  - README.md
133
142
  - Rakefile
134
143
  - lib/livefyre.rb
135
- - lib/livefyre/core.rb
144
+ - lib/livefyre/api/personalized_stream.rb
145
+ - lib/livefyre/core/network.rb
146
+ - lib/livefyre/core/site.rb
147
+ - lib/livefyre/entity/subscription.rb
148
+ - lib/livefyre/entity/timeline_cursor.rb
149
+ - lib/livefyre/entity/topic.rb
150
+ - lib/livefyre/factory/cursor_factory.rb
136
151
  - lib/livefyre/version.rb
137
152
  - livefyre.gemspec
138
- - spec/livefyre/core_spec.rb
153
+ - spec/livefyre/api/personalized_stream_spec.rb
154
+ - spec/livefyre/core/network_spec.rb
155
+ - spec/livefyre/core/site_spec.rb
156
+ - spec/livefyre/test_spec.rb
139
157
  homepage: http://github.com/livefyre/livefyre-ruby-utils
140
158
  licenses:
141
159
  - MIT
@@ -164,5 +182,8 @@ signing_key:
164
182
  specification_version: 4
165
183
  summary: Livefyre Ruby utility classes
166
184
  test_files:
167
- - spec/livefyre/core_spec.rb
185
+ - spec/livefyre/api/personalized_stream_spec.rb
186
+ - spec/livefyre/core/network_spec.rb
187
+ - spec/livefyre/core/site_spec.rb
188
+ - spec/livefyre/test_spec.rb
168
189
  has_rdoc:
data/lib/livefyre/core.rb DELETED
@@ -1,129 +0,0 @@
1
- require 'base64'
2
- require 'digest'
3
- require 'json'
4
- require 'jwt'
5
- require 'rest-client'
6
- require 'addressable/uri'
7
-
8
- module Livefyre
9
- class Network
10
- DEFAULT_USER = 'system'
11
- DEFAULT_EXPIRES = 86400
12
-
13
- def initialize(network_name, network_key)
14
- @network_name = network_name
15
- @network_key = network_key
16
- end
17
-
18
- def set_user_sync_url(url_template)
19
- raise ArgumentError, 'url_template should contain {id}' if !url_template.include?('{id}')
20
-
21
- response =
22
- RestClient.post(
23
- "http://#{@network_name}",
24
- { actor_token: build_livefyre_token, pull_profile_url: url_template }
25
- )
26
- response.code == 204
27
- end
28
-
29
- def sync_user(user_id)
30
- response =
31
- RestClient.post(
32
- "http://#{@network_name}/api/v3_0/user/#{user_id}/refresh",
33
- { lftoken: build_livefyre_token }
34
- )
35
- response.code == 200
36
- end
37
-
38
- def build_livefyre_token
39
- build_user_auth_token(DEFAULT_USER, DEFAULT_USER, DEFAULT_EXPIRES)
40
- end
41
-
42
- def build_user_auth_token(user_id, display_name, expires)
43
- raise ArgumentError, 'user_id must be alphanumeric' if !(user_id =~ /\A\p{Alnum}+\z/)
44
-
45
- JWT.encode({
46
- domain: @network_name,
47
- user_id: user_id,
48
- display_name: display_name,
49
- expires: Time.new.to_i + expires},
50
- @network_key)
51
- end
52
-
53
- def validate_livefyre_token(lf_token)
54
- token_attributes = JWT.decode(lf_token, @network_key)
55
-
56
- token_attributes['domain'] == @network_name \
57
- && token_attributes['user_id'] == DEFAULT_USER \
58
- && token_attributes['expires'] >= Time.new.to_i
59
- end
60
-
61
- def get_site(site_id, site_key)
62
- Site.new(@network_name, site_id, site_key)
63
- end
64
-
65
- class Site
66
- TYPE = ['reviews', 'sidenotes', 'ratings', 'counting', 'liveblog', 'livechat', 'livecomments']
67
-
68
- def initialize(network_name, site_id, site_key)
69
- @network_name = network_name
70
- @site_id = site_id
71
- @site_key = site_key
72
- end
73
-
74
- def build_collection_meta_token(title, article_id, url, tags='', type=nil)
75
- raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
76
- raise ArgumentError, 'title length should be under 255 char' if title.length > 255
77
-
78
- collection_meta = {
79
- url: url,
80
- tags: tags,
81
- title: title,
82
- articleId: article_id
83
- }
84
- if type
85
- if TYPE.include? type
86
- collection_meta[:type] = type
87
- else
88
- raise ArgumentError, 'type is not a recognized type. should be liveblog, livechat, livecomments, reviews, sidenotes, or an empty string'
89
- end
90
- end
91
-
92
- JWT.encode(collection_meta, @site_key)
93
- end
94
-
95
- def build_checksum(title, url, tags='')
96
- raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
97
- raise ArgumentError, 'title length should be under 255 char' if title.length > 255
98
-
99
- collection_meta = { url: url, tags: tags, title: title }
100
- Digest::MD5.new.update(collection_meta.to_json).hexdigest
101
- end
102
-
103
- def get_collection_content(article_id)
104
- response =
105
- RestClient.get(
106
- "http://bootstrap.#{@network_name}/bs3/#{@network_name}/#{@site_id}/#{Base64.encode64(article_id.to_s()).chomp}/init",
107
- :accepts => :json
108
- )
109
- response.code == 200 ? JSON.parse(response) : nil
110
- end
111
-
112
- def get_collection_id(article_id)
113
- content = get_collection_content(article_id)
114
- if content
115
- content['collectionSettings']['collectionId']
116
- end
117
- end
118
-
119
- def uri?(string)
120
- uri = Addressable::URI.parse(string)
121
- %w( ftp ftps http https ).include?(uri.scheme)
122
- rescue Addressable::URI::BadURIError
123
- false
124
- rescue Addressable::URI::InvalidURIError
125
- false
126
- end
127
- end
128
- end
129
- end
@@ -1,79 +0,0 @@
1
- # coding: utf-8
2
-
3
- require 'livefyre'
4
- require 'jwt'
5
-
6
- describe Livefyre::Network do
7
- before(:each) do
8
- @network = Livefyre.get_network('networkName', 'networkKey')
9
- end
10
-
11
- it 'should raise ArgumentError if url_template does not contain {id}' do
12
- expect{ @network.set_user_sync_url('blah.com/') }.to raise_error(ArgumentError)
13
- end
14
-
15
- it 'should raise ArgumentError if user_id is not alphanumeric' do
16
- expect{ @network.build_user_auth_token('fjoiwje.1fj', 'test', 100) }.to raise_error(ArgumentError)
17
- end
18
-
19
- it 'should validate a livefyre token' do
20
- @network.validate_livefyre_token(@network.build_livefyre_token).should == true
21
- end
22
- end
23
-
24
- describe Livefyre::Network::Site do
25
- before(:each) do
26
- @site = Livefyre.get_network('networkName', 'networkKey').get_site('siteId', 'siteKey')
27
- end
28
-
29
- it 'should raise ArgumentError if url is not a valid url for cmt' do
30
- expect{ @site.build_collection_meta_token('test', 'test', 'blah.com/', 'test') }.to raise_error(ArgumentError)
31
- end
32
-
33
- it 'should raise ArgumentError if title is more than 255 characters for cmt' do
34
- expect{ @site.build_collection_meta_token('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456', 'test', 'http://test.com', 'test') }.to raise_error(ArgumentError)
35
- end
36
-
37
- it 'should raise ArgumentError if not a valid type is passed in when building a collection meta token' do
38
- expect{ @site.build_collection_meta_token('', '', 'http://livefyre.com', '', 'bad type') }.to raise_error(ArgumentError)
39
- end
40
-
41
- it 'should check type and assign them to the correct field in the collection meta token' do
42
- @token = @site.build_collection_meta_token('', '', 'http://livefyre.com', '', 'reviews')
43
- @decoded = JWT.decode(@token, 'siteKey')
44
-
45
- expect(@decoded['type']).to eq('reviews')
46
-
47
- @token = @site.build_collection_meta_token('', '', 'http://livefyre.com', '', 'liveblog')
48
- @decoded = JWT.decode(@token, 'siteKey')
49
-
50
- expect(@decoded['type']).to eq('liveblog')
51
- end
52
-
53
- it 'should return a collection meta token' do
54
- expect{ @site.build_collection_meta_token('title', 'article_id', 'https://www.url.com', 'tags') }.to be_true
55
- end
56
-
57
- it 'should raise ArgumentError if url is not a valid url for checksum' do
58
- expect{ @site.build_checksum('test', 'blah.com/', 'test') }.to raise_error(ArgumentError)
59
- end
60
-
61
- it 'should raise ArgumentError if title is more than 255 characters for checksum' do
62
- expect{ @site.build_checksum('1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456', 'http://test.com', 'test') }.to raise_error(ArgumentError)
63
- end
64
-
65
- it 'should return a valid checksum' do
66
- expect(@site.build_checksum('title', 'https://www.url.com', 'tags')).to eq('6e2e4faf7b95f896260fe695eafb34ba')
67
- end
68
-
69
- it 'should check for valid and invalid urls' do
70
- expect{ @site.build_checksum('', 'test.com', '') }.to raise_error(ArgumentError)
71
-
72
- @site.build_checksum('', 'http://localhost:8000', '')
73
- @site.build_checksum('', 'http://清华大学.cn', '')
74
- @site.build_checksum('', 'http://www.mysite.com/myresumé.html', '')
75
- @site.build_checksum('', 'https://test.com/', '')
76
- @site.build_checksum('', 'ftp://test.com/', '')
77
- @site.build_checksum('', "https://test.com/path/test.-_~!$&'()*+,;=:@/dash", '')
78
- end
79
- end