trumpcare_tracker 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +7 -109
- data/lib/trumpcare_tracker/rake_task.rb +62 -0
- data/lib/trumpcare_tracker/reporters.rb +81 -0
- data/lib/trumpcare_tracker/tweet_bot.rb +64 -0
- data/lib/trumpcare_tracker/version.rb +1 -1
- data/lib/trumpcare_tracker.rb +3 -4
- metadata +4 -2
- data/trumpcare_homepage_report.csv +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa4f9aeac84d31d7ccc1b7ccec012883492782ee
|
4
|
+
data.tar.gz: ceafe378f45ea94ff7dd6fb637dc7159e7ee03ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac743a03cbb34d688c811c48b49afc9fc4dfa2c6325af6f92eb36a40a22d0ac3d9feec38b7b86b16d38dd3b345ea596870df4d291a5d2939b2cbaa5908291ca0
|
7
|
+
data.tar.gz: e5f7b6667e58b6b56f08d7c0b4ef28d16880cabe30395397881ce5f57c40069e445b308f35426a923ca30815ef3df4c8e9fb28c1b772e17d784f08ca7abf68fa
|
data/Rakefile
CHANGED
@@ -1,120 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
|
-
require '
|
5
|
-
require 'nokogiri'
|
6
|
-
require 'open-uri'
|
7
|
-
require 'pyr'
|
8
|
-
require 'trumpcare_tracker'
|
4
|
+
require 'date'
|
9
5
|
|
10
6
|
require 'rubocop/rake_task'
|
11
7
|
RuboCop::RakeTask.new
|
12
8
|
|
13
9
|
task default: %i[spec rubocop]
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
senator
|
20
|
-
official_user_name
|
21
|
-
alt_user_name
|
22
|
-
tweets_in_last_seven_days
|
23
|
-
trumpcare_tweets
|
24
|
-
tct_percentage
|
25
|
-
russia_tweets
|
26
|
-
rt_percentage
|
27
|
-
tct_to_rt_ratio
|
28
|
-
trumpcare_tweet_urls
|
29
|
-
russia_tweet_urls
|
30
|
-
]
|
31
|
-
audit_tweets(reps) { |tracker| csv << tracker.to_h.values }
|
32
|
-
end
|
33
|
-
|
34
|
-
File.open('trumpcare_tweet_report.csv', 'w') do |file|
|
35
|
-
file.write csv_tweet_report
|
36
|
-
end
|
11
|
+
require 'trumpcare_tracker/tweet_bot'
|
12
|
+
TrumpcareTracker::TweetBot.new('trumpcaretrackr') do
|
13
|
+
"#{Date.today.strftime('%A, %B %d %Y')} - "\
|
14
|
+
"here's how Senate Democrats have tweeted about TrumpCare the past week. (thread)"
|
37
15
|
end
|
38
16
|
|
39
|
-
|
40
|
-
|
41
|
-
csv_homepage_report = CSV.generate do |csv|
|
42
|
-
csv << %w[senator url trumpcare_mentions russia_mentions trumpcare_to_russia_ratio]
|
43
|
-
reps.each do |rep|
|
44
|
-
doc = Nokogiri::HTML(open(rep.url))
|
45
|
-
trumpcare_mentions_count = mentions_mapper(doc, TrumpcareTracker.trumpcare_keyword_regex).count
|
46
|
-
russia_mentions_count = mentions_mapper(doc, TrumpcareTracker.russia_keyword_regex).count
|
47
|
-
csv << [
|
48
|
-
rep.official_full,
|
49
|
-
rep.url,
|
50
|
-
trumpcare_mentions_count,
|
51
|
-
russia_mentions_count,
|
52
|
-
TrumpcareTracker.ratio(trumpcare_mentions_count, russia_mentions_count)
|
53
|
-
]
|
54
|
-
puts "Scraped #{rep.official_full}'s homepage"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
File.open('trumpcare_homepage_report.csv', 'w') do |file|
|
59
|
-
file.write csv_homepage_report
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def mentions_mapper(doc, regex)
|
64
|
-
doc.text.split("\n").select { |string| string.downcase.match?(regex) }
|
65
|
-
end
|
66
|
-
|
67
|
-
desc 'Audit and tweet results'
|
68
|
-
task :tweet do
|
69
|
-
audit_tweets(reps) do |tracker, rep|
|
70
|
-
tracker.to_tweet
|
71
|
-
tracker.client.update(
|
72
|
-
"#{rep.official_full}\n"\
|
73
|
-
"#{rep.office_locations.map { |off| "#{off.city} - #{off.phone}" }.join("\n")}"
|
74
|
-
)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def reps
|
79
|
-
democrats = PYR.reps do |r|
|
80
|
-
r.democrat = true
|
81
|
-
r.chamber = 'upper'
|
82
|
-
end
|
83
|
-
|
84
|
-
independents = PYR.reps do |r|
|
85
|
-
r.independent = true
|
86
|
-
r.chamber = 'upper'
|
87
|
-
end
|
88
|
-
|
89
|
-
democrats.objects.to_a + independents.objects.to_a
|
90
|
-
end
|
91
|
-
|
92
|
-
def handles
|
93
|
-
@_handles ||= CSV.read('twitter_handles.csv', headers: true)
|
94
|
-
end
|
95
|
-
|
96
|
-
def audit_rep(i, rep, &block)
|
97
|
-
puts "Sending requests to Twitter API for #{rep.official_full}"
|
98
|
-
alt_screen_name = handles.detect do |handle|
|
99
|
-
handle['official'].downcase.strip == rep.twitter.downcase.strip
|
100
|
-
end['personal/campaign']
|
101
|
-
tracker = TrumpcareTracker.new(rep.official_full, rep.twitter, alt_screen_name)
|
102
|
-
tracker.audit
|
103
|
-
block.call(tracker, rep) if block_given?
|
104
|
-
puts "#{i + 1} down. Pausing for 45 seconds to avoid hitting API rate limit."
|
105
|
-
sleep(45)
|
106
|
-
rescue Twitter::Error::TooManyRequests
|
107
|
-
puts 'Rate limit exceeded, waiting 2 minutes'
|
108
|
-
sleep(300)
|
109
|
-
audit_rep(i, rep, &block)
|
110
|
-
rescue Twitter::Error => e
|
111
|
-
puts e.message
|
112
|
-
audit_rep(i, rep, &block)
|
113
|
-
end
|
114
|
-
|
115
|
-
def audit_tweets(reps, &block)
|
116
|
-
reps.each_with_index do |rep, i|
|
117
|
-
next if rep.twitter.nil?
|
118
|
-
audit_rep(i, rep, &block)
|
119
|
-
end
|
120
|
-
end
|
17
|
+
require 'trumpcare_tracker/reporters'
|
18
|
+
TrumpcareTracker::Reporters.new
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'pyr'
|
5
|
+
require 'trumpcare_tracker'
|
6
|
+
|
7
|
+
class TrumpcareTracker
|
8
|
+
# Base rake task class
|
9
|
+
class RakeTask < Rake::TaskLib
|
10
|
+
module Methods
|
11
|
+
def reps
|
12
|
+
democrats = PYR.reps do |r|
|
13
|
+
r.democrat = true
|
14
|
+
r.chamber = 'upper'
|
15
|
+
end
|
16
|
+
|
17
|
+
independents = PYR.reps do |r|
|
18
|
+
r.independent = true
|
19
|
+
r.chamber = 'upper'
|
20
|
+
end
|
21
|
+
|
22
|
+
democrats.objects.to_a + independents.objects.to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def handles
|
26
|
+
@_handles ||= CSV.read('twitter_handles.csv', headers: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def audit_rep(i, rep, &block)
|
30
|
+
puts "Sending requests to Twitter API for #{rep.official_full}"
|
31
|
+
alt_screen_name = handles.detect do |handle|
|
32
|
+
handle['official'].downcase.strip == rep.twitter.downcase.strip
|
33
|
+
end['personal/campaign']
|
34
|
+
tracker = TrumpcareTracker.new(rep.official_full, rep.twitter, alt_screen_name)
|
35
|
+
tracker.audit
|
36
|
+
block.call(tracker, rep) if block_given?
|
37
|
+
puts "#{i + 1} down. Pausing for 45 seconds to avoid hitting API rate limit."
|
38
|
+
sleep(45)
|
39
|
+
rescue Twitter::Error::TooManyRequests
|
40
|
+
puts 'Rate limit exceeded, waiting 5 minutes'
|
41
|
+
sleep(300)
|
42
|
+
audit_rep(i, rep, &block)
|
43
|
+
rescue Twitter::Error => e
|
44
|
+
puts e.message
|
45
|
+
audit_rep(i, rep, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def audit_tweets(reps, &block)
|
49
|
+
reps.each_with_index do |rep, i|
|
50
|
+
next if rep.twitter.nil?
|
51
|
+
audit_rep(i, rep, &block)
|
52
|
+
end
|
53
|
+
ensure
|
54
|
+
`say "beep beep beep beep beep"`
|
55
|
+
end
|
56
|
+
|
57
|
+
def mentions_mapper(doc, regex)
|
58
|
+
doc.text.split("\n").select { |string| string.downcase.match?(regex) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'trumpcare_tracker/rake_task'
|
6
|
+
|
7
|
+
class TrumpcareTracker
|
8
|
+
# Rake tasks to generate audit reports in CSV format
|
9
|
+
#
|
10
|
+
# require 'trumpcare_tracker/reporters'
|
11
|
+
# TrumpcareTracker::Reporters.new
|
12
|
+
#
|
13
|
+
# $ bundle exec rake tracker:export
|
14
|
+
# $ bundle exec rake tracker:homepage_scraper
|
15
|
+
class Reporters < RakeTask
|
16
|
+
include RakeTask::Methods
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
export_task
|
20
|
+
homepage_scraper_task
|
21
|
+
end
|
22
|
+
|
23
|
+
def export_task
|
24
|
+
namespace :tracker do
|
25
|
+
desc 'Output a report of Senate Democrats Trumpcare tweets'
|
26
|
+
task :export do
|
27
|
+
csv_tweet_report = CSV.generate do |csv|
|
28
|
+
csv << %w[
|
29
|
+
senator
|
30
|
+
official_user_name
|
31
|
+
alt_user_name
|
32
|
+
tweets_in_last_seven_days
|
33
|
+
trumpcare_tweets
|
34
|
+
tct_percentage
|
35
|
+
russia_tweets
|
36
|
+
rt_percentage
|
37
|
+
tct_to_rt_ratio
|
38
|
+
trumpcare_tweet_urls
|
39
|
+
russia_tweet_urls
|
40
|
+
]
|
41
|
+
audit_tweets(reps) { |tracker| csv << tracker.to_h.values }
|
42
|
+
end
|
43
|
+
|
44
|
+
File.open('trumpcare_tweet_report.csv', 'w') do |file|
|
45
|
+
file.write csv_tweet_report
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def homepage_scraper_task
|
54
|
+
namespace :tracker do
|
55
|
+
desc 'Search Senators\' official homepage for TrumpCare and Russia Mentions'
|
56
|
+
task :homepage_scraper do
|
57
|
+
csv_homepage_report = CSV.generate do |csv|
|
58
|
+
csv << %w[senator url trumpcare_mentions russia_mentions trumpcare_to_russia_ratio]
|
59
|
+
reps.each do |rep|
|
60
|
+
doc = Nokogiri::HTML(open(rep.url))
|
61
|
+
trumpcare_mentions_count = mentions_mapper(doc, TrumpcareTracker.trumpcare_keyword_regex).count
|
62
|
+
russia_mentions_count = mentions_mapper(doc, TrumpcareTracker.russia_keyword_regex).count
|
63
|
+
csv << [
|
64
|
+
rep.official_full,
|
65
|
+
rep.url,
|
66
|
+
trumpcare_mentions_count,
|
67
|
+
russia_mentions_count,
|
68
|
+
TrumpcareTracker.ratio(trumpcare_mentions_count, russia_mentions_count)
|
69
|
+
]
|
70
|
+
puts "Scraped #{rep.official_full}'s homepage"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
File.open('trumpcare_homepage_report.csv', 'w') do |file|
|
75
|
+
file.write csv_homepage_report
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
require 'trumpcare_tracker/rake_task'
|
6
|
+
|
7
|
+
class TrumpcareTracker
|
8
|
+
# Provide a tweet bot that can be run as a rake task
|
9
|
+
#
|
10
|
+
# require 'trumpcare_tracker/tweet_bot'
|
11
|
+
# TrumpcareTracker::TweetBot.new('screen_user_name') do
|
12
|
+
# "Text for optional intro tweet to thread"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# $ bundle exec rake tracker:tweet_bot
|
16
|
+
class TweetBot < RakeTask
|
17
|
+
include RakeTask::Methods
|
18
|
+
|
19
|
+
def initialize(screen_name, &first_tweet_block)
|
20
|
+
namespace(:tracker) do
|
21
|
+
desc 'Audit Trumpcare tweet activity and post a thread of updates'
|
22
|
+
|
23
|
+
task(:tweet_bot) do
|
24
|
+
tracker = TrumpcareTracker.new('TrumpCareTracker', screen_name)
|
25
|
+
tweet = Twitter::Tweet.new(id: nil)
|
26
|
+
first_tweet = tracker.client.update(first_tweet_block.call) if block_given?
|
27
|
+
reps.each_with_index do |rep, i|
|
28
|
+
next if rep.twitter.nil?
|
29
|
+
reply_to_tweet = if i.zero?
|
30
|
+
first_tweet
|
31
|
+
else
|
32
|
+
tweet
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
tweet = post(rep, i, reply_to_tweet)
|
37
|
+
rescue Twitter::Error => e
|
38
|
+
puts e.message
|
39
|
+
puts 'Waiting 5 minutes to see if the issue can be resolved.'
|
40
|
+
sleep(300)
|
41
|
+
tweet = post(rep, i, reply_to_tweet)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(rep, index, reply_to_tweet)
|
49
|
+
puts "Sending requests to Twitter API for #{rep.official_full}"
|
50
|
+
tracker = TrumpcareTracker.new(rep.official_full, rep.twitter)
|
51
|
+
tweet = tracker.to_tweet(in_reply_to_status: reply_to_tweet)
|
52
|
+
sleep(rand(1..5))
|
53
|
+
contacts = "#{rep.official_full}\n"\
|
54
|
+
"#{rep.office_locations.map { |off| "#{off.city} - #{off.phone}" }.join("\n")}"
|
55
|
+
while contacts.length > 140
|
56
|
+
contacts = contacts.split("\n")[0..-2].join("\n")
|
57
|
+
end
|
58
|
+
tweet = tracker.client.update(contacts, in_reply_to_status: tweet)
|
59
|
+
puts "#{index + 1} down. Pausing for some time to avoid hitting API rate limit."
|
60
|
+
sleep(rand(30..60))
|
61
|
+
tweet
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/trumpcare_tracker.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'pyr'
|
4
3
|
require 'trumpcare_tracker/version'
|
5
4
|
require 'twitter'
|
6
5
|
|
@@ -23,7 +22,7 @@ class TrumpcareTracker
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def self.trumpcare_keyword_regex
|
26
|
-
/(ahca|trumpcare|healthcare|health|care|drug|medication|prescription|vaccine|obamacare)/
|
25
|
+
/(ahca|trumpcare|healthcare|health|care|drug|medication|prescription|vaccine|obamacare|cbo|premiums|insurance|deductibles)/
|
27
26
|
end
|
28
27
|
|
29
28
|
def self.russia_keyword_regex
|
@@ -141,7 +140,7 @@ class TrumpcareTracker
|
|
141
140
|
alt_screen_name.screen_name
|
142
141
|
end
|
143
142
|
|
144
|
-
def to_tweet
|
145
|
-
client.update(".#{self}")
|
143
|
+
def to_tweet(options = {})
|
144
|
+
client.update(".#{self}", options)
|
146
145
|
end
|
147
146
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trumpcare_tracker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- M. Simon Borg
|
@@ -99,8 +99,10 @@ files:
|
|
99
99
|
- bin/console
|
100
100
|
- bin/setup
|
101
101
|
- lib/trumpcare_tracker.rb
|
102
|
+
- lib/trumpcare_tracker/rake_task.rb
|
103
|
+
- lib/trumpcare_tracker/reporters.rb
|
104
|
+
- lib/trumpcare_tracker/tweet_bot.rb
|
102
105
|
- lib/trumpcare_tracker/version.rb
|
103
|
-
- trumpcare_homepage_report.csv
|
104
106
|
- trumpcare_tracker.gemspec
|
105
107
|
- trumpcare_tweet_report.csv
|
106
108
|
- twitter_handles.csv
|
@@ -1,49 +0,0 @@
|
|
1
|
-
senator,url,trumpcare_mentions,russia_mentions,trumpcare_to_russia_ratio
|
2
|
-
Sherrod Brown,https://www.brown.senate.gov,4,1,4.0
|
3
|
-
Tammy Baldwin,https://www.baldwin.senate.gov,1,0,0.0
|
4
|
-
Michael F. Bennet,https://www.bennet.senate.gov,1,5,0.2
|
5
|
-
Richard Blumenthal,https://www.blumenthal.senate.gov,1,0,0.0
|
6
|
-
Cory A. Booker,https://www.booker.senate.gov,1,2,0.5
|
7
|
-
Maria Cantwell,https://www.cantwell.senate.gov,2,0,0.0
|
8
|
-
Benjamin L. Cardin,https://www.cardin.senate.gov,0,1,0.0
|
9
|
-
Thomas R. Carper,https://www.carper.senate.gov/public,5,0,0.0
|
10
|
-
"Robert P. Casey, Jr.",https://www.casey.senate.gov,1,2,0.5
|
11
|
-
Christopher A. Coons,https://www.coons.senate.gov,2,1,2.0
|
12
|
-
Catherine Cortez Masto,https://www.cortezmasto.senate.gov,0,0,0.0
|
13
|
-
Richard J. Durbin,https://www.durbin.senate.gov,1,1,1.0
|
14
|
-
Joe Donnelly,https://www.donnelly.senate.gov,2,1,2.0
|
15
|
-
Tammy Duckworth,https://www.duckworth.senate.gov,0,1,0.0
|
16
|
-
Dianne Feinstein,https://www.feinstein.senate.gov,0,0,0.0
|
17
|
-
Al Franken,https://www.franken.senate.gov,8,2,4.0
|
18
|
-
Kirsten E. Gillibrand,https://www.gillibrand.senate.gov,0,0,0.0
|
19
|
-
Mazie K. Hirono,https://www.hirono.senate.gov,0,0,0.0
|
20
|
-
Martin Heinrich,https://www.heinrich.senate.gov,4,3,1.3333
|
21
|
-
Heidi Heitkamp,https://www.heitkamp.senate.gov/public,2,1,2.0
|
22
|
-
Kamala D. Harris,https://www.harris.senate.gov,0,2,0.0
|
23
|
-
Margaret Wood Hassan,https://www.hassan.senate.gov,0,1,0.0
|
24
|
-
Amy Klobuchar,https://www.klobuchar.senate.gov,0,0,0.0
|
25
|
-
Tim Kaine,https://www.kaine.senate.gov,4,0,0.0
|
26
|
-
Patrick J. Leahy,https://www.leahy.senate.gov,1,4,0.25
|
27
|
-
Edward J. Markey,https://www.markey.senate.gov,7,0,0.0
|
28
|
-
Robert Menendez,https://www.menendez.senate.gov,1,1,1.0
|
29
|
-
Patty Murray,https://www.murray.senate.gov/public,6,0,0.0
|
30
|
-
Christopher Murphy,https://www.murphy.senate.gov,0,1,0.0
|
31
|
-
Claire McCaskill,https://www.mccaskill.senate.gov,1,0,0.0
|
32
|
-
Jeff Merkley,https://www.merkley.senate.gov,1,1,1.0
|
33
|
-
"Joe Manchin, III",https://www.manchin.senate.gov/public,4,4,1.0
|
34
|
-
Bill Nelson,https://www.billnelson.senate.gov,1,0,0.0
|
35
|
-
Gary C. Peters,https://www.peters.senate.gov,0,0,0.0
|
36
|
-
Jack Reed,https://www.reed.senate.gov,3,1,3.0
|
37
|
-
Charles E. Schumer,https://www.schumer.senate.gov,0,0,0.0
|
38
|
-
Debbie Stabenow,https://www.stabenow.senate.gov,1,0,0.0
|
39
|
-
Jeanne Shaheen,https://www.shaheen.senate.gov,2,0,0.0
|
40
|
-
Brian Schatz,https://www.schatz.senate.gov,3,1,3.0
|
41
|
-
Jon Tester,https://www.tester.senate.gov,3,0,0.0
|
42
|
-
Tom Udall,https://www.tomudall.senate.gov,1,0,0.0
|
43
|
-
Chris Van Hollen,https://www.vanhollen.senate.gov,0,1,0.0
|
44
|
-
Ron Wyden,https://www.wyden.senate.gov,7,1,7.0
|
45
|
-
Sheldon Whitehouse,https://www.whitehouse.senate.gov,0,1,0.0
|
46
|
-
Mark R. Warner,https://www.warner.senate.gov,0,0,0.0
|
47
|
-
Elizabeth Warren,https://www.warren.senate.gov,0,5,0.0
|
48
|
-
"Angus S. King, Jr.",https://www.king.senate.gov,3,0,0.0
|
49
|
-
Bernard Sanders,https://www.sanders.senate.gov,0,5,0.0
|