twitter_with_auto_pagination 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -18
  3. data/lib/twitter_with_auto_pagination.rb +42 -1
  4. data/lib/twitter_with_auto_pagination/log_subscriber.rb +2 -2
  5. data/lib/twitter_with_auto_pagination/rest/api.rb +31 -0
  6. data/lib/twitter_with_auto_pagination/rest/extension/clusters.rb +43 -0
  7. data/lib/twitter_with_auto_pagination/rest/extension/favoriting.rb +106 -0
  8. data/lib/twitter_with_auto_pagination/rest/extension/friends_and_followers.rb +131 -0
  9. data/lib/twitter_with_auto_pagination/rest/extension/replying.rb +90 -0
  10. data/lib/twitter_with_auto_pagination/rest/extension/unfollowing.rb +29 -0
  11. data/lib/twitter_with_auto_pagination/rest/favorites.rb +20 -0
  12. data/lib/twitter_with_auto_pagination/rest/friends_and_followers.rb +94 -0
  13. data/lib/twitter_with_auto_pagination/rest/search.rb +19 -0
  14. data/lib/twitter_with_auto_pagination/rest/timelines.rb +37 -0
  15. data/lib/twitter_with_auto_pagination/rest/uncategorized.rb +83 -0
  16. data/lib/twitter_with_auto_pagination/rest/users.rb +62 -0
  17. data/lib/twitter_with_auto_pagination/rest/utils.rb +303 -0
  18. data/spec/helper.rb +60 -1
  19. data/spec/twitter_with_auto_pagination/client_spec.rb +150 -0
  20. data/twitter_with_auto_pagination.gemspec +1 -1
  21. metadata +17 -8
  22. data/lib/twitter_with_auto_pagination/client.rb +0 -139
  23. data/lib/twitter_with_auto_pagination/existing_api.rb +0 -127
  24. data/lib/twitter_with_auto_pagination/new_api.rb +0 -337
  25. data/lib/twitter_with_auto_pagination/utils.rb +0 -303
  26. data/spec/twitter_with_auto_pagination_spec.rb +0 -131
data/spec/helper.rb CHANGED
@@ -1,9 +1,68 @@
1
1
  require 'twitter_with_auto_pagination'
2
2
  require 'rspec'
3
+ require 'stringio'
4
+ require 'tempfile'
5
+ require 'timecop'
6
+ require 'webmock/rspec'
7
+
8
+ require_relative 'support/media_object_examples'
3
9
 
4
10
  RSpec.configure do |config|
5
11
  config.expect_with :rspec do |c|
6
12
  c.syntax = :expect
7
13
  end
8
- config.order = 'random'
14
+ end
15
+
16
+ BASE_URL = 'https://api.twitter.com'.freeze
17
+
18
+ def a_delete(path)
19
+ a_request(:delete, BASE_URL + path)
20
+ end
21
+
22
+ def a_get(path)
23
+ a_request(:get, BASE_URL + path)
24
+ end
25
+
26
+ def a_post(path)
27
+ a_request(:post, BASE_URL + path)
28
+ end
29
+
30
+ def a_put(path)
31
+ a_request(:put, BASE_URL + path)
32
+ end
33
+
34
+ def stub_delete(path)
35
+ stub_request(:delete, BASE_URL + path)
36
+ end
37
+
38
+ def stub_get(path)
39
+ stub_request(:get, BASE_URL + path)
40
+ end
41
+
42
+ def stub_post(path)
43
+ stub_request(:post, BASE_URL + path)
44
+ end
45
+
46
+ def stub_put(path)
47
+ stub_request(:put, BASE_URL + path)
48
+ end
49
+
50
+ def fixture_path
51
+ File.expand_path('../fixtures', __FILE__)
52
+ end
53
+
54
+ def fixture(file)
55
+ File.new(fixture_path + '/' + file)
56
+ end
57
+
58
+ def capture_warning
59
+ begin
60
+ old_stderr = $stderr
61
+ $stderr = StringIO.new
62
+ yield
63
+ result = $stderr.string
64
+ ensure
65
+ $stderr = old_stderr
66
+ end
67
+ result
9
68
  end
