radian6 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .bundle/*
2
+ .rvmrc
3
+ .DS_Store
4
+ *.tmproj
5
+ .*swp
6
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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
@@ -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 FilterGroup
3
+ attr_accessor :name, :queries, :id
4
+
5
+ def initialize(params)
6
+ params = {} unless params.is_a? Hash
7
+ @name = params[:name] || ''
8
+ @queries = params[:queries] || ''
9
+ @id = params[:id] || ''
10
+ end
11
+ end
12
+ 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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Radian6
2
+ VERSION = "0.1"
3
+ end
data/lib/radian6.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'radian6/api'
2
+ require 'radian6/post'
3
+ require 'radian6/sax/post_counter'
4
+ require 'radian6/sax/post'
5
+ require 'radian6/topic'
6
+ require 'radian6/filter_query'
7
+ require 'radian6/filter_group'
8
+
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
@@ -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