#!/usr/bin/env node require('dotenv').config(); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const util = require('util'); const fs = require('fs'); const GiteaReviews = require('./lib/gitea-reviews'); const GithubReviews = require('./lib/github-reviews'); const ethers = require('ethers'); 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 || 'http://localhost:7545'; const ipfsConfig = { host: process.env.IPFS_API_HOST || 'localhost', port: process.env.IPFS_API_PORT || '5001', protocol: process.env.IPFS_API_PROTOCOL || 'http', }; console.log('ipfsConfig:', ipfsConfig); 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); } 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) => { return { github: reviews[0], gitea: reviews[1] }; }); } async function initializeKredits() { // // Wallet setup // let wallet; try { wallet = await ethers.Wallet.fromEncryptedJson(walletJson, process.env.KREDITS_WALLET_PASSWORD); } catch(error) { console.warn('Could not load wallet:', error); process.exit(1); } // // Solidity VM provider/node setup // console.log('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 }; let kredits; try { kredits = await new Kredits(signer.provider, signer, opts).init(); } catch(error) { console.warn('Could not set up kredits:', error); process.exit(1); } console.log('Wallet address: ' + wallet.address); // // Check robot's wallet balance and alert when it's broke // ethProvider.getBalance(wallet.address).then(balance => { console.log('Wallet balance: ' + ethers.utils.formatEther(balance) + ' RBTC'); if (balance.lt(ethers.utils.parseEther('0.0001'))) { console.warn(`I\'m broke! Please send some RBTC to ${wallet.address} first.`); process.exit(1); } }); return kredits; } async function generateContributionData(reviews, Contributor) { const contributors = await Contributor.all(); const contributionData = {}; const nextDay = new Date(endDate); nextDay.setUTCDate(nextDay.getUTCDate() + 1); nextDay.setUTCHours(0, 0, 0, 0); const nextDayStr = nextDay.toISOString().split('.')[0] + 'Z'; [date, time] = nextDayStr.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 { contributionData[contributor.name] = { contributorId: contributor.id, contributorIpfsHash: contributor.ipfsHash, date, time, amount: kreditsAmount, kind: 'dev', details: { kind: 'review', 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]; const Contributor = kredits.Contributor; const Contribution = kredits.Contribution; async function createContribution(nickname, attrs) { console.log(`Creating review contribution for ${nickname}...`); console.log(util.inspect(attrs, { depth: 1, colors: true })); return Contribution.add(attrs) .catch(error => { console.error(`Error:`, error.message); }); } generateContributionData(reviews, Contributor).then( async (contributionData) => { if (argv.dry) { console.log('Contributions:'); console.log(util.inspect(contributionData, { depth: 3, colors: true })); return; } else { for (const nickname of Object.keys(contributionData)) { const description = `Reviewed ${contributionData[nickname].details.pullRequests.length} pull requests (from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]})`; await createContribution(nickname, { ...contributionData[nickname], description, }); } } } ); });