sports_db 0.0.2 → 0.0.3
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.
- data/app/models/score_notification.rb +11 -0
- data/lib/sports_db/news_builder.rb +47 -0
- data/lib/sports_db/sporting_news_feed_builder.rb +530 -0
- data/lib/sports_db/team_and_roster_builder.rb +78 -0
- data/lib/sports_db/version.rb +1 -1
- data/lib/sports_db.rb +4 -0
- data/lib/tasks/sports_db_tasks.rake +100 -0
- data/lib/time_service.rb +9 -0
- metadata +8 -3
@@ -0,0 +1,47 @@
|
|
1
|
+
module SportsDb
|
2
|
+
class NewsBuilder
|
3
|
+
|
4
|
+
def self.update_player_news
|
5
|
+
p "Updating SN Player news ..."
|
6
|
+
config = SimpleConfig.for(:feeds)
|
7
|
+
require 'open-uri'
|
8
|
+
|
9
|
+
articles = []
|
10
|
+
|
11
|
+
unless !config.player_news_url.blank?
|
12
|
+
p "No sporting news player new URL given."
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
open( config.player_news_url ) do |file|
|
17
|
+
doc = Nokogiri::XML(file.read)
|
18
|
+
|
19
|
+
doc.xpath('//row').each do |player_element|
|
20
|
+
player = Player.find_by_sporting_news_id( player_element['PLAYER_ID'].to_i )
|
21
|
+
if player
|
22
|
+
content = "#{player_element['COMMENT']}. #{player_element['IMPACT']}."
|
23
|
+
content.gsub!("..",".")
|
24
|
+
|
25
|
+
article = Article.new
|
26
|
+
article.title = "News for #{player.first_name} #{player.last_name}"
|
27
|
+
article.category = "Player News"
|
28
|
+
article.player = player
|
29
|
+
article.contents = content
|
30
|
+
articles << article
|
31
|
+
|
32
|
+
p "#{player.first_name} #{player.last_name}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Article.transaction do
|
38
|
+
Article.delete_all("player_id is not null")
|
39
|
+
articles.each {|article| article.save }
|
40
|
+
end
|
41
|
+
|
42
|
+
rescue Exception => e
|
43
|
+
Zumobi::ExceptionHandler.error e
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,530 @@
|
|
1
|
+
module SportsDb
|
2
|
+
class SportingNewsFeedBuilder
|
3
|
+
|
4
|
+
def self.update_news_feed
|
5
|
+
config_feeds = SimpleConfig.for(:feeds)
|
6
|
+
|
7
|
+
Article.transaction do
|
8
|
+
# #delete existing articles older than 30 days
|
9
|
+
# remove_date = TimeService.now - 30.day
|
10
|
+
# sn_articles_old = Article.find(:all, :conditions => ["source = 'Sporting News' and published_at > ?", remove_date])
|
11
|
+
# sn_articles_old.each {|article| article.destroy }
|
12
|
+
|
13
|
+
update_webgen_feeds(config_feeds.news_feeds, "news")
|
14
|
+
update_webgen_feeds(config_feeds.breaking_news_feeds, "breaking")
|
15
|
+
|
16
|
+
|
17
|
+
if CONFIG.affiliation_key == 'l.nfl.com'
|
18
|
+
update_webgen_feeds(config_feeds.team_feeds, "pro_team_news")
|
19
|
+
elsif CONFIG.affiliation_key == 'l.nba.com'
|
20
|
+
update_webgen_feeds(config_feeds.team_feeds, "pro_team_news")
|
21
|
+
elsif CONFIG.affiliation_key == 'l.mlb.com'
|
22
|
+
update_webgen_feeds(config_feeds.team_feeds, "pro_team_news")
|
23
|
+
update_webgen_feeds(config_feeds.fantasy_news_feeds, "fantasy")
|
24
|
+
elsif CONFIG.affiliation_key.match('l.ncaa.org')
|
25
|
+
update_webgen_feeds(ExternalFeed.find(:all, :conditions => ["content_type = ? and provider = ?", "news", "Sporting News"]), "ncaa_team_news")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.update_webgen_feeds(feed_list, list_type)
|
31
|
+
if list_type == "ncaa_team_news"
|
32
|
+
feed_list.each do |feed_obj|
|
33
|
+
feed_team = Team.find(feed_obj.team_id)
|
34
|
+
|
35
|
+
if !feed_team.nil?
|
36
|
+
feed_url = feed_obj.woven_feed_url
|
37
|
+
if CONFIG.woven_feed_server != "woven.zumobi.net"
|
38
|
+
feed_url = feed_url.gsub("woven.zumobi.net", CONFIG.woven_feed_server)
|
39
|
+
end
|
40
|
+
list_type = "team_news"
|
41
|
+
|
42
|
+
rss = retrieve_feed(feed_url)
|
43
|
+
parse_webgen_feeds(rss, feed_team.key, feed_url, list_type, feed_team.city_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
feed_list.each do |feed_title, url|
|
48
|
+
if list_type == "pro_team_news"
|
49
|
+
feed_key = feed_title
|
50
|
+
if Team.column_names.include?("tsn_key") && feed_key.to_s.match(CONFIG.affiliation_key)
|
51
|
+
feed_key = tsn_keys_to_stats_key(feed_key)
|
52
|
+
end
|
53
|
+
team = Team.find_by_key(feed_key)
|
54
|
+
feed_title = (!team.nil?) ? team.city_name : feed_title
|
55
|
+
list_type = "team_news"
|
56
|
+
elsif list_type == "breaking"
|
57
|
+
feed_key = CONFIG.affiliation_key
|
58
|
+
elsif list_type == "news"
|
59
|
+
feed_key = CONFIG.affiliation_key
|
60
|
+
elsif list_type == "fantasy"
|
61
|
+
feed_key = "fantasy"
|
62
|
+
end
|
63
|
+
|
64
|
+
if !feed_key.blank?
|
65
|
+
rss = retrieve_feed(url)
|
66
|
+
parse_webgen_feeds(rss, feed_key, url, list_type, feed_title)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue Exception => e
|
71
|
+
Zumobi::ExceptionHandler.error e
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.parse_webgen_feeds(rss, feed_key, url, list_type, feed_title)
|
75
|
+
p "News - #{feed_title} - #{url}"
|
76
|
+
|
77
|
+
article_count = 0
|
78
|
+
|
79
|
+
doc = Nokogiri::XML(rss)
|
80
|
+
source = "Sporting News"
|
81
|
+
if !doc.nil?
|
82
|
+
|
83
|
+
doc.xpath('//item').each do |node|
|
84
|
+
article_count += 1
|
85
|
+
|
86
|
+
if feed_key == CONFIG.affiliation_key
|
87
|
+
article_link = node.xpath('link').text
|
88
|
+
if !article_link.match(CONFIG.sn_breaking_news_match)
|
89
|
+
next
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
guid = node.xpath('guid').text
|
94
|
+
item_title = node.xpath('title').text
|
95
|
+
|
96
|
+
if Article.find_by_digest(guid).nil? && Article.find_by_title(item_title).nil?
|
97
|
+
article_obj = Article.new
|
98
|
+
|
99
|
+
article_obj.title = node.xpath('title').text
|
100
|
+
article_obj.published_at = node.xpath('pubDate').text
|
101
|
+
article_obj.link = node.xpath('link').text
|
102
|
+
article_obj.digest = guid
|
103
|
+
article_obj.source = source
|
104
|
+
|
105
|
+
if list_type == "team_news" || list_type == "team_news_no_filters"
|
106
|
+
article_obj.category = "Team News"
|
107
|
+
elsif list_type == "breaking" || list_type == "news"
|
108
|
+
article_obj.category = "League News"
|
109
|
+
elsif list_type == "fantasy"
|
110
|
+
article_obj.category = "Fantasy News"
|
111
|
+
else
|
112
|
+
article_obj.category = "News"
|
113
|
+
end
|
114
|
+
|
115
|
+
if guid.blank?
|
116
|
+
article_obj.set_digest
|
117
|
+
end
|
118
|
+
|
119
|
+
if list_type == "breaking"
|
120
|
+
article_obj.author = (!node.xpath('author').nil?) ? node.xpath('author').text : ""
|
121
|
+
article_obj.contents = (!node.xpath('description').nil?) ? node.xpath('description').text.strip : ""
|
122
|
+
else
|
123
|
+
article_obj.author = (!node.xpath('dc:creator').nil?) ? node.xpath('dc:creator').text : ""
|
124
|
+
article_obj.contents = (!node.xpath('content:encoded').nil?) ? node.xpath('content:encoded').text.strip : ""
|
125
|
+
thumb_image = (!node.xpath('media:thumbnail').nil?) ? node.xpath('media:thumbnail/@url').text : ""
|
126
|
+
if !thumb_image.blank?
|
127
|
+
if CONFIG.affiliation_key == 'l.nfl.com'
|
128
|
+
article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/110/url/' + CGI::escape(CGI::escape(thumb_image))
|
129
|
+
article_obj.article_image_url = CONFIG.image_service + 'transform/w/480/h/480/url/' + CGI::escape(CGI::escape(thumb_image))
|
130
|
+
else
|
131
|
+
article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/55/url/' + CGI::escape(CGI::escape(thumb_image))
|
132
|
+
article_obj.article_image_url = CONFIG.image_service + 'transform/w/280/h/280/url/' + CGI::escape(CGI::escape(thumb_image))
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
if article_obj.title.blank? || article_obj.title == "." || article_obj.contents.blank? || article_obj.contents == "."
|
138
|
+
next
|
139
|
+
end
|
140
|
+
|
141
|
+
if list_type == "fantasy"
|
142
|
+
set_fantasy_article_filter(article_obj, feed_key)
|
143
|
+
else
|
144
|
+
set_article_filter_association(article_obj, feed_key)
|
145
|
+
end
|
146
|
+
|
147
|
+
article_obj.save
|
148
|
+
else
|
149
|
+
Article.transaction do
|
150
|
+
article_obj = Article.find_by_digest(guid)
|
151
|
+
if article_obj.nil?
|
152
|
+
article_obj = Article.find_by_title(item_title)
|
153
|
+
end
|
154
|
+
|
155
|
+
article_obj.title = node.xpath('title').text
|
156
|
+
article_obj.published_at = node.xpath('pubDate').text
|
157
|
+
article_obj.link = node.xpath('link').text
|
158
|
+
|
159
|
+
if list_type == "breaking"
|
160
|
+
article_obj.author = (!node.xpath('author').nil?) ? node.xpath('author').text : ""
|
161
|
+
article_obj.contents = (!node.xpath('description').nil?) ? node.xpath('description').text.strip : ""
|
162
|
+
else
|
163
|
+
article_obj.author = (!node.xpath('dc:creator').nil?) ? node.xpath('dc:creator').text : ""
|
164
|
+
article_obj.contents = (!node.xpath('content:encoded').nil?) ? node.xpath('content:encoded').text.strip : ""
|
165
|
+
thumb_image = (!node.xpath('media:thumbnail').nil?) ? node.xpath('media:thumbnail/@url').text : ""
|
166
|
+
|
167
|
+
if !thumb_image.blank?
|
168
|
+
if CONFIG.affiliation_key == 'l.nfl.com'
|
169
|
+
article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/110/url/' + CGI::escape(CGI::escape(thumb_image))
|
170
|
+
article_obj.article_image_url = CONFIG.image_service + 'transform/w/480/h/480/url/' + CGI::escape(CGI::escape(thumb_image))
|
171
|
+
else
|
172
|
+
article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/55/url/' + CGI::escape(CGI::escape(thumb_image))
|
173
|
+
article_obj.article_image_url = CONFIG.image_service + 'transform/w/280/h/280/url/' + CGI::escape(CGI::escape(thumb_image))
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
if list_type == "fantasy"
|
179
|
+
set_fantasy_article_filter(article_obj, feed_key)
|
180
|
+
else
|
181
|
+
set_article_filter_association(article_obj, feed_key)
|
182
|
+
end
|
183
|
+
article_obj.save
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
p "Article - #{article_obj.title}"
|
188
|
+
|
189
|
+
remove_flash_elements(article_obj)
|
190
|
+
|
191
|
+
if (list_type == "team_news" || list_type == "breaking") && CONFIG.enable_notifications
|
192
|
+
if list_type == "team_news"
|
193
|
+
if Team.column_names.include?("tsn_key")
|
194
|
+
team = Team.find_by_key(feed_key)
|
195
|
+
notify_feed_key = team.tsn_key
|
196
|
+
else
|
197
|
+
notify_feed_key = feed_key
|
198
|
+
end
|
199
|
+
send_notification(article_obj, notify_feed_key, list_type)
|
200
|
+
else
|
201
|
+
send_notification(article_obj, feed_key, list_type)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
p "#{article_count} articles processed"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.set_article_filter_association(article, feed_key)
|
211
|
+
#all
|
212
|
+
if !article.news_filters.find(:first, :conditions => "news_filter_key = 'all'")
|
213
|
+
filter = NewsFilter.find(:first, :conditions => ["news_filter_key = 'all'"])
|
214
|
+
filter.articles << article
|
215
|
+
filter.save
|
216
|
+
end
|
217
|
+
|
218
|
+
if feed_key != CONFIG.affiliation_key
|
219
|
+
#team
|
220
|
+
if Team.column_names.include?("tsn_key") && feed_key.to_s.match(CONFIG.affiliation_key)
|
221
|
+
feed_key = tsn_keys_to_stats_key(feed_key)
|
222
|
+
end
|
223
|
+
team = Team.find_by_key(feed_key)
|
224
|
+
|
225
|
+
if !team.nil?
|
226
|
+
if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", team.key])
|
227
|
+
filter_team = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", team.key])
|
228
|
+
if !filter_team.nil?
|
229
|
+
filter_team.articles << article
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
#conference
|
234
|
+
if CONFIG.affiliation_key.match('l.ncaa.org')
|
235
|
+
conference_key = team.conference.key
|
236
|
+
else
|
237
|
+
conference_key = team.conference
|
238
|
+
end
|
239
|
+
if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", conference_key])
|
240
|
+
filter_conference = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", conference_key])
|
241
|
+
if !filter_conference.nil?
|
242
|
+
filter_conference.articles << article
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
else
|
247
|
+
#league
|
248
|
+
if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", feed_key])
|
249
|
+
filter_conference = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", feed_key])
|
250
|
+
if !filter_conference.nil?
|
251
|
+
filter_conference.articles << article
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.set_fantasy_article_filter(article, feed_key)
|
258
|
+
if !article.news_filters.find(:first, :conditions => "news_filter_key = 'fantasy'")
|
259
|
+
filter = NewsFilter.find(:first, :conditions => ["news_filter_key = 'fantasy'"])
|
260
|
+
if !filter.nil?
|
261
|
+
filter.articles << article
|
262
|
+
filter.save
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.remove_flash_elements(article)
|
268
|
+
doc = Nokogiri::HTML(article.contents)
|
269
|
+
objects = doc.search("embed")
|
270
|
+
objects.each do |obj|
|
271
|
+
obj.remove
|
272
|
+
end
|
273
|
+
|
274
|
+
contents = doc.children[1].to_html
|
275
|
+
contents = contents.gsub("<html><body>\n", '')
|
276
|
+
contents = contents.gsub('</body></html>', '')
|
277
|
+
|
278
|
+
article.contents = contents
|
279
|
+
article.save
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.send_notification(article, team_key, notify_type)
|
283
|
+
#checks to see if the article is new enough
|
284
|
+
#checks if notification has been sent yet. if there's a row in the notifications table, then it's been sent
|
285
|
+
if !article.nil? && !article.digest.blank?
|
286
|
+
n = Notification.find(:first, :conditions => ['title = ? and category = ?', article.title, team_key])
|
287
|
+
|
288
|
+
if (n.nil? && article.published_at > CONFIG.send_notifications_since.call())
|
289
|
+
n = Notification.new
|
290
|
+
n.title = article.title
|
291
|
+
n.article_digest = article.digest
|
292
|
+
n.category = team_key
|
293
|
+
n.article_id = article.id
|
294
|
+
n.article_link = article.link
|
295
|
+
n.notify_sent = 0
|
296
|
+
|
297
|
+
if team_key != CONFIG.affiliation_key
|
298
|
+
team = Team.find_by_tsn_key(team_key)
|
299
|
+
if !team.nil?
|
300
|
+
n.team_id = team.id
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
n.save
|
305
|
+
|
306
|
+
elsif (!n.nil? && n.notify_sent == 0 && article.published_at > CONFIG.send_notifications_since.call())
|
307
|
+
#putting a delay in for sending out notify rather than sending right when we get the article.
|
308
|
+
#i'm sure that there's a caching issue between the backend and front end. hopefully this will help work that out.
|
309
|
+
alert_title = article.title[0,90]
|
310
|
+
if article.title.length > 90
|
311
|
+
alert_title += "..."
|
312
|
+
end
|
313
|
+
|
314
|
+
n.notify_sent = 1
|
315
|
+
n.save
|
316
|
+
|
317
|
+
if notify_type == "team_news" && !n.team_id.nil?
|
318
|
+
send_push_notification(alert_title, [team_key], article.digest)
|
319
|
+
elsif notify_type == "breaking"
|
320
|
+
send_push_notification(alert_title, [team_key], article.digest)
|
321
|
+
end
|
322
|
+
|
323
|
+
else
|
324
|
+
if (!n.nil?)
|
325
|
+
#Rails.logger.info("Already sent (Notification_id: #{n.id})")
|
326
|
+
else
|
327
|
+
#Rails.logger.info("Article too old to notify (#{article.digest})")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Sends a notification for the specified app / notification class combo.
|
334
|
+
#
|
335
|
+
# Parameters:
|
336
|
+
# application_name - The name of the application the notification is intended for.
|
337
|
+
# tag - data goes into "tags" field
|
338
|
+
# alert - The text to display in the notification.
|
339
|
+
# digest - An additional field added for NFL - this is the article id.
|
340
|
+
# tag_key - An additional field added for NFL - this is the tag that the notifs is subscribed to team or league.
|
341
|
+
# sound (optional) - Whether or not to play a sound when the notification is sent. A value of ÔøΩtrueÔøΩ, ÔøΩ1ÔøΩ, or ÔøΩyesÔøΩ means to play the sound. Any other values will not play a sound.
|
342
|
+
def self.send_push_notification(alert_text, tags, digest)
|
343
|
+
p "--Notified! '#{alert_text[0,50]}' at #{DateTime.now.to_s}: Tag: #{tag}, Response: #{res.code} #{res.message}"
|
344
|
+
|
345
|
+
notifier = Zumobi::Notifier.new
|
346
|
+
notifier.push({
|
347
|
+
:aps => {:alert => alert_text, :sound => "1"},
|
348
|
+
:android => {:alert => alert_text, :extra => "#{tags.join(',')}|#{digest}"},
|
349
|
+
:tags => tags,
|
350
|
+
:tag_key => tags.join(','),
|
351
|
+
:digest => digest
|
352
|
+
})
|
353
|
+
end
|
354
|
+
|
355
|
+
def self.retrieve_feed(url)
|
356
|
+
body = make_request(url)
|
357
|
+
body
|
358
|
+
end
|
359
|
+
|
360
|
+
|
361
|
+
def self.remove_dup_and_old_articles()
|
362
|
+
teams = Team.find(:all, :conditions => ["division is not null"])
|
363
|
+
teams.each do |t|
|
364
|
+
p "#{t.city_name} #{t.team_name}"
|
365
|
+
|
366
|
+
news_filter = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", t.key])
|
367
|
+
remove_by_news_filter(news_filter)
|
368
|
+
end
|
369
|
+
|
370
|
+
p "All"
|
371
|
+
news_filter = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", "all"])
|
372
|
+
remove_by_news_filter(news_filter)
|
373
|
+
end
|
374
|
+
|
375
|
+
def self.remove_by_news_filter(news_filter)
|
376
|
+
if !news_filter.nil?
|
377
|
+
article_titles = {}
|
378
|
+
team_articles = news_filter.articles.find(:all)
|
379
|
+
|
380
|
+
team_articles.each do |a|
|
381
|
+
article_digest = Digest::SHA1.hexdigest(a.sn_url)
|
382
|
+
|
383
|
+
if article_titles[article_digest].blank?
|
384
|
+
article_titles[article_digest] = a
|
385
|
+
p "---- #{a.title}"
|
386
|
+
else
|
387
|
+
p "Dup! - #{a.title}"
|
388
|
+
|
389
|
+
this_article = a
|
390
|
+
saved_article = article_titles[article_digest]
|
391
|
+
|
392
|
+
if this_article.published_at > saved_article.published_at
|
393
|
+
article_titles[article_digest] = this_article
|
394
|
+
saved_article.destroy
|
395
|
+
else
|
396
|
+
this_article.destroy
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
private
|
404
|
+
|
405
|
+
def self.make_request(url)
|
406
|
+
begin
|
407
|
+
c = Curl::Easy.perform(url) do |curl|
|
408
|
+
curl.connect_timeout = 120
|
409
|
+
curl.follow_location = true
|
410
|
+
curl.max_redirects = 10
|
411
|
+
curl.timeout = 120
|
412
|
+
curl.encoding = 'gzip'
|
413
|
+
# curl.verbose = true
|
414
|
+
end
|
415
|
+
c.body_str
|
416
|
+
rescue Curl::Err::TimeoutError
|
417
|
+
#
|
418
|
+
rescue Curl::Err::GotNothingError
|
419
|
+
#
|
420
|
+
rescue Curl::Err::RecvError
|
421
|
+
#
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def self.tsn_keys_to_stats_key(tsn_key)
|
426
|
+
#static map for stats global-id to tsn team_key
|
427
|
+
keys = {
|
428
|
+
"l.nfl.com-t.1" => 324,
|
429
|
+
"l.nfl.com-t.2" => 338,
|
430
|
+
"l.nfl.com-t.3" => 345,
|
431
|
+
"l.nfl.com-t.4" => 348,
|
432
|
+
"l.nfl.com-t.5" => 352,
|
433
|
+
"l.nfl.com-t.6" => 366,
|
434
|
+
"l.nfl.com-t.7" => 327,
|
435
|
+
"l.nfl.com-t.8" => 329,
|
436
|
+
"l.nfl.com-t.9" => 365,
|
437
|
+
"l.nfl.com-t.10" => 356,
|
438
|
+
"l.nfl.com-t.11" => 336,
|
439
|
+
"l.nfl.com-t.12" => 332,
|
440
|
+
"l.nfl.com-t.13" => 339,
|
441
|
+
"l.nfl.com-t.14" => 341,
|
442
|
+
"l.nfl.com-t.15" => 357,
|
443
|
+
"l.nfl.com-t.16" => 361,
|
444
|
+
"l.nfl.com-t.17" => 355,
|
445
|
+
"l.nfl.com-t.18" => 331,
|
446
|
+
"l.nfl.com-t.19" => 351,
|
447
|
+
"l.nfl.com-t.20" => 354,
|
448
|
+
"l.nfl.com-t.21" => 363,
|
449
|
+
"l.nfl.com-t.22" => 326,
|
450
|
+
"l.nfl.com-t.23" => 334,
|
451
|
+
"l.nfl.com-t.24" => 335,
|
452
|
+
"l.nfl.com-t.25" => 347,
|
453
|
+
"l.nfl.com-t.26" => 362,
|
454
|
+
"l.nfl.com-t.27" => 323,
|
455
|
+
"l.nfl.com-t.28" => 343,
|
456
|
+
"l.nfl.com-t.29" => 364,
|
457
|
+
"l.nfl.com-t.30" => 350,
|
458
|
+
"l.nfl.com-t.31" => 359,
|
459
|
+
"l.nfl.com-t.32" => 325,
|
460
|
+
"l.mlb.com-t.1" => 225,
|
461
|
+
"l.mlb.com-t.2" => 226,
|
462
|
+
"l.mlb.com-t.3" => 234,
|
463
|
+
"l.mlb.com-t.4" => 254,
|
464
|
+
"l.mlb.com-t.5" => 238,
|
465
|
+
"l.mlb.com-t.6" => 228,
|
466
|
+
"l.mlb.com-t.7" => 229,
|
467
|
+
"l.mlb.com-t.8" => 230,
|
468
|
+
"l.mlb.com-t.9" => 231,
|
469
|
+
"l.mlb.com-t.10" => 233,
|
470
|
+
"l.mlb.com-t.11" => 227,
|
471
|
+
"l.mlb.com-t.12" => 235,
|
472
|
+
"l.mlb.com-t.13" => 236,
|
473
|
+
"l.mlb.com-t.14" => 237,
|
474
|
+
"l.mlb.com-t.15" => 239,
|
475
|
+
"l.mlb.com-t.16" => 252,
|
476
|
+
"l.mlb.com-t.17" => 244,
|
477
|
+
"l.mlb.com-t.18" => 245,
|
478
|
+
"l.mlb.com-t.19" => 246,
|
479
|
+
"l.mlb.com-t.20" => 240,
|
480
|
+
"l.mlb.com-t.21" => 241,
|
481
|
+
"l.mlb.com-t.22" => 242,
|
482
|
+
"l.mlb.com-t.23" => 232,
|
483
|
+
"l.mlb.com-t.24" => 247,
|
484
|
+
"l.mlb.com-t.25" => 248,
|
485
|
+
"l.mlb.com-t.26" => 253,
|
486
|
+
"l.mlb.com-t.27" => 251,
|
487
|
+
"l.mlb.com-t.28" => 243,
|
488
|
+
"l.mlb.com-t.29" => 249,
|
489
|
+
"l.mlb.com-t.30" => 250,
|
490
|
+
"l.nba.com-t.1" => 2,
|
491
|
+
"l.nba.com-t.2" => 14,
|
492
|
+
"l.nba.com-t.3" => 17,
|
493
|
+
"l.nba.com-t.4" => 18,
|
494
|
+
"l.nba.com-t.5" => 19,
|
495
|
+
"l.nba.com-t.6" => 20,
|
496
|
+
"l.nba.com-t.7" => 27,
|
497
|
+
"l.nba.com-t.8" => 1,
|
498
|
+
"l.nba.com-t.9" => 3,
|
499
|
+
"l.nba.com-t.10" => 4,
|
500
|
+
"l.nba.com-t.11" => 5,
|
501
|
+
"l.nba.com-t.12" => 8,
|
502
|
+
"l.nba.com-t.13" => 11,
|
503
|
+
"l.nba.com-t.14" => 15,
|
504
|
+
"l.nba.com-t.15" => 28,
|
505
|
+
"l.nba.com-t.16" => 6,
|
506
|
+
"l.nba.com-t.17" => 7,
|
507
|
+
"l.nba.com-t.18" => 10,
|
508
|
+
"l.nba.com-t.19" => 29,
|
509
|
+
"l.nba.com-t.20" => 16,
|
510
|
+
"l.nba.com-t.21" => 24,
|
511
|
+
"l.nba.com-t.22" => 26,
|
512
|
+
"l.nba.com-t.23" => 9,
|
513
|
+
"l.nba.com-t.24" => 12,
|
514
|
+
"l.nba.com-t.25" => 13,
|
515
|
+
"l.nba.com-t.26" => 21,
|
516
|
+
"l.nba.com-t.27" => 22,
|
517
|
+
"l.nba.com-t.28" => 23,
|
518
|
+
"l.nba.com-t.29" => 25,
|
519
|
+
"l.nba.com-t.32" => 5312
|
520
|
+
}
|
521
|
+
|
522
|
+
if !tsn_key.blank? && !keys[tsn_key].blank?
|
523
|
+
return keys[tsn_key]
|
524
|
+
else
|
525
|
+
return ''
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
end
|
530
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module SportsDb
|
2
|
+
class TeamAndRosterBuilder
|
3
|
+
|
4
|
+
def self.update_player_image_urls
|
5
|
+
p "update_player_image_urls ..."
|
6
|
+
config_feeds = SimpleConfig.for(:feeds)
|
7
|
+
require 'open-uri'
|
8
|
+
open( config_feeds.player_images_url ) do |file|
|
9
|
+
doc = Nokogiri::XML(file.read)
|
10
|
+
doc.xpath('//row').each do |player_element|
|
11
|
+
# The date is in format '08-MAY-77' and we need '8/5/1977' as a string. However, this still doesn't
|
12
|
+
# strip leading zeroes off days, which we need to do to match this date as a string.
|
13
|
+
# Convert to string, replace last bit so it says "19xx", parse to date, and then format
|
14
|
+
# in the D/M/Y format. Then finally remove leading zeroes.
|
15
|
+
if !player_element['BIRTH_DATE'].blank?
|
16
|
+
adj_birth_date = DateTime.strptime(player_element['BIRTH_DATE'].to_s, "%m/%d/%Y").to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
# Try only the name first.
|
20
|
+
matches = Player.find_by_last_comma_first( player_element['PLAYER_NAME'] )
|
21
|
+
# But if there's more than one, then attempt to whittle it down with the birthday
|
22
|
+
matches = matches.select {|p| p.birth_date == adj_birth_date } if (matches.length > 1)
|
23
|
+
|
24
|
+
if (matches.length == 1)
|
25
|
+
player = matches[0]
|
26
|
+
player.image_url = player_element['PLAYER_MUG']
|
27
|
+
player.sporting_news_id = player_element['PLAYER_ID'].to_i
|
28
|
+
player.save
|
29
|
+
|
30
|
+
p "img- #{player_element['PLAYER_NAME']} - #{player_element['PLAYER_MUG']}"
|
31
|
+
else
|
32
|
+
p "Unable to Match -- #{player_element['PLAYER_NAME']}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue Exception => e
|
38
|
+
Zumobi::ExceptionHandler.error e
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.update_team_top25_rank
|
42
|
+
p "Set Top 25 Rank"
|
43
|
+
|
44
|
+
rank_issuer_key = map_poll_key(CONFIG.rank_based_on_poll)
|
45
|
+
|
46
|
+
rankings = PollRanking.all(:conditions => ["issuer_key = ?", rank_issuer_key])
|
47
|
+
rankings.each do |obj|
|
48
|
+
team_obj = Team.find(obj.team_id)
|
49
|
+
# Only rank 1-25 (bug 24359)
|
50
|
+
if team_obj.present? && obj.rank.to_i < 26
|
51
|
+
p "#{team_obj.city_name} ##{obj.rank}"
|
52
|
+
team_obj.rank = obj.rank
|
53
|
+
team_obj.save
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.map_poll_key(poll_key)
|
59
|
+
case poll_key
|
60
|
+
when "poll-ap"
|
61
|
+
"1"
|
62
|
+
when "poll-usat"
|
63
|
+
"2"
|
64
|
+
when "ranking-bcs"
|
65
|
+
"3"
|
66
|
+
when "ap"
|
67
|
+
"ap"
|
68
|
+
when "coaches"
|
69
|
+
"coaches"
|
70
|
+
when "rpi"
|
71
|
+
"rpi"
|
72
|
+
else
|
73
|
+
"1"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/sports_db/version.rb
CHANGED
data/lib/sports_db.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require "sports_db/engine"
|
2
2
|
require "sports_db/external_feed_builder"
|
3
3
|
require "sports_db/media_builder"
|
4
|
+
require "sports_db/news_builder"
|
4
5
|
require "sports_db/news_filters_builder"
|
6
|
+
require "sports_db/sporting_news_feed_builder"
|
7
|
+
require "sports_db/team_and_roster_builder"
|
5
8
|
require "sports_db/twitter_builder"
|
9
|
+
require "time_service"
|
6
10
|
|
7
11
|
module SportsDb
|
8
12
|
|
@@ -2,3 +2,103 @@
|
|
2
2
|
# task :sports_db do
|
3
3
|
# # Task goes here
|
4
4
|
# end
|
5
|
+
|
6
|
+
namespace 'zu' do
|
7
|
+
|
8
|
+
task :set_logger => :environment do
|
9
|
+
RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace 'sports' do
|
13
|
+
desc "Run sports db migrations."
|
14
|
+
task :migrate => 'zu:set_logger' do
|
15
|
+
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
16
|
+
ActiveRecord::Migrator.migrate("vendor/plugins/sports_db/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
17
|
+
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Drops and recreates the sports_db database (as well as any application-specific migrations)."
|
21
|
+
task :reset => [ 'db:drop', 'db:create', 'zu:sports:migrate', 'db:migrate'] do
|
22
|
+
# Renamed this reset because there's a db:reset task that does the same thing...
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Incrementally update the database based on job's frequency. Usage: 'rake zu:sports:update[1]'"
|
26
|
+
task :update, [:frequency] => 'zu:set_logger' do |t, args|
|
27
|
+
case args.frequency
|
28
|
+
when "1" then SportsBuildManager.every_1_minute
|
29
|
+
when "15" then SportsBuildManager.every_15_minutes
|
30
|
+
when "60" then SportsBuildManager.every_60_minutes
|
31
|
+
when "720" then SportsBuildManager.every_720_minutes
|
32
|
+
when "1440" then SportsBuildManager.every_1440_minutes
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Run game_updater (sub-1 minute task)"
|
37
|
+
task :run_games_updater, [] => ['zu:set_logger'] do |t|
|
38
|
+
SportsBuildManager.games_updater
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Build the entire database 'from scratch' without reference to job scheduling"
|
42
|
+
task :build, [] => ['zu:set_logger'] do |t|
|
43
|
+
%w(one_time every_1440_minutes every_720_minutes every_15_minutes every_1_minute).each do |m|
|
44
|
+
SportsBuildManager.send(m)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Run one time and 12 hour builds prior to starting FeedFetcher on a clean instance of TSN server"
|
49
|
+
task :build_to_day, [] => ['zu:set_logger'] do |t|
|
50
|
+
SportsBuildManager.one_time
|
51
|
+
SportsBuildManager.every_1440_minutes
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Parse team info xml from sporting news and fetch all team logos to local disk."
|
55
|
+
task :get_team_logos => 'zu:set_logger' do
|
56
|
+
|
57
|
+
mkdir "#{Rails.root.to_s}/public/images/logos/" unless File.exists?( "#{Rails.root.to_s}/public/images/logos/" )
|
58
|
+
|
59
|
+
config = SimpleConfig.for(:feeds)
|
60
|
+
require 'open-uri'
|
61
|
+
open( config.team_info_url ) do |file|
|
62
|
+
|
63
|
+
doc = Nokogiri::XML(file.read)
|
64
|
+
doc.xpath('//row').each do |team_element|
|
65
|
+
%w{small medium large}.each do |size|
|
66
|
+
image = open( team_element['TEAM_LOGO'].gsub( /large/, size ) )
|
67
|
+
path = "#{Rails.root.to_s}/public/images/logos/#{size}"
|
68
|
+
mkdir path unless File.exists?( path )
|
69
|
+
/\.(\w+)$/ =~ team_element['TEAM_LOGO']
|
70
|
+
File.open("#{path}/#{team_element['TEAM_NAME_SHORT']}.#{$1}", "w") do |file|
|
71
|
+
file.puts( image.read )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Set up Stats LLC database, same schema as the mlb database, just with a different name, for testing"
|
79
|
+
task :create_statsllc_database => 'zu:set_logger' do
|
80
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read("#{Rails.root.to_s}/config/statsllc_database.yml"))
|
81
|
+
|
82
|
+
ActiveRecord::Base.establish_connection("#{Rails.env}")
|
83
|
+
ActiveRecord::Base.connection.drop_database(ActiveRecord::Base.configurations[Rails.env]["database"])
|
84
|
+
ActiveRecord::Base.connection.create_database(ActiveRecord::Base.configurations[Rails.env]["database"], ActiveRecord::Base.configurations[Rails.env])
|
85
|
+
|
86
|
+
ActiveRecord::Base.establish_connection("#{Rails.env}")
|
87
|
+
|
88
|
+
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
89
|
+
ActiveRecord::Migrator.migrate("vendor/plugins/sports_db/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
90
|
+
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Run migrations on statsllc db"
|
94
|
+
task :migrate_statsllc_database => 'zu:set_logger' do
|
95
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read("#{Rails.root.to_s}/config/statsllc_database.yml"))
|
96
|
+
|
97
|
+
ActiveRecord::Base.establish_connection("#{Rails.env}")
|
98
|
+
|
99
|
+
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
100
|
+
ActiveRecord::Migrator.migrate("vendor/plugins/sports_db/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
101
|
+
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/time_service.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
class TimeService
|
2
|
+
def self.now
|
3
|
+
config = SimpleConfig.for(:application)
|
4
|
+
config.timeservice.call()
|
5
|
+
rescue
|
6
|
+
Rails.logger.error("Could not call time service: SimpleConfig is not initialized, there's no application scope, no timeservice property, or it's not a lambda.")
|
7
|
+
return DateTime.now.utc
|
8
|
+
end
|
9
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Alx Dark
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2013-04-
|
17
|
+
date: 2013-04-26 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -71,16 +71,21 @@ files:
|
|
71
71
|
- app/models/player_stat.rb
|
72
72
|
- app/models/poll_ranking.rb
|
73
73
|
- app/models/score.rb
|
74
|
+
- app/models/score_notification.rb
|
74
75
|
- app/models/twitter.rb
|
75
76
|
- config/routes.rb
|
76
77
|
- lib/sports_db/engine.rb
|
77
78
|
- lib/sports_db/external_feed_builder.rb
|
78
79
|
- lib/sports_db/media_builder.rb
|
80
|
+
- lib/sports_db/news_builder.rb
|
79
81
|
- lib/sports_db/news_filters_builder.rb
|
82
|
+
- lib/sports_db/sporting_news_feed_builder.rb
|
83
|
+
- lib/sports_db/team_and_roster_builder.rb
|
80
84
|
- lib/sports_db/twitter_builder.rb
|
81
85
|
- lib/sports_db/version.rb
|
82
86
|
- lib/sports_db.rb
|
83
87
|
- lib/tasks/sports_db_tasks.rake
|
88
|
+
- lib/time_service.rb
|
84
89
|
- MIT-LICENSE
|
85
90
|
- Rakefile
|
86
91
|
- README.rdoc
|