28 Commits

Author SHA1 Message Date
Râu Cao
48fa2e937b 4.0.1 2022-11-02 18:51:09 +01:00
Râu Cao
de5ee5e323 Publish as @kredits/hubot-kredits 2022-11-02 18:50:47 +01:00
Râu Cao
7fb5fb747d Be less stringent with contracts version 2022-11-02 18:38:29 +01:00
Râu Cao
70a74ba5fb 4.0.0 2022-11-02 18:32:19 +01:00
Râu Cao
1ab5ae55c6 Use new @kredits/contracts release 2022-11-02 18:30:57 +01:00
Râu Cao
710bd90172 Update and adapt for new kredits contracts release 2022-10-31 13:01:29 +01:00
c27fefcfdc Merge pull request #64 from 67P/feature/49-code_review_kredits
Create contributions for pull request reviews
2021-01-29 11:41:00 +01:00
cbfd25e880 Exit with error message when API token is missing 2021-01-29 11:06:31 +01:00
8b82b8b159 Add timeframe to contribution description 2021-01-29 11:05:45 +01:00
084dec58a9 Remove unused variable 2021-01-29 11:01:46 +01:00
9a4e465608 Remove unused variable 2021-01-28 13:05:36 +01:00
93d7c8944b Collect contribution data for pull request reviews 2021-01-12 15:32:06 +01:00
ad0e34b990 3.8.0 2020-10-29 15:15:10 +01:00
063a9c6b37 Merge pull request #63 from 67P/bugfix/mediawiki_accident
Fix mediawiki integration
2020-10-29 15:13:12 +01:00
f05436e9b9 Fix mediawiki integration
Accidentally deleted a line in a recent PR, and it slipped through the
review.
2020-10-29 15:12:50 +01:00
e305643f69 Merge pull request #61 from 67P/chore/replace_deprecated_contract_calls
Replace deprecated contract API calls
2020-10-29 14:13:36 +01:00
82a003ffeb Merge pull request #62 from 67P/feature/wiki_changes
Only create small automatic contributions for wiki edits
2020-10-29 14:10:33 +01:00
fc0c113997 Only create small automatic contributions for wiki edits
We decided that it's too difficult for a machine to gauge the meaning
and value of wiki edits by line numbers, so automatic kredits are now
always a small contributions. Until we have new tools for larger wiki
contributions (e.g. mediawiki tags), we can create manual contributions
for those.
2020-10-29 12:56:49 +01:00
5c6540580b Replace deprecated contract API calls
Use the new method.
2020-10-29 12:07:42 +01:00
078f78417c 3.7.0 2020-07-18 13:06:48 +02:00
d870099059 Update to latest ethers.js patch release 2020-07-18 13:06:09 +02:00
b7482f2468 package-lock 2020-07-18 13:04:12 +02:00
94c256e3d9 Merge pull request #60 from 67P/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-18 12:59:15 +02:00
3b382eadb2 Merge pull request #59 from 67P/ethers-nonce-manager
Use new ethers.js NonceManager to handle transaction nonces
2020-07-18 12:58:45 +02:00
ec63980cd3 Use kredits-contracts 6.0.0 2020-07-17 13:50:37 +02:00
dependabot[bot]
2b86f37fcb Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 03:28:48 +00:00
a8e29f2197 Use ethers5 branch of kredits-contracts 2020-06-27 18:38:22 +02:00
b7ff55929c Use new ethers.js NonceManager to handle transaction nonces
So far we have failed to globally handle the transaction nonces.
The new ethers.js v5 comes with a NonceManager that helps us handling
transaction nonces and automatically increases the nonce for each
transaction.
2020-06-27 18:24:44 +02:00
14 changed files with 5760 additions and 1244 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
GITEA_TOKEN=your-token-here
GITHUB_TOKEN=your-token-here

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
wallet.json
.env

View File