@@ -0,0 +1,150 @@
1
+ require 'helper'
2
+
3
+ describe TwitterWithAutoPagination::Client do
4
+ let(:config) {
5
+ {
6
+ consumer_key: 'CK',
7
+ consumer_secret: 'CS',
8
+ access_token: 'AT',
9
+ access_token_secret: 'ATS',
10
+ }
11
+ }
12
+ let(:client) { TwitterWithAutoPagination::Client.new(config) }
13
+
14
+ describe '#user?' do
15
+ before do
16
+ stub_get('/1.1/users/show.json').with(query: {screen_name: 'sferik'}).to_return(body: fixture('sferik.json'), headers: {content_type: 'application/json; charset=utf-8'})
17
+ stub_get('/1.1/users/show.json').with(query: {screen_name: 'pengwynn'}).to_return(body: fixture('not_found.json'), status: 404, headers: {content_type: 'application/json; charset=utf-8'})
18
+ end
19
+ it 'requests the correct resource' do
20
+ client.user?('sferik')
21
+ expect(a_get('/1.1/users/show.json').with(query: {screen_name: 'sferik'})).to have_been_made
22
+ end
23
+ it 'returns true if user exists' do
24
+ user = client.user?('sferik')
25
+ expect(user).to be true
26
+ end
27
+ it 'returns false if user does not exist' do
28
+ user = client.user?('pengwynn')
29
+ expect(user).to be false
30
+ end
31
+ end
32
+
33
+ # describe '#initialize' do
34
+ # let(:default_call_count) { 0 }
35
+ #
36
+ # it 'sets call_count to 0' do
37
+ # expect(client.call_count).to eq(default_call_count)
38
+ # end
39
+ #
40
+ # context 'without params' do
41
+ # end
42
+ #
43
+ # context 'with params' do
44
+ # end
45
+ # end
46
+ #
47
+ # describe '#logger' do
48
+ # it 'has logger' do
49
+ # expect(client.logger).to be_truthy
50
+ # end
51
+ # end
52
+ #
53
+ # describe '#call_old_method' do
54
+ # end
55
+ #
56
+ # describe '#collect_with_max_id' do
57
+ # end
58
+ #
59
+ # describe '#collect_with_cursor' do
60
+ # end
61
+ #
62
+ # describe '#file_cache_key' do
63
+ # end
64
+ #
65
+ # describe '#namespaced_key' do
66
+ # end
67
+ #
68
+ # describe '#encode_json' do
69
+ # end
70
+ #
71
+ # describe '#decode_json' do
72
+ # end
73
+ #
74
+ # describe '#fetch_cache_or_call_api' do
75
+ # end
76
+ #
77
+ # describe '#user_timeline' do
78
+ # it 'calls old_user_timeline' do
79
+ # expect(client).to receive(:old_user_timeline)
80
+ # client.user_timeline
81
+ # end
82
+ #
83
+ # it 'calls collect_with_max_id' do
84
+ # expect(client).to receive(:collect_with_max_id)
85
+ # client.user_timeline
86
+ # end
87
+ # end
88
+ #
89
+ # describe '#user_photos' do
90
+ # it 'calls user_timeline' do
91
+ # expect(client).to receive(:user_timeline)
92
+ # client.user_photos
93
+ # end
94
+ # end
95
+ #
96
+ # describe '#friends' do
97
+ # it 'calls old_friends' do
98
+ # expect(client).to receive(:old_friends)
99
+ # client.friends
100
+ # end
101
+ #
102
+ # it 'calls collect_with_cursor' do
103
+ # expect(client).to receive(:collect_with_cursor)
104
+ # client.friends
105
+ # end
106
+ # end
107
+ #
108
+ # describe '#followers' do
109
+ # it 'calls old_followers' do
110
+ # expect(client).to receive(:old_followers)
111
+ # client.followers
112
+ # end
113
+ #
114
+ # it 'calls collect_with_cursor' do
115
+ # expect(client).to receive(:collect_with_cursor)
116
+ # client.followers
117
+ # end
118
+ # end
119
+ #
120
+ # describe '#friend_ids' do
121
+ # it 'calls old_friend_ids' do
122
+ # expect(client).to receive(:old_friend_ids)
123
+ # client.friend_ids
124
+ # end
125
+ #
126
+ # it 'calls collect_with_cursor' do
127
+ # expect(client).to receive(:collect_with_cursor)
128
+ # client.friend_ids
129
+ # end
130
+ # end
131
+ #
132
+ # describe '#follower_ids' do
133
+ # it 'calls old_follower_ids' do
134
+ # expect(client).to receive(:old_follower_ids)
135
+ # client.follower_ids
136
+ # end
137
+ #
138
+ # it 'calls collect_with_cursor' do
139
+ # expect(client).to receive(:collect_with_cursor)
140
+ # client.follower_ids
141
+ # end
142
+ # end
143
+ #
144
+ # describe '#users' do
145
+ # it 'calls old_users' do
146
+ # expect(client).to receive(:old_users)
147
+ # client.users([1, 2, 3])
148
+ # end
149
+ # end
150
+ end
@@ -22,5 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.required_ruby_version = '>= 2.3'
23
23
  spec.summary = spec.description
