diff --git a/src/commands/admin/deleteaccount.js b/src/commands/admin/deleteaccount.js new file mode 100644 index 0000000..8f6e52f --- /dev/null +++ b/src/commands/admin/deleteaccount.js @@ -0,0 +1,34 @@ +const Command = require('../../structures/Command'); +const Util = require('../../util'); + +module.exports = class DeleteAccount extends Command { + get name() { return 'deleteaccount'; } + + get _options() { return { + aliases: ['del', 'delacc'], + permissions: ['admin'], + minimumArgs: 1 + }; } + + async exec(message, { args }) { + const discordID = Util.resolveToUserID(args[0]); + if (!discordID) + message.channel.createMessage('That Discord user isn\'t valid.'); + const account = await Util.LBRY.findOrCreateAccount(this.client, discordID, false); + if (account.accountID) { + const supportsCount = await Util.LBRY.getSupportsCount(this.client, account.accountID); + if (!await this.client.messageAwaiter.confirm(message, { + header: `Are you sure you delete that account? *(${supportsCount.toLocaleString()} support[s])*` + })) return; + 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.'); + } + + get metadata() { return { + category: 'Admin', + description: 'Deletes a given Discord user\'s Curation account.', + usage: '' + }; } +}; diff --git a/src/sqlitedb.js b/src/sqlitedb.js index 04daa30..91aa713 100644 --- a/src/sqlitedb.js +++ b/src/sqlitedb.js @@ -44,4 +44,12 @@ module.exports = class SQLiteDB { pair(discordID, lbryID) { return this.model.create({ discordID, lbryID }); } + + /** + * Removes an ID pair + * @param {string} discordID + */ + remove(discordID) { + return this.client.sqlite.model.destroy({ where: { discordID } }); + } }; \ No newline at end of file diff --git a/src/structures/LBRY.js b/src/structures/LBRY.js index 3f66808..e9a9a72 100644 --- a/src/structures/LBRY.js +++ b/src/structures/LBRY.js @@ -1,6 +1,7 @@ const fetch = require('node-fetch'); const AbortController = require('abort-controller'); const config = require('config'); +const Util = require('../util'); class LBRY { constructor(client) { @@ -111,7 +112,8 @@ class LBRY { * @param {string} options.amount The amount to fund (integer/float string) */ fundAccount({ to, from, everything, amount }) { - return this._sdkRequest('account_fund', { to_account: to, from_account: from, everything, amount }); + return this._sdkRequest('account_fund', { + to_account: to, from_account: from, everything, amount: Util.LBRY.ensureDecimal(amount) }); } /** @@ -175,7 +177,7 @@ class LBRY { * @param {string} options.amount The amount to send */ sendToWallet({ amount, to }) { - return this._sdkRequest('wallet_send', { amount, addresses: to }); + return this._sdkRequest('wallet_send', { amount: Util.LBRY.ensureDecimal(amount), addresses: to }); } /** diff --git a/src/util.js b/src/util.js index 4bd1348..90e02e1 100644 --- a/src/util.js +++ b/src/util.js @@ -131,6 +131,20 @@ Util.cutoffArray = (texts, limit = 2000, rollbackAmount = 1, paddingAmount = 1) return texts; }; +/** + * Resolve argument to a user ID + * @memberof Util. + * @param {string} arg + * @returns {?string} + */ +Util.resolveToUserID = (arg) => { + if (/^\d{17,18}$/.test(arg)) + return arg; + else if (/^<@!?\d{17,18}>$/.test(arg)) + return arg.replace(/^<@!?(\d{17,18})>$/, '$1'); + else return null; +}; + /** * Hastebin-related functions * @memberof Util. @@ -177,30 +191,38 @@ Util.Hastebin = { * @memberof Util. */ Util.LBRY = { - async findOrCreateAccount(client, discordID) { + async findOrCreateAccount(client, discordID, create = true) { // Check SQLite const pair = await client.sqlite.get(discordID); if (pair) return { accountID: pair.lbryID }; // Check accounts via SDK - const response = await client.lbry.listAccounts({ page_size: Util.LBRY.getAccountCount(client) }); + 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) + if (foundAccount) { + await client.sqlite.pair(discordID, foundAccount.id); return { accountID: foundAccount.id }; + } // Create account if not found - const newAccount = await Util.LBRY.createAccount(client, discordID); - return { - accountID: newAccount.result.id, - newAccount: true - }; + if (create) { + const newAccount = await Util.LBRY.createAccount(client, discordID); + return { + accountID: newAccount.result.id, + newAccount: true + }; + } else return { accountID: null }; }, async getAccountCount(client) { const response = await client.lbry.listAccounts({ page_size: 1 }).then(r => r.json()); return response.result.total_items; }, + async getSupportsCount(client, accountID) { + const response = await client.lbry.listSupports({ accountID, page_size: 1 }).then(r => r.json()); + return response.result.total_items; + }, async createAccount(client, discordID) { console.info('Creating account for user', discordID); const account = await client.lbry.createAccount(discordID).then(r => r.json()); @@ -213,5 +235,32 @@ Util.LBRY = { const num = parseFloat(str); if (isNaN(num)) return null; return Number.isInteger(num) ? `${num}.0` : num.toString(); + }, + async deleteAccount(client, discordID, lbryID) { + // Abandon supports + await Util.LBRY.abandonAllClaims(client, lbryID); + + // Take out funds from account + const balanceResponse = await client.lbry.accountBalance(lbryID); + const amount = (await balanceResponse.json()).result.total; + if (parseFloat(amount) > 0) + await client.lbry.fundAccount({ from: lbryID, everything: true, amount }); + + // Remove account from SDK & SQLite + await client.lbry.removeAccount(lbryID); + await client.sqlite.remove(discordID); + }, + async abandonAllClaims(client, lbryID) { + if (!lbryID) + throw new Error('lbryID must be defined!'); + const supportsCount = await Util.LBRY.getSupportsCount(client, lbryID); + const supportsResponse = await client.lbry.listSupports({ + accountID: lbryID, page_size: supportsCount }); + console.info(`Abandoning claims for ${lbryID} (${supportsCount})`); + const supports = (await supportsResponse.json()).result.items; + for (let i = 0, len = supports.length; i < len; i++) { + const support = supports[i]; + await client.lbry.abandonSupport({ claimID: support.claim_id, accountID: lbryID }); + } } }; \ No newline at end of file