Compare commits

..

No commits in common. "master" and "0.1" have entirely different histories.
master ... 0.1

32 changed files with 1187 additions and 1752 deletions

View file

@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Node v18
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 18
node-version: 12.x
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
@ -24,7 +24,7 @@ jobs:
- name: Run ESLint
run: yarn run eslint:fix
- name: Commit changes
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@v4
with:
add: src
message: "chore(lint): Auto-fix linting errors"

3
.gitignore vendored
View file

@ -3,5 +3,4 @@ config/default.js
config/production.js
config/curate.sqlite
package-lock.json
.idea/
yarn-error.log
.idea/

View file

@ -1,3 +0,0 @@
{
"deepscan.enable": true
}

View file

@ -7,7 +7,7 @@ This bot allows the community of LBRY to support eachother through the [LBRY Fou
## Installation
* Pull the repo
* Install [Node.JS LTS](https://nodejs.org/) (Currently Node v18.x)
* Install [Node.JS LTS](https://nodejs.org/) (Currently Node v12.x)
* Install [Yarn](https://yarnpkg.com/) (`npm install yarn -g`)
* Install [Redis](https://redis.io/) ([quickstart](https://redis.io/topics/quickstart))
* Install LBRY-SDK
@ -19,7 +19,7 @@ This bot allows the community of LBRY to support eachother through the [LBRY Fou
This bot would not be possible without the following people/software:
* LBRY Inc. and the LBRY SDK
* Dysnomia - NodeJS Library for Discord, fork of Eris
* Eris - NodeJS Library for Discord
* LBRY Foundation
* Snazzah - Creator of the Faux command base and developer of the bot
* Coolguy3289 - Developer of the bot and command flow

View file

@ -11,42 +11,34 @@ module.exports = {
debug: false,
// [number] The main embed color (#ffffff -> 0xffffff)
embedColor: 0x15521c,
// [string|Array<string>] The role ID(s) for curator roles
// [string] curator_role_id
curatorRoleID: "",
// [string|Array<string>] The role ID(s) for trusted roles
trustedRoleID: "",
// [string|Array<string>] The role ID(s) for admin roles
// [string] admin_role_id
adminRoleID: "",
// [string] guild_id
guildID: "",
// [string] sdk_url
sdkURL: "",
// [string] The ABSOLUTE path to the main wallet file to back up
walletPath: "~/.lbryum/wallets/default_wallet",
// [string] The ABSOLUTE path folder to store wallet backups after every deletion
walletBackupFolder: "~/.lbryum_backup/",
// [string] Amount to auto-fund upon account creation
startingBalance: "",
// [Object] Dysnomia client options (subset of https://abal.moe/Eris/docs/Client)
// [Object] Eris client options (https://abal.moe/Eris/docs/Client)
discordConfig: {
autoreconnect: true,
allowedMentions: {
everyone: false,
roles: false,
users: true
},
maxShards: "auto",
messageLimit: 0,
gateway: {
autoreconnect: true,
maxShards: "auto",
intents: [
"guilds",
"guildMessages",
"guildMessageReactions",
"directMessages",
"directMessageReactions",
"messageContent"
]
}
intents: [
"guilds",
"guildEmojis",
"guildMessages",
"guildMessageReactions",
"directMessages",
"directMessageReactions"
]
},
// [Object] Redis config
redis: {

View file

@ -1,6 +1,6 @@
{
"name": "lbry-curate",
"version": "0.1.1",
"version": "0.0.1",
"description": "Support the LBRY Community through Discord!",
"main": "src/bot.js",
"scripts": {
@ -9,20 +9,21 @@
"eslint:fix": "eslint ./src --fix"
},
"dependencies": {
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#0df1369a822dba8851c667b22d5bac34f4e849e6",
"cat-loggr": "^1.2.2",
"config": "^3.3.9",
"eventemitter3": "^5.0.1",
"abort-controller": "^3.0.0",
"cat-loggr": "^1.1.0",
"config": "^3.3.1",
"eris": "^0.13.3",
"eventemitter3": "^4.0.4",
"fuzzy": "^0.1.3",
"ioredis": "^5.3.2",
"just-clone": "^6.2.0",
"moment": "^2.30.1",
"moment": "^2.27.0",
"node-fetch": "^2.3.0",
"redis": "^3.0.2",
"require-reload": "^0.2.2",
"sequelize": "^6.35.2",
"sqlite3": "^5.1.7"
"sequelize": "^6.3.4",
"sqlite3": "^5.0.0"
},
"devDependencies": {
"eslint": "^8.56.0"
"eslint": "^7.6.0"
},
"repository": {
"type": "git",

View file

@ -1,4 +1,4 @@
const Dysnomia = require('@projectdysnomia/dysnomia');
const Eris = require('eris');
const Database = require('./database');
const EventHandler = require('./events');
const CommandLoader = require('./commandloader');
@ -8,13 +8,12 @@ const path = require('path');
const CatLoggr = require('cat-loggr');
const config = require('config');
const LBRY = require('./structures/LBRY');
const clone = require('just-clone');
class CurateBot extends Dysnomia.Client {
class CurateBot extends Eris.Client {
constructor({ packagePath, mainDir } = {}) {
// Initialization
const pkg = require(packagePath || `${mainDir}/package.json`);
super(`Bot ${config.token}`, clone(config.discordConfig));
super(config.token, JSON.parse(JSON.stringify(config.discordConfig)));
this.dir = mainDir;
this.pkg = pkg;
this.logger = new CatLoggr({
@ -24,8 +23,10 @@ class CurateBot extends Dysnomia.Client {
{ name: 'error', color: CatLoggr._chalk.black.bgRed, err: true },
{ name: 'warn', color: CatLoggr._chalk.black.bgYellow, err: true },
{ name: 'init', color: CatLoggr._chalk.black.bgGreen },
{ name: 'webserv', color: CatLoggr._chalk.black.bgBlue },
{ name: 'info', color: CatLoggr._chalk.black.bgCyan },
{ name: 'assert', color: CatLoggr._chalk.cyan.bgBlack },
{ name: 'poster', color: CatLoggr._chalk.yellow.bgBlack },
{ name: 'debug', color: CatLoggr._chalk.magenta.bgBlack, aliases: ['log', 'dir'] },
{ name: 'limiter', color: CatLoggr._chalk.gray.bgBlack },
{ name: 'fileload', color: CatLoggr._chalk.white.bgBlack }
@ -102,17 +103,17 @@ class CurateBot extends Dysnomia.Client {
}
/**
* Kills the bot
* KIlls the bot
*/
dieGracefully() {
return super.disconnect(false);
return super.disconnect();
}
// Typing
/**
* Start typing in a channel
* @param {Dysnomia.TextableChannel} channel The channel to start typing in
* @param {Channel} channel The channel to start typing in
*/
async startTyping(channel) {
if (this.isTyping(channel)) return;
@ -124,7 +125,7 @@ class CurateBot extends Dysnomia.Client {
/**
* Whether the bot is currently typing in a channel
* @param {Dysnomia.TextableChannel} channel
* @param {Channel} channel
*/
isTyping(channel) {
return this.typingIntervals.has(channel.id);
@ -132,7 +133,7 @@ class CurateBot extends Dysnomia.Client {
/**
* Stops typing in a channel
* @param {Dysnomia.TextableChannel} channel
* @param {Channel} channel
*/
stopTyping(channel) {
if (!this.isTyping(channel)) return;

View file

@ -37,7 +37,6 @@ module.exports = class AbaondonAll extends Command {
header: 'Are you sure you want to abandon **all supports** from **all accounts**?'
})) return;
await this.client.startTyping(message.channel);
await Util.LBRY.syncPairs(this.client);
const pairs = await this.client.sqlite.getAll();
let count = 0;
for (let i = 0, len = pairs.length; i < len; i++) {
@ -51,7 +50,7 @@ module.exports = class AbaondonAll extends Command {
}
get metadata() { return {
category: 'Admin',
category: 'Curator',
description: 'Abandons all supports of the bot or of a given account.',
usage: '[id|@mention]'
}; }

View file

@ -13,12 +13,12 @@ module.exports = class AdminBalance extends Command {
const response = await this.client.lbry.walletBalance();
const wallet = await response.json();
if (await this.handleResponse(message, response, wallet)) return;
return message.channel.createMessage({ embeds: [{
return message.channel.createMessage({ embed: {
color: config.embedColor,
description: `**Available:** ${wallet.result.available} LBC\n\n` +
`Reserved in Supports: ${wallet.result.reserved_subtotals.supports} LBC\n` +
`Total: ${wallet.result.total} LBC`
}] });
} });
}
get metadata() { return {

View file

@ -1,60 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
const GenericPager = require('../../structures/GenericPager');
module.exports = class AllSupports extends Command {
get name() { return 'allsupports'; }
get _options() { return {
aliases: ['asups', 'allsups'],
permissions: ['admin'],
minimumArgs: 0
}; }
async exec(message, { args }) {
let givenClaim;
if (args[0]) {
givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');
}
await Util.LBRY.syncPairs(this.client);
const pairs = await this.client.sqlite.getAll();
if (pairs.length <= 0)
return message.channel.createMessage('No users found in the database.');
const allSupports = [];
for (const pair of pairs) {
const supportsCount = await Util.LBRY.getSupportsCount(this.client, pair.lbryID);
if (supportsCount <= 0) continue;
const supportsResponse = await this.client.lbry.listSupports({
accountID: pair.lbryID, page_size: supportsCount, claimID: givenClaim });
const supports = (await supportsResponse.json()).result.items;
for (const support of supports)
allSupports.push({
...support,
pair
});
}
if (allSupports.length <= 0)
return message.channel.createMessage('No supports found.');
const paginator = new GenericPager(this.client, message, {
items: allSupports,
header: `All supports${
givenClaim ? ` on claim \`${givenClaim}\`` : ''}`, itemTitle: 'Supports',itemsPerPage: 5,
display: item => `> ${item.name} \`${item.claim_id}\`\n> <@${item.pair.discordID}> ${item.amount} LBC\n`
});
return paginator.start(message.channel.id, message.author.id);
}
get metadata() { return {
category: 'Admin',
description: 'List all supports from all users.',
usage: '[claimID]'
}; }
};

View file

@ -21,15 +21,8 @@ module.exports = class DeleteAccount extends Command {
header:
`Are you sure you want to delete that account? *(${supportsCount.toLocaleString()} support[s])*`
})) return;
try {
await Util.LBRY.deleteAccount(this.client, discordID, account.accountID);
return message.channel.createMessage('Deleted account.');
} catch (e) {
return message.channel.createMessage(
'Failed to delete the account. An error most likely occured while backing up the wallet.' +
`\n\`\`\`\n${e.toString()}\`\`\``
);
}
await Util.LBRY.deleteAccount(this.client, discordID, account.accountID);
return message.channel.createMessage('Deleted account.');
} else
return message.channel.createMessage('That user does not have an account.');
}

View file

@ -1,39 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class DeleteAll extends Command {
get name() { return 'deleteall'; }
get _options() { return {
aliases: ['delall'],
permissions: ['admin'],
minimumArgs: 0
}; }
async exec(message) {
await Util.LBRY.syncPairs(this.client);
const pairs = await this.client.sqlite.getAll();
if (!await this.client.messageAwaiter.confirm(message, {
header:
`Are you sure you want to delete **all** ${pairs.length} accounts?`
})) return;
for (const pair of pairs) {
try {
await Util.LBRY.deleteAccount(this.client, pair.discordID, pair.lbryID);
} catch (e) {
return message.channel.createMessage(
'Failed to delete an account. An error most likely occured while backing up the wallet.' +
`\n\`\`\`\n${e.toString()}\`\`\``
);
}
}
return message.channel.createMessage('Deleted all accounts.');
}
get metadata() { return {
category: 'Admin',
description: 'Deletes all accounts in the database.'
}; }
};

View file

@ -1,5 +1,4 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class Deposit extends Command {
get name() { return 'deposit'; }
@ -10,8 +9,7 @@ module.exports = class Deposit extends Command {
}; }
async exec(message) {
const account = await Util.LBRY.findSDKAccount(this.client, account => account.is_default);
const response = await this.client.lbry.listAddresses({ account_id: account.id });
const response = await this.client.lbry.listAddresses();
const address = await response.json();
if (await this.handleResponse(message, response, address)) return;
return message.channel.createMessage(`Address: ${address.result.items[0].address}`);

View file

@ -1,72 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
const config = require('config');
module.exports = class FundAll extends Command {
get name() { return 'fundall'; }
get _options() { return {
permissions: ['admin'],
minimumArgs: 1
}; }
async exec(message, { args }) {
const givenAmount = Util.LBRY.ensureDecimal(args[0]);
if (!givenAmount)
return message.channel.createMessage('The second argument must be a numeric amount of LBC to send!');
await Util.LBRY.syncPairs(this.client);
const pairs = await this.client.sqlite.getAll();
if (pairs.length <= 0) {
await this.client.startTyping(message.channel);
const curatorRoles = Array.isArray(config.curatorRoleID)
? config.curatorRoleID : [config.curatorRoleID];
const members = await this.client.guilds.get(config.guildID).fetchMembers();
for (const member of members) {
if (curatorRoles.map(r => member.roles.includes(r)).includes(true)) {
const account = await Util.LBRY.findOrCreateAccount(this.client, member.id);
pairs.push({ discordID: member.id, lbryID: account.accountID });
}
}
await Util.halt(5000);
this.client.stopTyping(message.channel);
}
if (!await this.client.messageAwaiter.confirm(message, {
header: `Are you sure you want to fund **all** accounts? *(${givenAmount} LBC)*`
})) return;
await this.client.startTyping(message.channel);
const resultLines = [];
let funded = 0,
errored = 0;
for (const pair of pairs) {
const response = await this.client.lbry.fundAccount({ to: pair.lbryID, amount: givenAmount });
await Util.halt(2000);
const transaction = await response.json();
if ('code' in transaction) {
console.info('Failed to fund account', pair.lbryID, transaction.code, transaction.message);
resultLines.push(`${pair.discordID} ! ${transaction.code} - ${transaction.message}`);
errored++;
} else {
console.info('Funded account', pair.lbryID, transaction.result.txid);
resultLines.push(`${pair.discordID} - https://explorer.lbry.com/tx/${transaction.result.txid}`);
funded++;
}
await Util.halt(2000);
}
this.client.stopTyping(message.channel);
return message.channel.createMessage(errored
? `Failed to fund ${errored} accounts! (${funded} funded)`
: `Successfully funded ${funded} account(s)!`, {
name: 'result.txt',
file: Buffer.from(resultLines.join('\n'), 'utf8')
});
}
get metadata() { return {
category: 'Admin',
description: 'Funds all users in the database a specified amount of LBC.',
usage: '<amount>'
}; }
};

View file

@ -1,49 +0,0 @@
const Command = require('../../structures/Command');
const GenericPager = require('../../structures/GenericPager');
module.exports = class ListAll extends Command {
get name() { return 'listall'; }
get _options() { return {
permissions: ['admin'],
minimumArgs: 0
}; }
async exec(message, { args }) {
const pairs = await this.client.sqlite.getAll();
if (pairs.length <= 0)
return message.channel.createMessage('No users found in the database.');
for (const pair of pairs) {
const response = await this.client.lbry.accountBalance(pair.lbryID);
const wallet = await response.json();
if (!wallet.code) {
pair.wallet_available = wallet.result.available;
pair.wallet_reserve = wallet.result.reserved_subtotals.supports;
pair.wallet_ok = true;
} else {
console.error([
'There was an error while retrieving the balance of an account.',
'This was likely caused by an old version of the Bot\'s SQLite database file. ' +
'Run the sync command to avoid this error!'
].join('\n'));
}
}
const paginator = new GenericPager(this.client, message, {
items: pairs, itemTitle: 'Users', itemsPerPage: 5,
display: pair => `> <@${pair.discordID}> - \`${pair.lbryID}\`\n` +
`> ${pair.wallet_ok
? `${pair.wallet_available} available, ${pair.wallet_reserve} staked.`
: 'Wallet Unavailable'}\n`
});
if (args[0])
paginator.toPage(args[0]);
return paginator.start(message.channel.id, message.author.id);
}
get metadata() { return {
category: 'Admin',
description: 'List all users in the database.',
usage: '[page]'
}; }
};

View file

@ -1,20 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class Sync extends Command {
get name() { return 'sync'; }
get _options() { return {
permissions: ['admin']
}; }
async exec(message) {
const synced = await Util.LBRY.syncPairs(this.client);
return message.channel.createMessage(`Synced ${synced} new pairs.`);
}
get metadata() { return {
category: 'Admin',
description: 'Sync SDK-Discord pairs.'
}; }
};

View file

@ -1,7 +1,7 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class Abandon extends Command {
module.exports = class Abaondon extends Command {
get name() { return 'abandon'; }
get _options() { return {
@ -11,8 +11,8 @@ module.exports = class Abandon extends Command {
}; }
async exec(message, { args }) {
const givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
const givenClaim = args[0];
if (!/^[a-f0-9]{40}$/.test(givenClaim))
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');

View file

@ -10,46 +10,19 @@ module.exports = class Balance extends Command {
permissions: ['curatorOrAdmin']
}; }
async exec(message, { args }) {
if (args.length) {
if (!Util.CommandPermissions.admin(this.client, message)) {
const admins = (Array.isArray(config.adminRoleID) ? config.adminRoleID : [config.adminRoleID])
.map(id => `"${this.client.guilds.get(config.guildID).roles.get(id).name}"`);
return message.channel.createMessage(
`You need to have the ${admins.join('/')} role(s) to see others balances!`);
}
const discordID = Util.resolveToUserID(args[0]);
if (!discordID)
return message.channel.createMessage('That Discord user isn\'t valid.');
const account = await Util.LBRY.findOrCreateAccount(this.client, discordID, false);
if (!account.accountID)
return message.channel.createMessage('That Discord user does not have an account.');
const response = await this.client.lbry.accountBalance(account.accountID);
const wallet = await response.json();
if (await this.handleResponse(message, response, wallet)) return;
return message.channel.createMessage({ embeds: [{
color: config.embedColor,
description: `<@${discordID}> has **${wallet.result.available}** LBC available.\n\n` +
`Reserved in Supports: ${wallet.result.reserved_subtotals.supports} LBC\n` +
`Total: ${wallet.result.total} LBC`
}] });
} else {
const account = await Util.LBRY.findOrCreateAccount(this.client, message.author.id);
const response = await this.client.lbry.accountBalance(account.accountID);
const wallet = await response.json();
if (await this.handleResponse(message, response, wallet)) return;
return message.channel.createMessage({ embeds: [{
color: config.embedColor,
description: `You have **${wallet.result.available}** LBC available.\n\n` +
`Reserved in Supports: ${wallet.result.reserved_subtotals.supports} LBC\n` +
`Total: ${wallet.result.total} LBC` +
(account.newAccount ? '\n\n:warning: This account was just created. ' +
'Please wait a few seconds, and run the command again to get an accurate balance.' : '')
}] });
}
async exec(message) {
const account = await Util.LBRY.findOrCreateAccount(this.client, message.author.id);
const response = await this.client.lbry.accountBalance(account.accountID);
const wallet = await response.json();
if (await this.handleResponse(message, response, wallet)) return;
return message.channel.createMessage({ embed: {
color: config.embedColor,
description: `You have **${wallet.result.available}** LBC available.\n\n` +
`Reserved in Supports: ${wallet.result.reserved_subtotals.supports} LBC\n` +
`Total: ${wallet.result.total} LBC` +
(account.newAccount ? '\n\n:warning: This account was just created. ' +
'Please wait a few seconds, and run the command again to get an accurate balance.' : '')
} });
}
get metadata() { return {

View file

@ -15,8 +15,8 @@ module.exports = class Support extends Command {
if (!givenAmount)
return message.channel.createMessage('The second argument must be a numeric amount of LBC to send!');
const givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
const givenClaim = args[0];
if (!/^[a-f0-9]{40}$/.test(givenClaim))
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');

View file

@ -42,14 +42,12 @@ module.exports = class Supports extends Command {
const supportsResponse = await this.client.lbry.listSupports({
accountID: account.accountID, page_size: supportsCount, claimID: givenClaim });
console.debug(
`Displaying supports for ${
account.accountID}${givenClaim ? ` and claimID ${givenClaim}` : ''}, (${supportsCount})`);
`Displaying supports for ${account.accountID}${givenClaim ? ` and claimID ${givenClaim}` : ''}, (${supportsCount})`);
const supports = (await supportsResponse.json()).result.items;
const paginator = new GenericPager(this.client, message, {
items: supports,
header: `All supports for <@${discordID || message.author.id}>${
givenClaim ? ` on claim \`${givenClaim}\`` : ''}`, itemTitle: 'Supports',itemsPerPage: 5,
display: item => `> ${item.name} \`${item.claim_id}\`\n> ${item.amount} LBC\n`
header: `All supports for <@${discordID || message.author.id}>${givenClaim ? ` on claim \`${givenClaim}\`` : ''}`, itemTitle: 'Supports',
display: item => `*lbry://**${item.name}***#\`${item.claim_id}\` (${item.amount} LBC)`
});
return paginator.start(message.channel.id, message.author.id);
}

View file

@ -60,7 +60,7 @@ module.exports = class Help extends Command {
value: command.metadata.note
});
return message.channel.createMessage({ embeds: [embed] });
return message.channel.createMessage({ embed });
}
} else {
// Display general help command
@ -89,7 +89,7 @@ module.exports = class Help extends Command {
inline: true
});
});
return message.channel.createMessage({ embeds: [embed] });
return message.channel.createMessage({ embed });
}
}

View file

@ -1,40 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class TAbandon extends Command {
get name() { return 'tabandon'; }
get _options() { return {
aliases: ['taban', 'tdrop'],
permissions: ['trustedOrAdmin'],
minimumArgs: 1
}; }
async exec(message, { args }) {
const givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');
if (!await this.client.messageAwaiter.confirm(message, {
header:
'Are you sure you want to abandon a claim from a **trusted** account?'
})) return;
const account = await Util.LBRY.findSDKAccount(this.client, account => account.is_default);
// Drop support
const response = await this.client.lbry.abandonSupport({
accountID: account.id, claimID: givenClaim });
const transaction = await response.json();
if (await this.handleResponse(message, response, transaction)) return;
const txid = transaction.result.txid;
return message.channel.createMessage(`Abandon successful! https://explorer.lbry.com/tx/${txid}`);
}
get metadata() { return {
category: 'Trusted',
description: 'Abandons a support on a given claim from the trusted account.',
usage: '<claim>'
}; }
};

View file

@ -1,30 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
const config = require('config');
module.exports = class TBalance extends Command {
get name() { return 'tbalance'; }
get _options() { return {
aliases: ['tbal', 'trustedbal', 'trustedbalance'],
permissions: ['trustedOrAdmin']
}; }
async exec(message) {
const account = await Util.LBRY.findSDKAccount(this.client, account => account.is_default);
const response = await this.client.lbry.accountBalance(account.id);
const wallet = await response.json();
if (await this.handleResponse(message, response, wallet)) return;
return message.channel.createMessage({ embeds: [{
color: config.embedColor,
description: `**${wallet.result.available}** LBC is available in the trusted account.\n\n` +
`Reserved in Supports: ${wallet.result.reserved_subtotals.supports} LBC\n` +
`Total: ${wallet.result.total} LBC`
}] });
}
get metadata() { return {
category: 'Trusted',
description: 'Shows the trusted wallet balance.'
}; }
};

View file

@ -1,51 +0,0 @@
const Command = require('../../structures/Command');
const Util = require('../../util');
module.exports = class TSupport extends Command {
get name() { return 'tsupport'; }
get _options() { return {
aliases: ['tsup'],
permissions: ['trustedOrAdmin'],
minimumArgs: 2
}; }
async exec(message, { args }) {
const givenAmount = Util.LBRY.ensureDecimal(args[1]);
if (!givenAmount)
return message.channel.createMessage('The second argument must be a numeric amount of LBC to send!');
const givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');
// Get and check balance
const account = await Util.LBRY.findSDKAccount(this.client, account => account.is_default);
const walletResponse = await this.client.lbry.accountBalance(account.id);
const wallet = await walletResponse.json();
if (await this.handleResponse(message, walletResponse, wallet)) return;
const balance = wallet.result.available;
if (parseFloat(givenAmount) > parseFloat(balance))
return message.channel.createMessage('You don\'t have enough LBC to do this!');
if (!await this.client.messageAwaiter.confirm(message, {
header:
'Are you sure you want to support a claim from a **trusted** account?'
})) return;
// Create support
const response = await this.client.lbry.createSupport({
accountID: account.id, claimID: givenClaim, amount: givenAmount });
const transaction = await response.json();
if (await this.handleResponse(message, response, transaction)) return;
const txid = transaction.result.txid;
return message.channel.createMessage(`Support successful! https://explorer.lbry.com/tx/${txid}`);
}
get metadata() { return {
category: 'Trusted',
description: 'Support a given claim from the trusted account.',
usage: '<claim> <amount>'
}; }
};

View file

@ -1,42 +0,0 @@
const Command = require('../../structures/Command');
const GenericPager = require('../../structures/GenericPager');
const Util = require('../../util');
module.exports = class TSupports extends Command {
get name() { return 'tsupports'; }
get _options() { return {
aliases: ['tsups'],
permissions: ['trustedOrAdmin'],
minimumArgs: 0
}; }
async exec(message, { args }) {
let givenClaim;
if (args[0]) {
givenClaim = Util.resolveToClaimID(args[0]);
if (!givenClaim)
// @TODO use claim_search for invalid claim ids
return message.channel.createMessage('That Claim ID isn\'t valid.');
}
const account = await Util.LBRY.findSDKAccount(this.client, account => account.is_default);
const supportsCount = await Util.LBRY.getSupportsCount(this.client, account.id);
if (supportsCount <= 0)
return message.channel.createMessage('No supports found.');
const supportsResponse = await this.client.lbry.listSupports({
accountID: account.id, page_size: supportsCount, claimID: givenClaim });
const supports = (await supportsResponse.json()).result.items;
const paginator = new GenericPager(this.client, message, {
items: supports,
header: `All supports for the trusted account${
givenClaim ? ` on claim \`${givenClaim}\`` : ''}`, itemTitle: 'Supports', itemsPerPage: 5,
display: item => `> ${item.name} \`${item.claim_id}\`\n> ${item.amount} LBC\n`
});
return paginator.start(message.channel.id, message.author.id);
}
get metadata() { return {
category: 'Trusted',
description: 'Shows the list of supports from the trusted account.',
usage: '[claimID]'
}; }
};

View file

@ -1,4 +1,4 @@
const { default: Redis } = require('ioredis');
const redis = require('redis');
const { EventEmitter } = require('eventemitter3');
/**
@ -19,7 +19,7 @@ module.exports = class Database extends EventEmitter {
connect({ host = 'localhost', port, password, prefix }) {
console.info('Connecting to redis...');
return new Promise((resolve, reject) => {
this.redis = new Redis(port, host, { password, keyPrefix: prefix });
this.redis = redis.createClient({ host, port, password, prefix });
this.redis.on('error', this.onError.bind(this));
this.redis.on('warning', w => console.warn('Redis Warning', w));
this.redis.on('end', () => this.onClose.bind(this));
@ -37,32 +37,67 @@ module.exports = class Database extends EventEmitter {
// #region Redis functions
hget(key, hashkey) {
return this.redis.hget(key, hashkey);
return new Promise((resolve, reject) => {
this.redis.HGET(key, hashkey, (err, value) => {
if (err) reject(err);
resolve(value);
});
});
}
hset(key, hashkey, value) {
return this.redis.hset(key, hashkey, value);
return new Promise((resolve, reject) => {
this.redis.HSET(key, hashkey, value, (err, res) => {
if (err) reject(err);
resolve(res);
});
});
}
incr(key) {
return this.redis.incr(key);
return new Promise((resolve, reject) => {
this.redis.incr(key, (err, res) => {
if (err) reject(err);
resolve(res);
});
});
}
get(key) {
return this.redis.get(key);
return new Promise((resolve, reject) => {
this.redis.get(key, function(err, reply) {
if (err) reject(err);
resolve(reply);
});
});
}
expire(key, ttl) {
return this.redis.expire(key, ttl);
return new Promise((resolve, reject) => {
this.redis.expire(key, ttl, (err, value) => {
if (err) reject(err);
resolve(value);
});
});
}
exists(key) {
return this.redis.exists(key);
return new Promise((resolve, reject) => {
this.redis.exists(key, (err, value) => {
if (err) reject(err);
resolve(value === 1);
});
});
}
set(key, value) {
return this.redis.set(key, value);
return new Promise((resolve, reject) => {
this.redis.set(key, value, (err, res) => {
if (err) reject(err);
resolve(res);
});
});
}
// #endregion

View file

@ -40,11 +40,11 @@ module.exports = class Events {
}
}
onReaction(message, emoji, member) {
const id = `${message.id}:${member.id}`;
onReaction(message, emoji, userID) {
const id = `${message.id}:${userID}`;
if (this.client.messageAwaiter.reactionCollectors.has(id)) {
const collector = this.client.messageAwaiter.reactionCollectors.get(id);
collector._onReaction(emoji, member.id);
collector._onReaction(emoji, userID);
}
}
};

View file

@ -42,9 +42,6 @@ class Command {
` \`${this.metadata.usage}\`` : ''}`);
// Check commmand permissions
const curators = Array.isArray(config.curatorRoleID) ? config.curatorRoleID : [config.curatorRoleID];
const admins = Array.isArray(config.adminRoleID) ? config.adminRoleID : [config.adminRoleID];
const trusteds = Array.isArray(config.trustedRoleID) ? config.trustedRoleID : [config.trustedRoleID];
if (this.options.permissions.length)
for (const i in this.options.permissions) {
const perm = this.options.permissions[i];
@ -56,22 +53,13 @@ class Command {
embed: 'I need the permission `Embed Links` to use this command!',
emoji: 'I need the permission `Use External Emojis` to use this command!',
elevated: 'Only the elevated users of the bot can use this command!',
curator: `This command requires you to have the ${
curators.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} role!`,
admin: `This command requires you to have the ${
admins.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} role!`,
curatorOrAdmin: `This command requires you to have the ${
curators.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} or ${
admins.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} role!`,
trustedOrAdmin: `This command requires you to have the ${
trusteds.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} or ${
admins.map(id =>
`"${this.client.guilds.get(config.guildID).roles.get(id).name}"`).join('/')} role!`,
curator: `This command requires you to have the "${
message.guild.roles.get(config.curatorRoleID).name}" role!`,
admin: `This command requires you to have the "${
message.guild.roles.get(config.adminRoleID).name}" role!`,
curatorOrAdmin: `This command requires you to have the "${
message.guild.roles.get(config.curatorRoleID).name}" or "${
message.guild.roles.get(config.adminRoleID).name}" roles!`,
guild: 'This command must be ran in a guild!',
}[perm]);
}

View file

@ -21,7 +21,7 @@ class GenericPager extends Paginator {
constructor(client, message, {
items = [], itemsPerPage = 15,
display = item => item.toString(),
embedExtra = {}, itemTitle = 'Items',
embedExtra = {}, itemTitle = 'words.item.many',
header = null, footer = null
} = {}) {
super(client, message, { items, itemsPerPage });
@ -70,7 +70,7 @@ class GenericPager extends Paginator {
value: displayPage.join('\n')
});
return { embeds: [embed] };
return { embed };
} else {
const top = `${this.itemTitle} ` +
`(${this.items.length}, Page ${this.pageNumber}/${this.maxPages})`;

View file

@ -1,3 +1,5 @@
const fetch = require('node-fetch');
const AbortController = require('abort-controller');
const config = require('config');
const Util = require('../util');
@ -180,13 +182,10 @@ class LBRY {
}
/**
* List account addresses or details.
* @param {object} options
* @param {string} options.to How many items should be per page
* @param {string} options.amount The amount to send
* List account addresses or details of single address.
*/
listAddresses({ page_size = 1, account_id } = {}) {
return this._sdkRequest('address_list', { page_size, account_id });
listAddresses() {
return this._sdkRequest('address_list', { page_size: 1 });
}
/**

View file

@ -1,6 +1,5 @@
const fetch = require('node-fetch');
const config = require('config');
const fs = require('fs');
const path = require('path');
/**
* Represents the utilities for the bot
@ -49,7 +48,7 @@ Util.Prefix = {
Util.Prefix.regex(client, prefixes), '$2').replace(/\s\s+/g, ' ').trim();
},
escapeRegex(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}!]/g, '\\$&');
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}
};
@ -69,40 +68,24 @@ Util.CommandPermissions = {
curator: (client, message) => {
const member = message.guildID ? message.member :
client.guilds.get(config.guildID).members.get(message.author.id);
const roles = Array.isArray(config.curatorRoleID) ? config.curatorRoleID : [config.curatorRoleID];
if (!member) return false;
if (Util.CommandPermissions.elevated(client, message)) return true;
return roles.map(r => member.roles.includes(r)).includes(true);
return member.roles.includes(config.curatorRoleID);
},
admin: (client, message) => {
const member = message.guildID ? message.member :
client.guilds.get(config.guildID).members.get(message.author.id);
const roles = Array.isArray(config.adminRoleID) ? config.adminRoleID : [config.adminRoleID];
if (!member) return false;
if (Util.CommandPermissions.elevated(client, message)) return true;
return roles.map(r => member.roles.includes(r)).includes(true);
return member.roles.includes(config.adminRoleID);
},
curatorOrAdmin: (client, message) => {
const member = message.guildID ? message.member :
client.guilds.get(config.guildID).members.get(message.author.id);
const roles = [
...(Array.isArray(config.adminRoleID) ? config.adminRoleID : [config.adminRoleID]),
...(Array.isArray(config.curatorRoleID) ? config.curatorRoleID : [config.curatorRoleID]),
];
if (!member) return false;
if (Util.CommandPermissions.elevated(client, message)) return true;
return roles.map(r => member.roles.includes(r)).includes(true);
},
trustedOrAdmin: (client, message) => {
const member = message.guildID ? message.member :
client.guilds.get(config.guildID).members.get(message.author.id);
const roles = [
...(Array.isArray(config.adminRoleID) ? config.adminRoleID : [config.adminRoleID]),
...(Array.isArray(config.trustedRoleID) ? config.trustedRoleID : [config.trustedRoleID]),
];
if (!member) return false;
if (Util.CommandPermissions.elevated(client, message)) return true;
return roles.map(r => member.roles.includes(r)).includes(true);
return member.roles.includes(config.curatorRoleID) ||
member.roles.includes(config.adminRoleID);
},
};
@ -162,20 +145,6 @@ Util.resolveToUserID = (arg) => {
else return null;
};
/**
* Resolve argument to a claim ID
* @memberof Util.
* @param {string} arg
* @returns {?string}
*/
Util.resolveToClaimID = (arg) => {
if (/^[a-f0-9]{40}$/.test(arg))
return arg;
else if (/^lbry:\/\/@?[\w-]+#([a-f0-9]{40})$/.test(arg))
return arg.replace(/^lbry:\/\/@?[\w-]+#([a-f0-9]{40})$/, '$1');
else return null;
};
/**
* Make a promise that resolves after some time
* @memberof Util.
@ -232,26 +201,6 @@ Util.Hastebin = {
* @memberof Util.
*/
Util.LBRY = {
async syncPairs(client) {
const response = await client.lbry.listAccounts({ page_size: await Util.LBRY.getAccountCount(client) });
const accounts = await response.json();
let syncedAccounts = 0;
for (const account of accounts.result.items) {
if (/\d{17,19}/.test(account.name)) {
if (await client.sqlite.get(account.name)) continue;
await client.sqlite.pair(account.name, account.id);
syncedAccounts++;
}
}
return syncedAccounts;
},
async findSDKAccount(client, fn) {
const response = await client.lbry.listAccounts({ page_size: await Util.LBRY.getAccountCount(client) });
const accounts = await response.json();
return accounts.result.items.find(fn);
},
async findOrCreateAccount(client, discordID, create = true) {
// Check SQLite
const pair = await client.sqlite.get(discordID);
@ -259,7 +208,9 @@ Util.LBRY = {
return { accountID: pair.lbryID };
// Check accounts via SDK
const foundAccount = await Util.LBRY.findSDKAccount(client, account => account.name === discordID);
const response = await client.lbry.listAccounts({ page_size: await Util.LBRY.getAccountCount(client) });
const accounts = await response.json();
const foundAccount = accounts.result.items.find(account => account.name === discordID);
if (foundAccount) {
await client.sqlite.pair(discordID, foundAccount.id);
return { accountID: foundAccount.id };
@ -299,27 +250,14 @@ Util.LBRY = {
return Number.isInteger(num) ? `${num}.0` : num.toString();
},
async deleteAccount(client, discordID, lbryID) {
// Backup the wallet before doing any delete function
try {
Util.LBRY.backupWallet();
} catch (err) {
console.error('Error occurred while backing up wallet file!');
console.error(err);
throw err;
}
// Abandon supports
await Util.LBRY.abandonAllClaims(client, lbryID);
// Take out funds from account
const balanceResponse = await client.lbry.accountBalance(lbryID);
let amount = (await balanceResponse.json()).result.total;
while (amount >= 2) {
const amount = (await balanceResponse.json()).result.total;
if (parseFloat(amount) > 0)
await client.lbry.fundAccount({ from: lbryID, everything: true, amount });
const finalBalance = await client.lbry.accountBalance(lbryID);
amount = (await finalBalance.json()).result.total;
await Util.halt(3000);
}
// Remove account from SDK & SQLite
await client.lbry.removeAccount(lbryID);
@ -336,27 +274,7 @@ Util.LBRY = {
for (let i = 0, len = supports.length; i < len; i++) {
const support = supports[i];
await client.lbry.abandonSupport({ claimID: support.claim_id, accountID: lbryID });
await Util.halt(3000);
}
return { count: supports.length };
},
backupWallet() {
const wallet = fs.readFileSync(config.walletPath);
const d = new Date();
const date = [
d.getUTCFullYear(),
d.getUTCMonth().toString().padStart(2, '0'),
d.getUTCDay().toString().padStart(2, '0'),
].join('-');
const time = [
d.getUTCHours().toString().padStart(2, '0'),
d.getUTCMinutes().toString().padStart(2, '0'),
d.getUTCSeconds().toString().padStart(2, '0'),
d.getUTCMilliseconds().toString()
].join('-');
const backupName = 'default_wallet.' + date + '_' + time + '.bak';
const backupPath = path.join(config.walletBackupFolder, backupName);
fs.writeFileSync(backupPath, wallet);
console.log(`Backed up wallet file: ${backupPath}`);
}
};
};

2137
yarn.lock

File diff suppressed because it is too large Load diff