radian6 0.1
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 +6 -0
- data/Gemfile +3 -0
- data/License.md +8 -0
- data/README.md +48 -0
- data/Rakefile +23 -0
- data/lib/radian6/api.rb +183 -0
- data/lib/radian6/filter_group.rb +12 -0
- data/lib/radian6/filter_query.rb +12 -0
- data/lib/radian6/post.rb +40 -0
- data/lib/radian6/sax/post.rb +101 -0
- data/lib/radian6/sax/post_counter.rb +61 -0
- data/lib/radian6/topic.rb +51 -0
- data/lib/radian6/version.rb +3 -0
- data/lib/radian6.rb +8 -0
- data/radian6.gemspec +35 -0
- data/spec/lib/radian6/api_spec.rb +153 -0
- data/spec/lib/radian6/filter_group_spec.rb +17 -0
- data/spec/lib/radian6/filter_query_spec.rb +16 -0
- data/spec/lib/radian6/post_spec.rb +51 -0
- data/spec/lib/radian6/topic_spec.rb +63 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/radian6_spec_helper.rb +76 -0
- metadata +142 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/License.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2012 Riccardo Cambiassi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Radian6
|
2
|
+
|
3
|
+
Radian6 gem is a (rough) ruby interface to the Radian6 EST API (http://labs.radian6.com/api/)
|
4
|
+
|
5
|
+
## INSTALLATION
|
6
|
+
|
7
|
+
gem install radian6
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
# connect to the API
|
12
|
+
api = Radian6::API.new(username, password, app_key, :sandbox =>
|
13
|
+
true, :proxy => myproxy, :debug => true, :async => false)
|
14
|
+
|
15
|
+
# get a XML dump of the posts within a specified range
|
16
|
+
posts_xml = api.fetchRangeTopicPostsXML( ((Time.now - 3600)*1000).to_s, (Time.now * 1000).to_s, [topic])
|
17
|
+
|
18
|
+
# get topics
|
19
|
+
topics = api.topics
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## Status
|
24
|
+
|
25
|
+
This is just the first draft of the wrapper. It can be improved in many, many ways.
|
26
|
+
The API class in particular is quite messy, as it's the result of several experiments, expect it to change quite dramatically in the near future.
|
27
|
+
|
28
|
+
Feel free to help (see Contributing below)
|
29
|
+
|
30
|
+
## TODO
|
31
|
+
|
32
|
+
* Clean up API class (maybe structuring the various calls in different "services" as suggested by the API documentation)
|
33
|
+
* better coverage for object output (right now only Post and Topic are supported)
|
34
|
+
* remove the option to generate objects from xml as it's too slow and hogs the memory when dealing with big pages.
|
35
|
+
* remove async connection option and isolate it via a Connector class interface
|
36
|
+
|
37
|
+
## Contributing to the code (a.k.a. submitting a pull request)
|
38
|
+
|
39
|
+
1. Fork the project.
|
40
|
+
2. Create a topic branch.
|
41
|
+
3. Implement your feature or bug fix.
|
42
|
+
4. Commit and push your changes.
|
43
|
+
5. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
|
44
|
+
|
45
|
+
|
46
|
+
## Copyright
|
47
|
+
|
48
|
+
Copyright (c) 2012 Riccardo Cambiassi. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :test => :spec
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
namespace :doc do
|
11
|
+
require 'yard'
|
12
|
+
YARD::Rake::YardocTask.new do |task|
|
13
|
+
task.files = ['HISTORY.mkd', 'LICENSE.mkd', 'lib/**/*.rb']
|
14
|
+
task.options = [
|
15
|
+
'--protected',
|
16
|
+
'--output-dir', 'doc/yard',
|
17
|
+
'--tag', 'format:Supported formats',
|
18
|
+
'--tag', 'authenticated:Requires Authentication',
|
19
|
+
'--tag', 'rate_limited:Rate Limited',
|
20
|
+
'--markup', 'markdown',
|
21
|
+
]
|
22
|
+
end
|
23
|
+
end
|
data/lib/radian6/api.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'em-http-request'
|
5
|
+
require 'uri'
|
6
|
+
require 'digest/md5'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
module Radian6
|
10
|
+
class API
|
11
|
+
class HarvestError < StandardError; end
|
12
|
+
attr_reader :auth_appkey, :auth_token
|
13
|
+
def initialize(username, password, app_key, opts={})
|
14
|
+
@auth_appkey = app_key
|
15
|
+
opts = { :sandbox => false, :debug => false, :async => false }.merge(opts)
|
16
|
+
@debug = opts[:debug]
|
17
|
+
@async = opts[:async]
|
18
|
+
@proxy = opts[:proxy]
|
19
|
+
if opts[:sandbox]
|
20
|
+
@endpoint = "http://sandboxapi.radian6.com/socialcloud/v1/"
|
21
|
+
else
|
22
|
+
@endpoint = "http://api.radian6.com/socialcloud/v1/"
|
23
|
+
end
|
24
|
+
|
25
|
+
authenticate(username, password)
|
26
|
+
return self
|
27
|
+
end
|
28
|
+
|
29
|
+
def topics(cache=false)
|
30
|
+
if (cache)
|
31
|
+
xml = File.read('./fixtures/topics.xml')
|
32
|
+
else
|
33
|
+
log("App Key: #{@auth_appkey.inspect}")
|
34
|
+
log("Auth Token: #{@auth_token.inspect}")
|
35
|
+
xml = api_get( "topics", { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
36
|
+
end
|
37
|
+
log("Received XML\n#{xml.inspect}")
|
38
|
+
return Radian6::Topic.from_xml(xml)
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetchRecentTopicPosts(hours=1,topics=[62727],media=[1,2,4,5,8,9,10,11,12,13,16],start_page=0,page_size=1000,dump_file=false)
|
42
|
+
path = "data/topicdata/recent/#{hours}/#{topics.join(',')}/#{media.join(',')}/#{start_page}/#{page_size}"
|
43
|
+
xml = api_get(path, { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
44
|
+
return Radian6::Post.from_xml(xml)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetchRangeTopicPosts(range_start,range_end,topics=[62727],media=[1,2,4,5,8,9,10,11,12,13,16],start_page=0,page_size=1000,dump_file=false)
|
48
|
+
# BEWARE: range_start and range_end should be UNIX epochs in milliseconds, not seconds
|
49
|
+
xml = fetchRangeTopicPostsXML(range_start, range_end, topics, media, start_page, page_size)
|
50
|
+
|
51
|
+
if dump_file
|
52
|
+
log "\tDumping to file #{dump_file} at #{Time.now}"
|
53
|
+
f = File.new(dump_file, "w")
|
54
|
+
f.write(xml)
|
55
|
+
f.close
|
56
|
+
|
57
|
+
counter = Radian6::SAX::PostCounter.new
|
58
|
+
parser = Nokogiri::XML::SAX::Parser.new(counter)
|
59
|
+
parser.parse(xml)
|
60
|
+
log "\tFinished parsing the file at #{Time.now}"
|
61
|
+
raise counter.error if counter.error
|
62
|
+
return counter
|
63
|
+
else
|
64
|
+
return Radian6::Post.from_xml(xml)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetchRangeTopicPostsXML(range_start, range_end, topics=[], media=[1,2,4,5,8,9,10,11,12,13,16], start_page=1, page_size=1000)
|
69
|
+
# BEWARE: range_start and range_end should be UNIX epochs in milliseconds, not seconds
|
70
|
+
path = "data/topicdata/range/#{range_start}/#{range_end}/#{topics.join(',')}/#{media.join(',')}/#{start_page}/#{page_size}?includeFullContent=1"
|
71
|
+
log "\tGetting page #{start_page} for range #{range_start} to #{range_end} in topic #{topics.join(', ')} at #{Time.now}"
|
72
|
+
xml = api_get(path, { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
73
|
+
return xml
|
74
|
+
end
|
75
|
+
|
76
|
+
def fetchMediaTypes
|
77
|
+
path = "lookup/mediaproviders"
|
78
|
+
xml = api_get(path, { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
79
|
+
return xml
|
80
|
+
end
|
81
|
+
|
82
|
+
def eachRangeTopicPostsXML(range_start, range_end, topics=[62727], media=[1,2,4,5,8,9,10,11,12,13,16], page_size=1000)
|
83
|
+
page = 1
|
84
|
+
fetched_article_count = 0
|
85
|
+
botched = 0
|
86
|
+
total_count = 1
|
87
|
+
begin
|
88
|
+
xml = sanitize_xml( fetchRangeTopicPostsXML(range_start, range_end, topics, media, page, page_size) )
|
89
|
+
counter = get_posts_counter_for(xml)
|
90
|
+
total_count = counter.total
|
91
|
+
fetched_article_count = (page -1) * page_size + counter.count
|
92
|
+
|
93
|
+
yield page, xml, counter
|
94
|
+
|
95
|
+
page += 1
|
96
|
+
end while total_count > fetched_article_count
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetchRangeConversationCloudData(range_start, range_end, topics=[62727], media=[1,2,4,5,8,9,10,11,12,13,16], segmentation=0)
|
100
|
+
path = "data/tagclouddata/#{range_start}/#{range_end}/#{topics.join(',')}/#{media.join(',')}/#{segmentation}"
|
101
|
+
api_get(path, { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetchInfluencerData(topics=[], show_sentiment = 0, show_engagement = 0)
|
105
|
+
path = "data/influencerdata/#{topics.join(",")}/#{show_sentiment}/#{show_engagement}/"
|
106
|
+
api_get(path, { 'auth_appkey' => @auth_appkey, 'auth_token' => @auth_token })
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def sanitize_xml(xml)
|
112
|
+
xml.gsub(/\u0000/, '')
|
113
|
+
end
|
114
|
+
|
115
|
+
def authenticate(username, password)
|
116
|
+
log("Username -> #{username.inspect}")
|
117
|
+
log("Password -> #{password.inspect}")
|
118
|
+
md5_pass = Digest::MD5::hexdigest(password)
|
119
|
+
xml = api_get("auth/authenticate",
|
120
|
+
{ 'auth_user' => username,
|
121
|
+
'auth_pass' => md5_pass,
|
122
|
+
'auth_appkey' => @auth_appkey})
|
123
|
+
log("Received XML\n#{xml.inspect}")
|
124
|
+
doc = Nokogiri::XML(xml)
|
125
|
+
@auth_token = doc.xpath('//auth/token').text
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_posts_counter_for(xml)
|
129
|
+
counter = Radian6::SAX::PostCounter.new
|
130
|
+
parser = Nokogiri::XML::SAX::Parser.new(counter)
|
131
|
+
parser.parse(xml)
|
132
|
+
raise counter.error if counter.error
|
133
|
+
counter
|
134
|
+
end
|
135
|
+
|
136
|
+
def log(string)
|
137
|
+
puts "#{self.class}: #{string}" if @debug
|
138
|
+
end
|
139
|
+
|
140
|
+
def api_get(method, headers={})
|
141
|
+
if @async
|
142
|
+
get_async(method, headers)
|
143
|
+
else
|
144
|
+
get_sync(method, headers)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_async(method, headers={})
|
149
|
+
options = {
|
150
|
+
:connect_timeout => 3600,
|
151
|
+
:inactivity_timeout => 3600,
|
152
|
+
}
|
153
|
+
options[:proxy] = { :host => @proxy.host, :port => @proxy.port.to_i } if @proxy
|
154
|
+
|
155
|
+
unless method == "auth/authenticate"
|
156
|
+
headers['auth_appkey'] = @auth_appkey
|
157
|
+
headers['auth_token'] = @auth_token
|
158
|
+
end
|
159
|
+
http = EventMachine::HttpRequest.new(@endpoint + method, options ).get :head => headers
|
160
|
+
http.response
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_sync(method, args={})
|
164
|
+
unless method == "auth/authenticate"
|
165
|
+
args['auth_appkey'] = @auth_appkey
|
166
|
+
args['auth_token'] = @auth_token
|
167
|
+
end
|
168
|
+
|
169
|
+
url = URI.parse(@endpoint + method)
|
170
|
+
|
171
|
+
protocol = @proxy.nil? ? Net::HTTP : Net::HTTP::Proxy(@proxy.host, @proxy.port)
|
172
|
+
|
173
|
+
res = protocol.start(url.host, url.port ) do |http|
|
174
|
+
http.open_timeout = 3600
|
175
|
+
http.read_timeout = 3600
|
176
|
+
log " GET #{url.request_uri}"
|
177
|
+
http.get(url.request_uri, args)
|
178
|
+
end
|
179
|
+
res.body # maybe handle errors too...
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Radian6
|
2
|
+
class FilterQuery
|
3
|
+
attr_accessor :query, :isExcludeQuery, :id
|
4
|
+
|
5
|
+
def initialize(params)
|
6
|
+
params = {} unless params.is_a? Hash
|
7
|
+
@isExcludeQuery = params[:isExcludeQuery] || ''
|
8
|
+
@query = params[:query] || ''
|
9
|
+
@id = params[:id] || ''
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/radian6/post.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Radian6
|
2
|
+
class Post
|
3
|
+
attr_accessor :author, :avatar, :username, :title, :created_at, :updated_at, :id, :body, :source, :permalink
|
4
|
+
|
5
|
+
def initialize(params)
|
6
|
+
params = {} unless params.is_a? Hash
|
7
|
+
@username = params[:username] || ""
|
8
|
+
@title = params[:title] || ""
|
9
|
+
@id = params[:id] || ""
|
10
|
+
@body = params[:body] || ""
|
11
|
+
@source = params[:source].downcase || "" rescue ""
|
12
|
+
@permalink= params[:permalink] || ""
|
13
|
+
@author = params[:author] || ""
|
14
|
+
@avatar = params[:avatar] || ""
|
15
|
+
@created_at=params[:created_at] || ""
|
16
|
+
@updated_at=params[:updated_at] || ""
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_xml(xml)
|
20
|
+
xml_posts = Nokogiri::XML(xml).root.xpath('//article') rescue []
|
21
|
+
posts = []
|
22
|
+
xml_posts.each_with_index do |xml_message, index|
|
23
|
+
post = self.new({
|
24
|
+
:username => xml_message.xpath('./description/author').text,
|
25
|
+
:title => xml_message.xpath('./description/headline').text,
|
26
|
+
:author => xml_message.xpath('./description/author').text,
|
27
|
+
:avatar => xml_message.xpath('./avatar').text,
|
28
|
+
:created_at => xml_message.xpath('./publish_date').text,
|
29
|
+
:updated_at => xml_message.xpath('./publish_date').text,
|
30
|
+
:id => xml_message.object_id,
|
31
|
+
:body => xml_message.xpath('./description/content').text,
|
32
|
+
:source => xml_message.xpath('./media_provider').text,
|
33
|
+
:permalink => xml_message.xpath('./article_url').text
|
34
|
+
})
|
35
|
+
posts << post
|
36
|
+
end
|
37
|
+
return posts
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Radian6
|
4
|
+
module SAX
|
5
|
+
class Post < Nokogiri::XML::SAX::Document
|
6
|
+
attr_reader :posts
|
7
|
+
def initialize
|
8
|
+
@post_elem = false
|
9
|
+
@current_post = {}
|
10
|
+
@posts = []
|
11
|
+
@path = []
|
12
|
+
@xpaths = {
|
13
|
+
:prefix => "radian6_RiverOfNews_export/article/",
|
14
|
+
:paths => {'description/author' => "username",
|
15
|
+
'description/headline' => "title",
|
16
|
+
'description/author' => "author",
|
17
|
+
'avatar' => "avatar",
|
18
|
+
'publish_date' => "created_at",
|
19
|
+
'description/content' => "body",
|
20
|
+
'media_provider' => "source",
|
21
|
+
'article_url' => "permalink"
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_element name, attrs = []
|
27
|
+
@path << name
|
28
|
+
if is_resource_elem?
|
29
|
+
@post_elem = find_resource_elem(name)
|
30
|
+
end
|
31
|
+
if name == "article"
|
32
|
+
attrs.each do |a|
|
33
|
+
@current_post[:id] = a[1] if a[0].to_s.downcase == "id"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def end_element name
|
39
|
+
@path.pop
|
40
|
+
@post_elem = false
|
41
|
+
if name == "article"
|
42
|
+
post = Radian6::Post.new(@current_post)
|
43
|
+
new_post(post)
|
44
|
+
@current_post = {}
|
45
|
+
end
|
46
|
+
if name == "radian6_RiverOfNews_export"
|
47
|
+
end_of_file
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def characters text
|
52
|
+
update_post(text)
|
53
|
+
end
|
54
|
+
|
55
|
+
def cdata_block text
|
56
|
+
update_post(text)
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_post(text)
|
60
|
+
if @post_elem
|
61
|
+
@current_post[@post_elem] = text
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def new_post(post)
|
66
|
+
@posts << post
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_of_file
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def is_resource_elem?
|
74
|
+
xpaths.include?(current_path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_resource_elem(name)
|
78
|
+
xpaths_map[current_path].to_sym
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_path
|
82
|
+
@path.join("/")
|
83
|
+
end
|
84
|
+
|
85
|
+
def xpaths
|
86
|
+
@expanded_paths ||= @xpaths[:paths].keys.map { |x| @xpaths[:prefix] + x }
|
87
|
+
end
|
88
|
+
|
89
|
+
def xpaths_map
|
90
|
+
return @expanded_paths_map if @expanded_paths_map
|
91
|
+
|
92
|
+
@expanded_paths_map = {}
|
93
|
+
@xpaths[:paths].keys.each do |path|
|
94
|
+
@expanded_paths_map[@xpaths[:prefix] + path] = @xpaths[:paths][path]
|
95
|
+
end
|
96
|
+
return @expanded_paths_map
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Radian6
|
4
|
+
module SAX
|
5
|
+
class PostCounter < Nokogiri::XML::SAX::Document
|
6
|
+
attr_reader :count, :total, :error, :last_timestamp
|
7
|
+
def initialize
|
8
|
+
@in_count = false
|
9
|
+
@in_total = false
|
10
|
+
@in_error = false
|
11
|
+
end
|
12
|
+
def start_element name, attrs = []
|
13
|
+
unless done?
|
14
|
+
case name
|
15
|
+
when "article_count"
|
16
|
+
@in_count = true
|
17
|
+
when "total_article_count"
|
18
|
+
@in_total = true
|
19
|
+
when "error"
|
20
|
+
@in_error = true
|
21
|
+
when "publish_date"
|
22
|
+
fetch_timestamp(attrs)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def end_element name
|
28
|
+
unless done?
|
29
|
+
case name
|
30
|
+
when "article_count"
|
31
|
+
@in_count = false
|
32
|
+
when "total_article_count"
|
33
|
+
@in_total = false
|
34
|
+
when "error"
|
35
|
+
@in_error = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def characters text
|
41
|
+
unless done?
|
42
|
+
if @in_count
|
43
|
+
@count = text.to_i
|
44
|
+
elsif @in_total
|
45
|
+
@total = text.to_i
|
46
|
+
elsif @in_error
|
47
|
+
@error = text
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_timestamp(attrs = [])
|
53
|
+
@last_timestamp ||= attrs.select { |key, value| key == "epoch" }.first[1].to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
def done?
|
57
|
+
(@count.nil? || @total.nil? || @last_timestamp.nil?) ? false : true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Radian6
|
2
|
+
class Topic
|
3
|
+
attr_accessor :name, :groups, :id
|
4
|
+
|
5
|
+
def initialize(params)
|
6
|
+
params = {} unless params.is_a? Hash
|
7
|
+
@name = params[:name] || ""
|
8
|
+
@groups = params[:groups] || ""
|
9
|
+
@id = params[:id] || ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_xml(xml)
|
13
|
+
xml_topics = Nokogiri::XML(xml).root.xpath('//topicFilters/topicFilter') rescue []
|
14
|
+
topics = []
|
15
|
+
xml_topics.each do |xml_topic|
|
16
|
+
topics << parse_topic(xml_topic)
|
17
|
+
end
|
18
|
+
return topics
|
19
|
+
end
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.parse_topic(xml_topic)
|
23
|
+
groups = []
|
24
|
+
xml_topic.xpath('./filterGroups/filterGroup').each do |xml_group|
|
25
|
+
groups << parse_group(xml_group)
|
26
|
+
end
|
27
|
+
self.new({
|
28
|
+
:name => xml_topic.xpath('./name').text,
|
29
|
+
:id => xml_topic.xpath('./topicFilterId').text,
|
30
|
+
:groups => groups
|
31
|
+
})
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse_group(xml_group)
|
35
|
+
queries = []
|
36
|
+
xml_group.xpath('./filterQueries/filterQuery').each do |xml_query|
|
37
|
+
queries << FilterQuery.new({
|
38
|
+
:id => xml_query.xpath('./filterQueryId').text,
|
39
|
+
:query => xml_query.xpath('./query').text,
|
40
|
+
:isExcludeQuery => xml_query.xpath('./filterQueryTypeId').text
|
41
|
+
})
|
42
|
+
end
|
43
|
+
|
44
|
+
FilterGroup.new({
|
45
|
+
:name => xml_group.xpath('./name').text,
|
46
|
+
:id => xml_group.xpath('./filterGroupId').text,
|
47
|
+
:queries => queries
|
48
|
+
})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/radian6.rb
ADDED
data/radian6.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/radian6/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.add_development_dependency('rake', '~> 0.8')
|
6
|
+
s.add_development_dependency('rspec', '~> 2.5')
|
7
|
+
s.add_development_dependency('yard')
|
8
|
+
s.add_development_dependency('webmock')
|
9
|
+
s.add_runtime_dependency('nokogiri', '>= 1.4.4')
|
10
|
+
s.add_runtime_dependency('em-http-request')
|
11
|
+
s.authors = ["Vincent Spehner"]
|
12
|
+
s.description = %q{A Ruby wrapper for the Radian6 REST API}
|
13
|
+
s.post_install_message =<<eos
|
14
|
+
********************************************************************************
|
15
|
+
|
16
|
+
Thank you for installing radian6
|
17
|
+
|
18
|
+
Follow @vzmind on Twitter for announcements, updates, and news.
|
19
|
+
https://twitter.com/vzmind
|
20
|
+
|
21
|
+
********************************************************************************
|
22
|
+
eos
|
23
|
+
s.email = ['bru@codewitch.org']
|
24
|
+
# s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.homepage = 'https://github.com/vzmind/radian6'
|
27
|
+
s.name = 'radian6'
|
28
|
+
s.platform = Gem::Platform::RUBY
|
29
|
+
s.require_paths = ['lib']
|
30
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if s.respond_to? :required_rubygems_version=
|
31
|
+
s.rubyforge_project = s.name
|
32
|
+
s.summary = %q{Ruby wrapper for the Radian6 API}
|
33
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
34
|
+
s.version = Radian6::VERSION.dup
|
35
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'radian6'
|
3
|
+
|
4
|
+
describe Radian6::API do
|
5
|
+
include Radian6SpecHelper
|
6
|
+
before do
|
7
|
+
stub_request(:get, "http://api.radian6.com/socialcloud/v1/auth/authenticate").
|
8
|
+
to_return(:status => 200, :body => auth_xml)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "initialization" do
|
12
|
+
it "should authenticate user" do
|
13
|
+
r6 = Radian6::API.new "username", "password", "123456789"
|
14
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/auth/authenticate").
|
15
|
+
with(:headers => {'Auth-Pass' => '5f4dcc3b5aa765d61d8327deb882cf99', 'Auth-User' => 'username'})
|
16
|
+
|
17
|
+
r6.should_not be_nil
|
18
|
+
r6.auth_token.should == "abcdefghi"
|
19
|
+
r6.auth_appkey.should == "123456789"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "after initialization" do
|
24
|
+
before do
|
25
|
+
@r6 = Radian6::API.new "username", "password", "123456789";
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#topics" do
|
29
|
+
before do
|
30
|
+
stub_request(:get, /.*api.radian6.com.+/).
|
31
|
+
to_return(:status => 200, :body => topics_xml)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should request topics" do
|
35
|
+
@r6.topics
|
36
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/topics").
|
37
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return a collection" do
|
41
|
+
@r6.topics.should respond_to :each
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "fetchRecentTopicPosts" do
|
46
|
+
before do
|
47
|
+
@topics = [123456]
|
48
|
+
stub_request(:get, /.*api.radian6.com.+/).
|
49
|
+
to_return(:status => 200, :body => recent_xml)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should make the correct request" do
|
53
|
+
@r6.fetchRecentTopicPosts 1, @topics, [1]
|
54
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/data/topicdata/recent/1/123456/1/0/1000").
|
55
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return a collection" do
|
59
|
+
@r6.fetchRecentTopicPosts(1, @topics, [1]).should respond_to :each
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return radian6 posts" do
|
63
|
+
Radian6::Post.should_receive(:from_xml)
|
64
|
+
@r6.fetchRecentTopicPosts(1, @topics, [1])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "fetchRangeTopicPostsXML" do
|
69
|
+
before do
|
70
|
+
@topics = [123456]
|
71
|
+
stub_request(:get, /.*api.radian6.com.+/).
|
72
|
+
to_return(:status => 200, :body => range_xml)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should make the correct request" do
|
76
|
+
@r6.fetchRangeTopicPostsXML "1308738914000", "1308738964000", @topics, [1]
|
77
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/data/topicdata/range/1308738914000/1308738964000/123456/1/1/1000?includeFullContent=1").
|
78
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not return radian6 posts" do
|
82
|
+
Radian6::Post.should_not_receive(:from_xml)
|
83
|
+
@r6.fetchRangeTopicPostsXML("1308738914000", "1308738964000", @topics, [1])
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should return xml" do
|
87
|
+
@r6.fetchRangeTopicPostsXML("1308738914000", "1308738964000", @topics, [1]).should == range_xml
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "eachRangeTopicPostsXML" do
|
92
|
+
before do
|
93
|
+
@topics = [123456]
|
94
|
+
end
|
95
|
+
context "when posts span multiple pages" do
|
96
|
+
it "should loop over all the pages" do
|
97
|
+
stub_request(:get, /.*api.radian6.com.+\/[123]\/10/).
|
98
|
+
to_return(:status => 200, :body => pages_xml(10, 33))
|
99
|
+
stub_request(:get, /.*api.radian6.com.+\/4\/10/).
|
100
|
+
to_return(:status => 200, :body => pages_xml(3, 33))
|
101
|
+
|
102
|
+
counter = 1
|
103
|
+
@r6.eachRangeTopicPostsXML("1308738914000", "1308738964000", @topics, [1], 10) do |page, xml|
|
104
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/data/topicdata/range/1308738914000/1308738964000/123456/1/#{counter}/10?includeFullContent=1").
|
105
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
106
|
+
|
107
|
+
page.should == counter
|
108
|
+
if counter == 4
|
109
|
+
xml.should == pages_xml(3, 33)
|
110
|
+
else
|
111
|
+
xml.should == pages_xml(10, 33)
|
112
|
+
end
|
113
|
+
counter += 1
|
114
|
+
end
|
115
|
+
|
116
|
+
counter.should == 5
|
117
|
+
end
|
118
|
+
end
|
119
|
+
context "when all posts fit in one page" do
|
120
|
+
it "should make just one call" do
|
121
|
+
stub_request(:get, /.*api.radian6.com.+\/1\/10/).
|
122
|
+
to_return(:status => 200, :body => pages_xml(10, 10))
|
123
|
+
|
124
|
+
counter = 1
|
125
|
+
@r6.eachRangeTopicPostsXML("1308738914000", "1308738964000", @topics, [1], 10) do |page, xml|
|
126
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/data/topicdata/range/1308738914000/1308738964000/123456/1/#{counter}/10?includeFullContent=1").
|
127
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
128
|
+
counter += 1
|
129
|
+
end
|
130
|
+
counter.should == 2
|
131
|
+
end
|
132
|
+
end
|
133
|
+
context "when there are no posts" do
|
134
|
+
it "should loop just once" do
|
135
|
+
stub_request(:get, /.*api.radian6.com.+\/1\/20/).
|
136
|
+
to_return(:status => 200, :body => pages_xml(0, 0))
|
137
|
+
|
138
|
+
counter = 1
|
139
|
+
@r6.eachRangeTopicPostsXML("1308738914000", "1308738964000", @topics, [1], 20) do |page, xml|
|
140
|
+
WebMock.should have_requested(:get, "http://api.radian6.com/socialcloud/v1/data/topicdata/range/1308738914000/1308738964000/123456/1/#{counter}/20?includeFullContent=1").
|
141
|
+
with(:headers => {'Auth-Appkey' => '123456789', 'Auth-Token' => 'abcdefghi'})
|
142
|
+
|
143
|
+
page.should == 1
|
144
|
+
xml.should == pages_xml(0, 0)
|
145
|
+
counter += 1
|
146
|
+
end
|
147
|
+
|
148
|
+
counter.should == 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Radian6::FilterGroup do
|
4
|
+
|
5
|
+
describe "initialization" do
|
6
|
+
filter_group_attributes = [:name, :queries, :id]
|
7
|
+
['Radian6::FilterGroup.new({})', 'Radian6::FilterGroup.new(nil)', 'Radian6::FilterGroup.new([])'].each do |subj|
|
8
|
+
context "initializing with #{subj}" do
|
9
|
+
subject { eval subj}
|
10
|
+
filter_group_attributes.each do |method|
|
11
|
+
its(method.to_sym) { should be_empty }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Radian6::FilterQuery do
|
4
|
+
describe "initialization" do
|
5
|
+
filter_query_attributes = [ :query, :isExcludeQuery, :id]
|
6
|
+
['Radian6::FilterQuery.new({})', 'Radian6::FilterQuery.new(nil)', 'Radian6::FilterQuery.new([])'].each do |subj|
|
7
|
+
context "initializing with #{subj}" do
|
8
|
+
subject { eval subj}
|
9
|
+
filter_query_attributes.each do |method|
|
10
|
+
its(method.to_sym) { should be_empty }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'radian6'
|
3
|
+
|
4
|
+
describe Radian6::Post do
|
5
|
+
include Radian6SpecHelper
|
6
|
+
post_attributes = %w{author avatar username title created_at updated_at id body source permalink}
|
7
|
+
describe ".from_xml" do
|
8
|
+
context "nil parameter" do
|
9
|
+
it 'should return a collection' do
|
10
|
+
Radian6::Post.from_xml(nil).should respond_to :each
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "bad parameter" do
|
15
|
+
it 'should return a collection' do
|
16
|
+
Radian6::Post.from_xml(27).should respond_to :each
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "bad xml" do
|
21
|
+
it 'should return a collection' do
|
22
|
+
Radian6::Post.from_xml('wibble').should respond_to :each
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "empty string" do
|
27
|
+
it 'should return a collection' do
|
28
|
+
Radian6::Post.from_xml('').should respond_to :each
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "proccesing recent_xml" do
|
33
|
+
it "should produce Radian6::Posts" do
|
34
|
+
Radian6::Post.from_xml(recent_xml).each do |post|
|
35
|
+
post.is_a?(Radian6::Post).should be true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "initialization" do
|
42
|
+
['Radian6::Post.new({})', 'Radian6::Post.new(nil)', 'Radian6::Post.new([])'].each do |subj|
|
43
|
+
context "initializing with #{subj}" do
|
44
|
+
subject { eval subj}
|
45
|
+
post_attributes.each do |method|
|
46
|
+
its(method.to_sym) { should be_empty }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Radian6::Topic do
|
4
|
+
include Radian6SpecHelper
|
5
|
+
|
6
|
+
topic_attributes = [:name, :groups, :id]
|
7
|
+
describe ".from_xml" do
|
8
|
+
context "nil parameter" do
|
9
|
+
it 'should return a collection' do
|
10
|
+
Radian6::Topic.from_xml(nil).should respond_to :each
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "bad parameter" do
|
15
|
+
it 'should return a collection' do
|
16
|
+
Radian6::Topic.from_xml(27).should respond_to :each
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "bad xml" do
|
21
|
+
it 'should return a collection' do
|
22
|
+
Radian6::Topic.from_xml('wibble').should respond_to :each
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "empty string" do
|
27
|
+
it 'should return a collection' do
|
28
|
+
Radian6::Topic.from_xml('').should respond_to :each
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "proccesing topics_xml" do
|
33
|
+
it "should produce Radian6::Topics" do
|
34
|
+
Radian6::Topic.from_xml(topics_xml).each do |topic|
|
35
|
+
topic.is_a?(Radian6::Topic).should be true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
it "should create FilterGroups for each topic" do
|
39
|
+
Radian6::Topic.from_xml(topics_xml).each do |topic|
|
40
|
+
topic.groups.each{|group| group.is_a?(Radian6::FilterGroup).should be true}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
it "should create FilterQueries for each FilterGroup" do
|
44
|
+
Radian6::Topic.from_xml(topics_xml).each do |topic|
|
45
|
+
topic.groups.each do |group|
|
46
|
+
group.queries.each{|query| query.is_a?(Radian6::FilterQuery).should be true}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "initialization" do
|
54
|
+
['Radian6::Topic.new({})', 'Radian6::Topic.new(nil)', 'Radian6::Topic.new([])'].each do |subj|
|
55
|
+
context "initializing with #{subj}" do
|
56
|
+
subject { eval subj}
|
57
|
+
topic_attributes.each do |method|
|
58
|
+
its(method) { should be_empty }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'rspec'
|
6
|
+
require 'webmock/rspec'
|
7
|
+
|
8
|
+
# require 'rspec/mocks/standalone'
|
9
|
+
|
10
|
+
require 'support/radian6_spec_helper.rb'
|
11
|
+
# Dir[File.firname.join("spec/support/**/*.rb")].each {|f| require f}
|
12
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
13
|
+
require 'radian6'
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.mock_with :rspec
|
17
|
+
|
18
|
+
config.before(:suite) do
|
19
|
+
WebMock.disable_net_connect!(:allow_localhost => true)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Radian6SpecHelper
|
2
|
+
def auth_xml
|
3
|
+
"<auth><token>abcdefghi</token></auth>"
|
4
|
+
end
|
5
|
+
|
6
|
+
def topics_xml
|
7
|
+
'<topicFilters>
|
8
|
+
<topicFilter>
|
9
|
+
<name><![CDATA[FILTER TEST]]></name>
|
10
|
+
<topicFilterId>111111</topicFilterId>
|
11
|
+
<filterGroups>
|
12
|
+
<filterGroup>
|
13
|
+
<filterGroupId>222222</filterGroupId>
|
14
|
+
<name><![CDATA[Group 2 test]]></name>
|
15
|
+
<filterQueries>
|
16
|
+
<filterQuery>
|
17
|
+
<query><![CDATA["apple" AND "banana"]]></query>
|
18
|
+
<filterQueryId>333333</filterQueryId>
|
19
|
+
<filterQueryTypeId>0</filterQueryTypeId>
|
20
|
+
</filterQuery>
|
21
|
+
<filterQuery>
|
22
|
+
<query><![CDATA["coconut" AND "date"]]></query>
|
23
|
+
<filterQueryId>444444</filterQueryId>
|
24
|
+
<filterQueryTypeId>0</filterQueryTypeId>
|
25
|
+
</filterQuery>
|
26
|
+
</filterQueries>
|
27
|
+
</filterGroup>
|
28
|
+
<filterGroup>
|
29
|
+
<filterGroupId>555555</filterGroupId>
|
30
|
+
<name><![CDATA[Group 5 test]]></name>
|
31
|
+
<filterQueries>
|
32
|
+
<filterQuery>
|
33
|
+
<query><![CDATA["elderberry" AND "fig"]]></query>
|
34
|
+
<filterQueryId>666666</filterQueryId>
|
35
|
+
<filterQueryTypeId>0</filterQueryTypeId>
|
36
|
+
</filterQuery>
|
37
|
+
</filterQueries>
|
38
|
+
</filterGroup>
|
39
|
+
</filterGroups>
|
40
|
+
</topicFilter>
|
41
|
+
</topicFilters>'
|
42
|
+
end
|
43
|
+
|
44
|
+
def recent_xml
|
45
|
+
'<radian6_RiverOfNews_export>
|
46
|
+
<article ID="111111">
|
47
|
+
<description charset="UTF-8">
|
48
|
+
<headline><![CDATA[TEST HEADLINE 1]]></headline>
|
49
|
+
<author fbid="-1"><![CDATA[author]]></author>
|
50
|
+
<content><![CDATA[article content]]></content>
|
51
|
+
</description>
|
52
|
+
<article_url><![CDATA[http://example.com/111111]]></article_url>
|
53
|
+
<media_provider id="10">TWITTER</media_provider>
|
54
|
+
<publish_date epoch="1308738914000">Jun 22, 2011 11:35 AM</publish_date>
|
55
|
+
</article>
|
56
|
+
<article ID="222222">
|
57
|
+
<description charset="UTF-8">
|
58
|
+
<headline><![CDATA[TEST HEADLINE 2]]></headline>
|
59
|
+
<author fbid="-1"><![CDATA[author]]></author>
|
60
|
+
<content><![CDATA[article content]]></content>
|
61
|
+
</description>
|
62
|
+
<article_url><![CDATA[http://example.com/222222]]></article_url>
|
63
|
+
<media_provider id="10">TWITTER</media_provider>
|
64
|
+
<publish_date epoch="1308738914000">Jun 22, 2011 11:35 AM</publish_date>
|
65
|
+
</article>
|
66
|
+
</radian6_RiverOfNews_export>'
|
67
|
+
end
|
68
|
+
alias :range_xml :recent_xml
|
69
|
+
|
70
|
+
def pages_xml(article_count=10, total_article_count=100)
|
71
|
+
"<radian6_RiverOfNews_export>
|
72
|
+
<article_count>#{article_count}</article_count>
|
73
|
+
<total_article_count>#{total_article_count}</total_article_count>
|
74
|
+
</radian6_RiverOfNews_export>"
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radian6
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vincent Spehner
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70256223536480 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.8'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70256223536480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70256223535720 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.5'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70256223535720
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yard
|
38
|
+
requirement: &70256223535140 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70256223535140
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: webmock
|
49
|
+
requirement: &70256223534440 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70256223534440
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: nokogiri
|
60
|
+
requirement: &70256223529600 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.4.4
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70256223529600
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: em-http-request
|
71
|
+
requirement: &70256223528960 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70256223528960
|
80
|
+
description: A Ruby wrapper for the Radian6 REST API
|
81
|
+
email:
|
82
|
+
- bru@codewitch.org
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- Gemfile
|
89
|
+
- License.md
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- lib/radian6.rb
|
93
|
+
- lib/radian6/api.rb
|
94
|
+
- lib/radian6/filter_group.rb
|
95
|
+
- lib/radian6/filter_query.rb
|
96
|
+
- lib/radian6/post.rb
|
97
|
+
- lib/radian6/sax/post.rb
|
98
|
+
- lib/radian6/sax/post_counter.rb
|
99
|
+
- lib/radian6/topic.rb
|
100
|
+
- lib/radian6/version.rb
|
101
|
+
- radian6.gemspec
|
102
|
+
- spec/lib/radian6/api_spec.rb
|
103
|
+
- spec/lib/radian6/filter_group_spec.rb
|
104
|
+
- spec/lib/radian6/filter_query_spec.rb
|
105
|
+
- spec/lib/radian6/post_spec.rb
|
106
|
+
- spec/lib/radian6/topic_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- spec/support/radian6_spec_helper.rb
|
109
|
+
homepage: https://github.com/vzmind/radian6
|
110
|
+
licenses: []
|
111
|
+
post_install_message: ! "********************************************************************************\n\n
|
112
|
+
\ Thank you for installing radian6\n\n Follow @vzmind on Twitter for announcements,
|
113
|
+
updates, and news.\n https://twitter.com/vzmind\n\n********************************************************************************\n"
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 1.3.6
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project: radian6
|
131
|
+
rubygems_version: 1.8.10
|
132
|
+
signing_key:
|
133
|
+
specification_version: 3
|
134
|
+
summary: Ruby wrapper for the Radian6 API
|
135
|
+
test_files:
|
136
|
+
- spec/lib/radian6/api_spec.rb
|
137
|
+
- spec/lib/radian6/filter_group_spec.rb
|
138
|
+
- spec/lib/radian6/filter_query_spec.rb
|
139
|
+
- spec/lib/radian6/post_spec.rb
|
140
|
+
- spec/lib/radian6/topic_spec.rb
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/support/radian6_spec_helper.rb
|