Initial commit.
This commit is contained in:
commit
bd4ceedf57
6 changed files with 2053 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
config/
|
||||
*.s??
|
14
.tern-project
Executable file
14
.tern-project
Executable file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"libs": [
|
||||
"ecma5",
|
||||
"ecma6",
|
||||
"browser",
|
||||
"chai",
|
||||
"underscore"
|
||||
],
|
||||
"plugins": {
|
||||
"node": {},
|
||||
"es_modules": {},
|
||||
"jsx": {}
|
||||
}
|
||||
}
|
12
config.js.sample
Normal file
12
config.js.sample
Normal file
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
slack: {
|
||||
ignoreLastTimestamp: false,
|
||||
token: process.env.SLACK_TOKEN,
|
||||
channelIds: ["#daily-typing-tourney"],
|
||||
username: "Daily Typing Tourney",
|
||||
},
|
||||
twitter: {
|
||||
username: process.env.TWITTER_USERNAME,
|
||||
password: process.env.TWITTER_PASSWORD,
|
||||
},
|
||||
};
|
198
index.js
Normal file
198
index.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
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 opts = new firefox.Options();
|
||||
|
||||
const isProduction = process.argv[2] == '--production';
|
||||
|
||||
if(isProduction) {
|
||||
opts.addArguments('--headless');
|
||||
}
|
||||
|
||||
const driver = new selenium.Builder()
|
||||
.forBrowser('firefox')
|
||||
.setFirefoxOptions(opts)
|
||||
.build();
|
||||
|
||||
const getNewTournament = () => {
|
||||
return q.resolve()
|
||||
.then(() => driver.get('https://10fastfingers.com/login'))
|
||||
.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'));
|
||||
}
|
||||
|
||||
const getLastTournamentMessage = () => {
|
||||
return q.resolve()
|
||||
.then(() => slack.search.messages({
|
||||
token: config.slack.token,
|
||||
query: `in:${config.slack.channelIds.join(',')} has:link 10fastfingers competition`,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 users = _.times(doc('#competition-rank-table tbody tr').length, () => ({}));
|
||||
Array.from(doc('#competition-rank-table tbody tr td.rank span')).forEach((rank, idx) => {
|
||||
try {
|
||||
users[idx].rank = parseInt(rank.children[0].data);
|
||||
}
|
||||
catch(e) { }
|
||||
});
|
||||
|
||||
Array.from(doc('#competition-rank-table tbody tr td.username a')).forEach((username, idx) => {
|
||||
users[idx].username = username.children[0].data;
|
||||
users[idx].userHref = username.attribs.href;
|
||||
});
|
||||
|
||||
Array.from(doc('#competition-rank-table tbody tr td.wpm')).forEach((wpm, idx) => {
|
||||
try {
|
||||
users[idx].wpm = parseFloat(wpm.children[0].data);
|
||||
}
|
||||
catch(e) { }
|
||||
});
|
||||
|
||||
Array.from(doc('#competition-rank-table tbody tr td.keystrokes')).forEach((keystrokes, idx) => {
|
||||
try {
|
||||
users[idx].keystrokes = parseFloat(/[0-9\.]+/gi.exec(keystrokes.children[0].data)[0]);
|
||||
}
|
||||
catch(e) { }
|
||||
});
|
||||
|
||||
Array.from(doc('#competition-rank-table tbody tr td.tests_taken')).forEach((testsTaken, idx) => {
|
||||
try {
|
||||
users[idx].testsTaken = parseFloat(testsTaken.children[0].data);
|
||||
}
|
||||
catch(e) { }
|
||||
});
|
||||
|
||||
return users;
|
||||
});
|
||||
};
|
||||
|
||||
const dayName = new Date().toLocaleString('en-US', { weekday: 'long' });
|
||||
|
||||
const maybePostMessage = (msg) => {
|
||||
if(!isProduction) {
|
||||
return q.resolve();
|
||||
}
|
||||
|
||||
return q.all(config.slack.channelIds.map(channelId => {
|
||||
const final = Object.assign({
|
||||
unfurl_links: true,
|
||||
unfurl_media: true,
|
||||
}, msg, {
|
||||
token: config.slack.token,
|
||||
channel: channelId,
|
||||
username: config.slack.username,
|
||||
});
|
||||
|
||||
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 => {
|
||||
if(isProduction && msg && new Date(msg.message.ts * 1000).getDate() == new Date().getDate()) {
|
||||
if(!config.slack.ignoreLastTimestamp) {
|
||||
console.log('We already ran today!');
|
||||
debugger;
|
||||
process.exit(0);
|
||||
throw new Error('I shouldn\'t get here!!!');
|
||||
}
|
||||
else {
|
||||
return q.all([getLastTournamentResults(msg), getNewTournament(), maybePostMessage({ text: 'Please disregard the following messages!' })]);
|
||||
}
|
||||
}
|
||||
|
||||
return q.all([getLastTournamentResults(msg), getNewTournament()])
|
||||
})
|
||||
.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.username}* ${result.wpm}WPM`).join('');
|
||||
|
||||
return maybePostMessage({
|
||||
text: text,
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return maybePostMessage({
|
||||
text: `A new tournament is up at <${tournamentUrl}>!`,
|
||||
});
|
||||
})
|
||||
.delay(10000)
|
||||
.then(() => driver.quit())
|
||||
.catch(e => {
|
||||
driver.quit();
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
26
package.json
Normal file
26
package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "daily-typing-tourney",
|
||||
"version": "1.0.0",
|
||||
"description": "Generates typing tournaments and posts to Slack. Gets results from previous.",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/empathicqubit/daily-typing-tourney",
|
||||
"author": "empathicqubit <empathicqubit@allons.me>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"debug": "nodemon -- --inspect-brk ./index.js",
|
||||
"start": "node ./index.js --production"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"geckodriver": "^1.11.0",
|
||||
"lodash": "^4.17.10",
|
||||
"node-fetch": "^2.1.2",
|
||||
"q": "^1.5.1",
|
||||
"selenium-webdriver": "^4.0.0-alpha.1",
|
||||
"slack": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.18.2"
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue