-
Notifications
You must be signed in to change notification settings - Fork 0
/
flickr_friend.rb
173 lines (136 loc) · 4.27 KB
/
flickr_friend.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/usr/bin/env ruby
# This script uses Selenium WebDriver and Firefox to scrape the Flickr friends
# and followers lists of a Flickr user.
# Then it groups the contacts into 3 sets: mutual friends, only followers, and
# only following.
require 'selenium-webdriver'
require 'set'
require 'optparse'
# Find the highest-numbered page by parsing the pages links.
def get_last_page web
web.find_elements(:css, 'div.Pages a[href*="contacts/"]').map { |elem|
elem.attribute('href').match(%r{contacts(?:/rev)?/\?page=(\d+)})[1].to_i
}.max
end
# Parse user IDs and names from the contact list.
def get_names web
web.find_elements(:css, 'td.contact-list-name').map { |elem|
a_elem = elem.find_element(:tag_name, 'a')
id = a_elem.attribute('href').match(%r{photos/(.*)/})[1]
name = a_elem.text.strip
{ id: id, name: name }
}
end
# Get the user name.
def get_user_name web
m = web.current_url.match(%r{people/(.+)/contacts})
raise 'Unable to retrieve user name. Please log in to Flickr before running this script.' unless m
m[1]
end
# Process the contact list, returning a list of users from all the pages in
# the list.
def process_list what, web
puts "Fetching #{what} page 1..."
base_url = 'https://www.flickr.com/people/me/contacts/'
base_url += 'rev/' if what == :follower
web.get base_url
username = get_user_name web
last_page = get_last_page web
names = get_names web
# Only the first page can use people/me. Subsequent pages need the actual
# user name.
base_url = "https://www.flickr.com/people/#{username}/contacts/"
base_url += 'rev/' if what == :follower
(2..last_page).each { |page|
puts "Fetching #{what} page #{page}..."
web.get "#{base_url}?page=#{page}"
names += get_names web
}
names
end
def show_list flist
flist.each_with_index { |name, i|
puts "#{i + 1}: #{name[:id]} - #{name[:name]}"
}
end
# Wait for user to log in to Flickr, if necessary.
def check_login web
url = 'https://www.flickr.com/people/me'
web.get url
m = web.current_url.match(/login\.yahoo\.com/)
return unless m
puts 'Please log in to Flickr'
wait = Selenium::WebDriver::Wait.new(timeout: 900)
wait.until { web.current_url.match(%r{flickr\.com/people}) }
end
# Use Spotlight to find the Firefox binary.
def setup_firefox_path
firefox_app = nil
IO.popen('mdfind "kMDItemFSName = Firefox*.app"') { |io|
firefox_app = io.gets
}
raise "Can't find Firefox app bundle" unless firefox_app
firefox_app.chomp!
Selenium::WebDriver::Firefox::Binary.path = File.join(firefox_app, 'Contents/MacOS/firefox')
end
def parse_opts
options = {}
optp = OptionParser.new
optp.banner = <<-EOM
This script uses Selenium WebDriver and Firefox to scrape the Flickr friends
and followers lists of a Flickr user.
Then it groups the contacts into 3 sets: mutual friends, only followers, and
only following.
Usage: #{File.basename $PROGRAM_NAME} [options]
EOM
optp.on('-h', '-?', '--help', 'Option help') {
puts optp
exit
}
optp.on('-m', '--mutual', 'Show mutual friends') {
options[:mutual_friends] = true
}
optp.on('-r', '--only-friends', 'Show only-friends') {
options[:only_friends] = true
}
optp.on('-o', '--only-followers', 'Show only-followers') {
options[:only_followers] = true
}
optp.separator ' If none of -m/-r/-o are specified, display all 3 categories.'
begin
optp.parse!
rescue OptionParser::ParseError => err
warn "Error parsing command line: #{err}\n\n"
warn optp
exit 1
end
if !options[:mutual_friends] && !options[:only_friends] && !options[:only_followers]
# If none of the 3 options are specified, show everything.
options[:mutual_friends] = options[:only_friends] = options[:only_followers] = true
end
options
end
options = parse_opts
web = nil
begin
setup_firefox_path
web = Selenium::WebDriver.for :firefox
check_login web
following = Set.new(process_list(:following, web))
follower = Set.new(process_list(:follower, web))
if options[:mutual_friends]
puts 'Mutual followers:'
show_list(following & follower)
end
if options[:only_followers]
puts 'Only followers:'
show_list(follower - following)
end
if options[:only_friends]
puts 'Only following:'
show_list(following - follower)
end
ensure
web.close if web
end
__END__