24
24
  spec.test_files = Dir.glob('spec/**/*')
25
- spec.version = '0.6.2'
25
+ spec.version = '0.7.0'
26
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twitter_with_auto_pagination
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shinohara Teruki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-04 00:00:00.000000000 Z
11
+ date: 2016-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: twitter
@@ -91,13 +91,22 @@ files:
91
91
  - README.md
92
92
  - Rakefile
93
93
  - lib/twitter_with_auto_pagination.rb
94
- - lib/twitter_with_auto_pagination/client.rb
95
- - lib/twitter_with_auto_pagination/existing_api.rb
96
94
  - lib/twitter_with_auto_pagination/log_subscriber.rb
97
- - lib/twitter_with_auto_pagination/new_api.rb
98
- - lib/twitter_with_auto_pagination/utils.rb
95
+ - lib/twitter_with_auto_pagination/rest/api.rb
96
+ - lib/twitter_with_auto_pagination/rest/extension/clusters.rb
97
+ - lib/twitter_with_auto_pagination/rest/extension/favoriting.rb
98
+ - lib/twitter_with_auto_pagination/rest/extension/friends_and_followers.rb
99
+ - lib/twitter_with_auto_pagination/rest/extension/replying.rb
100
+ - lib/twitter_with_auto_pagination/rest/extension/unfollowing.rb
101
+ - lib/twitter_with_auto_pagination/rest/favorites.rb
102
+ - lib/twitter_with_auto_pagination/rest/friends_and_followers.rb
103
+ - lib/twitter_with_auto_pagination/rest/search.rb
104
+ - lib/twitter_with_auto_pagination/rest/timelines.rb
105
+ - lib/twitter_with_auto_pagination/rest/uncategorized.rb
106
+ - lib/twitter_with_auto_pagination/rest/users.rb
107
+ - lib/twitter_with_auto_pagination/rest/utils.rb
99
108
  - spec/helper.rb
100
- - spec/twitter_with_auto_pagination_spec.rb
109
+ - spec/twitter_with_auto_pagination/client_spec.rb
101
110
  - twitter_with_auto_pagination.gemspec
102
111
  homepage: http://github.com/ts-3156/twitter_with_auto_pagination/
103
112
  licenses:
@@ -125,4 +134,4 @@ specification_version: 4
125
134
  summary: Add auto paginate feature to Twitter gem.
126
135
  test_files:
127
136
  - spec/helper.rb
