diff --git a/.env.example b/.env.example index 9c87309..67f864d 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,10 @@ MYSQL_DATABASE=phase2 TWITTER_CONSUMER_KEY= TWITTER_CONSUMER_SECRET= TWITTER_CALLBACK_URL=https://ceremony.tornado.cash/api/oauth_callback/twitter +TWITTER_ACCESS_TOKEN_KEY= +TWITTER_ACCESS_TOKEN_SECRET= +TWITTER_HASHTAG= +TWITTER_INTERVAL_ATTESTATION=300000 GITHUB_CLIEND_ID= GITHUB_CLIENT_SECRET= diff --git a/package.json b/package.json index f088f47..712f932 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "nuxt": "^2.0.0", "nuxt-buefy": "^0.3.2", "oauth": "^0.9.15", - "sequelize": "^5.21.3" + "sequelize": "^5.21.3", + "twitter": "^1.7.1" }, "devDependencies": { "@nuxtjs/eslint-config": "^1.0.1", diff --git a/pages/index.vue b/pages/index.vue index 90da205..fcfeab4 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -57,7 +57,9 @@ diff --git a/server/attestationWatcher.js b/server/attestationWatcher.js new file mode 100644 index 0000000..ee66528 --- /dev/null +++ b/server/attestationWatcher.js @@ -0,0 +1,78 @@ +const fs = require('fs') +const Twitter = require('twitter') + +const { + TWITTER_CONSUMER_KEY, + TWITTER_CONSUMER_SECRET, + TWITTER_ACCESS_TOKEN_KEY, + TWITTER_ACCESS_TOKEN_SECRET, + TWITTER_HASHTAG, + TWITTER_INTERVAL_ATTESTATION +} = process.env + +const client = new Twitter({ + consumer_key: TWITTER_CONSUMER_KEY, + consumer_secret: TWITTER_CONSUMER_SECRET, + access_token_key: TWITTER_ACCESS_TOKEN_KEY, + access_token_secret: TWITTER_ACCESS_TOKEN_SECRET +}) + +const { Contribution } = require('./models') + +async function attestationWatcher() { + // get the last saved tweet + let initTweet + try { + initTweet = require('/tmp/lastTweet.json').lastTweet + } catch (e) { + initTweet = process.env.LAST_TWEET + } + + // get all contributions without attestation + const contributions = await Contribution.findAll({ + where: { + socialType: 'twitter', + attestation: null + }, + attributes: ['id', 'handle', 'socialType', 'attestation'] + }) + + const params = { + since_id: initTweet, + q: `#${TWITTER_HASHTAG} -filter:retweets`, + result_type: 'recent', + count: 100 + } + + // search tweets with params + client.get('search/tweets', params, function(error, tweets, response) { + if (!error) { + tweets.statuses.forEach((tweet) => { + contributions.forEach((contribution) => { + // compare account compliance + if (contribution.handle === tweet.user.screen_name) { + // update the database record by id + Contribution.update({ attestation: tweet.id_str }, { where: { id: contribution.id } }) + console.log( + `Succesful attestation https://${contribution.socialType}.com/${contribution.handle}/status/${tweet.id_str}` + ) + } + }) + }) + + // save the last tweet received + fs.writeFileSync( + '/tmp/lastTweet.json', + JSON.stringify({ lastTweet: tweets.search_metadata.max_id_str }) + ) + } else { + console.error('attestationWatcher error', error) + } + }) + + setTimeout(() => { + attestationWatcher() + }, TWITTER_INTERVAL_ATTESTATION) +} + +module.exports = attestationWatcher diff --git a/server/controllers/contribute.js b/server/controllers/contribute.js index 99870e8..c2375c6 100644 --- a/server/controllers/contribute.js +++ b/server/controllers/contribute.js @@ -46,9 +46,9 @@ router.get('/challenge', (req, res) => { router.get('/contributions', async (req, res) => { const contributions = await Contribution.findAll({ - attributes: ['id', 'name', 'company', 'handle', 'socialType'] + attributes: ['id', 'name', 'company', 'handle', 'socialType', 'attestation'] }) - res.json(contributions).send() + res.json(contributions) }) router.post('/response', upload.single('response'), async (req, res) => { diff --git a/server/index.js b/server/index.js index 7c67efb..d3d345c 100644 --- a/server/index.js +++ b/server/index.js @@ -9,6 +9,7 @@ const config = require('../nuxt.config.js') const sessionsController = require('./controllers/authorize') const contributionController = require('./controllers/contribute') const models = require('./models') +const attestationWatcher = require('./attestationWatcher') const app = express() @@ -50,5 +51,8 @@ async function start() { app.listen(port, host, () => { console.log(`Server is running on port ${port}.`) }) + + attestationWatcher() + console.log('attestationWatcher started') } start() diff --git a/server/models/contribution.js b/server/models/contribution.js index 839ee5b..0162dbb 100644 --- a/server/models/contribution.js +++ b/server/models/contribution.js @@ -24,7 +24,8 @@ module.exports = (sequelize, DataTypes) => { company: DataTypes.STRING, handle: DataTypes.STRING, socialType: DataTypes.STRING, - hash: DataTypes.STRING + hash: DataTypes.STRING, + attestation: DataTypes.STRING }, { hooks: { diff --git a/yarn.lock b/yarn.lock index 0eb68c0..01828c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3004,6 +3004,11 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-extend@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" + integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -4308,7 +4313,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -7405,7 +7410,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: +psl@^1.1.24, psl@^1.1.28: version "1.7.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== @@ -7462,7 +7467,7 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -7768,6 +7773,32 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +request@^2.72.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" @@ -8896,6 +8927,14 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -8935,6 +8974,14 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +twitter@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/twitter/-/twitter-1.7.1.tgz#0762378f1dc1c050e48f666aca904e24b1a962f4" + integrity sha1-B2I3jx3BwFDkj2ZqypBOJLGpYvQ= + dependencies: + deep-extend "^0.5.0" + request "^2.72.0" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"