cardboardbox/cmds_SharpBot/Utility/todo.js

255 lines
7.1 KiB
JavaScript

const {stripIndents} = require('common-tags');
class TodoItem {
constructor(description, completed = false) {
this.description = description;
this.completed = completed;
}
complete(completed = true) {
this.completed = completed;
}
toString() {
return `${this.completed ? ':ballot_box_with_check:' : ':black_square_button: '} ${this.description}`;
}
static fromJSON({description, completed}) {
return new TodoItem(description, completed);
}
}
class TodoList {
constructor(name) {
this.name = name;
this.items = [];
}
_validateIndex(index) {
if (Number.isNaN(index)) {
// This will throw the error below
index = 0;
}
index -= 1;
if (index < 0 || index > this.items.length) {
throw 'Please specify a valid number!';
}
return index;
}
add(description, completed = false) {
this.items.push(new TodoItem(description, completed));
}
remove(index) {
index = this._validateIndex(index);
this.items.splice(index, 1);
}
markComplete(index, completed = true) {
index = this._validateIndex(index);
this.items[index].complete(completed);
}
toString() {
return stripIndents`
**List: ${this.name}**
**Items:** ${this.items.length === 0 ? 'None' : ''}
${this.items.map((item, i) => `${i + 1}. ${item.toString()}`).join('\n')}
`;
}
static fromJSON({name, items}) {
const todoList = new TodoList(name);
todoList.items = items.map(item => TodoItem.fromJSON(item));
return todoList;
}
}
class TodoLists {
constructor(bot) {
this._bot = bot;
this.storage = bot.storage('todos');
this.lists = {};
const lists = this.storage.get(TodoLists.listsStorageKey);
if (!lists) {
this.lists = {
[TodoLists.mainListName]: new TodoList(TodoLists.mainListName)
};
} else {
Object.keys(lists)
.forEach(listName => {
this.lists[listName] = TodoList.fromJSON(lists[listName]);
});
}
let currentList = this.storage.get(TodoLists.currentListStorageKey);
if (!this.getList(currentList)) {
currentList = TodoLists.mainListName;
}
this.currentListName = currentList;
}
/**
*
* @returns {TodoList}
*/
getCurrentList() {
return this.getList(this.currentListName);
}
save() {
this.storage.set(TodoLists.currentListStorageKey, this.currentListName);
this.storage.set(TodoLists.listsStorageKey, this.lists);
this.storage.save();
}
getList(name) {
return this.lists[name];
}
getListNames() {
return Object.keys(this.lists);
}
deleteList(name) {
if (!this.getList(name)) {
throw `List '${name}' does not exist!`;
}
if (name === TodoLists.mainListName) {
throw `List '${name}' cannot be deleted!`;
}
delete this.lists[name];
if (this.currentListName === name) {
this.switchList(TodoLists.mainListName);
}
}
createList(name) {
if (this.getList(name)) {
throw `List'${name}' already exists!`;
}
this.lists[name] = new TodoList(name);
this.switchList(name);
}
switchList(name) {
if (!this.getList(name)) {
throw `List '${name}' does not exist!`;
}
this.currentListName = name;
}
}
TodoLists.listsStorageKey = 'lists';
TodoLists.currentListStorageKey = 'currentListName';
TodoLists.mainListName = 'Main';
module.exports.run = (bot, msg, args) => {
const send = (message, deleteInSeconds = 5, prefix = ':white_check_mark: ') => msg.edit({
embed: bot.utils.embed(`Todo List Utility (message will self-destruct in ${deleteInSeconds} seconds)`, `${prefix}${message}`)
})
.then(m => m.delete(deleteInSeconds * 1000));
if (args.length < 1) {
return send(Lists.getCurrentList(), 45, '');
}
const {options, leftover} = bot.utils.parseArgs(args, ['d', 'c', 'i', 'l', 'n', 'r', 's']);
let indexOption = parseInt(leftover[0], 10);
const ensureProperNumberOfArgs = (max, message = 'Expected list item number only', min = 1) => {
if (leftover.length > max || leftover.length < min) {
throw message;
}
};
if (options.c) {
ensureProperNumberOfArgs(1);
Lists.getCurrentList().markComplete(indexOption);
send(`Item ${indexOption} marked complete!`);
} else if (options.i) {
ensureProperNumberOfArgs(1);
Lists.getCurrentList().markComplete(indexOption, false);
send(`Item ${indexOption} marked incomplete!`);
} else if (options.d) {
ensureProperNumberOfArgs(1);
Lists.getCurrentList().remove(indexOption);
send(`Item ${indexOption} deleted!`);
} else if (options.l) {
send(`Available lists: ${Lists.getListNames().join(', ')}`, 30);
} else if (options.n) {
ensureProperNumberOfArgs(1, 'Expected name of new list to be a single word');
Lists.createList(leftover[0]);
send(`List '${leftover[0]}' created!`);
} else if (options.r) {
ensureProperNumberOfArgs(1, 'Expected name of list to delete to be a single word');
Lists.deleteList(leftover[0]);
send(`List '${leftover[0]}' deleted!`);
} else if (options.s) {
ensureProperNumberOfArgs(1, 'Expected name of chosen list to be a single word');
Lists.switchList(leftover[0]);
send(`Switched to list '${leftover[0]}'`);
} else {
Lists.getCurrentList().add(leftover.join(' '));
send(`Item '${leftover.join(' ')}' added!`);
}
Lists.save();
};
/**
* @type {TodoLists}
*/
let Lists;
module.exports.init = bot => {
Lists = new TodoLists(bot);
};
module.exports.info = {
name: 'todo',
usage: 'todo | todo [options...] <index> | todo <text of item to add>',
description: 'Manage a todo list. The command by itself will show the items in the current list.',
options: [
{
name: '-c',
usage: '-c ',
description: 'Mark an item complete'
},
{
name: '-i',
usage: '-i ',
description: 'Mark an item incomplete'
},
{
name: '-d',
usage: '-d',
description: 'Delete an item'
},
{
name: '-l',
usage: '-l',
description: 'Show all lists'
},
{
name: '-n',
usage: '-n ',
description: 'Make a new list and set it as the current list'
},
{
name: '-r',
usage: '-r ',
description: 'Remove a list. If the list being removed is the list currently in use, automatically switches back to the Main list.'
},
{
name: '-s',
usage: '-s ',
description: 'Switch lists'
}
]
};