Skip to content

Instantly share code, notes, and snippets.

@krolow
Last active August 25, 2016 02:04
Show Gist options
  • Save krolow/84cb6be5bd296b293579203e3870d1c2 to your computer and use it in GitHub Desktop.
Save krolow/84cb6be5bd296b293579203e3870d1c2 to your computer and use it in GitHub Desktop.
Brasileirao em node.js
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;
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