diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9c7435c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,81 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es2020": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module" + }, + "rules": { + "arrow-spacing": [ + "error", + { + "before": true, + "after": true + } + ], + "brace-style": [ + "error", + "1tbs", + { + "allowSingleLine": true + } + ], + "comma-dangle": [ + "error", + "never" + ], + "comma-spacing": "error", + "eqeqeq": [ + "error", + "smart" + ], + "func-call-spacing": "error", + "line-comment-position": "error", + "prefer-const": "error", + "prefer-destructuring": "error", + "no-array-constructor": "error", + "no-dupe-else-if": "error", + "no-duplicate-imports": "error", + "no-extra-semi": "error", + "no-inline-comments": "error", + "no-invalid-regexp": "error", + "no-invalid-this": "error", + "no-lonely-if": "error", + "no-new": "error", + "no-new-object": "error", + "no-new-wrappers": "error", + "no-return-await": "error", + "no-self-compare": "error", + "no-undef-init": "error", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-useless-constructor": "error", + "no-unused-vars": "off", + "prefer-regex-literals": [ + "error", + { + "disallowRedundantWrapping": true + } + ], + "prefer-spread": "error", + "prefer-template": "error", + "require-await": "error", + "semi": "error", + "semi-style": "error", + "space-before-blocks": "error", + "use-isnan": "error", + "valid-typeof": "error", + "quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef8e722 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +pnpm-lock.yaml +.idea/ diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..57c5a66 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +tasks: + - init: npm install + command: npm run start diff --git a/README.md b/README.md new file mode 100644 index 0000000..34722f8 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Wiki Utilities +Wiki Utilities is a Discord bot, which allows wiki (think Fandom, Wikipedia) admins to take administrative actions, such as deleting, moving, protecting pages and more, through Discord. As Wiki Utilities requires credentials, it is a self-hosted bot, meaning you need to host it yourself to use it. + +### Features +* Blocking users. +* Listing pages in a category. +* Deleting pages. +* Editing pages. +* Moving (i.e. renaming) pages. +* Modifying the protection levels of pages. +* Undeleting (i.e. restoring) pages. + +Upcoming: + +Integration with [RcGcDw](https://gitlab.com/piotrex43/RcGcDw/) (see [#110](https://gitlab.com/piotrex43/RcGcDw/-/issues/110)), allowing admins to lazily react to log messages with emojis, to either block the user, revert the edit, or delete the page. + +### Installing +1. Clone this repo to your machine by running `git clone https://github.com/Sidemen19/Wiki-Utilities.git`. +2. Go to the Discord developer's website while you are logged in to your Discord account [here](https://discordapp.com/developers/applications/). + * Create a new application. + * Copy the Client ID. + * Next, add a Bot (NOTE - you need to make the bot private (meaning only you can invite it to servers), by toggling the switch). + +3. Get the bot's token from the Bot page, copy it, and paste it into the `token` key of [config.json](config.json). +4. Invite Wiki Utilities to a server, by going to `https://discord.com/oauth2/authorize?client_id=INSERT_CLIENT_ID_HERE&scope=bot&permissions=330816`. +5. Install required dependencies, using `pnpm install` or `npm install`. +6. Get the bot online by running `node .` in the root directory. + +### Configuration +All configuration options are stored in [config.json](config.json). + +* `token`: The token of the bot. Get this by following the above instructions. +* `prefixes`: An array of all the prefixes the bot recognises. Defaults to just `wu!`. +* `owners`: An array of all the owners of the bot. Put your Discord ID here, and any others if you want. +* `wiki` + * `url`: The URL to the wiki. Example: `https://community.fandom.com` + * `allowed_roles`: An array of role IDs, the members of it will be able to take administrative wiki actions. (NOTE: give this only to a trusted role, this is basically giving admin rights to whoever is in this role.) + * `blacklisted_users`: An array of user IDs, this overrides `allowed_roles`, removing the right from any untrustworthy users. + * `rcgcdw_extension` (not completed yet) + * `enabled`: Whether the extension is enabled or not. + * `channel_id`: The channel ID of the webhook. + * `emojis`: Any custom emojis to use when reacting, instead of the default regional indicators. + * `credentials` (these must be obtained from `Special:BotPasswords`) + * `username`: The username. (it is recommended to use a separate bot account, and give that admin rights, instead of your main account). + * `password`: The password. + + +### Support +https://discord.com/invite/2ZjJbBJ \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..4dc5baa --- /dev/null +++ b/config.json @@ -0,0 +1,27 @@ +{ + "token": "", + "prefixes": ["wu!"], + "owners": [""], + "wiki": { + "url": "", + "allowed_roles": [""], + "blacklisted_users": [], + "rcgcdw_extension": { + "enabled": false, + "channel_id": "", + "emojis": { + "delete": "\uD83C\uDDE9", + "revert": "\uD83C\uDDF7", + "block": "\uD83C\uDDE7" + } + }, + "user_map": { + "enabled": true, + "": "" + }, + "credentials": { + "username": "", + "password": "" + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b7e883 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "wiki-utilities", + "version": "1.0.0", + "description": "Discord bot for taking administrative actions on a Fandom wiki through Discord.", + "main": "src/bot/index.js", + "scripts": { + "start": "node .", + "lint": "eslint .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "engines": { + "node": "12.x", + "pnpm": ">=3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Sidemen19/Wiki-Utilities.git" + }, + "keywords": [ + "discord", + "discord-bot", + "bot", + "wiki", + "fandom", + "mediawiki" + ], + "author": "Sidemen19", + "license": "MIT", + "dependencies": { + "@sidemen19/mediawiki.js": "^1.0.0", + "common-tags": "^1.8.0", + "discord-akairo": "^8.1.0", + "discord.js": "^12.4.1", + "got": "^11.8.0" + }, + "devDependencies": { + "eslint": "^7.12.1" + } +} diff --git a/src/bot/commands/utilities/help.js b/src/bot/commands/utilities/help.js new file mode 100644 index 0000000..6d0f035 --- /dev/null +++ b/src/bot/commands/utilities/help.js @@ -0,0 +1,83 @@ +const Command = require('../../structs/Command'); + +class HelpCommand extends Command { + constructor() { + super('help', { + aliases: ['help', 'halp', 'h'], + description: { + content: 'Sends information on the bot\'s commands.', + usages: ['', '[command]'], + examples: ['', 'tag'] + }, + category: 'Utilities', + clientPermissions: ['EMBED_LINKS'], + args: [ + { + id: 'command', + type: 'commandAlias' + } + ] + }); + } + + exec(message, { command }) { + const embed = { + author: {}, + fields: [], + color: 'YELLOW' + }; + const [prefix] = this.handler.prefix(message); + + if (command) { + embed.author.name = `${this.client.util.capitalise(command.aliases[0])} Command Help`; + embed.author.icon_url = this.client.user.displayAvatarURL(); + embed.description = command.description.content || 'No description provided.'; + + if (command.aliases && command.aliases.length > 1) { + embed.fields.push({ + name: 'Aliases', + value: command.aliases.slice(1).join('\n') + }); + } + + if (command.description.usages && command.description.usages.length) { + embed.fields.push({ + name: 'Usages', + value: command.description.usages.map(usage => `${prefix}${command.aliases[0]} ${this.formatUsage(usage)}`).join('\n') + }); + } + + if (command.description.examples && command.description.examples.length) { + embed.fields.push({ + name: 'Examples', + value: command.description.examples.map(example => `${prefix}${command.aliases[0]} ${example}`).join('\n') + }); + } + } else { + embed.description = `A list of all available commands.\nFor information on a specific command, send \`${prefix}${this.aliases[0]} \``; + + for (const category of this.handler.categories.values()) { + const commands = category + .filter(cmd => cmd.aliases.length > 0 && !cmd.ownerOnly) + .map(cmd => `\`${cmd.aliases[0]}\``) + .join(' '); + if (!commands.length) continue; + + embed.fields.push({ + name: `${category.id}`, + value: commands + }); + } + } + + return message.util.send({ embed: embed }); + } + + formatUsage(usage) { + return usage + .replace(/<[^>]+>/g, '**$&**') + .replace(/\[[^\]]+]/g, '*$&*'); + } +} + +module.exports = HelpCommand; \ No newline at end of file diff --git a/src/bot/commands/utilities/ping.js b/src/bot/commands/utilities/ping.js new file mode 100644 index 0000000..f7dad8d --- /dev/null +++ b/src/bot/commands/utilities/ping.js @@ -0,0 +1,21 @@ +const Command = require('../../structs/Command'); + +class PingCommand extends Command { + constructor() { + super('ping', { + aliases: ['ping'], + description: { + content: 'Gets the ping of the bot.' + }, + category: 'Utilities' + }); + } + + async exec(message, args) { + const ping = await message.util.send(`:heartbeat: ${this.client.ws.ping}`); + const RTT = (ping.editedAt || ping.createdAt) - (message.editedAt || message.createdAt); + await ping.edit(`${ping}ms\n:stopwatch: ${RTT}ms`); + } +} + +module.exports = PingCommand; \ No newline at end of file diff --git a/src/bot/commands/utilities/reload.js b/src/bot/commands/utilities/reload.js new file mode 100644 index 0000000..2de5c36 --- /dev/null +++ b/src/bot/commands/utilities/reload.js @@ -0,0 +1,59 @@ +const { stripIndents } = require('common-tags'); +const Command = require('../../structs/Command'); +const { Listener, Inhibitor, Argument } = require('discord-akairo'); + +class ReloadCommand extends Command { + constructor() { + super('reload', { + aliases: ['reload', 'rl'], + description: { + content: 'Reloads a module.', + usages: ['[command]'], + examples: ['blacklist', ''] + }, + category: 'Utilities', + ownerOnly: true, + args: [ + { + id: 'module', + type: Argument.union( + 'command', + 'commandAlias', + 'listener', + 'inhibitor' + ), + prompt: { + start: message => `${message.author}, which module do you wish to reload?`, + retry: message => `${message.author}, that doesn't look like a valid module!` + } + } + ] + }); + } + + exec(message, { module }) { + try { + const reloaded = module.reload(); + + let type; + if (reloaded instanceof Command) { + type = 'command'; + } else if (reloaded instanceof Listener) { + type = 'listener'; + } else if (reloaded instanceof Inhibitor) { + type = 'inhibitor'; + } + + return message.util.send(`Successfully reloaded ${type} **${reloaded.id}**.`); + } catch (err) { + return message.util.send(stripIndents` + Something went wrong. + \`\`\`apache + ${err.message} + \`\`\` + `); + } + } +} + +module.exports = ReloadCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Action.js b/src/bot/commands/wiki/actions/Action.js new file mode 100644 index 0000000..23fa96b --- /dev/null +++ b/src/bot/commands/wiki/actions/Action.js @@ -0,0 +1,32 @@ +const MediaWikiJS = require('@sidemen19/mediawiki.js'); + +class Action { + constructor(data) { + this.message = data.message; + this.config = this.message.client.config.wiki; + } + + async commit() { + try { + this.initBot(); + await this.exec(); + } catch (err) { + return this.message.util.send(err.message); + } + } + + initBot() { + return this.bot = new MediaWikiJS({ + server: this.config.url, + path: '', + botUsername: this.config.credentials.username, + botPassword: this.config.credentials.password + }); + } + + exec() { + console.error('exec() not implemented'); + } +} + +module.exports = Action; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Block.js b/src/bot/commands/wiki/actions/Block.js new file mode 100644 index 0000000..9b6ba90 --- /dev/null +++ b/src/bot/commands/wiki/actions/Block.js @@ -0,0 +1,52 @@ +const Action = require('./Action'); +const { stripIndents } = require('common-tags'); + +class BlockAction extends Action { + constructor(data) { + super(data); + this.message = data.message; + this.args = data.args; + } + + async exec() { + const type = this.args.unblock ? 'Unblocking' : 'Blocking'; + const initMessage = await this.message.util.send(`${type} user...`); + + await this.bot.login(); + + const body = await this.bot.block({ + user: this.args.user, + expiry: this.args.expiry, + reason: this.args.reason, + autoblock: true + }); + + if (body.error) { + if (body.error.includes('alreadyblocked')) { + if (this.args.unblock) { + await this.bot.unblock({ + user: this.args.user, + reason: this.args.reason + }); + return initMessage.edit('Successfully unblocked user.'); + } + + return initMessage.edit('That user is already blocked! To unblock them, you can pass the `--unblock` flag.'); + } else if (this.args.unblock) { + return initMessage.edit('That user is not blocked!'); + } + + return initMessage.edit(stripIndents` + Error occurred while ${type} user. + \`\`\`apache + ${body.error.code} + + ${body.error.info} + \`\`\``); + } + + return this.message.util.send('Successfully blocked user!'); + } + } + +module.exports = BlockAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Category.js b/src/bot/commands/wiki/actions/Category.js new file mode 100644 index 0000000..e2ba30e --- /dev/null +++ b/src/bot/commands/wiki/actions/Category.js @@ -0,0 +1,44 @@ +const Action = require('./Action'); +const { stripIndents } = require('common-tags'); + +class CategoryAction extends Action { + constructor(data) { + super(data, { + needsRole: false, + needsCredentials: false + }); + this.message = data.message; + this.client = this.message.client; + + this.args = data.args; + } + + async exec() { + const latestMessage = await this.message.channel.send(`Getting pages in category **${this.args.category}**...`); + + try { + let pages = await this.bot.getPagesInCategory(`Category:${this.args.category}`, true); + if (!pages || !pages.length) { + return latestMessage.edit(stripIndents` + The **${this.args.category}** category is empty! + <${this.bot.server}/wiki/Category:${encodeURIComponent(this.args.category)} + `); + } + + pages = pages.map(page => { + return `[${page}](<${this.bot.server}/wiki/${encodeURIComponent(page)})`; + }); + + return latestMessage.edit('', { + embed: { + color: 'YELLOW', + description: this.client.util.trimArray(pages, 25).join('\n') + } + }); + } catch (err) { + return latestMessage.edit(err.message); + } + } +} + +module.exports = CategoryAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Delete.js b/src/bot/commands/wiki/actions/Delete.js new file mode 100644 index 0000000..85a6f25 --- /dev/null +++ b/src/bot/commands/wiki/actions/Delete.js @@ -0,0 +1,32 @@ +const Action = require('./Action'); + +class DeleteAction extends Action { + constructor(data) { + super(data, { + needsRole: true, + needsCredentials: true + }); + this.message = data.message; + + this.args = data.args; + } + + async exec() { + const initMessage = await this.message.util.send('Deleting page...'); + + try { + await this.bot.login(); + + await this.bot.delete({ + title: this.args.page, + reason: this.args.reason + }); + } catch (err) { + return initMessage.edit(err.message); + } + + return initMessage.edit('Successfully deleted page!'); + } +} + +module.exports = DeleteAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Edit.js b/src/bot/commands/wiki/actions/Edit.js new file mode 100644 index 0000000..7c80d1a --- /dev/null +++ b/src/bot/commands/wiki/actions/Edit.js @@ -0,0 +1,40 @@ +const Action = require('./Action'); + +class EditAction extends Action { + constructor(data) { + super(data); + + this.message = data.message; + this.args = data.args; + } + + async exec() { + const latestMessage = await this.message.util.send('Editing page...'); + + try { + await this.bot.login(); + + if (this.args.pos === 'prepend') { + await this.bot.prepend({ + title: this.args.page, + content: `${this.args.content}\n`, + summary: this.args.summary + }); + } + + if (this.args.pos === 'append') { + await this.bot.append({ + title: this.args.page, + content: `\n${this.args.content}`, + summary: this.args.summary + }); + } + + return latestMessage.edit('Successfully edited contents of page.'); + } catch (err) { + return latestMessage.edit(err.message); + } + } +} + +module.exports = EditAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Move.js b/src/bot/commands/wiki/actions/Move.js new file mode 100644 index 0000000..1ee3739 --- /dev/null +++ b/src/bot/commands/wiki/actions/Move.js @@ -0,0 +1,28 @@ +const Action = require('./Action'); + +class MoveAction extends Action { + constructor(data) { + super(data); + + this.message = data.message; + this.args = data.args; + } + + async exec() { + const initMessage = await this.message.channel.send('Moving page...'); + + try { + this.bot.move({ + from: this.args.old, + to: this.args.new, + reason: this.args.reason + }); + } catch (err) { + return initMessage.edit(err.message); + } + + return initMessage.edit('Successfully moved page!'); + } +} + +module.exports = MoveAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Protect.js b/src/bot/commands/wiki/actions/Protect.js new file mode 100644 index 0000000..a69e4de --- /dev/null +++ b/src/bot/commands/wiki/actions/Protect.js @@ -0,0 +1,35 @@ +const Action = require('./Action'); + +class ProtectAction extends Action { + constructor(data) { + super(data); + + this.message = data.message; + this.client = this.message.client; + + this.args = data.args; + } + + async exec() { + const initMessage = await this.message.util.send('Protecting page...'); + + try { + await this.bot.login(); + + await this.bot.protect({ + title: this.args.page, + expiry: this.args.expiry, + protections: { + edit: this.args.usergroup + }, + reason: this.args.reason + }); + } catch (err) { + return initMessage.edit(err.message); + } + + return initMessage.edit('Successfully protected page!'); + } +} + +module.exports = ProtectAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/actions/Undelete.js b/src/bot/commands/wiki/actions/Undelete.js new file mode 100644 index 0000000..6ff4551 --- /dev/null +++ b/src/bot/commands/wiki/actions/Undelete.js @@ -0,0 +1,30 @@ +const Action = require('./Action'); + +class UndeleteAction extends Action { + constructor(data) { + super(data); + this.message = data.message; + this.client = data.message.client; + + this.args = data.args; + } + + async exec() { + const initMessage = await this.message.channel.send('Restoring page...'); + + try { + await this.bot.login(); + + await this.bot.restore({ + title: this.args.page, + reason: this.args.reason + }); + } catch (err) { + return initMessage.edit(err.message); + } + + return initMessage.edit('Successfully restored page!'); + } +} + +module.exports = UndeleteAction; \ No newline at end of file diff --git a/src/bot/commands/wiki/block.js b/src/bot/commands/wiki/block.js new file mode 100644 index 0000000..cd6fcad --- /dev/null +++ b/src/bot/commands/wiki/block.js @@ -0,0 +1,61 @@ +const BlockAction = require('./actions/Block'); +const Command = require('../../structs/Command'); + +class BlockCommand extends Command { + constructor() { + super('block', { + aliases: ['block', 'ban'], + description: { + content: 'Blocks a given user on wiki.', + usages: [' ', ' '], + examples: ['Sidemen19 "1 hour"', 'Sidemen19 never vandalism'] + }, + category: 'Wiki', + channel: 'guild', + flags: ['--unblock', '-ub', '-u'], + optionFlags: ['--reason=', '-r='] + }); + } + + *args() { + const user = yield { + type: 'string', + prompt: { + start: message => `${message.author}, which user do you wish to block?` + } + }; + + const unblock = yield { + match: 'flag', + flag: ['--unblock', '-ub', '-u'] + }; + + const expiry = unblock + ? false + : yield { + type: 'duration', + prompt: { + start: message => `${message.author}, for how long shall I block this user for?`, + retry: message => `${message.author}, that doesn't look like a valid time!` + } + }; + + const reason = yield { + type: 'summary', + match: 'option', + flag: ['--reason=', '-r='], + default: 'No reason provided' + }; + + return { user, unblock, expiry, reason }; + } + + exec(message, args) { + return new BlockAction({ + message: message, + args: args + }).commit(); + } +} + +module.exports = BlockCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/category.js b/src/bot/commands/wiki/category.js new file mode 100644 index 0000000..f9fb261 --- /dev/null +++ b/src/bot/commands/wiki/category.js @@ -0,0 +1,58 @@ +const Command = require('../../structs/Command'); +const CategoryAction = require('./actions/Category'); + +class CategoryCommand extends Command { + constructor() { + super('category', { + aliases: ['category', 'cat'], + description: { + content: 'Lists all pages in a given category on the set wiki.', + usages: ['', '--cfd', '--stub'], + examples: ['Stubs', '--cfd', '--stub'] + }, + category: 'Wiki', + channel: 'guild', + flags: ['--cfd', '--cands', '--stub', '--stubs', '-s'] + }); + } + + *args() { + const cfd = yield { + id: 'cfd', + match: 'flag', + flag: ['--cfd', '-c'] + }; + + const stub = yield { + id: 'stub', + match: 'flag', + flag: ['--stub', '--stubs', '-s'] + }; + + let category; + + if (!cfd && !stub) { + category = yield { + type: 'string', + match: 'text', + prompt: { + start: message => `${message.author}, which category shall I list the pages of?` + } + }; + } + + if (cfd) category = 'Candidates for deletion'; + if (stub) category = 'Stubs'; + + return { category }; + } + + exec(message, args) { + return new CategoryAction({ + message: message, + args: args + }).commit(); + } +} + +module.exports = CategoryCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/delete.js b/src/bot/commands/wiki/delete.js new file mode 100644 index 0000000..0817c8a --- /dev/null +++ b/src/bot/commands/wiki/delete.js @@ -0,0 +1,43 @@ +const DeleteAction = require('./actions/Delete'); +const Command = require('../../structs/Command'); + +class DeleteCommand extends Command { + constructor() { + super('delete', { + aliases: ['delete', 'del', 'delet'], + description: { + content: 'Deletes a given page on the set wiki, with an optional reason for deletion.', + usages: [' [reason]'], + examples: ['Project:Rules -r=haha', 'User:Spam Account -r="spamming on pages"'] + }, + category: 'Wiki', + channel: 'guild', + args: [ + { + id: 'page', + type: 'string', + match: 'text', + prompt: { + start: message => `${message.author}, which page do you wish to delete?` + } + }, + { + id: 'reason', + type: 'summary', + match: 'option', + flag: ['--reason=', '-r='], + default: 'No reason provided' + } + ] + }); + } + + exec(message, args) { + return new DeleteAction({ + message: message, + args: args + }).commit(); + } +} + +module.exports = DeleteCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/edit.js b/src/bot/commands/wiki/edit.js new file mode 100644 index 0000000..34118fa --- /dev/null +++ b/src/bot/commands/wiki/edit.js @@ -0,0 +1,65 @@ +const EditAction = require('./actions/Edit'); +const Command = require('../../structs/Command'); + +class EditCommand extends Command { + constructor() { + super('edit', { + aliases: ['edit'], + description: { + content: 'Edits a given page on the set wiki, with the option of either appending or prepending content.', + usages: [' --prepend', ' --append'], + examples: ['PewDiePie {{Stub}} --prepend', '"Sidemen Gaming" [[Category:YouTubers]] --append'] + }, + category: 'Wiki', + channel: 'guild', + optionFlags: ['--summary=', '-s=', '--pos=', '-pos=', '-p='] + }); + } + + *args() { + const page = yield { + type: 'string', + prompt: { + start: message => `${message.author}, which page do you wish to edit?` + } + }; + + const content = yield { + type: 'string', + prompt: { + start: message => `${message.author}, what do you wish to add to ${page}?` + } + }; + + const summary = yield { + match: 'option', + type: 'summary', + flag: ['--summary=', '-s='], + default: 'No summary provided' + }; + + const pos = yield { + type: [ + ['prepend', 'p'], + ['append', 'a'] + ], + match: 'option', + flag: ['--pos=', '-pos=', '-p='], + prompt: { + start: message => `${message.author}, do you wish to \`prepend\` or \`append\` this content to ${page}?`, + retry: message => `${message.author}, do you wish to \`prepend\` or \`append\` this content to ${page}?` + } + }; + + return { page, content, summary, pos }; + } + + exec(message, args) { + return new EditAction({ + message: message, + args: args + }).commit(); + } +} + +module.exports = EditCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/move.js b/src/bot/commands/wiki/move.js new file mode 100644 index 0000000..ac96737 --- /dev/null +++ b/src/bot/commands/wiki/move.js @@ -0,0 +1,50 @@ +const MoveAction = require('./actions/Move'); +const Command = require('../../structs/Command'); + +class MoveCommand extends Command { + constructor() { + super('move', { + aliases: ['move', 'rename'], + description: { + content: 'Moves (renames) a page on the set wiki.', + usages: [' [-r=reason]'], + examples: ['"Jake Paul" trash -r="name says it"'] + }, + channel: 'guild', + optionFlags: ['--reason', '-r'], + category: 'Wiki', + args: [ + { + id: 'old', + type: 'string', + prompt: { + start: message => `${message.author}, which page shall I move?` + } + }, + { + id: 'new', + type: 'string', + prompt: { + start: message => `${message.author}, what shall the page's new name be?` + } + }, + { + id: 'reason', + type: 'summary', + match: 'option', + flag: ['--reason=', '-r='], + default: 'No reason provided' + } + ] + }); + } + + exec(message, args) { + return new MoveAction({ + message: message, + args: args + }).commit(); + } +} + +module.exports = MoveCommand; \ No newline at end of file diff --git a/src/bot/commands/wiki/protect.js b/src/bot/commands/wiki/protect.js new file mode 100644 index 0000000..23b4413 --- /dev/null +++ b/src/bot/commands/wiki/protect.js @@ -0,0 +1,70 @@ +const { stripIndents } = require('common-tags'); +const Command = require('../../structs/Command'); +const ProtectAction = require('./actions/Protect'); + +class ProtectCommand extends Command { + constructor() { + super('protect', { + aliases: ['protect', 'prt'], + description: { + content: 'Protects a given page on the wiki with an optional expiry.', + usages: ['