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.
- checksums.yaml +4 -4
- data/README.md +8 -18
- data/lib/twitter_with_auto_pagination.rb +42 -1
- data/lib/twitter_with_auto_pagination/log_subscriber.rb +2 -2
- data/lib/twitter_with_auto_pagination/rest/api.rb +31 -0
- data/lib/twitter_with_auto_pagination/rest/extension/clusters.rb +43 -0
- data/lib/twitter_with_auto_pagination/rest/extension/favoriting.rb +106 -0
- data/lib/twitter_with_auto_pagination/rest/extension/friends_and_followers.rb +131 -0
- data/lib/twitter_with_auto_pagination/rest/extension/replying.rb +90 -0
- data/lib/twitter_with_auto_pagination/rest/extension/unfollowing.rb +29 -0
- data/lib/twitter_with_auto_pagination/rest/favorites.rb +20 -0
- data/lib/twitter_with_auto_pagination/rest/friends_and_followers.rb +94 -0
- data/lib/twitter_with_auto_pagination/rest/search.rb +19 -0
- data/lib/twitter_with_auto_pagination/rest/timelines.rb +37 -0
- data/lib/twitter_with_auto_pagination/rest/uncategorized.rb +83 -0
- data/lib/twitter_with_auto_pagination/rest/users.rb +62 -0
- data/lib/twitter_with_auto_pagination/rest/utils.rb +303 -0
- data/spec/helper.rb +60 -1
- data/spec/twitter_with_auto_pagination/client_spec.rb +150 -0
- data/twitter_with_auto_pagination.gemspec +1 -1
- metadata +17 -8
- data/lib/twitter_with_auto_pagination/client.rb +0 -139
- data/lib/twitter_with_auto_pagination/existing_api.rb +0 -127
- data/lib/twitter_with_auto_pagination/new_api.rb +0 -337
- data/lib/twitter_with_auto_pagination/utils.rb +0 -303
- data/spec/twitter_with_auto_pagination_spec.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c889d340f03d3c1ef475e99fb2ca4860a809312
|
4
|
+
data.tar.gz: 216b8dd6601a1417778a6e6fa70af3b8b5c97493
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53a6aae09286af007d3e5dd857b42263f3173ee4da5ce9b1b8392a16a6da82db6b4f265612d92a3b0b98406d4ef85ad59122eee90aed0284a091a9f335d78509
|
7
|
+
data.tar.gz: 3ad068e546f28b445f83654e7603f053a96e4d3c192aec2365d6ccdafff2ffd87414caa7a72edef75b5b3371bef69589f97771614902871ef257f867ae6a4868
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
twitter-with-auto-pagination
|
2
2
|
============================
|
3
3
|
|
4
|
-
[](https://badge.fury.io/rb/twitter_with_auto_pagination)
|
5
|
+
[](https://travis-ci.org/ts-3156/twitter-with-auto-pagination)
|
6
6
|
|
7
7
|
Add auto pagination, auto caching and parallelly fetching features to Twitter gem.
|
8
8
|
|
@@ -26,16 +26,14 @@ Add `twitter_with_auto_pagination` to your Gemfile, and bundle.
|
|
26
26
|
|
27
27
|
## Configuration
|
28
28
|
|
29
|
-
You can pass configuration options as a block to `
|
29
|
+
You can pass configuration options as a block to `Twitter::REST::Client.new` just like Twitter gem.
|
30
30
|
|
31
31
|
```
|
32
|
-
client =
|
32
|
+
client = Twitter::REST::Client.new do |config|
|
33
33
|
config.consumer_key = "YOUR_CONSUMER_KEY"
|
34
34
|
config.consumer_secret = "YOUR_CONSUMER_SECRET"
|
35
35
|
config.access_token = "YOUR_ACCESS_TOKEN"
|
36
36
|
config.access_token_secret = "YOUR_ACCESS_SECRET"
|
37
|
-
config.log_level = :debug # optional
|
38
|
-
config.logger = Logger.new(STDOUT) # optional
|
39
37
|
end
|
40
38
|
```
|
41
39
|
|
@@ -135,25 +133,17 @@ client.close_friends
|
|
135
133
|
```
|
136
134
|
|
137
135
|
```
|
138
|
-
client.
|
136
|
+
client.removing(past_me, cur_me)
|
139
137
|
```
|
140
138
|
|
141
139
|
```
|
142
|
-
client.
|
140
|
+
client.removed(past_me, cur_me)
|
143
141
|
```
|
144
142
|
|
145
143
|
```
|
146
|
-
client.
|
144
|
+
client.replying
|
147
145
|
```
|
148
146
|
|
149
147
|
```
|
150
|
-
client.
|
151
|
-
```
|
152
|
-
|
153
|
-
```
|
154
|
-
client.users_which_you_faved
|
155
|
-
```
|
156
|
-
|
157
|
-
```
|
158
|
-
client.users_who_faved_you
|
148
|
+
client.replied
|
159
149
|
```
|
@@ -1,4 +1,45 @@
|
|
1
|
-
require '
|
1
|
+
require 'twitter'
|
2
|
+
|
3
|
+
require 'twitter_with_auto_pagination/log_subscriber'
|
2
4
|
|
3
5
|
module TwitterWithAutoPagination
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'twitter_with_auto_pagination/rest/api'
|
9
|
+
|
10
|
+
module Twitter
|
11
|
+
module REST
|
12
|
+
class Client
|
13
|
+
prepend TwitterWithAutoPagination::REST::API
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@cache = ActiveSupport::Cache::FileStore.new(File.join('tmp', 'api_cache'))
|
17
|
+
@call_count = 0
|
18
|
+
|
19
|
+
@uid = options.has_key?(:uid) ? options.delete(:uid).to_i : nil
|
20
|
+
@screen_name = options.has_key?(:screen_name) ? options.delete(:screen_name).to_s : nil
|
21
|
+
|
22
|
+
logger =
|
23
|
+
if options.has_key?(:logger)
|
24
|
+
options.delete(:logger)
|
25
|
+
else
|
26
|
+
Dir.mkdir('log') unless File.exists?('log')
|
27
|
+
Logger.new('log/twitter_with_auto_pagination.log')
|
28
|
+
end
|
29
|
+
logger.level = options.has_key?(:log_level) ? options.delete(:log_level) : :debug
|
30
|
+
@@logger = @logger = logger
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.logger
|
36
|
+
@@logger
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_accessor :call_count, :logger
|
40
|
+
attr_reader :cache
|
41
|
+
|
42
|
+
INDENT = 4
|
43
|
+
end
|
44
|
+
end
|
4
45
|
end
|
@@ -38,7 +38,7 @@ module TwitterWithAutoPagination
|
|
38
38
|
# sql = color(sql, sql_color(sql), true)
|
39
39
|
|
40
40
|
key = payload.delete(:key)
|
41
|
-
debug { "#{name} #{key} #{(payload.inspect)}" }
|
41
|
+
debug { "#{name}#{key.nil? ? '' : " #{key}"} #{(payload.inspect)}" }
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -73,7 +73,7 @@ module TwitterWithAutoPagination
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def logger
|
76
|
-
|
76
|
+
Twitter::REST::Client.logger
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'twitter_with_auto_pagination/rest/favorites'
|
2
|
+
require 'twitter_with_auto_pagination/rest/friends_and_followers'
|
3
|
+
require 'twitter_with_auto_pagination/rest/search'
|
4
|
+
require 'twitter_with_auto_pagination/rest/timelines'
|
5
|
+
require 'twitter_with_auto_pagination/rest/users'
|
6
|
+
require 'twitter_with_auto_pagination/rest/uncategorized'
|
7
|
+
|
8
|
+
require 'twitter_with_auto_pagination/rest/extension/clusters'
|
9
|
+
require 'twitter_with_auto_pagination/rest/extension/favoriting'
|
10
|
+
require 'twitter_with_auto_pagination/rest/extension/friends_and_followers'
|
11
|
+
require 'twitter_with_auto_pagination/rest/extension/replying'
|
12
|
+
require 'twitter_with_auto_pagination/rest/extension/unfollowing'
|
13
|
+
|
14
|
+
module TwitterWithAutoPagination
|
15
|
+
module REST
|
16
|
+
module API
|
17
|
+
include TwitterWithAutoPagination::REST::Favorites
|
18
|
+
include TwitterWithAutoPagination::REST::FriendsAndFollowers
|
19
|
+
include TwitterWithAutoPagination::REST::Search
|
20
|
+
include TwitterWithAutoPagination::REST::Timelines
|
21
|
+
include TwitterWithAutoPagination::REST::Users
|
22
|
+
include TwitterWithAutoPagination::REST::Uncategorized
|
23
|
+
|
24
|
+
include TwitterWithAutoPagination::REST::Extension::Clusters
|
25
|
+
include TwitterWithAutoPagination::REST::Extension::Favoriting
|
26
|
+
include TwitterWithAutoPagination::REST::Extension::FriendsAndFollowers
|
27
|
+
include TwitterWithAutoPagination::REST::Extension::Replying
|
28
|
+
include TwitterWithAutoPagination::REST::Extension::Unfollowing
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'twitter_with_auto_pagination/rest/utils'
|
2
|
+
|
3
|
+
module TwitterWithAutoPagination
|
4
|
+
module REST
|
5
|
+
module Extension
|
6
|
+
module Clusters
|
7
|
+
include TwitterWithAutoPagination::REST::Utils
|
8
|
+
|
9
|
+
def clusters_belong_to(text)
|
10
|
+
return [] if text.blank?
|
11
|
+
|
12
|
+
exclude_words = JSON.parse(File.read(Rails.configuration.x.constants['cluster_bad_words_path']))
|
13
|
+
special_words = JSON.parse(File.read(Rails.configuration.x.constants['cluster_good_words_path']))
|
14
|
+
|
15
|
+
# クラスタ用の単語の出現回数を記録
|
16
|
+
cluster_word_counter =
|
17
|
+
special_words.map { |sw| [sw, text.scan(sw)] }
|
18
|
+
.delete_if { |item| item[1].empty? }
|
19
|
+
.each_with_object(Hash.new(1)) { |item, memo| memo[item[0]] = item[1].size }
|
20
|
+
|
21
|
+
# 同一文字種の繰り返しを見付ける。漢字の繰り返し、ひらがなの繰り返し、カタカナの繰り返し、など
|
22
|
+
text.scan(/[一-龠〆ヵヶ々]+|[ぁ-んー~]+|[ァ-ヴー~]+|[a-zA-Z0-9]+|[、。!!??]+/).
|
23
|
+
|
24
|
+
# 複数回繰り返される文字を除去
|
25
|
+
map { |w| w.remove /[?!?!。、w]|(ー{2,})/ }.
|
26
|
+
|
27
|
+
# 文字数の少なすぎる単語、ひらがなだけの単語、除外単語を除去する
|
28
|
+
delete_if { |w| w.length <= 1 || (w.length <= 2 && w =~ /^[ぁ-んー~]+$/) || exclude_words.include?(w) }.
|
29
|
+
|
30
|
+
# 出現回数を記録
|
31
|
+
each { |w| cluster_word_counter[w] += 1 }
|
32
|
+
|
33
|
+
# 複数個以上見付かった単語のみを残し、出現頻度順にソート
|
34
|
+
cluster_word_counter.select { |_, v| v > 3 }.sort_by { |_, v| -v }.to_h
|
35
|
+
end
|
36
|
+
|
37
|
+
def clusters_assigned_to
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'twitter_with_auto_pagination/rest/utils'
|
2
|
+
|
3
|
+
module TwitterWithAutoPagination
|
4
|
+
module REST
|
5
|
+
module Extension
|
6
|
+
module Favoriting
|
7
|
+
include TwitterWithAutoPagination::REST::Utils
|
8
|
+
|
9
|
+
def _count_users_with_two_sided_threshold(users, options)
|
10
|
+
min = options.has_key?(:min) ? options[:min] : 0
|
11
|
+
max = options.has_key?(:max) ? options[:max] : 1000
|
12
|
+
users.each_with_object(Hash.new(0)) { |u, memo| memo[u.id] += 1 }.
|
13
|
+
select { |_k, v| min <= v && v <= max }.
|
14
|
+
sort_by { |_, v| -v }.to_h
|
15
|
+
end
|
16
|
+
|
17
|
+
def _extract_favorite_users(favs, options = {})
|
18
|
+
counted_value = _count_users_with_two_sided_threshold(favs.map { |t| t.user }, options)
|
19
|
+
counted_value.map do |uid, cnt|
|
20
|
+
fav = favs.find { |f| f.user.id.to_i == uid.to_i }
|
21
|
+
Array.new(cnt, fav.user)
|
22
|
+
end.flatten
|
23
|
+
end
|
24
|
+
|
25
|
+
def _retrieve_favs(*args)
|
26
|
+
options = args.extract_options!
|
27
|
+
if args.empty?
|
28
|
+
favorites(options)
|
29
|
+
elsif uid_or_screen_name?(args[0])
|
30
|
+
favorites(args[0], options)
|
31
|
+
elsif args[0].kind_of?(Array) && args[0].all? { |t| t.respond_to?(:text) }
|
32
|
+
args[0]
|
33
|
+
else
|
34
|
+
raise ArgumentError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def users_which_you_faved(*args)
|
39
|
+
options = args.extract_options!
|
40
|
+
instrument(__method__, nil, options) do
|
41
|
+
favs = _retrieve_favs(*args, options)
|
42
|
+
result = _extract_favorite_users(favs, options)
|
43
|
+
if options.has_key?(:uniq) && !options[:uniq]
|
44
|
+
result
|
45
|
+
else
|
46
|
+
result.uniq { |r| r.id }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
logger.warn "#{__method__} #{user.inspect} #{e.class} #{e.message}"
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
|
54
|
+
alias favoriting users_which_you_faved
|
55
|
+
|
56
|
+
def users_who_faved_you(*args)
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
alias favorited users_who_faved_you
|
61
|
+
|
62
|
+
def _retrieve_replying_replied_and_favoriting(*args)
|
63
|
+
names = %i(replying replied favoriting)
|
64
|
+
options = args.extract_options!
|
65
|
+
if args.empty?
|
66
|
+
_fetch_parallelly(names.map { |n| {method: n, args: [options]} })
|
67
|
+
elsif uid_or_screen_name?(args[0])
|
68
|
+
_fetch_parallelly(names.map { |n| {method: n, args: [args[0], options]} })
|
69
|
+
elsif names.all? { |n| args[0].respond_to?(n) }
|
70
|
+
names.map { |n| args[0].send(n) }
|
71
|
+
else
|
72
|
+
raise ArgumentError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def close_friends(*args)
|
77
|
+
options = {uniq: false}.merge(args.extract_options!)
|
78
|
+
min_max = {
|
79
|
+
min: options.has_key?(:min) ? options.delete(:min) : 0,
|
80
|
+
max: options.has_key?(:max) ? options.delete(:max) : 1000
|
81
|
+
}
|
82
|
+
|
83
|
+
instrument(__method__, nil, options) do
|
84
|
+
replying, replied, favoriting = _retrieve_replying_replied_and_favoriting(*args, options)
|
85
|
+
|
86
|
+
users = replying + replied + favoriting
|
87
|
+
return [] if users.empty?
|
88
|
+
|
89
|
+
score = _count_users_with_two_sided_threshold(users, min_max)
|
90
|
+
replying_score = _count_users_with_two_sided_threshold(replying, min_max)
|
91
|
+
replied_score = _count_users_with_two_sided_threshold(replied, min_max)
|
92
|
+
favoriting_score = _count_users_with_two_sided_threshold(favoriting, min_max)
|
93
|
+
|
94
|
+
score.keys.map { |uid| users.find { |u| u.id.to_i == uid.to_i } }.map do |u|
|
95
|
+
u[:score] = score[u.id]
|
96
|
+
u[:replying_score] = replying_score[u.id]
|
97
|
+
u[:replied_score] = replied_score[u.id]
|
98
|
+
u[:favoriting_score] = favoriting_score[u.id]
|
99
|
+
u
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'twitter_with_auto_pagination/rest/utils'
|
2
|
+
require 'parallel'
|
3
|
+
|
4
|
+
module TwitterWithAutoPagination
|
5
|
+
module REST
|
6
|
+
module Extension
|
7
|
+
module FriendsAndFollowers
|
8
|
+
include TwitterWithAutoPagination::REST::Utils
|
9
|
+
|
10
|
+
def _fetch_parallelly(signatures) # [{method: :friends, args: ['ts_3156', ...], {...}]
|
11
|
+
result = Array.new(signatures.size)
|
12
|
+
|
13
|
+
Parallel.each_with_index(signatures, in_threads: result.size) do |signature, i|
|
14
|
+
result[i] = send(signature[:method], *signature[:args])
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
def friends_and_followers(*args)
|
21
|
+
_fetch_parallelly(
|
22
|
+
[
|
23
|
+
{method: :friends, args: args},
|
24
|
+
{method: :followers, args: args}])
|
25
|
+
end
|
26
|
+
|
27
|
+
def friends_followers_and_statuses(*args)
|
28
|
+
_fetch_parallelly(
|
29
|
+
[
|
30
|
+
{method: :friends, args: args},
|
31
|
+
{method: :followers, args: args},
|
32
|
+
{method: :user_timeline, args: args}])
|
33
|
+
end
|
34
|
+
|
35
|
+
def _retrieve_friends_and_followers(*args)
|
36
|
+
obj = args[0]
|
37
|
+
if obj.nil?
|
38
|
+
friends_and_followers
|
39
|
+
elsif uid_or_screen_name?(obj)
|
40
|
+
friends_and_followers(obj)
|
41
|
+
elsif obj.respond_to?(:friends) && obj.respond_to?(:followers)
|
42
|
+
[obj.friends, obj.followers]
|
43
|
+
else
|
44
|
+
raise ArgumentError, args.inspect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def one_sided_friends(me = nil)
|
49
|
+
instrument(__method__, nil) do
|
50
|
+
_friends, _followers = _retrieve_friends_and_followers(me)
|
51
|
+
_friends.to_a - _followers.to_a
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def one_sided_followers(me = nil)
|
56
|
+
instrument(__method__, nil) do
|
57
|
+
_friends, _followers = _retrieve_friends_and_followers(me)
|
58
|
+
_followers.to_a - _friends.to_a
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def mutual_friends(me = nil)
|
63
|
+
instrument(__method__, nil) do
|
64
|
+
_friends, _followers = _retrieve_friends_and_followers(me)
|
65
|
+
_friends.to_a & _followers.to_a
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def _retrieve_friends(*args)
|
70
|
+
if args.size == 1
|
71
|
+
args[0].nil? ? friends : friends(args[0])
|
72
|
+
elsif args.all? { |obj| uid_or_screen_name?(obj) }
|
73
|
+
_fetch_parallelly(args.map { |obj| {method: :friends, args: [obj]} })
|
74
|
+
elsif args.all? { |obj| obj.respond_to?(:friends) }
|
75
|
+
args.map { |obj| obj.friends }
|
76
|
+
else
|
77
|
+
raise ArgumentError, args.inspect
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def common_friends(me, you)
|
82
|
+
instrument(__method__, nil) do
|
83
|
+
my_friends, your_friends = _retrieve_friends(me, you)
|
84
|
+
my_friends.to_a & your_friends.to_a
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def _retrieve_followers(*args)
|
89
|
+
if args.size == 1
|
90
|
+
args[0].nil? ? followers : followers(args[0])
|
91
|
+
elsif args.all? { |obj| uid_or_screen_name?(obj) }
|
92
|
+
_fetch_parallelly(args.map { |obj| {method: :followers, args: [obj]} })
|
93
|
+
elsif args.all? { |obj| obj.respond_to?(:followers) }
|
94
|
+
args.map { |obj| obj.followers }
|
95
|
+
else
|
96
|
+
raise ArgumentError, args.inspect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def common_followers(me, you)
|
101
|
+
instrument(__method__, nil) do
|
102
|
+
my_followers, your_followers = _retrieve_followers(me, you)
|
103
|
+
my_followers.to_a & your_followers.to_a
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _extract_inactive_users(users)
|
108
|
+
two_weeks_ago = 2.weeks.ago.to_i
|
109
|
+
users.select do |u|
|
110
|
+
(Time.parse(u.status.created_at).to_i < two_weeks_ago) rescue false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def inactive_friends(user = nil)
|
115
|
+
instrument(__method__, nil) do
|
116
|
+
_friends = _retrieve_friends(user)
|
117
|
+
_extract_inactive_users(_friends)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def inactive_followers(user = nil)
|
122
|
+
instrument(__method__, nil) do
|
123
|
+
_followers = _retrieve_followers(user)
|
124
|
+
_extract_inactive_users(_followers)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|