128
- - spec/twitter_with_auto_pagination_spec.rb
137
+ - spec/twitter_with_auto_pagination/client_spec.rb
@@ -1,139 +0,0 @@
1
- require 'active_support'
2
- require 'active_support/cache'
3
- require 'active_support/core_ext/string'
4
-
5
- require 'twitter_with_auto_pagination/log_subscriber'
6
- require 'twitter_with_auto_pagination/utils'
7
- require 'twitter_with_auto_pagination/existing_api'
8
- require 'twitter_with_auto_pagination/new_api'
9
-
10
- require 'twitter'
11
- require 'hashie'
12
- require 'parallel'
13
-
14
- module TwitterWithAutoPagination
15
- class Client < Twitter::REST::Client
16
- def initialize(options = {})
17
- @cache = ActiveSupport::Cache::FileStore.new(File.join('tmp', 'api_cache'))
18
- @call_count = 0
19
-
20
- @uid = options.has_key?(:uid) ? options.delete(:uid).to_i : nil
21
- @screen_name = options.has_key?(:screen_name) ? options.delete(:screen_name).to_s : nil
22
-
23
- logger =
24
- if options.has_key?(:logger)
25
- options.delete(:logger)
26
- else
27
- Dir.mkdir('log') unless File.exists?('log')
28
- Logger.new('log/twitter_with_auto_pagination.log')
29
- end
30
- logger.level = options.has_key?(:log_level) ? options.delete(:log_level) : :debug
31
- @@logger = @logger = logger
32
-
33
- super
34
- end
35
-
36
- def self.logger
37
- @@logger
38
- end
39
-
40
- attr_accessor :call_count
41
- attr_reader :cache, :authenticated_user, :logger
42
-
43
- INDENT = 4
44
-
45
- include TwitterWithAutoPagination::Utils
46
-
47
- alias :old_verify_credentials :verify_credentials
48
- alias :old_friendship? :friendship?
49
- alias :old_user? :user?
50
- alias :old_user :user
51
- alias :old_friend_ids :friend_ids
52
- alias :old_follower_ids :follower_ids
53
- alias :old_friends :friends
54
- alias :old_followers :followers
55
- alias :old_users :users
56
- alias :old_home_timeline :home_timeline
57
- alias :old_user_timeline :user_timeline
58
- alias :old_mentions_timeline :mentions_timeline
59
- alias :old_favorites :favorites
60
- alias :old_search :search
61
-
62
- include TwitterWithAutoPagination::ExistingApi
63
- include TwitterWithAutoPagination::NewApi
64
-
65
- def usage_stats_wday_series_data(times)
66
- wday_count = times.each_with_object((0..6).map { |n| [n, 0] }.to_h) do |time, memo|
67
- memo[time.wday] += 1
68
- end
69
- wday_count.map { |k, v| [I18n.t('date.abbr_day_names')[k], v] }.map do |key, value|
70
- {name: key, y: value, drilldown: key}
71
- end
72
- end
73
-
74
- def usage_stats_wday_drilldown_series(times)
75
- hour_count =
76
- (0..6).each_with_object((0..6).map { |n| [n, nil] }.to_h) do |wday, wday_memo|
77
- wday_memo[wday] =
78
- times.select { |t| t.wday == wday }.map { |t| t.hour }.each_with_object((0..23).map { |n| [n, 0] }.to_h) do |hour, hour_memo|
79
- hour_memo[hour] += 1
80
- end
81
- end
82
- hour_count.map { |k, v| [I18n.t('date.abbr_day_names')[k], v] }.map do |key, value|
83
- {name: key, id: key, data: value.to_a.map{|a| [a[0].to_s, a[1]] }}
84
- end
85
- end
86
-
87
- def usage_stats_hour_series_data(times)
88
- hour_count = times.each_with_object((0..23).map { |n| [n, 0] }.to_h) do |time, memo|
89
- memo[time.hour] += 1
90
- end
91
- hour_count.map do |key, value|
92
- {name: key.to_s, y: value, drilldown: key.to_s}
93
- end
94
- end
95
-
96
- def usage_stats_hour_drilldown_series(times)
97
- wday_count =
98
- (0..23).each_with_object((0..23).map { |n| [n, nil] }.to_h) do |hour, hour_memo|
99
- hour_memo[hour] =
100
- times.select { |t| t.hour == hour }.map { |t| t.wday }.each_with_object((0..6).map { |n| [n, 0] }.to_h) do |wday, wday_memo|
101
- wday_memo[wday] += 1
102
- end
103
- end
104
- wday_count.map do |key, value|
105
- {name: key.to_s, id: key.to_s, data: value.to_a.map{|a| [I18n.t('date.abbr_day_names')[a[0]], a[1]] }}
106
- end
107
- end
108
-
109
- def twitter_addiction_series(times)
110
- five_mins = 5.minutes
111
- wday_expended_seconds =
112
- (0..6).each_with_object((0..6).map { |n| [n, nil] }.to_h) do |wday, wday_memo|
113
- target_times = times.select { |t| t.wday == wday }
114
- wday_memo[wday] = target_times.empty? ? nil : target_times.each_cons(2).map {|a, b| (a - b) < five_mins ? a - b : five_mins }.sum
115
- end
116
- days = times.map{|t| t.to_date.to_s(:long) }.uniq.size
117
- weeks = (days > 7) ? days / 7.0 : 1.0
118
- wday_expended_seconds.map { |k, v| [I18n.t('date.abbr_day_names')[k], (v.nil? ? nil : v / weeks / 60)] }.map do |key, value|
119
- {name: key, y: value}
120
- end
121
- end
122
-
123
- def usage_stats(user, options = {})
124
- n_days_ago = options.has_key?(:days) ? options[:days].days.ago : 100.years.ago
125
- tweets = options.has_key?(:tweets) ? options.delete(:tweets) : user_timeline(user)
126
- times =
127
- # TODO Use user specific time zone
128
- tweets.map { |t| ActiveSupport::TimeZone['Tokyo'].parse(t.created_at.to_s) }.
129
- select { |t| t > n_days_ago }
130
- [
131
- usage_stats_wday_series_data(times),
132
- usage_stats_wday_drilldown_series(times),
133
- usage_stats_hour_series_data(times),
134
- usage_stats_hour_drilldown_series(times),
135
- twitter_addiction_series(times)
136
- ]
137
- end
138
- end
139
- end
@@ -1,127 +0,0 @@
1
- module TwitterWithAutoPagination
2
- module ExistingApi
3
- def verify_credentials(*args)
4
- options = {skip_status: true}.merge(args.extract_options!)
5
- fetch_cache_or_call_api(__method__, args) {
6
- call_old_method("old_#{__method__}", *args, options)
7
- }
8
- end
9
-
10
- def friendship?(*args)
11
- options = args.extract_options!
12
- fetch_cache_or_call_api(__method__, args) {
13
- call_old_method("old_#{__method__}", *args, options)
14
- }
15
- end
16
-
17
- def user?(*args)
18
- options = args.extract_options!
19
- args[0] = verify_credentials(skip_status: true).id if args.empty?
20
- fetch_cache_or_call_api(__method__, args[0], options) {
21
- call_old_method("old_#{__method__}", args[0], options)
22
- }
23
- end
24
-
25
- def user(*args)
26
- options = args.extract_options!
27
- args[0] = verify_credentials(skip_status: true).id if args.empty?
28
- fetch_cache_or_call_api(__method__, args[0], options) {
29
- call_old_method("old_#{__method__}", args[0], options)
30
- }
31
- end
32
-
33
- def friend_ids(*args)
34
- options = {count: 5000, cursor: -1}.merge(args.extract_options!)
35
- args[0] = verify_credentials(skip_status: true).id if args.empty?
36
- fetch_cache_or_call_api(__method__, args[0], options) {
37
- collect_with_cursor("old_#{__method__}", *args, options)
38
- }
39
- end
40
-
41
- def follower_ids(*args)
42
- options = {count: 5000, cursor: -1}.merge(args.extract_options!)
43
- args[0] = verify_credentials(skip_status: true).id if args.empty?
44
- fetch_cache_or_call_api(__method__, args[0], options) {
45
- collect_with_cursor("old_#{__method__}", *args, options)
46
- }
47
- end
48
-
49
- # specify reduce: false to use tweet for inactive_*
50
- def friends(*args)
51
- options = {count: 200, include_user_entities: true, cursor: -1}.merge(args.extract_options!)
52
- options[:reduce] = false unless options.has_key?(:reduce)
53
- args[0] = verify_credentials(skip_status: true).id if args.empty?
54
- fetch_cache_or_call_api(__method__, args[0], options) {
55
- collect_with_cursor("old_#{__method__}", *args, options)
56
- }
57
- end
58
-
59
- # specify reduce: false to use tweet for inactive_*
60
- def followers(*args)
61
- options = {count: 200, include_user_entities: true, cursor: -1}.merge(args.extract_options!)
62
- options[:reduce] = false unless options.has_key?(:reduce)
63
- args[0] = verify_credentials(skip_status: true).id if args.empty?
64
- fetch_cache_or_call_api(__method__, args[0], options) {
65
- collect_with_cursor("old_#{__method__}", *args, options)
66
- }
67
- end
68
-
69
- # use compact, not use sort and uniq
70
- # specify reduce: false to use tweet for inactive_*
71
- # TODO Perhaps `old_users` automatically merges result...
72
- def users(*args)
73
- options = args.extract_options!
74
- options[:reduce] = false
75
- users_per_workers = args.first.compact.each_slice(100).to_a
76
- processed_users = []
77
-
78
- Parallel.each_with_index(users_per_workers, in_threads: [users_per_workers.size, 10].min) do |users_per_worker, i|
79
- _users = fetch_cache_or_call_api(__method__, users_per_worker, options) {
80
- call_old_method("old_#{__method__}", users_per_worker, options)
81
- }
82
-
83
- processed_users << {i: i, users: _users}
84
- end
85
-
86
- processed_users.sort_by{|p| p[:i] }.map{|p| p[:users] }.flatten.compact
87
- end
88
-
89
- def home_timeline(*args)
90
- options = {count: 200, include_rts: true, call_limit: 3}.merge(args.extract_options!)
91
- fetch_cache_or_call_api(__method__, user.id, options) {
92
- collect_with_max_id("old_#{__method__}", options)
93
- }
94
- end
95
-
96
- def user_timeline(*args)
97
- options = {count: 200, include_rts: true, call_limit: 3}.merge(args.extract_options!)
98
- args[0] = verify_credentials(skip_status: true).id if args.empty?
99
- fetch_cache_or_call_api(__method__, args[0], options) {
100
- collect_with_max_id("old_#{__method__}", *args, options)
101
- }
102
- end
103
-
104
- def mentions_timeline(*args)
105
- options = {count: 200, include_rts: true, call_limit: 1}.merge(args.extract_options!)
106
- fetch_cache_or_call_api(__method__, user.id, options) {
107
- collect_with_max_id("old_#{__method__}", options)
108
- }
109
- end
110
-
111
- def favorites(*args)
112
- options = {count: 100, call_count: 1}.merge(args.extract_options!)
113
- args[0] = verify_credentials(skip_status: true).id if args.empty?
114
- fetch_cache_or_call_api(__method__, args[0], options) {
115
- collect_with_max_id("old_#{__method__}", *args, options)
116
- }
117
- end
118
-
119
- def search(*args)
120
- options = {count: 100, result_type: :recent, call_limit: 1}.merge(args.extract_options!)
121
- options[:reduce] = false
122
- fetch_cache_or_call_api(__method__, args[0], options) {
123
- collect_with_max_id("old_#{__method__}", *args, options) { |response| response.attrs[:statuses] }
124
- }
125
- end
126
- end
127
- end