@@ -10,15 +10,20 @@ contributions.
## Setup
### Ethereum wallet
### Wallet
You will need an Ethereum wallet for your bot, so it can interact with the
Ethereum smart contracts. `npm run create-wallet` will do the job for you.
You will need a keypair/wallet for your bot, so it can interact with the smart
contracts. `npm run create-wallet` will do the job for you.
The wallet must be funded with enough ETH to interact with the contracts.
The wallet must be funded with enough native chain tokens to interact with the
contracts (i.e. it must be able to pay gas/tx fees)
### Contract permissions
**Warning: outdated instructions!**
*TODO adapt instructions for new permission model*
The bot wallet needs the following Aragon contract permissions to interact
with [kredits-contracts]:
@@ -41,11 +46,10 @@ As usual in Hubot, you can add all config as environment variables.
| --- | --- |
| `KREDITS_WEBHOOK_TOKEN` | A string for building your secret webhook URLs |
| `KREDITS_ROOM` | The bot will talk to you in this room |
| `KREDITS_WALLET_PATH` | Path to an Etherum wallet JSON file (default: `./wallet.json`) |
| `KREDITS_WALLET_PATH` | Path to an wallet JSON file (default: `./wallet.json`) |
| `KREDITS_WALLET_PASSWORD` | Wallet password |
| `KREDITS_PROVIDER_URL` | Ethereum JSON-RPC URL (default: `http://localhost:7545`) |
| `KREDITS_PROVIDER_URL` | JSON-RPC URL of a blockchain node (default: `http://localhost:7545`) |
| `KREDITS_WEB_URL` | URL of the Kredits Web app (default: `https://kredits.kosmos.org`) |
| `KREDITS_DAO_ADDRESS` | DAO Kernel address |
| `KREDITS_SESSION_SECRET` | Secret used by [grant](https://www.npmjs.com/package/grant) to sign the session ID |
| `KREDITS_GRANT_HOST` | Host used by [grant](https://www.npmjs.com/package/grant) to generate OAuth redirect URLs (default: `localhost:8888`) |
| `KREDITS_GRANT_PROTOCOL` | Protocol (http or https) used by [grant](https://www.npmjs.com/package/grant") to generate the OAuth redirect URLs (default: "http") |

View File

@@ -1,13 +1,11 @@
const fs = require('fs');
const util = require('util');
const fetch = require('node-fetch');
const ethers = require('ethers');
const Kredits = require('kredits-contracts');
const NonceManager = require('@ethersproject/experimental').NonceManager;
const Kredits = require('@kredits/contracts');
const walletPath = process.env.KREDITS_WALLET_PATH || './wallet.json';
const walletJson = fs.readFileSync(walletPath);
const providerUrl = process.env.KREDITS_PROVIDER_URL;
const daoAddress = process.env.KREDITS_DAO_ADDRESS;
const providerUrl = process.env.KREDITS_PROVIDER_URL || 'http://localhost:7545';
const ipfsConfig = {
host: process.env.IPFS_API_HOST || 'localhost',
@@ -37,22 +35,16 @@ module.exports = async function(robot) {
// Ethereum provider/node setup
//
let ethProvider;
if (providerUrl) {
ethProvider = new ethers.providers.JsonRpcProvider(providerUrl);
} else {
ethProvider = new ethers.getDefaultProvider('rinkeby');
}
const signer = wallet.connect(ethProvider);
robot.logger.info('[hubot-kredits] Using blockchain node/API at', providerUrl);
const ethProvider = new ethers.providers.JsonRpcProvider(providerUrl);
const signer = new NonceManager(wallet.connect(ethProvider));
//
// Kredits contracts setup
//
const opts = { ipfsConfig };
if (daoAddress) {
opts.addresses = { Kernel: daoAddress };
}
let kredits;
try {
@@ -62,8 +54,8 @@ module.exports = async function(robot) {
process.exit(1);
}
const Contributor = kredits.Contributor;
const Proposal = kredits.Proposal;
const Contribution = kredits.Contribution;
// TODO const Reimbursement = kredits.Reimbursement;
robot.logger.info('[hubot-kredits] Wallet address: ' + wallet.address);
@@ -72,9 +64,9 @@ module.exports = async function(robot) {
//
ethProvider.getBalance(wallet.address).then(balance => {
robot.logger.info('[hubot-kredits] Wallet balance: ' + ethers.utils.formatEther(balance) + 'ETH');
robot.logger.info('[hubot-kredits] Wallet balance: ' + ethers.utils.formatEther(balance) + ' RBTC');
if (balance.lt(ethers.utils.parseEther('0.0001'))) {
messageRoom(`Yo gang, I\'m broke! Please drop me some ETH to ${wallet.address}. kthxbai.`);
messageRoom(`Yo gang, I\'m broke! Please send some RBTC to ${wallet.address}. kthxbai.`);
}
});
@@ -82,30 +74,9 @@ module.exports = async function(robot) {
// Robot chat commands/interaction
//
robot.respond(/got ETH\??/i, res => {
robot.respond(/got RBTC\??/i, res => {
ethProvider.getBalance(wallet.address).then((balance) => {
res.send(`My wallet contains ${ethers.utils.formatEther(balance)} ETH`);
});
});
robot.respond(/propose (\d*)\s?\S*\s?to (\S+)(?:\sfor (.*))?$/i, res => {
let [_, amount, githubUser, description] = res.match;
let url = null;
createProposal(githubUser, amount, description, url).then((result) => {
messageRoom('Sounds good! Will be listed on https://kredits.kosmos.org in a bit...');
});
});
robot.respond(/list open proposals/i, res => {
Proposal.all().then((proposals) => {
proposals.forEach((proposal) => {
if (!proposal.executed) {
Contributor.getById(proposal.contributorId).then((contributor) => {
messageRoom(`* ${proposal.amount} kredits to ${contributor.name} for ${proposal.description}`);
});
}
});
messageRoom('https://kredits.kosmos.org');
res.send(`My wallet contains ${ethers.utils.formatEther(balance)} RBTC`);
});
});
@@ -114,31 +85,23 @@ module.exports = async function(robot) {
//
function watchContractEvents() {
ethProvider.getBlockNumber().then((blockNumber) => {
ethProvider.getBlockNumber().then(blockNumber => {
// current block is the last mined one, thus we check from the next
// mined one onwards to prevent getting previous events
let nextBlock = blockNumber + 1;
robot.logger.debug(`[hubot-kredits] Watching events from block ${nextBlock} onward`);
ethProvider.resetEventsBlock(nextBlock);
Proposal.on('ProposalCreated', handleProposalCreated);
// TODO handle all known events (that make sense here)
// Contribution.on('ContributorAdded', handleContributorAdded);
Contribution.on('ContributionAdded', handleContributionAdded);
});
}
function handleProposalCreated(proposalId, creatorAccount, contributorId, amount) {
Contributor.getById(contributorId).then((contributor) => {
Proposal.getById(proposalId).then((proposal) => {
robot.logger.debug(`[hubot-kredits] Proposal created (${proposal.description})`);
// messageRoom(`Let's give ${contributor.name} some kredits for ${proposal.url} (${proposal.description}): https://kredits.kosmos.org`);
});
});
}
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})`);
Contributor.getById(contributorId).then(_ => {
Contribution.getById(contributionId).then(contribution => {
robot.logger.debug(`[hubot-kredits] Contribution #${contribution.id} added (${amount} kredits for "${contribution.description}")`);
});
});
}

View File

@@ -1,5 +1,4 @@
const util = require('util');
const fetch = require('node-fetch');
const amountFromLabels = require('./utils/amount-from-labels');
const kindFromLabels = require('./utils/kind-from-labels');
@@ -56,7 +55,7 @@ module.exports = async function(robot, kredits) {
robot.logger.debug(`[hubot-kredits] contribution attributes:`);
robot.logger.debug(util.inspect(contributionAttr, { depth: 1, colors: true }));
return Contribution.addContribution(contributionAttr).catch(error => {
return Contribution.add(contributionAttr).catch(error => {
robot.logger.error(`[hubot-kredits] Error:`, error);
messageRoom(`I tried to add a contribution for ${giteaUser} for ${url}, but I encountered an error when submitting the tx:`);
messageRoom(error.message);

View File

@@ -61,7 +61,7 @@ module.exports = async function(robot, kredits) {
robot.logger.debug(`[hubot-kredits] contribution attributes:`);
robot.logger.debug(util.inspect(contributionAttr, { depth: 1, colors: true }));
return Contribution.addContribution(contributionAttr).catch(error => {
return Contribution.add(contributionAttr).catch(error => {
robot.logger.error(`[hubot-kredits] Error:`, error);
messageRoom(`I tried to add a contribution for ${githubUser} for ${url}, but I encountered an error when submitting the tx:`);
messageRoom(error.message);

View File

@@ -48,7 +48,7 @@ module.exports = async function(robot, kredits) {
kind: 'docs'
};
return Contribution.addContribution(contribution).catch(error => {
return Contribution.add(contribution).catch(error => {
robot.logger.error(`[hubot-kredits] Adding contribution failed:`, error);
});
}).catch(() => {
@@ -126,6 +126,7 @@ module.exports = async function(robot, kredits) {
return [...new Set(changes.map(c => `"${c.title}"`))].join(', ');
}
// Currently not used
function calculateAmountForChanges(details) {
let amount;
@@ -144,9 +145,8 @@ module.exports = async function(robot, kredits) {
const dateNow = new Date();
const dateYesterday = dateNow.setDate(dateNow.getDate() - 1);
const date = (new Date(dateYesterday)).toISOString().split('T')[0];
const details = analyzeUserChanges(user, changes);
const amount = calculateAmountForChanges(details);
const amount = 500;
let desc = `Added ${details.charsAdded} characters of text.`;
if (details.pagesChanged.length > 0) {

View File

@@ -1,5 +1,9 @@
const fetch = require('node-fetch');
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
module.exports = async function(robot, kredits) {
function messageRoom(message) {
@@ -13,9 +17,6 @@ module.exports = async function(robot, kredits) {
const zoomAccessToken = process.env.KREDITS_ZOOM_JWT;
const walletTransactionCount = await kredits.provider.getTransactionCount(kredits.signer.address);
let nonce = walletTransactionCount;
async function createContributionFor (displayName, meeting) {
const contributor = await getContributorByZoomDisplayName(displayName);
@@ -35,7 +36,7 @@ module.exports = async function(robot, kredits) {
time: meeting.end_time.split('T')[1]
}
return Contribution.add(contribution, { nonce: nonce++ })
return Contribution.add(contribution)
.then(tx => {
robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`);
})
@@ -78,6 +79,7 @@ module.exports = async function(robot, kredits) {
for (const displayName of names) {
await createContributionFor(displayName, meetingDetails);
await sleep(60000); // potentially to prevent too many transactions at the sametime. transactions need to be ordered because of the nonce. not sure though if this is needed.
};
}

6461
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,31 @@
{
"name": "hubot-kredits",
"version": "3.6.0",
"name": "@kredits/hubot-kredits",
"version": "4.0.1",
"description": "Kosmos Kredits functionality for chat bots",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"create-wallet": "scripts/create-wallet.js"
"create-wallet": "scripts/create-wallet.js",
"review-kredits": "scripts/review-kredits.js"
},
"dependencies": {
"@ethersproject/experimental": "5.0.0",
"@kredits/contracts": "^7.0.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"eth-provider": "^0.2.2",
"ethers": "^4.0.27",
"ethers": "^5.0.5",
"express": "^4.17.1",
"express-session": "^1.16.2",
"grant-express": "^4.6.1",
"group-array": "^1.0.0",
"kosmos-schemas": "^1.1.2",
"kredits-contracts": "^5.4.0",
"node-cron": "^2.0.3",
"node-fetch": "^2.3.0",
"prompt": "^1.0.0"
"prompt": "^1.0.0",
"yargs": "^16.2.0"
},
"repository": {
"type": "git",
@@ -35,7 +39,8 @@
"author": "Kosmos Developers <mail@kosmos.org>",
"contributors": [
"Sebastian Kippe <sebastian@kip.pe>",
"Michael Bumann <hello@michaelbumann.com>"
"Michael Bumann <hello@michaelbumann.com>",
"Garret Alfert <alfert@wevelop.de>"
],
"license": "MIT",
"bugs": {

25
repos.json Normal file
View File

@@ -0,0 +1,25 @@
{
"github": [
"67P/botka",
"67P/hal8000",
"67P/hubot-kredits",
"67P/hubot-remotestorage-logger",
"67P/hyperchannel",
"67P/kosmos-schemas",
"67P/kredits-contracts",
"67P/kredits-signup",
"67P/kredits-web",
"67P/remotestorage-module-kosmos",
"67P/waves",
"sockethub/sockethub"
],
"gitea": [
"kosmos/akkounts",
"kosmos/chef",
"kosmos/gitea.kosmos.org",
"kosmos/ipfs-cookbook",
"kosmos/kredits-ipfs-pinner",
"kosmos/website",
"kosmos/wormhole"
]
}

View File

@@ -0,0 +1,97 @@
const fetch = require('node-fetch');
module.exports = class GiteaReviews {
token = null;
kreditsAmounts = null;
pageLimit = 100;
constructor (token, kreditsAmounts) {
this.token = token;
this.kreditsAmounts = kreditsAmounts;
}
async request (path) {
return fetch(
`https://gitea.kosmos.org/api/v1${path}`,
{
headers: {
'accepts': 'application/json',
'Authorization': `token ${this.token}`
}
}
).then(response => response.json());
}
async getReviewContributions (repos, startDate, endDate) {
let reviewContributions = {}
await Promise.all(repos.map(async (repo) => {
let page = 1;
let result;
do {
try {
result = await this.request(`/repos/${repo}/pulls?state=closed&limit=${this.pageLimit}&page=${page}`);
} catch(error) {
console.log(`failed to fetch PRs for repo ${repo}:`, error.message);
continue;
}
if (!result || result.length === 0) {
continue;
}
let pullRequests = result.filter(pr => {
if (!pr.merged) return false; // only interested in merged PRs
// check if the PR has been merged in the given timeframe
const mergeDate = new Date(pr.merged_at);
if (mergeDate < startDate || mergeDate > endDate) return false;
// check if the PR has a kredits label
return pr.labels.some(label => label.name.match(/kredits-[123]/));
});
await Promise.all(pullRequests.map(async (pr) => {
let reviews;
try {
reviews = await this.request(`/repos/${repo}/pulls/${pr.number}/reviews`);
} catch(error) {
console.log(`failed to fetch reviews for repo ${repo}, PR ${pr.number}:`, error.message);
return;
}
if (!reviews || reviews.length === 0) {
return;
}
reviews = reviews.filter(review => {
return ['APPROVED', 'REJECTED'].includes(review.state);
});
reviews.forEach(review => {
if (typeof reviewContributions[review.user.login] === 'undefined') {
reviewContributions[review.user.login] = [];
}
let kreditsLabel = pr.labels.find(label => label.name.match(/kredits-[123]/));
reviewContributions[review.user.login].push({
pr,
prNumber: pr.number,
review,
reviewState: review.state,
kredits: this.kreditsAmounts[kreditsLabel.name]
});
});
}));
page++;
} while (result && result.length > 0);
}));
return reviewContributions;
}
}

View File

@@ -0,0 +1,98 @@
const fetch = require('node-fetch');
module.exports = class GithubReviews {
token = null;
kreditsAmounts = null;
pageLimit = 100;
constructor (token, kreditsAmounts) {
this.token = token;
this.kreditsAmounts = kreditsAmounts;
}
async request (path) {
return fetch(
`https://api.github.com${path}`,
{
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'Kosmos Kredits for reviews',
'Authorization': `token ${this.token}`
}
}
).then(response => response.json());
}
async getReviewContributions (repos, startDate, endDate) {
let reviewContributions = {}
await Promise.all(repos.map(async (repo) => {
let page = 1;
let result;
do {
try {
result = await this.request(`/repos/${repo}/pulls?state=closed&perPage=${this.pageLimit}&page=${page}`);
} catch(error) {
console.log(`failed to fetch PRs for repo ${repo}:`, error.message);
continue;
}
if (!result || result.length === 0) {
continue;
}
let pullRequests = result.filter(pr => {
if (!pr.merged_at) return false; // only interested in merged PRs
// check if the PR has been merged in the given timeframe
const mergeDate = new Date(pr.merged_at);
if (mergeDate < startDate || mergeDate > endDate) return false;
// check if the PR has a kredits label
return pr.labels.some(label => label.name.match(/kredits-[123]/));
});
await Promise.all(pullRequests.map(async (pr) => {
let reviews;
try {
reviews = await this.request(`/repos/${repo}/pulls/${pr.number}/reviews`);
} catch(error) {
console.log(`failed to fetch reviews for repo ${repo}, PR ${pr.number}:`, error.message);
return;
}
if (!reviews || reviews.length === 0) {
return;
}
reviews = reviews.filter(review => {
return ['APPROVED', 'REJECTED'].includes(review.state);
});
reviews.forEach(review => {
if (typeof reviewContributions[review.user.login] === 'undefined') {
reviewContributions[review.user.login] = [];
}
let kreditsLabel = pr.labels.find(label => label.name.match(/kredits-[123]/));
reviewContributions[review.user.login].push({
pr,
prNumber: pr.number,
review,
reviewState: review.state,
kredits: this.kreditsAmounts[kreditsLabel.name]
});
});
}));
page++;
} while (result && result.length > 0);
}));
return reviewContributions;
}
}

189
scripts/review-kredits.js Executable file
View File

@@ -0,0 +1,189 @@
#!/usr/bin/env node
require('dotenv').config({ path: '../.env' });
const GiteaReviews = require('./lib/gitea-reviews');
const GithubReviews = require('./lib/github-reviews');
const ethers = require('ethers');
const Kredits = require('kredits-contracts');
const util = require('util');
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
const providerUrl = process.env.KREDITS_PROVIDER_URL;
const daoAddress = process.env.KREDITS_DAO_ADDRESS;
const ipfsConfig = {
host: process.env.IPFS_API_HOST || 'localhost',
port: process.env.IPFS_API_PORT || '5001',
protocol: process.env.IPFS_API_PROTOCOL || 'http'
};
const kreditsAmounts = {
'kredits-1': 100,
'kredits-2': 300,
'kredits-3': 1000
};
const repos = require('../repos.json');
const argv = yargs(hideBin(process.argv))
.option('start', {
alias: 's',
description: 'Include reviews for PRs merged after this date'
})
.option('end', {
alias: 'e',
description: 'Include reviews for PRs merged before this date'
})
.option('dry', {
alias: 'd',
type: 'boolean',
description: 'Only list contribution details without creating them'
})
.help()
.version()
.demandOption('start', 'Please provide a start date')
.default('end', function now () {
return (new Date()).toISOString().split('.')[0]+"Z";
})
.example([
['$0 --start 2020-11-01 --end 2020-11-30T23:59:59Z', 'Create contributions for reviews of pull requests merged in November 2020'],
['$0 --start 2021-01-01', 'Create contributions for reviews of pull requests merged from Januar 2021 until now'],
])
.argv
const startTimestamp = Date.parse(argv.start);
const endTimestamp = Date.parse(argv.end);
if (isNaN(startTimestamp)) {
console.log('The provided start date is invalid');
process.exit(1);
}
if (isNaN(endTimestamp)) {
console.log('The provided end date is invalid');
process.exit(1);
}
// check for existence of GITHUB_TOKEN and GITEA_TOKEN
if (!process.env.GITHUB_TOKEN || !process.env.GITEA_TOKEN) {
console.log('Please set both GITHUB_TOKEN and GITEA_TOKEN');
process.exit(1);
}
const startDate = new Date(startTimestamp);
const endDate = new Date(endTimestamp);
async function getAllReviews(repos, startDate, endDate) {
const githubReviews = new GithubReviews(process.env.GITHUB_TOKEN, kreditsAmounts);
const giteaReviews = new GiteaReviews(process.env.GITEA_TOKEN, kreditsAmounts);
return Promise.all([
githubReviews.getReviewContributions(repos.github, startDate, endDate),
giteaReviews.getReviewContributions(repos.gitea, startDate, endDate)
]).then(reviews => {
// console.log(util.inspect(reviews[0], { depth: 3, colors: true }));
return { github: reviews[0], gitea: reviews[1] }
});
}
async function initializeKredits () {
//
// Ethereum provider/node setup
//
let ethProvider;
if (providerUrl) {
ethProvider = new ethers.providers.JsonRpcProvider(providerUrl);
} else {
ethProvider = new ethers.getDefaultProvider('rinkeby');
}
//
// Kredits contracts setup
//
const opts = { ipfsConfig };
if (daoAddress) {
opts.addresses = { Kernel: daoAddress };
}
let kredits;
try {
kredits = await new Kredits(ethProvider, null, opts).init();
} catch(error) {
console.log('Could not set up kredits:', error);
process.exit(1);
}
return kredits;
}
async function generateContributionData(reviews, Contributor, startDate, endDate) {
const dateFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' };
const contributors = await Contributor.all();
const contributionData = {};
const now = (new Date()).toISOString().split('.')[0]+"Z";
[date, time] = now.split('T');
function addContributionDataForPlatform(platform) {
for (const [username, platformReviews] of Object.entries(reviews[platform])) {
const contributor = contributors.find(c => {
return c[`${platform}_username`] === username;
});
if (!contributor) {
console.log(`Could not find contributor for ${platform} user "${username}"`);
continue;
}
const urls = platformReviews.map(review => review.pr.html_url);
const kreditsAmount = platformReviews.reduce((amount, review) => {
return review.kredits + amount;
}, 0);
if (typeof contributionData[contributor.name] !== 'undefined') {
contributionData[contributor.name].amount += kreditsAmount;
contributionData[contributor.name].details.pullRequests.push(...urls);
} else {
const formattedStartDate = startDate.toLocaleString('en-us', dateFormatOptions);
const formattedEndDate = endDate.toLocaleString('en-us', dateFormatOptions);
contributionData[contributor.name] = {
contributorId: contributor.id,
contributorIpfsHash: contributor.ipfsHash,
date,
time,
amount: kreditsAmount,
kind: 'dev',
description: `PR reviews from ${formattedStartDate} to ${formattedEndDate}`,
details: {
'pullRequests': urls
}
}
}
}
}
addContributionDataForPlatform('gitea');
addContributionDataForPlatform('github');
return contributionData;
}
Promise.all([initializeKredits(), getAllReviews(repos, startDate, endDate)]).then((values) => {
const kredits = values[0];
const reviews = values[1];
generateContributionData(reviews, kredits.Contributor, startDate, endDate).then(contributionData => {
if (argv.dry) {
console.log('contributions:');
console.log(util.inspect(contributionData, { depth: 3, colors: true }));
}
// TODO create contributions
});
});