daily-typing-tourney/index.js

271 lines
8.8 KiB
JavaScript
Raw Permalink Normal View History

2018-07-17 16:47:32 -04:00
const url = require('url');
const _ = require('lodash');
const selenium = require('selenium-webdriver');
const { By, Util, until } = selenium
const firefox = require('selenium-webdriver/firefox');
const slack = require('slack');
const cheerio = require('cheerio');
const config = require('./config/config');
const q = require('q');
const fetch = require('node-fetch');
const argv = require('yargs').argv;
2018-07-17 16:47:32 -04:00
2018-07-30 12:56:20 -04:00
require('geckodriver');
const isProduction = !!argv.production;
const notHeadless = !!argv.notheadless;
console.log('Production?', isProduction);
console.log('Run non-headless?', notHeadless);
2018-07-17 16:47:32 -04:00
const getNewTournament = () => {
const opts = new firefox.Options();
2018-07-17 16:47:32 -04:00
if(isProduction && !notHeadless) {
opts.addArguments('--headless');
}
const driver = new selenium.Builder()
.forBrowser('firefox')
.setFirefoxOptions(opts)
.build();
2018-07-17 16:47:32 -04:00
let href;
2018-07-17 16:47:32 -04:00
return q.resolve()
.then(() => driver.get('https://10fastfingers.com/login'))
// Effing GPDR
.then(() => driver.wait(until.elementsLocated(By.css(".cc-dismiss"))))
.then(() => driver.findElement(By.css('.cc-dismiss')).click())
2018-07-17 16:47:32 -04:00
.then(() => driver.findElement(By.css('.social-login.twitter-btn-tb')).click())
.then(() => driver.wait(until.elementsLocated(By.css("#username_or_email"))))
.then(() => driver.findElement(By.css('#username_or_email')).sendKeys(config.twitter.username))
.then(() => driver.findElement(By.css('#password')).sendKeys(config.twitter.password))
.then(() => driver.findElement(By.css('#oauth_form input[type="submit"]')).click())
.then(() => driver.wait(until.urlContains('/typing-test/')))
.then(() => driver.get('https://10fastfingers.com/competitions'))
.then(() => driver.findElement(By.css('[href="#create-game"]')).click())
.then(() => driver.findElement(By.css('#private-competition')).click())
.then(() => driver.findElement(By.css('#speedtestid1')).click())
.then(() => driver.findElement(By.css('#link-create-competition')).click())
.then(() => driver.findElement(By.css('#share-link a')).getAttribute('href'))
.then(h => (href = h, driver.quit()))
.then(() => href)
.catch((e) => {
driver.quit();
throw e;
});
2018-07-17 16:47:32 -04:00
}
const getLastTournamentMessage = () => {
return q.resolve()
.then(() => slack.search.messages({
token: config.slack.token,
query: `in:${config.slack.channels.map(x => x.name).join(',')} has:link 10fastfingers competition`,
2018-07-17 16:47:32 -04:00
sort: 'timestamp',
}))
.then(slackResults => {
const slackMatches = slackResults.messages.matches;
if(!slackMatches.length) {
return [];
}
let attachment;
let slackMatch;
for (const m of slackMatches) {
slackMatch = m;
attachment = m.attachments.find(x => /\/10fastfingers.com\/competition\//gi.test(x.from_url));
if(attachment) {
break;
}
}
console.log('Last message', slackMatch);
2018-07-17 16:47:32 -04:00
return attachment && slackMatch && {
message: slackMatch,
attachment: attachment,
};
});
};
const getLastTournamentResults = (msg) => {
const urlPieces = new URL(msg.attachment.from_url).pathname.split('/');
urlPieces.reverse();
const hash = urlPieces.find(x => x);
const params = new url.URLSearchParams();
params.append('hash_id', hash);
let users;
2018-07-17 16:47:32 -04:00
return q.resolve()
.then(() => fetch(`https://10fastfingers.com/competitions/get_competition_rankings`, {
method: 'POST',
body: params,
headers: {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
},
}))
.then(r => r.text())
.then(text => {
const doc = cheerio.load(text);
const rows = doc('#competition-rank-table tbody tr').has('td.tests_taken');
users = _.times(rows.length, () => ({}));
Array.from(rows.find('td.rank span')).forEach((rank, idx) => {
2018-07-17 16:47:32 -04:00
try {
users[idx].rank = parseInt(rank.children[0].data);
}
catch(e) { }
});
Array.from(rows.find('td.username a')).forEach((username, idx) => {
2018-07-17 16:47:32 -04:00
users[idx].username = username.children[0].data;
users[idx].userHref = username.attribs.href;
})
2018-07-17 16:47:32 -04:00
Array.from(rows.find('td.wpm')).forEach((wpm, idx) => {
2018-07-17 16:47:32 -04:00
try {
users[idx].wpm = parseFloat(wpm.children[0].data);
}
catch(e) { }
});
Array.from(rows.find('td.keystrokes')).forEach((keystrokes, idx) => {
2018-07-17 16:47:32 -04:00
try {
2018-07-26 11:34:56 -04:00
users[idx].keystrokes = parseFloat((/[0-9\.]+/gi).exec(keystrokes.children[0].data)[0]);
2018-07-17 16:47:32 -04:00
}
catch(e) { }
});
Array.from(rows.find('td.tests_taken')).forEach((testsTaken, idx) => {
2018-07-17 16:47:32 -04:00
try {
users[idx].testsTaken = parseFloat(testsTaken.children[0].data);
}
catch(e) { }
});
})
.then(() => {
return q.all(config.slack.channels.map(channel => {
return slack.conversations.members({
token: config.slack.token,
channel: channel.id,
});
}));
})
.then(channels => {
const promises =
_(channels)
.map(x => x.members)
.flatten()
.map(memberId =>
slack.users.info({
token: config.slack.token,
user: memberId,
})
)
.value();
return q.all(promises);
})
.then(members => {
let done = false;
for(const user of users) {
for(const member of members) {
if(new RegExp(member.user.real_name.replace(/\s+/g, '.*'), 'gi').test(user.username)) {
user.slackId = member.user.id;
break;
}
}
}
2018-07-17 16:47:32 -04:00
return users;
});
};
const dayName = new Date().toLocaleString('en-US', { weekday: 'long' });
const maybePostMessage = (msg) => {
if(!isProduction) {
console.log(msg);
2018-07-17 16:47:32 -04:00
return q.resolve();
}
return q.all(config.slack.channels.map(channel => {
2018-07-17 16:47:32 -04:00
const final = Object.assign({
unfurl_links: true,
unfurl_media: true,
}, msg, {
token: config.slack.token,
channel: channel.id,
2018-07-17 16:47:32 -04:00
username: config.slack.username,
2018-07-26 19:16:29 -04:00
icon_url: config.slack.icon_url,
2018-07-17 16:47:32 -04:00
});
return slack.chat.postMessage(final);
}));
}
let tournamentUrl;
let results;
q.resolve()
.then(() => {
if(!config.twitter.username || !config.twitter.password) {
throw new Error('You must provide a TWITTER_USERNAME and TWITTER_PASSWORD');
}
return getLastTournamentMessage();
})
.then(msg => {
const promises = [];
2018-07-26 11:34:56 -04:00
const now = new Date();
if(isProduction && msg &&
(
2018-07-26 11:34:56 -04:00
// No early
now.getHours() < 5
// No weekends
2018-07-26 11:34:56 -04:00
|| [0, 6].includes(now.getDay())
|| new Date(msg.message.ts * 1000).getDate() == now.getDate())
) {
2018-07-17 16:47:32 -04:00
if(!config.slack.ignoreLastTimestamp) {
console.log('We already ran today!');
debugger;
process.exit(0);
throw new Error('I shouldn\'t get here!!!');
}
else {
promises.push(maybePostMessage({ text: 'Please disregard the following messages!' }));
2018-07-17 16:47:32 -04:00
}
}
promises.push(getLastTournamentResults(msg));
promises.push(getNewTournament());
return q.all(promises);
2018-07-17 16:47:32 -04:00
})
.spread((r, tu) => (results = r, tournamentUrl = tu))
.then(() => {
const text = `Happy ${dayName}! Here are the results for the last tournament:` + results.map(result => `
#${result.rank}: *${result.slackId ? `<@${result.slackId}>` : result.username}* ${result.wpm}WPM`).join('');
2018-07-17 16:47:32 -04:00
return maybePostMessage({
text: text,
});
})
.then(() => {
return maybePostMessage({
text: `A new tournament is up at <${tournamentUrl}>!`,
});
})
.delay(10000)
.catch(e => {
console.error(e);
process.exit(1);
});