module SportsDb class SportingNewsFeedBuilder def self.update_news_feed config_feeds = SimpleConfig.for(:feeds) Article.transaction do # #delete existing articles older than 30 days # remove_date = TimeService.now - 30.day # sn_articles_old = Article.find(:all, :conditions => ["source = 'Sporting News' and published_at > ?", remove_date]) # sn_articles_old.each {|article| article.destroy } update_webgen_feeds(config_feeds.news_feeds, "news") update_webgen_feeds(config_feeds.breaking_news_feeds, "breaking") if CONFIG.affiliation_key == 'l.nfl.com' update_webgen_feeds(config_feeds.team_feeds, "pro_team_news") elsif CONFIG.affiliation_key == 'l.nba.com' update_webgen_feeds(config_feeds.team_feeds, "pro_team_news") elsif CONFIG.affiliation_key == 'l.mlb.com' update_webgen_feeds(config_feeds.team_feeds, "pro_team_news") update_webgen_feeds(config_feeds.fantasy_news_feeds, "fantasy") elsif CONFIG.affiliation_key.match('l.ncaa.org') update_webgen_feeds(ExternalFeed.find(:all, :conditions => ["content_type = ? and provider = ?", "news", "Sporting News"]), "ncaa_team_news") end end end def self.update_webgen_feeds(feed_list, list_type) if list_type == "ncaa_team_news" feed_list.each do |feed_obj| feed_team = Team.find(feed_obj.team_id) if !feed_team.nil? feed_url = feed_obj.woven_feed_url if CONFIG.woven_feed_server != "woven.zumobi.net" feed_url = feed_url.gsub("woven.zumobi.net", CONFIG.woven_feed_server) end list_type = "team_news" rss = retrieve_feed(feed_url) parse_webgen_feeds(rss, feed_team.key, feed_url, list_type, feed_team.city_name) end end else feed_list.each do |feed_title, url| if list_type == "pro_team_news" feed_key = feed_title if Team.column_names.include?("tsn_key") && feed_key.to_s.match(CONFIG.affiliation_key) feed_key = tsn_keys_to_stats_key(feed_key) end team = Team.find_by_key(feed_key) feed_title = (!team.nil?) ? team.city_name : feed_title list_type = "team_news" elsif list_type == "breaking" feed_key = CONFIG.affiliation_key elsif list_type == "news" feed_key = CONFIG.affiliation_key elsif list_type == "fantasy" feed_key = "fantasy" end if !feed_key.blank? rss = retrieve_feed(url) parse_webgen_feeds(rss, feed_key, url, list_type, feed_title) end end end rescue Exception => e Zumobi::ExceptionHandler.error e end def self.parse_webgen_feeds(rss, feed_key, url, list_type, feed_title) p "News - #{feed_title} - #{url}" article_count = 0 doc = Nokogiri::XML(rss) source = "Sporting News" if !doc.nil? doc.xpath('//item').each do |node| article_count += 1 if feed_key == CONFIG.affiliation_key article_link = node.xpath('link').text if !article_link.match(CONFIG.sn_breaking_news_match) next end end guid = node.xpath('guid').text item_title = node.xpath('title').text if Article.find_by_digest(guid).nil? && Article.find_by_title(item_title).nil? article_obj = Article.new article_obj.title = node.xpath('title').text article_obj.published_at = node.xpath('pubDate').text article_obj.link = node.xpath('link').text article_obj.digest = guid article_obj.source = source if list_type == "team_news" || list_type == "team_news_no_filters" article_obj.category = "Team News" elsif list_type == "breaking" || list_type == "news" article_obj.category = "League News" elsif list_type == "fantasy" article_obj.category = "Fantasy News" else article_obj.category = "News" end if guid.blank? article_obj.set_digest end if list_type == "breaking" article_obj.author = (!node.xpath('author').nil?) ? node.xpath('author').text : "" article_obj.contents = (!node.xpath('description').nil?) ? node.xpath('description').text.strip : "" else article_obj.author = (!node.xpath('dc:creator').nil?) ? node.xpath('dc:creator').text : "" article_obj.contents = (!node.xpath('content:encoded').nil?) ? node.xpath('content:encoded').text.strip : "" thumb_image = (!node.xpath('media:thumbnail').nil?) ? node.xpath('media:thumbnail/@url').text : "" if !thumb_image.blank? if CONFIG.affiliation_key == 'l.nfl.com' article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/110/url/' + CGI::escape(CGI::escape(thumb_image)) article_obj.article_image_url = CONFIG.image_service + 'transform/w/480/h/480/url/' + CGI::escape(CGI::escape(thumb_image)) else article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/55/url/' + CGI::escape(CGI::escape(thumb_image)) article_obj.article_image_url = CONFIG.image_service + 'transform/w/280/h/280/url/' + CGI::escape(CGI::escape(thumb_image)) end end end if article_obj.title.blank? || article_obj.title == "." || article_obj.contents.blank? || article_obj.contents == "." next end if list_type == "fantasy" set_fantasy_article_filter(article_obj, feed_key) else set_article_filter_association(article_obj, feed_key) end article_obj.save else Article.transaction do article_obj = Article.find_by_digest(guid) if article_obj.nil? article_obj = Article.find_by_title(item_title) end article_obj.title = node.xpath('title').text article_obj.published_at = node.xpath('pubDate').text article_obj.link = node.xpath('link').text if list_type == "breaking" article_obj.author = (!node.xpath('author').nil?) ? node.xpath('author').text : "" article_obj.contents = (!node.xpath('description').nil?) ? node.xpath('description').text.strip : "" else article_obj.author = (!node.xpath('dc:creator').nil?) ? node.xpath('dc:creator').text : "" article_obj.contents = (!node.xpath('content:encoded').nil?) ? node.xpath('content:encoded').text.strip : "" thumb_image = (!node.xpath('media:thumbnail').nil?) ? node.xpath('media:thumbnail/@url').text : "" if !thumb_image.blank? if CONFIG.affiliation_key == 'l.nfl.com' article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/110/url/' + CGI::escape(CGI::escape(thumb_image)) article_obj.article_image_url = CONFIG.image_service + 'transform/w/480/h/480/url/' + CGI::escape(CGI::escape(thumb_image)) else article_obj.thumb_image_url = CONFIG.image_service + 'crop/w/55/url/' + CGI::escape(CGI::escape(thumb_image)) article_obj.article_image_url = CONFIG.image_service + 'transform/w/280/h/280/url/' + CGI::escape(CGI::escape(thumb_image)) end end end if list_type == "fantasy" set_fantasy_article_filter(article_obj, feed_key) else set_article_filter_association(article_obj, feed_key) end article_obj.save end end p "Article - #{article_obj.title}" remove_flash_elements(article_obj) if (list_type == "team_news" || list_type == "breaking") && CONFIG.enable_notifications if list_type == "team_news" if Team.column_names.include?("tsn_key") team = Team.find_by_key(feed_key) notify_feed_key = team.tsn_key else notify_feed_key = feed_key end send_notification(article_obj, notify_feed_key, list_type) else send_notification(article_obj, feed_key, list_type) end end end p "#{article_count} articles processed" end end def self.set_article_filter_association(article, feed_key) #all if !article.news_filters.find(:first, :conditions => "news_filter_key = 'all'") filter = NewsFilter.find(:first, :conditions => ["news_filter_key = 'all'"]) filter.articles << article filter.save end if feed_key != CONFIG.affiliation_key #team if Team.column_names.include?("tsn_key") && feed_key.to_s.match(CONFIG.affiliation_key) feed_key = tsn_keys_to_stats_key(feed_key) end team = Team.find_by_key(feed_key) if !team.nil? if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", team.key]) filter_team = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", team.key]) if !filter_team.nil? filter_team.articles << article end end #conference if CONFIG.affiliation_key.match('l.ncaa.org') conference_key = team.conference.key else conference_key = team.conference end if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", conference_key]) filter_conference = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", conference_key]) if !filter_conference.nil? filter_conference.articles << article end end end else #league if !article.news_filters.find(:first, :conditions => ["news_filter_key = ?", feed_key]) filter_conference = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", feed_key]) if !filter_conference.nil? filter_conference.articles << article end end end end def self.set_fantasy_article_filter(article, feed_key) if !article.news_filters.find(:first, :conditions => "news_filter_key = 'fantasy'") filter = NewsFilter.find(:first, :conditions => ["news_filter_key = 'fantasy'"]) if !filter.nil? filter.articles << article filter.save end end end def self.remove_flash_elements(article) doc = Nokogiri::HTML(article.contents) objects = doc.search("embed") objects.each do |obj| obj.remove end contents = doc.children[1].to_html contents = contents.gsub("\n", '') contents = contents.gsub('', '') article.contents = contents article.save end def self.send_notification(article, team_key, notify_type) #checks to see if the article is new enough #checks if notification has been sent yet. if there's a row in the notifications table, then it's been sent if !article.nil? && !article.digest.blank? n = Notification.find(:first, :conditions => ['title = ? and category = ?', article.title, team_key]) if (n.nil? && article.published_at > CONFIG.send_notifications_since.call()) n = Notification.new n.title = article.title n.article_digest = article.digest n.category = team_key n.article_id = article.id n.article_link = article.link n.notify_sent = 0 if team_key != CONFIG.affiliation_key team = Team.find_by_tsn_key(team_key) if !team.nil? n.team_id = team.id end end n.save elsif (!n.nil? && n.notify_sent == 0 && article.published_at > CONFIG.send_notifications_since.call()) #putting a delay in for sending out notify rather than sending right when we get the article. #i'm sure that there's a caching issue between the backend and front end. hopefully this will help work that out. alert_title = article.title[0,90] if article.title.length > 90 alert_title += "..." end n.notify_sent = 1 n.save if notify_type == "team_news" && !n.team_id.nil? send_push_notification(alert_title, [team_key], article.digest) elsif notify_type == "breaking" send_push_notification(alert_title, [team_key], article.digest) end else if (!n.nil?) #Rails.logger.info("Already sent (Notification_id: #{n.id})") else #Rails.logger.info("Article too old to notify (#{article.digest})") end end end end # Sends a notification for the specified app / notification class combo. # # Parameters: # application_name - The name of the application the notification is intended for. # tag - data goes into "tags" field # alert - The text to display in the notification. # digest - An additional field added for NFL - this is the article id. # tag_key - An additional field added for NFL - this is the tag that the notifs is subscribed to team or league. # 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. def self.send_push_notification(alert_text, tags, digest) p "--Notified! '#{alert_text[0,50]}' at #{DateTime.now.to_s}: Tag: #{tag}, Response: #{res.code} #{res.message}" notifier = Zumobi::Notifier.new notifier.push({ :aps => {:alert => alert_text, :sound => "1"}, :android => {:alert => alert_text, :extra => "#{tags.join(',')}|#{digest}"}, :tags => tags, :tag_key => tags.join(','), :digest => digest }) end def self.retrieve_feed(url) body = make_request(url) body end def self.remove_dup_and_old_articles() teams = Team.find(:all, :conditions => ["division is not null"]) teams.each do |t| p "#{t.city_name} #{t.team_name}" news_filter = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", t.key]) remove_by_news_filter(news_filter) end p "All" news_filter = NewsFilter.find(:first, :conditions => ["news_filter_key = ?", "all"]) remove_by_news_filter(news_filter) end def self.remove_by_news_filter(news_filter) if !news_filter.nil? article_titles = {} team_articles = news_filter.articles.find(:all) team_articles.each do |a| article_digest = Digest::SHA1.hexdigest(a.sn_url) if article_titles[article_digest].blank? article_titles[article_digest] = a p "---- #{a.title}" else p "Dup! - #{a.title}" this_article = a saved_article = article_titles[article_digest] if this_article.published_at > saved_article.published_at article_titles[article_digest] = this_article saved_article.destroy else this_article.destroy end end end end end private def self.make_request(url) begin c = Curl::Easy.perform(url) do |curl| curl.connect_timeout = 120 curl.follow_location = true curl.max_redirects = 10 curl.timeout = 120 curl.encoding = 'gzip' # curl.verbose = true end c.body_str rescue Curl::Err::TimeoutError # rescue Curl::Err::GotNothingError # rescue Curl::Err::RecvError # end end def self.tsn_keys_to_stats_key(tsn_key) #static map for stats global-id to tsn team_key keys = { "l.nfl.com-t.1" => 324, "l.nfl.com-t.2" => 338, "l.nfl.com-t.3" => 345, "l.nfl.com-t.4" => 348, "l.nfl.com-t.5" => 352, "l.nfl.com-t.6" => 366, "l.nfl.com-t.7" => 327, "l.nfl.com-t.8" => 329, "l.nfl.com-t.9" => 365, "l.nfl.com-t.10" => 356, "l.nfl.com-t.11" => 336, "l.nfl.com-t.12" => 332, "l.nfl.com-t.13" => 339, "l.nfl.com-t.14" => 341, "l.nfl.com-t.15" => 357, "l.nfl.com-t.16" => 361, "l.nfl.com-t.17" => 355, "l.nfl.com-t.18" => 331, "l.nfl.com-t.19" => 351, "l.nfl.com-t.20" => 354, "l.nfl.com-t.21" => 363, "l.nfl.com-t.22" => 326, "l.nfl.com-t.23" => 334, "l.nfl.com-t.24" => 335, "l.nfl.com-t.25" => 347, "l.nfl.com-t.26" => 362, "l.nfl.com-t.27" => 323, "l.nfl.com-t.28" => 343, "l.nfl.com-t.29" => 364, "l.nfl.com-t.30" => 350, "l.nfl.com-t.31" => 359, "l.nfl.com-t.32" => 325, "l.mlb.com-t.1" => 225, "l.mlb.com-t.2" => 226, "l.mlb.com-t.3" => 234, "l.mlb.com-t.4" => 254, "l.mlb.com-t.5" => 238, "l.mlb.com-t.6" => 228, "l.mlb.com-t.7" => 229, "l.mlb.com-t.8" => 230, "l.mlb.com-t.9" => 231, "l.mlb.com-t.10" => 233, "l.mlb.com-t.11" => 227, "l.mlb.com-t.12" => 235, "l.mlb.com-t.13" => 236, "l.mlb.com-t.14" => 237, "l.mlb.com-t.15" => 239, "l.mlb.com-t.16" => 252, "l.mlb.com-t.17" => 244, "l.mlb.com-t.18" => 245, "l.mlb.com-t.19" => 246, "l.mlb.com-t.20" => 240, "l.mlb.com-t.21" => 241, "l.mlb.com-t.22" => 242, "l.mlb.com-t.23" => 232, "l.mlb.com-t.24" => 247, "l.mlb.com-t.25" => 248, "l.mlb.com-t.26" => 253, "l.mlb.com-t.27" => 251, "l.mlb.com-t.28" => 243, "l.mlb.com-t.29" => 249, "l.mlb.com-t.30" => 250, "l.nba.com-t.1" => 2, "l.nba.com-t.2" => 14, "l.nba.com-t.3" => 17, "l.nba.com-t.4" => 18, "l.nba.com-t.5" => 19, "l.nba.com-t.6" => 20, "l.nba.com-t.7" => 27, "l.nba.com-t.8" => 1, "l.nba.com-t.9" => 3, "l.nba.com-t.10" => 4, "l.nba.com-t.11" => 5, "l.nba.com-t.12" => 8, "l.nba.com-t.13" => 11, "l.nba.com-t.14" => 15, "l.nba.com-t.15" => 28, "l.nba.com-t.16" => 6, "l.nba.com-t.17" => 7, "l.nba.com-t.18" => 10, "l.nba.com-t.19" => 29, "l.nba.com-t.20" => 16, "l.nba.com-t.21" => 24, "l.nba.com-t.22" => 26, "l.nba.com-t.23" => 9, "l.nba.com-t.24" => 12, "l.nba.com-t.25" => 13, "l.nba.com-t.26" => 21, "l.nba.com-t.27" => 22, "l.nba.com-t.28" => 23, "l.nba.com-t.29" => 25, "l.nba.com-t.32" => 5312 } if !tsn_key.blank? && !keys[tsn_key].blank? return keys[tsn_key] else return '' end end end end