trumpcare_tracker 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe0ebb8e71f2a9e43a10fadc44bdfdba50ef3611
4
- data.tar.gz: a6557125ce42eac222e47e67644b327ed384e6ec
3
+ metadata.gz: 141bd2e1843ec05907a0eb07c7ab97befa22f14b
4
+ data.tar.gz: 0ed7d0da381a117a102f9936092772ea3ef57799
5
5
  SHA512:
6
- metadata.gz: 6a798fc04fda293a830e127f8254f8e02294c6ee0df9f6d8242fc07a0f3cc958df1ab677143241546b0df341e092f329ca5689b952ae00fead9c0942135b0d6f
7
- data.tar.gz: c4ed4f819da83b93bcba2850039778e192c7b6b7d2c244497bb13fee8028f9c16fd07ae71a49d56d50a15f5380dec86fdfaa7a55651aeea6a7206ca7dcb7a426
6
+ metadata.gz: 2fdc6ed15df67450b7bb5f71a01ae2a9d1eef1534ad5f5649c6149fcafe02980f0bc9e9878f3f09b522acebbc55b2350c5a22d5c027d228fd4f48313a2c46f89
7
+ data.tar.gz: 46712d4277da517da3d3ae112e5b8918e879064b2da700f73dc89313f1a7916fe3c649fecf7b31c0f375d510f7b6b8b7713a231781dea84323e2bed422be6eac
data/README.md CHANGED
@@ -45,11 +45,14 @@ TrumpcareTracker::Reporters.new
45
45
  run the tasks like so
46
46
 
47
47
  ```
48
- bundle exec rake tracker:tweet_bot # Audit tweets and post to Twitter timeline with Senator's phone numbers
48
+ bundle exec rake tracker:tweet_bot:democrats
49
+ bundle exec rake tracker:tweet_bot:republicans # Audit tweets and post to Twitter timeline with Senator's phone numbers
49
50
 
50
- bundle exec rake tracker:export # Audit tweets and export to CSV
51
+ bundle exec rake tracker:export:democrats
52
+ bundle exec rake tracker:export:republicans # Audit tweets and export to CSV
51
53
 
52
- bundle exec rake tracker:homepage_scraper # Audit websites and export to CSV
54
+ bundle exec rake tracker:homepage_scraper:democrats
55
+ bundle exec rake tracker:homepage_scraper:republicans # Audit websites and export to CSV
53
56
  ```
54
57
 
55
58
  ## Development
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
3
4
  require 'trumpcare_tracker/version'
4
5
  require 'twitter'
5
6
 
@@ -10,7 +11,7 @@ class TrumpcareTracker
10
11
  ACCESS_TOKEN = ENV['TCT_ACCESS_TOKEN']
11
12
  ACCESS_TOKEN_SECRET = ENV['TCT_ACCESS_TOKEN_SECRET']
12
13
 
13
- attr_reader :user, :screen_name, :alt_screen_name
14
+ attr_reader :user, :screen_name, :alt_screen_name, :requests
14
15
 
15
16
  def self.ratio(numerator, denominator)
16
17
  return 0.0 if denominator.zero?
@@ -22,17 +23,23 @@ class TrumpcareTracker
22
23
  end
23
24
 
24
25
  def self.trumpcare_keyword_regex
25
- /(ahca|trumpcare|healthcare|health|care|drug|medication|medicaid|prescription|vaccine|obamacare|cbo|premiums|insurance|deductibles|aca|o-care|a.h.c.a|a.c.a|pre-existing conditions|hhs)/
26
+ /(ahca|trumpcare|healthcare|health|care|drug|medic|prescription|vaccin|obamacare|cbo|premiums|insurance|deductibles|aca.| aca |aca |o-care|a.h.c.a|a.c.a|pre-existing conditions|hhs|showusthebill|show us the bill)/
26
27
  end
27
28
 
28
29
  def self.russia_keyword_regex
29
- /(russia|comey|sessions|mueller|fbi|flynn|obstruction of justice|collusion|putin|kremlin)/
30
+ /(russia|comey|sessions|mueller|fbi|flynn|obstruction of justice|collusion|putin|kremlin|kislyak|attorney general| intelligence committee|rosenstein|witch hunt|impeach|perjur|under oath)/
30
31
  end
