Last active
August 25, 2016 02:04
-
-
Save krolow/84cb6be5bd296b293579203e3870d1c2 to your computer and use it in GitHub Desktop.
Brasileirao em node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var request = require('request'); | |
var Handlebars = require('handlebars'); | |
var crypto = require('crypto'); | |
var CHAMPIONSHIP_URL = 'https://webtask.it.auth0.com/api/run/wt-krolow-gmail_com-0/brasileirao?serie=%serie%'; | |
function fetchChampionship (serie) { | |
return new Promise(function (resolve, reject) { | |
request( | |
{ | |
url: CHAMPIONSHIP_URL.replace(/%serie%/, serie), | |
json: true, | |
timeout: 130000, | |
}, | |
function (error, response, body) { | |
if (error || response.statusCode !== 200) { | |
return reject(error || response.statusCode); | |
} | |
return resolve(body); | |
} | |
); | |
}); | |
} | |
function grabTeamMatches (rounds, team) { | |
return rounds | |
.reduce(function (acc, curr) { | |
return acc.concat( | |
addRoundToMatches( | |
curr.round, | |
filterTeamMatches(team, curr.matches) | |
) | |
); | |
}, []); | |
} | |
function addRoundToMatches (round, matches) { | |
return matches.map(function (match) { | |
match.round = round; | |
return match; | |
}); | |
} | |
function filterTeamMatches (team, matches) { | |
return matches.filter(isTeamPlayingMatch.bind(null, team)); | |
} | |
function isTeamPlayingMatch (team, match) { | |
var expression = new RegExp('^' + team + '.*', 'gi'); | |
return expression.test(match.home.name) || expression.test(match.guest.name); | |
} | |
function filterMatchesWithDateAndTime (matches) { | |
return matches.filter(function (match) { | |
var date = new Date(match.datetime); | |
return date.getUTCHours() !== 0; | |
}); | |
} | |
function getTemplate () { | |
return Handlebars.compile("\ | |
BEGIN:VCALENDAR\r\n\ | |
VERSION:2.0\r\n\ | |
NAME:Calendário Brasileirao {{serie}} - Jogos {{team}}\r\n\ | |
X-WR-CALNAME;VALUE=TEXT:Calendário Brasileirao {{serie}} - Jogos {{team}}\r\n\ | |
X-WR-CALDESC:Calendário Brasileirao {{serie}} - Jogos {{team}}\r\n\ | |
X-GOOGLE-CALENDAR-CONTENT-TITLE:Calendário Brasileirao {{serie}} - Jogos {{team}}\r\n\ | |
CALSCALE:GREGORIAN\r\n\ | |
METHOD:PUBLISH\r\n\ | |
BEGIN:VTIMEZONE\r\n\ | |
REFRESH-INTERVAL;VALUE=DURATION:PT12H\r\n\ | |
X-PUBLISHED-TTL:PT12H\r\n\ | |
TZID:America/Sao_Paulo\r\n\ | |
TZURL:http://tzurl.org/zoneinfo-outlook/America/Sao_Paulo\r\n\ | |
X-LIC-LOCATION:America/Sao_Paulo\r\n\ | |
BEGIN:DAYLIGHT\r\n\ | |
TZOFFSETFROM:-0300\r\n\ | |
TZOFFSETTO:-0200\r\n\ | |
TZNAME:BRST\r\n\ | |
DTSTART:19701018T000000\r\n\ | |
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=3SU\r\n\ | |
END:DAYLIGHT\r\n\ | |
BEGIN:STANDARD\r\n\ | |
TZOFFSETFROM:-0300\r\n\ | |
TZOFFSETTO:-0300\r\n\ | |
TZNAME:BRT\r\n\ | |
DTSTART:19700215T000000\r\n\ | |
RRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=3SU\r\n\ | |
END:STANDARD\r\n\ | |
END:VTIMEZONE\r\n\ | |
{{#matches}}\ | |
BEGIN:VEVENT\r\n\ | |
UID:{{uid}}\r\n\ | |
DTSTAMP:{{date}}\r\n\ | |
DTSTART;TZID=\"America/Sao_Paulo\":{{start}}\r\n\ | |
DTEND;TZID=\"America/Sao_Paulo\":{{end}}\r\n\ | |
SUMMARY:{{summary}}\r\n\ | |
DESCRIPTION:{{description}}\r\n\ | |
LOCATION:{{location}}\r\n\ | |
BEGIN:VALARM\r\n\ | |
ACTION:DISPLAY\r\n\ | |
DESCRIPTION:{{description}}\r\n\ | |
TRIGGER:-PT15M\r\n\ | |
END:VALARM\r\n\ | |
END:VEVENT\r\n\ | |
{{/matches}}\r\n\ | |
END:VCALENDAR"); | |
} | |
function prepareMatches (serie, matches) { | |
return matches.map(function (match) { | |
return { | |
date: new Date().toISOString().replace(/\-|\:|\.[0-9]{3}/gi, ''), | |
uid: crypto.createHash('md5').update(match.home.name + match.guest.name).digest('base64'), | |
summary: match.home.name + ' vs ' + match.guest.name, | |
description: prepareDescription(serie, match), | |
start: prepareDate(new Date(match.datetime)), | |
end: prepareDate(new Date(new Date(match.datetime).getTime() + 110 * 60000)), | |
location: match.stadium, | |
}; | |
}); | |
} | |
function prepareDescription (serie, match) { | |
return 'Campeonato Brasileiro Série: ' + serie.toUpperCase() + '\\n' | |
+ 'Rodada: ' + match.round + '\\n' | |
+ 'Jogo: ' + match.home.name + ' vs ' + match.guest.name + '\\n' | |
+ 'Estádio: ' + match.stadium; | |
} | |
function prepareDate (date) { | |
return date.getFullYear() | |
.toString() | |
.concat(addZeroIfNeeded(date.getMonth() + 1)) | |
.concat(addZeroIfNeeded(date.getDate())) | |
.concat('T') | |
.concat(addZeroIfNeeded(date.getHours())) | |
.concat(addZeroIfNeeded(date.getMinutes())) | |
.concat(addZeroIfNeeded(date.getSeconds())); | |
} | |
function addZeroIfNeeded (value) { | |
var string = value.toString(); | |
if (string.length == 2) { | |
return string; | |
} | |
return '0' + string; | |
} | |
function validate (data) { | |
if (!data.serie || ['a', 'b'].indexOf(data.serie.toLowerCase()) === -1) { | |
return false; | |
} | |
if (!data.team) { | |
return false; | |
} | |
return true; | |
} | |
var template = getTemplate(); | |
function generateIcal (serie, team, championship) { | |
return template({ | |
serie: serie.toUpperCase(), | |
team: team, | |
matches: prepareMatches( | |
serie, | |
filterMatchesWithDateAndTime( | |
grabTeamMatches(championship.rounds, team) | |
) | |
), | |
}); | |
} | |
function fetchChampionshipAndGenerateIcal (serie, team) { | |
return fetchChampionship(serie) | |
.then(generateIcal.bind(null, serie, team)); | |
} | |
function generateFilename (serie, team) { | |
return 'campeonato-brasileiro-' | |
+ serie.toLowerCase() | |
+ '-jogos-' | |
+ team.split(' ').join('-').toLowerCase() | |
+ '.ical'; | |
} | |
function app (context, req, res) { | |
var serie = context.data.serie; | |
var team = context.data.team | |
if (!validate({ serie: serie, team: team })) { | |
res.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'}); | |
return res.end('You must pass query string: team and serie (a or b)'); | |
} | |
fetchChampionshipAndGenerateIcal(serie, team) | |
.then(function (ical) { | |
res.writeHead(200, { | |
'Content-Type': 'application/force-download', | |
'Content-disposition':'attachment; filename=' + generateFilename(serie, team), | |
}); | |
res.end(ical); | |
}) | |
.catch(function (error) { | |
if (!isNaN(parseInt(err, 10))) { | |
res.writeHead(err); | |
return res.end(); | |
} | |
res.writeHead(500); | |
res.end(JSON.stringify({ | |
error: { | |
msg: 'Something went wrong :(', | |
error: err.stack, | |
} | |
})); | |
}); | |
} | |
module.exports = app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Promise = require('bluebird'); | |
var request = require('request'); | |
var $ = require('cheerio'); | |
var url = require('url'); | |
var http = require('http'); //remove | |
var CHAMPIONSHIP_URL = 'http://globoesporte.globo.com/futebol/brasileirao-serie-%serie%/'; | |
var ROUND_URL = 'http://globoesporte.globo.com%widget%%round%/jogos.html'; | |
function fetchChampionship (serie) { | |
return fetch(CHAMPIONSHIP_URL.replace(/%serie%/, serie)); | |
} | |
function urlForRound (widget) { | |
return ROUND_URL.replace(/%widget%/, widget); | |
} | |
function fetchRound (url, round) { | |
return fetch(url.replace(/%round%/, round)); | |
} | |
function fetch (url) { | |
return new Promise(function (resolve, reject) { | |
request(url, function (error, response, body) { | |
if (error || response.statusCode !== 200) { | |
return reject(error || response.statusCode); | |
} | |
return resolve({url: url, context: $(body)}); | |
}); | |
}); | |
} | |
function grabRound (context) { | |
var round = $('.tabela-navegacao-jogos .tabela-navegacao-seletor', context); | |
var current = parseInt(round.attr('data-rodada'), 10); | |
var last = parseInt(round.attr('data-rodadas-length'), 10); | |
return { | |
current: current, | |
last: last, | |
}; | |
} | |
function grabMatches (context) { | |
var matches = [] | |
$('.placar-jogo', context).each(function () { | |
matches.push(grabMatch(this)); | |
}); | |
return matches; | |
} | |
function grabMatch (context) { | |
var homeCtx = $('span.placar-jogo-equipes-mandante', context); | |
var guestCtx = $('span.placar-jogo-equipes-visitante', context); | |
return { | |
home: { | |
name: $('meta', homeCtx).attr('content'), | |
shield: $('img', homeCtx).attr('src'), | |
score: $('.placar-jogo-equipes-placar-mandante', context).text(), | |
}, | |
guest: { | |
name: $('meta', guestCtx).attr('content'), | |
shield: $('img', guestCtx).attr('src'), | |
score: $('.placar-jogo-equipes-placar-visitante', context).text(), | |
}, | |
stadium: $('.placar-jogo-informacoes-local', context).text(), | |
datetime: grabMatchDatetime(context), | |
url: $('a.placar-jogo-link', context).attr('href'), | |
}; | |
} | |
function grabMatchDatetime (context) { | |
var date = $('meta[itemprop="startDate"]', context).attr('content'); | |
var time = $('div.placar-jogo-informacoes', context) | |
.text() | |
.match(/[0-9]{2}\:[0-9]{2}/); | |
if (time === null) { | |
return new Date(date + ' 00:00:00'); | |
} | |
var date = new Date(date + ' ' + time.shift() + ':00'); | |
date.setHours(date.getHours() + 3); | |
return date; | |
} | |
function grabWidgetUrl (context) { | |
return $('aside.lista-de-jogos', context).attr('data-url-pattern-navegador-jogos'); | |
} | |
function isValidSerie (serie) { | |
return ['a', 'b'].indexOf(serie.toLowerCase()) !== -1; | |
} | |
function parseChampionship (data) { | |
} | |
function fetchChampionshipAndParse (serie) { | |
return fetchChampionship(serie) | |
.then(parseChampionship); | |
} | |
function parseChampionship (data) { | |
var round = grabRound(data.context); | |
var currentRound = { | |
round: round.current, | |
matches: grabMatches(data.context), | |
}; | |
return { | |
url: data.url, | |
widget: grabWidgetUrl(data.context), | |
round: grabRound(data.context), | |
rounds: [currentRound], | |
}; | |
} | |
function fetchRoundsAndParse (championship) { | |
var rounds = roundsAsArray( | |
championship.round.current, | |
championship.round.last | |
); | |
var url = urlForRound(championship.widget); | |
return Promise.map( | |
rounds, | |
fetchRoundAndParse.bind(null, url), | |
{ concurrency: 2 } | |
).then(joinRoundsWithChampionship.bind(null, championship) | |
); | |
} | |
function fetchRoundAndParse (url, round) { | |
return fetchRound(url, round) | |
.then(parseRound.bind(null, round)); | |
} | |
function joinRoundsWithChampionship (championship, rounds) { | |
championship.rounds = championship.rounds.concat(rounds); | |
return sortChampionshipRounds(championship); | |
} | |
function sortChampionshipRounds (championship) { | |
championship.rounds = championship.rounds.sort(function (a, b) { | |
if (a.round > b.round) { | |
return 1; | |
} | |
return -1; | |
}); | |
return championship; | |
} | |
function fetchChampionshipRoundsAndParseIfNeeded (cache, serie) { | |
var result; | |
if (cache.has(serie)) { | |
result = Promise.resolve(cache.get(serie)); | |
} | |
if (cache.isValid(serie)) { | |
return result; | |
} | |
var promise = fetchChampionshipRoundsAndParse(serie) | |
.then(function (data) { | |
cache.write(serie, data); | |
return data; | |
}); | |
if (result) { | |
return result; | |
} | |
return promise; | |
} | |
function fetchChampionshipRoundsAndParse (serie) { | |
return fetchChampionshipAndParse(serie) | |
.then(fetchRoundsAndParse); | |
} | |
function parseRound (round, data) { | |
return { | |
round: round, | |
matches: grabMatches(data.context), | |
}; | |
}; | |
function roundsAsArray (current, last) { | |
var rounds = []; | |
for (var i=1; i<=last; i++) { | |
if (i === current) { | |
continue; | |
} | |
rounds.push(i); | |
} | |
return rounds; | |
} | |
function cache (expiration) { | |
var storage = {}; | |
return { | |
has: function (serie) { | |
return typeof storage[serie] !== 'undefined'; | |
}, | |
isValid: function (serie) { | |
if (!this.has(serie)) { | |
return false; | |
} | |
return new Date().getTime() < storage[serie].expiration; | |
}, | |
write: function (serie, data) { | |
storage[serie] = { | |
expiration: new Date().getTime() + expiration, | |
data: data, | |
}; | |
}, | |
get: function (serie) { | |
return storage[serie].data; | |
}, | |
size: Object.keys(storage), | |
}; | |
} | |
var internalCache = cache(900000); //15 minutes | |
var headers = { | |
'Content-Type': 'application/json; charset=utf-8' | |
}; | |
function app (context, req, res) { | |
var serie = context.data.serie; | |
if (!serie || !isValidSerie(serie)) { | |
res.writeHead(400, headers) | |
return res.end( | |
JSON.stringify({ | |
error: { | |
msg: 'You must pass as query string the championship serie' | |
} | |
}) | |
); | |
} | |
fetchChampionshipRoundsAndParseIfNeeded(internalCache, serie.toLowerCase()) | |
.then(function (data) { | |
res.writeHead(200, headers); | |
res.end(JSON.stringify(data)) | |
}) | |
.catch(function (err) { | |
if (!isNaN(parseInt(err, 10))) { | |
res.writeHead(err); | |
return res.end(); | |
} | |
res.writeHead(500); | |
res.end(JSON.stringify({ | |
error: { | |
msg: 'Something went wrong :(', | |
error: err.stack, | |
} | |
})); | |
}); | |
} | |
module.exports = app; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment