Add admin commands

This commit is contained in:
Snazzah 2021-06-24 23:50:10 -05:00
parent d09c8fa311
commit 2c156c6853
No known key found for this signature in database
GPG key ID: 5E71D54F3D86282E
11 changed files with 526 additions and 0 deletions

View file

@ -0,0 +1,67 @@
import { oneLine } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { confirm, resolveUser } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class AbandonAllCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'abandonall',
description: 'Abandons all supports of all accounts or of a given account.',
category: 'Admin',
aliases: ['abanall', 'dropall'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['abandonall', 'abandonall @user'],
usage: '[user]'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
// Single user abandon
if (ctx.args[0]) {
const discordID = resolveUser(ctx.args[0]);
if (!discordID) return "That Discord user isn't valid.";
const account = await this.lbryx.ensureAccount(discordID, false);
if (!account.id) return 'That user does not have an account.';
const supportsCount = await this.lbryx.getSupportsCount(account.id);
if (supportsCount <= 0) return 'That user does not have any supports.';
if (
!(await confirm(
ctx,
oneLine`
Are you sure you want to abandon **all supports** from that account?
*(${supportsCount.toLocaleString()} support[s])*
`
))
)
return;
await this.lbryx.abandonAllClaims(account.id);
return `Abandoned ${supportsCount.toLocaleString()} support(s).`;
}
// Abandon ALL supports
await this.lbryx.sync();
const pairs = this.lbryx.getIDs();
if (pairs.length <= 0) return 'No pairs in the database.';
if (!(await confirm(ctx, 'Are you sure you want to abandon **all supports** from **all accounts**?')))
return;
await this.client.startTyping(ctx.channel.id);
let count = 0;
for (const [, lbryID] of pairs) {
const result = await this.lbryx.abandonAllClaims(lbryID);
if (result) count += result.count;
}
this.client.stopTyping(ctx.channel.id);
return `Abandoned ${count.toLocaleString()} supports(s).`;
}
}

View file

@ -0,0 +1,24 @@
import { DexareClient } from 'dexare';
import { GeneralCommand } from '../../util/abstracts';
export default class AdminBalanceCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'adminbalance',
description: 'Shows the master wallet balance.',
category: 'Admin',
aliases: ['abal', 'adminbal'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['adminbalance']
}
});
this.filePath = __filename;
}
async run() {
const wallet = await this.lbry.walletBalance();
return this.displayWallet(wallet, 'Master Wallet Balance');
}
}

View file

@ -0,0 +1,71 @@
import { CommandContext, DexareClient } from 'dexare';
import { Support } from '../../modules/lbry/types';
import { GeneralCommand } from '../../util/abstracts';
import { paginate } from '../../util/pager';
export default class AllSupportsommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'allsupports',
description: 'List all supports from all users.',
category: 'Admin',
aliases: ['asups', 'allsups'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['allsupports', 'allsupports @channel#a/video#b'],
usage: '[claim]'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
let claim: string | null = null;
if (ctx.args[0]) {
claim = await this.lbryx.resolveClaim(ctx.args[0]);
if (!claim) return "That claim isn't valid.";
}
await this.lbryx.sync();
const pairs = this.lbryx.getIDs();
if (pairs.length <= 0) return 'No users found in the database.';
const allSupports: (Support & {
discordID: string;
accountID: string;
})[] = [];
for (const [discordID, accountID] of pairs) {
const supportsCount = await this.lbryx.getSupportsCount(accountID);
if (supportsCount <= 0) continue;
const supports = await this.lbry.supportList({
account_id: accountID,
page_size: supportsCount,
claim_id: claim || undefined
});
for (const support of supports.items)
allSupports.push({
...support,
discordID,
accountID
});
}
if (allSupports.length <= 0) return 'No supports found.';
await paginate(
ctx,
{
title: 'Supports',
items: allSupports.map(
(item) => `> ${item.name} \`${item.claim_id}\`\n> <@${item.discordID}> ${item.amount} LBC`
),
itemSeparator: '\n\n'
},
{
author: { name: `All supports ${claim ? ` on claim \`${claim}\`` : ''}` }
}
);
}
}

View file

@ -0,0 +1,48 @@
import { stripIndents } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { confirm, resolveUser } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class DeleteAccountommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'deleteaccount',
description: "Delete a user's Curation account.",
category: 'Admin',
aliases: ['del', 'delacc'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['deleteaccount @user'],
usage: '<user>'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
const discordID = resolveUser(ctx.args[0]);
if (!discordID) return "That Discord user isn't valid.";
const account = await this.lbryx.ensureAccount(discordID, false);
if (!account.id) return 'That user does not have an account.';
const supportsCount = await this.lbryx.getSupportsCount(account.id);
if (
!(await confirm(
ctx,
`Are you sure you want to delete that account? *(${supportsCount.toLocaleString()} support[s])*`
))
)
return;
try {
await this.lbryx.deleteAccount(discordID, account.id);
return 'Deleted account.';
} catch (e) {
return stripIndents`
Failed to delete the account. An error most likely occured while backing up the wallet.
\`\`\`\n${e.toString()}\`\`\`
`;
}
}
}

View file

@ -0,0 +1,47 @@
import { stripIndents } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { confirm } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class DeleteAllCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'deleteall',
description: 'Deletes all accounts in the database.',
category: 'Admin',
aliases: ['delall'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['delall']
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
await this.lbryx.sync();
const pairs = this.lbryx.getIDs();
if (pairs.length <= 0) return 'No pairs in the database.';
if (
!(await confirm(
ctx,
`Are you sure you want to delete **all** ${pairs.length.toLocaleString()} accounts?`
))
)
return;
for (const [discordID, lbryID] of pairs) {
try {
await this.lbryx.deleteAccount(discordID, lbryID);
} catch (e) {
return stripIndents`
Failed to delete an account. An error most likely occured while backing up the wallet.
\`\`\`\n${e.toString()}\`\`\`
`;
}
}
return 'Deleted all accounts.';
}
}

View file

@ -0,0 +1,25 @@
import { DexareClient } from 'dexare';
import { GeneralCommand } from '../../util/abstracts';
export default class DepositCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'deposit',
description: 'Gets the address of the master wallet.',
category: 'Admin',
aliases: ['dp'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['deposit']
}
});
this.filePath = __filename;
}
async run() {
const accountID = await this.lbryx.getDefaultAccount();
const addresses = await this.lbry.addressList({ account_id: accountID, page_size: 1 });
return `Address: ${addresses.items[0].address}`;
}
}

View file

@ -0,0 +1,40 @@
import { stripIndents } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { confirm, ensureDecimal, resolveUser } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class FundCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'fund',
description: "Fund a user's account with some LBC.",
category: 'Admin',
aliases: ['fundacc', 'fundaccount'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['fund @user 2.0'],
usage: '<user> <amount>'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
const discordID = resolveUser(ctx.args[0]);
if (!discordID) return "That Discord user isn't valid.";
const account = await this.lbryx.ensureAccount(discordID, false);
if (!account.id) return 'That user does not have an account.';
const amount = ensureDecimal(ctx.args[1]);
if (!amount) return 'You must give a numeric amount of LBC to send!';
if (!(await confirm(ctx, `Are you sure you want to fund this account ${amount} LBC?`))) return;
const transaction = await this.lbry.accountFund({ amount, to_account: account.id, broadcast: true });
this.log('info', `Funded account ${account.id} ${amount}`, transaction);
return stripIndents`
Successfully funded account!
🔗 https://explorer.lbry.com/tx/${transaction.txid}
`;
}
}

View file

@ -0,0 +1,77 @@
import { CommandContext, DexareClient } from 'dexare';
import { confirm, ensureDecimal, wait } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class FundAllCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'fundall',
description: 'Fund all users in the database with some LBC.',
category: 'Admin',
userPermissions: ['lbry.admin'],
metadata: {
examples: ['fundall 2.0'],
usage: '<amount>'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
const amount = ensureDecimal(ctx.args[0]);
if (!amount) return 'You must give a numeric amount of LBC to send!';
await this.lbryx.sync();
const pairs = this.lbryx.getIDs();
// empty DB population
if (pairs.length <= 0) {
const procMsg = await ctx.reply('No users in the database, creating accounts...');
await this.client.startTyping(ctx.channel.id);
const rolesConfig: string | string[] = this.client.config.curatorRoles;
const curatorRoles = Array.isArray(rolesConfig) ? rolesConfig : [rolesConfig];
const members = await this.client.bot.guilds.get(this.client.config.guildID)!.fetchMembers();
for (const member of members) {
if (curatorRoles.map((r) => member.roles.includes(r)).includes(true)) {
const account = await this.lbryx.ensureAccount(member.id);
pairs.push([member.id, account.id]);
}
}
await wait(5000);
this.client.stopTyping(ctx.channel.id);
await procMsg.delete().catch(() => {});
}
if (!(await confirm(ctx, `Are you sure you want to fund **all** accounts ${amount} LBC?`))) return;
await this.client.startTyping(ctx.channel.id);
const resultLines = [];
let funded = 0,
errored = 0;
for (const [discordID, accountID] of pairs) {
try {
const transaction = await this.lbry.accountFund({ to_account: accountID, amount, broadcast: true });
console.info('Funded account', accountID, transaction.txid);
resultLines.push(`${discordID} - https://explorer.lbry.com/tx/${transaction.txid}`);
funded++;
} catch (e) {
this.log('info', 'Failed to fund account', accountID, e);
resultLines.push(`${discordID} ! ${e.toString()}`);
errored++;
}
await wait(3000);
}
this.client.stopTyping(ctx.channel.id);
await ctx.reply(
errored
? `Failed to fund ${errored} accounts! (${funded} funded)`
: `Successfully funded ${funded} account(s)!`,
{
name: 'result.txt',
file: Buffer.from(resultLines.join('\n'), 'utf8')
}
);
}
}