31
32
 
32
- def initialize(user, screen_name, alt_screen_name = nil)
33
+ def initialize(user, screen_name, alt_screen_name = '')
34
+ @audited = false
35
+ @requests = 0
33
36
  @user = user
34
- @screen_name = client.user screen_name
35
- @alt_screen_name = client.user alt_screen_name if alt_screen_name
37
+ @screen_name = screen_name.to_s
38
+ @alt_screen_name = alt_screen_name.to_s
39
+ end
40
+
41
+ def audited?
42
+ @audited
36
43
  end
37
44
 
38
45
  # Instantiate a Twitter Rest Client with API authorization
@@ -51,8 +58,10 @@ class TrumpcareTracker
51
58
 
52
59
  # Make two cursored API calls to fetch the 400 most recent tweets
53
60
  def fetch_timeline(screen_name)
54
- return [] unless screen_name
61
+ return [] if screen_name.to_s.empty?
62
+ @requests += 2
55
63
  timeline = client.user_timeline(screen_name, exclude_replies: true, count: 200)
64
+ return timeline if timeline.empty?
56
65
  timeline + client.user_timeline(
57
66
  screen_name,
58
67
  exclude_replies: true,
@@ -69,11 +78,22 @@ class TrumpcareTracker
69
78
  end
70
79
  end
71
80
 
72
- def reduce_by_keywords(regex)
73
- recent_tweets.each_with_object([]) do |tweet, memo|
74
- tweet_with_full_text = client.status(tweet.id, tweet_mode: 'extended')
75
- memo << tweet if tweet_match?(tweet_with_full_text, regex)
81
+ def filter_by_keywords
82
+ recent_tweets.each do |tweet|
83
+ tweet_with_full_text = fetch_tweet_with_full_text(tweet)
84
+ if tweet_match?(tweet_with_full_text, self.class.trumpcare_keyword_regex)
85
+ stats[:trumpcare_tweets] << tweet
86
+ elsif tweet_match?(tweet_with_full_text, self.class.russia_keyword_regex)
87
+ stats[:russia_tweets] << tweet
88
+ end
76
89
  end
90
+
91
+ stats
92
+ end
93
+
94
+ def fetch_tweet_with_full_text(tweet)
95
+ @requests += 1
96
+ client.status(tweet.id, tweet_mode: 'extended')
77
97
  end
78
98
 
79
99
  def tweet_match?(tweet, regex)
@@ -81,7 +101,7 @@ class TrumpcareTracker
81
101
  if full_text.downcase.match?(regex)
82
102
  true
83
103
  elsif tweet.quoted_tweet?
84
- quoted_tweet = client.status(tweet.quoted_tweet.id, tweet_mode: 'extended')
104
+ quoted_tweet = fetch_tweet_with_full_text(tweet.quoted_tweet)
85
105
  tweet_match?(quoted_tweet, regex)
86
106
  else
87
107
  false
@@ -89,19 +109,21 @@ class TrumpcareTracker
89
109
  end
90
110
 
91
111
  def trumpcare_tweets
92
- @_trumpcare_tweets ||= reduce_by_keywords(self.class.trumpcare_keyword_regex)
112
+ stats[:trumpcare_tweets]
93
113
  end
94
114
 
95
115
  def russia_tweets
96
- @_russia_tweets ||= reduce_by_keywords(self.class.russia_keyword_regex)
116
+ stats[:russia_tweets]
97
117
  end
98
118
 
99
119
  def audit
100
- puts to_s
120
+ filter_by_keywords unless audited?
121
+ @audited = true
101
122
  end
102
123
 
103
124
  def to_s
104
- @_to_s ||= "@#{screen_name.screen_name}'s last 7 days\n#{recent_tweets_count} tweets\n"\
125
+ audit unless audited?
126
+ @_to_s ||= "@#{screen_name}'s last 7 days\n#{recent_tweets_count} tweets\n"\
105
127
  "#{trumpcare_tweets_count} TrumpCare - #{trumpcare_tweets_percentage}%\n"\
106
128
  "#{russia_tweets_count} Russia - #{russia_tweets_percentage}%\n"\
107
129
  "#{trumpcare_to_russia_tweets_ratio} TrumpCare tweets for every Russia tweet"
@@ -111,6 +133,13 @@ class TrumpcareTracker
111
133
  @_recent_tweets_count ||= recent_tweets.count
112
134
  end
113
135
 
136
+ def stats
137
+ @_stats ||= {
138
+ trumpcare_tweets: [],
139
+ russia_tweets: []
140
+ }
141
+ end
142
+
114
143
  def trumpcare_tweets_count
115
144
  @_trumpcare_tweets_count ||= trumpcare_tweets.count
116
145
  end
@@ -120,22 +149,22 @@ class TrumpcareTracker
120
149
  end
121
150
 
122
151
  def trumpcare_tweets_percentage
123
- self.class.percentage(trumpcare_tweets.count, recent_tweets.count)
152
+ self.class.percentage(trumpcare_tweets_count, recent_tweets_count)
124
153
  end
125
154
 
126
155
  def russia_tweets_percentage
127
- self.class.percentage(russia_tweets.count, recent_tweets.count)
156
+ self.class.percentage(russia_tweets_count, recent_tweets_count)
128
157
  end
129
158
 
130
159
  def trumpcare_to_russia_tweets_ratio
131
- self.class.ratio(trumpcare_tweets.count, russia_tweets.count)
160
+ self.class.ratio(trumpcare_tweets_count, russia_tweets_count)
132
161
  end
133
162
 
134
163
  def to_h
135
164
  {
136
165
  senator: user,
137
- official_user_name: screen_name.screen_name,
138
- alt_user_name: alt_screen_name_screen_name,
166
+ official_user_name: screen_name,
167
+ alt_user_name: alt_screen_name,
139
168
  tweets_in_last_seven_days: recent_tweets.count,
140
169
  trumpcare_tweet_count: trumpcare_tweets.count,
141
170
  tct_percentage: trumpcare_tweets_percentage,
@@ -147,12 +176,8 @@ class TrumpcareTracker
147
176
  }
148
177
  end
149
178
 
150
- def alt_screen_name_screen_name
151
- return '' unless alt_screen_name
152
- alt_screen_name.screen_name
153
- end
154
-
155
179
  def to_tweet(options = {})
180
+ @requests += 1
156
181
  client.update(".#{self}", options)
157
182
  end
158
183
  end
@@ -9,56 +9,97 @@ require 'trumpcare_tracker'
9
9
  class TrumpcareTracker
10
10
  # Base rake task class
11
11
  class RakeTask < Rake::TaskLib
12
- module Methods
13
- def reps
14
- democrats = PYR.reps do |r|
15
- r.democrat = true
16
- r.chamber = 'upper'
17
- end
18
-
19
- independents = PYR.reps do |r|
20
- r.independent = true
21
- r.chamber = 'upper'
22
- end
23
-
24
- democrats.objects.to_a + independents.objects.to_a
12
+ def democrats(chamber = '')
13
+ chamber = chamber.to_s
14
+
15
+ democrats = PYR.reps do |r|
16
+ r.democrat = true
17
+ r.chamber = chamber unless chamber.empty?
25
18
  end
26
19
 
27
- def handles
28
- @_handles ||= CSV.read(File.expand_path('../twitter_handles.csv', __FILE__), headers: true)
20
+ independents = PYR.reps do |r|
21
+ r.independent = true
22
+ r.chamber = chamber unless chamber.empty?
29
23
  end
30
24
 
31
- def audit_rep(i, rep, &block)
32
- puts "Sending requests to Twitter API for #{rep.official_full}"
33
- alt_screen_name = handles.detect do |handle|
34
- handle['official'].downcase.strip == rep.twitter.downcase.strip
35
- end['personal/campaign']
36
- tracker = TrumpcareTracker.new(rep.official_full, rep.twitter, alt_screen_name)
37
- tracker.audit
38
- block.call(tracker, rep) if block_given?
39
- puts "#{i + 1} down. Pausing for 45 seconds to avoid hitting API rate limit."
40
- sleep(45)
41
- rescue Twitter::Error::TooManyRequests
42
- puts 'Rate limit exceeded, waiting 5 minutes'
43
- sleep(300)
44
- audit_rep(i, rep, &block)
45
- rescue Twitter::Error => e
46
- puts e.message
47
- audit_rep(i, rep, &block)
25
+ democrats.objects.to_a + independents.objects.to_a
26
+ end
27
+
28
+ def senate_democrats
29
+ democrats('upper')
30
+ end
31
+
32
+ def house_democrats
33
+ democrats('lower')
34
+ end
35
+
36
+ def republicans(chamber = '')
37
+ chamber = chamber.to_s
38
+
39
+ republicans = PYR.reps do |r|
40
+ r.republican = true
41
+ r.chamber = chamber unless chamber.empty?
42
+ end
43
+
44
+ republicans.objects
45
+ end
46
+
47
+ def senate_republicans
48
+ republicans('upper')
49
+ end
50
+
51
+ def house_republicans
52
+ republicans('lower')
53
+ end
54
+
55
+ def handles
56
+ @_handles ||= CSV.read(File.expand_path('../twitter_handles.csv', __FILE__), headers: true)
57
+ end
58
+
59
+ def audit_rep(i, rep, &block)
60
+ puts "Sending requests to Twitter API for #{rep.official_full}"
61
+ rep_handles = handles.detect do |handle|
62
+ handle['official'].downcase.strip == rep.twitter.downcase.strip
63
+ end
64
+
65
+ alt_screen_name = if rep_handles
66
+ rep_handles['personal/campaign']
48
67
  end
49
68
 
50
- def audit_tweets(reps, &block)
51
- reps.each_with_index do |rep, i|
52
- next if rep.twitter.nil?
53
- audit_rep(i, rep, &block)
54
- end
55
- ensure
56
- `say "beep beep beep beep beep"`
69
+ tracker = TrumpcareTracker.new(rep.official_full, rep.twitter, alt_screen_name)
70
+ start_time = Time.now
71
+ tracker.audit
72
+ duration = (Time.now - start_time).round(2)
73
+ puts tracker.to_s
74
+ block.call(tracker, rep) if block_given?
75
+
76
+ puts "#{i + 1} down. #{tracker.requests} requests took #{duration} seconds."
77
+ interval = tracker.requests - duration
78
+ if interval.positive?
79
+ puts "Waiting #{interval} seconds to avoid hitting API limit"
80
+ sleep(interval)
57
81
  end
58
82
 
59
- def mentions_mapper(doc, regex)
60
- doc.text.split("\n").select { |string| string.downcase.match?(regex) }
83
+ rescue Twitter::Error::TooManyRequests
84
+ puts 'Rate limit exceeded, waiting 5 minutes'
85
+ sleep(300)
86
+ audit_rep(i, rep, &block)
87
+ rescue Twitter::Error => e
88
+ puts e.message
89
+ audit_rep(i, rep, &block)
90
+ end
91
+
92
+ def audit_tweets(reps, &block)
93
+ reps.each_with_index do |rep, i|
94
+ next if rep.twitter.nil?
95
+ audit_rep(i, rep, &block)
61
96
  end
97
+ ensure
98
+ `say "beep beep beep beep beep"`
99
+ end
100
+
101
+ def mentions_mapper(doc, regex)
102
+ doc.text.split("\n").select { |string| string.downcase.match?(regex) }
62
103
  end
63
104
  end
64
105
  end
@@ -10,72 +10,115 @@ class TrumpcareTracker
10
10
  # require 'trumpcare_tracker/reporters'
11
11
  # TrumpcareTracker::Reporters.new
12
12
  #
13
- # $ bundle exec rake tracker:export
14
- # $ bundle exec rake tracker:homepage_scraper
15
- class Reporters < RakeTask
16
- include RakeTask::Methods
17
-
13
+ # $ rake tracker:export:senate_democrats
14
+ # $ rake tracker:export:house_democrats
15
+ # $ rake tracker:export:senate_republicans
16
+ # $ rake tracker:export:house_republicans
17
+ # $ rake tracker:export:all
18
+ #
19
+ # $ rake tracker:homepage_scraper:senate_democrats
20
+ # $ rake tracker:homepage_scraper:house_democrats
21
+ # $ rake tracker:homepage_scraper:senate_republicans
22
+ # $ rake tracker:homepage_scraper:house_republicans
23
+ # $ rake tracker:homepage_scraper:all
24
+ class Reporters < RakeTask
18
25
  def initialize
19
26
  export_task
20
27
  homepage_scraper_task
21
28
  end
22
29
 
23
30
  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
31
+ namespace(:tracker) do
32
+ namespace(:export) do
33
+ desc 'Output a report of Senate Democrats Trumpcare tweets'
34
+ task(:senate_democrats) { export(:senate_democrats) }
35
+
36
+ desc 'Output a report of House Democrats Trumpcare tweets'
37
+ task(:house_democrats) { export(:house_democrats) }
38
+
39
+ desc 'Output a report of Senate Republicans Trumpcare tweets'
40
+ task(:senate_republicans) { export(:senate_republicans) }
41
+
42
+ desc 'Output a report of House Republicans Trumpcare tweets'
43
+ task(:house_republicans) { export(:house_republicans) }
44
+
45
+ all_tasks
47
46
  end
48
47
  end
49
48
  end
50
49
 
50
+ def export(caucus)
51
+ csv_tweet_report = CSV.generate do |csv|
52
+ csv << %w[
53
+ senator
54
+ official_user_name
55
+ alt_user_name
56
+ tweets_in_last_seven_days
57
+ trumpcare_tweets
58
+ tct_percentage
59
+ russia_tweets
60
+ rt_percentage
61
+ tct_to_rt_ratio
62
+ trumpcare_tweet_urls
63
+ russia_tweet_urls
64
+ ]
65
+ audit_tweets(send(caucus)) { |tracker| csv << tracker.to_h.values }
66
+ end
67
+
68
+ File.open("trumpcare_tweet_report_#{caucus}.csv", 'w') do |file|
69
+ file.write csv_tweet_report
70
+ end
71
+ end
72
+
51
73
 
52
74
 
53
75
  def homepage_scraper_task
54
- namespace :tracker do
76
+ namespace(:tracker) do
55
77
  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
78
+ namespace(:homepage_scraper) do
79
+ desc 'Search Senate Democrat\'s homepages'
80
+ task(:senate_democrats) { homepage_scraper(:senate_democrats) }
81
+
82
+ desc 'Search House Democrat\'s homepages'
83
+ task(:house_democrats) { homepage_scraper(:house_democrats) }
84
+
85
+ desc 'Search Senate Republican\'s homepages'
86
+ task(:senate_republicans) { homepage_scraper(:senate_republicans) }
87
+
88
+ desc 'Search House Republican\'s homepages'
89
+ task(:house_republicans) { homepage_scraper(:house_republicans) }
90
+
91
+ all_tasks
77
92
  end
78
93
  end
79
94
  end
95
+
96
+ def homepage_scraper(caucus)
97
+ csv_homepage_report = CSV.generate do |csv|
98
+ csv << %w[senator url trumpcare_mentions russia_mentions trumpcare_to_russia_ratio]
99
+ send(caucus).each do |rep|
100
+ doc = Nokogiri::HTML(open(rep.url))
101
+ trumpcare_mentions_count = mentions_mapper(doc, TrumpcareTracker.trumpcare_keyword_regex).count
102
+ russia_mentions_count = mentions_mapper(doc, TrumpcareTracker.russia_keyword_regex).count
103
+ csv << [
104
+ rep.official_full,
105
+ rep.url,
106
+ trumpcare_mentions_count,
107
+ russia_mentions_count,
108
+ TrumpcareTracker.ratio(trumpcare_mentions_count, russia_mentions_count)
109
+ ]
110
+ puts "Scraped #{rep.official_full}'s homepage"
111
+ end
112
+ end
113
+
114
+ File.open("trumpcare_homepage_report_#{caucus}.csv", 'w') do |file|
115
+ file.write csv_homepage_report
116
+ end
117
+ end
118
+
119
+ def all_tasks
120
+ desc 'Run for each caucus'
121
+ task(all: [:senate_democrats, :house_democrats, :senate_republicans, :house_republicans])
122
+ end
80
123
  end
81
124
  end