diff --git a/README.md b/README.md index 5c2cef9..c6e9d23 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository provides scripts for integrating [Kosmos Kredits](https://wiki.kosmos.org/Kredits) in [Hubot](http://hubot.github.com/) chatbots. The bot will watch for project-related things happening on the -Internet and automatically create proposals for issuing kredits for project +Internet and automatically create ERC721 tokens for issuing kredits for project contributions. ## Setup @@ -33,7 +33,7 @@ As usual in Hubot, you can add all config as environment variables. The GitHub integration will watch for closed issues and merged pull requests, which carry a kredits label: `kredits-1`, `kredits-2`, `kredits-3` for small, medium and large contributions. If there are multiple people assigned, it will -issue proposals for all of them. +issue contribution tokens for all of them. #### Setup @@ -50,7 +50,7 @@ Point a GitHub organization webhook to the following URL: ### MediaWiki The MediaWiki integration will periodically check for wiki page creations and -edits. It will create kredits proposals based on amount of text added. +edits. It will create kredits contribution tokens based on amount of text added. #### Setup diff --git a/index.js b/index.js index 54cd839..10f35e2 100644 --- a/index.js +++ b/index.js @@ -121,6 +121,7 @@ module.exports = async function(robot) { ethProvider.resetEventsBlock(nextBlock); Proposal.on('ProposalCreated', handleProposalCreated); + Contribution.on('ContributionAdded', handleContributionAdded); }); } @@ -133,6 +134,14 @@ module.exports = async function(robot) { }); } + function handleContributionAdded(contributionId, contributorId, amount) { + Contributor.getById(contributorId).then((contributor) => { + Contribution.getById(contributionId).then((contribution) => { + robot.logger.debug(`[hubot-kredits] Contribution #${contribution.id} added (${contribution.description})`); + }); + }); + } + watchContractEvents(); // diff --git a/integrations/github.js b/integrations/github.js index 053ac64..46cfc5e 100644 --- a/integrations/github.js +++ b/integrations/github.js @@ -1,6 +1,10 @@ const util = require('util'); const fetch = require('node-fetch'); +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + module.exports = async function(robot, kredits) { function messageRoom(message) { @@ -16,7 +20,6 @@ module.exports = async function(robot, kredits) { } const Contributor = kredits.Contributor; - const Proposal = kredits.Proposal; const Contribution = kredits.Contribution; function getContributorByGithubUser(username) { @@ -32,9 +35,9 @@ module.exports = async function(robot, kredits) { }); } - function createProposal(githubUser, amount, description, url, details) { + function createContribution(githubUser, amount, description, url, details) { return getContributorByGithubUser(githubUser).then(contributor => { - robot.logger.debug(`[hubot-kredits] Creating proposal to issue ${amount}₭S to ${githubUser} for ${url}...`); + robot.logger.debug(`[hubot-kredits] Creating contribution token for ${amount}₭S to ${githubUser} for ${url}...`); let contributionAttr = { contributorId: contributor.id, @@ -46,9 +49,10 @@ module.exports = async function(robot, kredits) { kind: 'dev' }; - return Proposal.addProposal(contributionAttr).catch(error => { + return Contribution.addContribution(contributionAttr).catch(error => { robot.logger.error(`[hubot-kredits] Error:`, error); - messageRoom(`I wanted to propose giving kredits to GitHub user ${githubUser} for ${url}, but I cannot find their info. Please add them as a contributor: https://kredits.kosmos.org`); + messageRoom(`I tried to add a contribution for ${githubUser} for ${url}, but I encountered an error when submitting the tx:`); + messageRoom(error.message); }); }); } @@ -76,7 +80,7 @@ module.exports = async function(robot, kredits) { return amount; } - function handleGitHubIssueClosed(data) { + async function handleGitHubIssueClosed(data) { let recipients; let issue = data.issue; let assignees = issue.assignees.map(a => a.login); @@ -87,7 +91,7 @@ module.exports = async function(robot, kredits) { let description = `${repoName}: ${issue.title}`; if (amount === 0) { - robot.logger.info('[hubot-kredits] Proposal amount from issue label is zero; ignoring'); + robot.logger.info('[hubot-kredits] Kredits amount from issue label is zero; ignoring'); return Promise.resolve(); } else if (repoBlackList.includes(repoName)) { robot.logger.debug(`[hubot-kredits] ${repoName} is on black list; ignoring`); @@ -100,15 +104,15 @@ module.exports = async function(robot, kredits) { recipients = [issue.user.login]; } - let proposalPromises = []; - recipients.forEach(recipient => { - proposalPromises.push( - createProposal(recipient, amount, description, web_url, issue) - .catch(err => robot.logger.error(err)) - ); - }); + for (const recipient of recipients) { + try { + await createContribution(recipient, amount, description, web_url, issue); + await sleep(60000); + } + catch (err) { robot.logger.error(err); } + } - return Promise.all(proposalPromises); + return Promise.resolve(); } function handleGitHubPullRequestClosed(data) { @@ -131,29 +135,28 @@ module.exports = async function(robot, kredits) { } return response.json(); }) - .then(issue => { + .then(async (issue) => { let amount = amountFromIssueLabels(issue); let repoName = pull_request.base.repo.full_name; let description = `${repoName}: ${pull_request.title}`; if (amount === 0) { - robot.logger.info('[hubot-kredits] Proposal amount from issue label is zero; ignoring'); + robot.logger.info('[hubot-kredits] Kredits amount from issue label is zero; ignoring'); return Promise.resolve(); } else if (repoBlackList.includes(repoName)) { robot.logger.debug(`[hubot-kredits] ${repoName} is on black list; ignoring`); return Promise.resolve(); } - let proposalPromises = []; - recipients.forEach(recipient => { - robot.logger.debug(`[hubot-kredits] Creating proposal for ${recipient}...`); - proposalPromises.push( - createProposal(recipient, amount, description, web_url, pull_request) - .catch(err => robot.logger.error(err)) - ); - }); + for (const recipient of recipients) { + try { + await createContribution(recipient, amount, description, web_url, pull_request); + await sleep(60000); + } + catch (err) { robot.logger.error(err); } + } - return Promise.all(proposalPromises); + return Promise.resolve(); }); } @@ -167,10 +170,12 @@ module.exports = async function(robot, kredits) { robot.logger.info(`Received GitHub hook. Event: ${evt}, action: ${data.action}`); if (evt === 'pull_request' && data.action === 'closed') { - handleGitHubPullRequestClosed(data).then(() => res.send(200)); + handleGitHubPullRequestClosed(data); + res.send(200); } else if (evt === 'issues' && data.action === 'closed') { - handleGitHubIssueClosed(data).then(() => res.send(200)); + handleGitHubIssueClosed(data); + res.send(200); } else { res.send(200); } diff --git a/integrations/mediawiki.js b/integrations/mediawiki.js index 8acd394..b814120 100644 --- a/integrations/mediawiki.js +++ b/integrations/mediawiki.js @@ -4,6 +4,10 @@ const fetch = require('node-fetch'); const groupArray = require('group-array'); const cron = require('node-cron'); +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + module.exports = async function(robot, kredits) { function messageRoom(message) { @@ -13,7 +17,6 @@ module.exports = async function(robot, kredits) { robot.logger.debug('[hubot-kredits] Loading MediaWiki integration...') const Contributor = kredits.Contributor; - const Proposal = kredits.Proposal; const Contribution = kredits.Contribution; const wikiURL = process.env.KREDITS_MEDIAWIKI_URL; @@ -30,9 +33,9 @@ module.exports = async function(robot, kredits) { }); } - function createProposal(username, amount, description, url, details={}) { + function createContribution(username, amount, description, url, details={}) { return getContributorByWikiUser(username).then(contributor => { - robot.logger.debug(`[hubot-kredits] Creating proposal to issue ${amount}₭S to ${contributor.name} for ${url}...`); + robot.logger.debug(`[hubot-kredits] Creating contribution token for ${amount}₭S to ${contributor.name} for ${url}...`); let contribution = { contributorId: contributor.id, @@ -44,8 +47,8 @@ module.exports = async function(robot, kredits) { kind: 'docs' }; - return Proposal.addProposal(contribution).catch(error => { - robot.logger.error(`[hubot-kredits] Adding proposal failed:`, error); + return Contribution.addContribution(contribution).catch(error => { + robot.logger.error(`[hubot-kredits] Adding contribution failed:`, error); }); }).catch(() => { robot.logger.info(`[hubot-kredits] No contributor found for ${username}`); @@ -107,14 +110,15 @@ module.exports = async function(robot, kredits) { return results; } - function createProposals (changes) { + async function createContributions (changes) { let promises = []; - Object.keys(changes).forEach(user => { - promises.push(createProposalForUserChanges(user, changes[user])); - }); + for (const user of Object.keys(changes)) { + await createContributionForUserChanges(user, changes[user]); + await sleep(60000); + } - return Promise.all(promises); + return Promise.resolve(); } function pageTitlesFromChanges(changes) { @@ -136,7 +140,7 @@ module.exports = async function(robot, kredits) { return amount; } - function createProposalForUserChanges (user, changes) { + function createContributionForUserChanges (user, changes) { const details = analyzeUserChanges(user, changes); const amount = calculateAmountForChanges(details); @@ -157,7 +161,7 @@ module.exports = async function(robot, kredits) { url = `${wikiURL}index.php?title=${rc.title}&diff=${rc.revid}&oldid=${rc.old_revid}`; } - return createProposal(user, amount, desc, url, details); + return createContribution(user, amount, desc, url, details); } function updateTimestampForNextFetch () { @@ -168,7 +172,7 @@ module.exports = async function(robot, kredits) { function processWikiChangesSinceLastRun () { fetchChanges() .then(res => groupChangesByUser(res)) - .then(res => createProposals(res)) + .then(res => createContributions(res)) .then(() => updateTimestampForNextFetch()); }