View file

@ -0,0 +1,51 @@
import { stripIndents } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { Balance } from '../../modules/lbry/types';
import { GeneralCommand } from '../../util/abstracts';
import { paginate } from '../../util/pager';
export default class ListAllCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'listall',
description: 'List all users in the database.',
category: 'Admin',
userPermissions: ['lbry.admin'],
metadata: {
examples: ['listall', 'listall 2'],
usage: '[page]'
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
const pairs = this.lbryx.getIDs();
const walletMap = new Map<string, Balance>();
for (const [, accountID] of pairs) {
try {
const wallet = await this.lbry.accountBalance({ account_id: accountID });
walletMap.set(accountID, wallet);
} catch (e) {}
}
await paginate(ctx, {
title: 'Users',
items: pairs.map(([discordID, accountID]) => {
const bal = walletMap.get(accountID);
return stripIndents`
> <@${discordID}> - \`${accountID}\`
> ${
bal
? `${bal.available} available, ${bal.reserved_subtotals.supports} staked.`
: 'Wallet Unavailable'
}
`;
}),
itemSeparator: '\n\n',
startPage: parseInt(ctx.args[0])
});
}
}

View file

@ -0,0 +1,23 @@
import { DexareClient } from 'dexare';
import { GeneralCommand } from '../../util/abstracts';
export default class SyncCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'sync',
description: 'Syncs ID pairs with the SDK.',
category: 'Admin',
userPermissions: ['lbry.admin'],
metadata: {
examples: ['sync']
}
});
this.filePath = __filename;
}
async run() {
const synced = await this.lbryx.sync();
return `Synced ${synced.toLocaleString()} new pairs.`;
}
}

View file

@ -0,0 +1,53 @@
import { oneLine, stripIndents } from 'common-tags';
import { CommandContext, DexareClient } from 'dexare';
import { confirm, ensureDecimal } from '../../util';
import { GeneralCommand } from '../../util/abstracts';
export default class WithdrawCommand extends GeneralCommand {
constructor(client: DexareClient<any>) {
super(client, {
name: 'withdraw',
description: 'Sends funds to an address from the master wallet.',
category: 'Admin',
aliases: ['wd'],
userPermissions: ['lbry.admin'],
metadata: {
examples: ['wd abcd1234 2.0']
}
});
this.filePath = __filename;
}
async run(ctx: CommandContext) {
const addr = ctx.args[0];
if (!addr || !/^[\w]{34}$/.test(addr)) return 'The address is invalid!';
const amount = ensureDecimal(ctx.args[1]);
if (!amount) return 'You must give a numeric amount of LBC to send!';
// Check if the balance is more than requested
const balance = await this.lbry.walletBalance();
const availableBalance = parseFloat(balance.available);
if (parseFloat(amount) > availableBalance)
return 'There is not enough available LBC in the wallet to send that amount!';
// Send to wallet
if (
!(await confirm(
ctx,
oneLine`
Are you sure you want to send ${amount} to \`${addr}\`?
*(remaining: ${availableBalance - parseFloat(amount)})*
`
))
)
return;
const transaction = await this.lbry.walletSend({ amount, addresses: addr });
this.log('info', `Withdrew ${amount} from master wallet to ${addr}`, transaction);
return stripIndents`
Sent ${amount} LBC to \`${addr}\`.
🔗 https://explorer.lbry.com/tx/${transaction.txid}
`;
}
}