Skip to content

Commit

Permalink
Merge pull request #4 from LartTyler/feature/help-command
Browse files Browse the repository at this point in the history
Merge feature/help-command into master
  • Loading branch information
LartTyler committed May 3, 2020
2 parents 31c2749 + 9af5b8f commit 2cc3c04
Show file tree
Hide file tree
Showing 17 changed files with 887 additions and 637 deletions.
20 changes: 20 additions & 0 deletions src/Command/CommandMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {Command} from './index';

export class CommandMap {
public readonly commands: Command[] = [];
protected keywordLookup: {[key: string]: Command} = {};

public add(command: Command) {
if (this.commands.indexOf(command) !== -1)
return;

this.commands.push(command);

for (const keyword of command.getKeywords())
this.keywordLookup[keyword] = command;
}

public find(keyword: string) {
return this.keywordLookup[keyword] ?? null;
}
}
109 changes: 109 additions & 0 deletions src/Command/Commands/BuyCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {DateTime} from 'luxon';
import {BuyData} from '../../Models/BuyData';
import {WeekDay} from '../../util';
import {
Command,
CommandSender,
createTimezoneRequiredMessage,
createUseHelpMessage,
Emoji,
notifyServers,
} from '../index';

export class BuyCommand implements Command {
public getKeywords(): string[] {
return [
'buy',
'buying',
];
}

getUsage(): string {
return '<amount>';
}

getSummary(): string {
return 'Updates your island\'s current buy price.';
}

getHelpText(): string {
return 'This command cannot be run on Sunday, or when the shop isn\'t open on your island. Buy prices will ' +
'automatically be expired when prices change at 12pm, and when the shop closes at 10pm.\n\n' +
'You _must_ set your timezone with `:prefix timezone` before you can use this command.';
}

public async execute(sender: CommandSender, args: string[]): Promise<void> {
if (!sender.userInfo?.timezone) {
await sender.message.reply(createTimezoneRequiredMessage(sender.channel));

return;
}

const now = DateTime.fromObject({zone: sender.userInfo.timezone});

if (now.weekday === WeekDay.SUNDAY) {
await sender.message.reply('The shop doesn\'t buy turnips on Sunday.');

return;
} else if (now.hour >= 22 || now.hour < 3) {
await sender.message.reply('The shop on your island has already closed.');

return;
} else if (now.hour < 8) {
await sender.message.reply('The shop on your island hasn\'t opened yet.');

return;
}

const price = parseInt(args.find(arg => /\d+/.test(arg)) || '', 10);

if (isNaN(price)) {
await sender.message.reply(createUseHelpMessage(sender.channel, this));

return;
}

await BuyData.findOneAndUpdate(
{
userId: sender.user.id,
year: now.year,
week: now.weekNumber,
day: now.weekday,
morning: now.hour < 12,
},
{
'$set': {
price,
},
},
{
upsert: true,
},
);

let expiration: DateTime;

if (now.hour < 12)
expiration = now.plus({hours: 12 - now.hour}).startOf('hour');
else
expiration = now.plus({days: 1}).startOf('day').minus({hours: 2});

sender.userInfo.currentData = sender.userInfo.currentData || {};

sender.userInfo.currentData.buyPrice = price;
sender.userInfo.currentData.buyExpiration = expiration.toUTC().toJSDate();

await sender.userInfo.save();

await sender.message.react(Emoji.CHECKMARK);

console.debug(
'Updated buy price to %d for %s (valid until %s)',
price,
sender.user.tag,
expiration.toUTC().toISO(),
);

await notifyServers(sender, `**:displayName** updated their current buy price to ${price} bells.`);
}
}
152 changes: 152 additions & 0 deletions src/Command/Commands/NotifyCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import {TextChannel} from 'discord.js';
import {ServerConfig} from '../../Models/ServerConfig';
import {Command, CommandSender, createUseHelpMessage} from '../index';

enum Action {
Enable,
Disable,
Toggle,
}

export class NotifyCommand implements Command {
getKeywords(): string[] {
return [
'notify',
'notifications',
];
}

getUsage(): string {
return '(on | off) [channels...]';
}

getSummary(): string {
return 'Manages buy and sell price notification messages for a channel. Cannot be used in direct messages.';
}

getHelpText(): string {
return 'This command can be used to specify channels that the bot should post to if a user posts a price ' +
'update to another server, or via direct message. This command has three main forms.\n\n' +
'• `:prefix notify`\n' +
'> Toggles notifications for the specified channels.\n\n' +
'• `:prefix notify on`\n' +
'> Enables notifications for the specified channels.\n\n' +
'• `:prefix notify off`\n' +
'> Disables notifications for the specified channels.\n\n' +
'All forms accept an optional list of channels that should be updated, e.g. ' +
'`:prefix notify #channel-a #channel-b #channel-c`. If no channels are specified, only the channel the ' +
'command was run in will be updated.';
}

public async execute(sender: CommandSender, args: string[]): Promise<void> {
if (!(sender.channel instanceof TextChannel)) {
await sender.channel.send('This command can only be run from within a Discord server.');

return;
}

const action = this.toAction(args[0] ?? null);

if (action === null) {
await sender.message.reply(createUseHelpMessage(sender.channel, this));

return;
}

const config = sender.config ?? new ServerConfig({
serverId: sender.channel.guild.id,
});

const channels: TextChannel[] = [];

// No channels mentioned means that we're managing notifications for the channel the message was sent in.
if (sender.message.mentions.channels.size === 0)
channels.push(sender.channel);
else
sender.message.mentions.channels.forEach(channel => channels.push(channel));

console.log(channels.length);
console.log(channels.map(channel => channel.name));

const skipped: TextChannel[] = [];

for (const channel of channels) {
if (!channel.permissionsFor(sender.user)?.has('MANAGE_CHANNELS')) {
skipped.push(channel);

continue;
}

if (action === Action.Enable)
this.enableNotifications(config.notifyChannels, channel.id);
else if (action === Action.Disable)
this.disableNotifications(config.notifyChannels, channel.id);
else
this.toggleNotifications(config.notifyChannels, channel.id);
}

if (skipped.length === channels.length) {
await sender.message.reply(
'Sorry, you don\'t have sufficient permissions for any of the channels you mentioned.',
);

return;
}

if (skipped.length > 0) {
await sender.message.reply(
`I was only able to update notification settings for ${channels.length -
skipped.length} of the channels ` +
'you mentioned. You don\'t have sufficient permissions to enable notifications for the following ' +
`channel(s): ${skipped.map(channel => channel.name).join(', ')}`,
);
} else
await sender.message.reply('Notification settings updated!');

await config.save();
}

protected toAction(input: string | null): Action | null {
if (input === null || input.charAt(0) === '<')
return Action.Toggle;

switch (input.toLowerCase()) {
case 'on':
case 'enable':
return Action.Enable;

case 'off':
case 'disable':
return Action.Disable;

default:
return null;
}
}

protected enableNotifications(channels: string[], id: string, index?: number): string[] {
index = index ?? channels.indexOf(id);

if (index === -1)
channels.push(id);

return channels;
}

protected disableNotifications(channels: string[], id: string, index?: number): string[] {
index = index ?? channels.indexOf(id);

if (index === -1)
channels.push(id);

return channels;

}

protected toggleNotifications(channels: string[], id: string): string[] {
const index = channels.indexOf(id);

return index === -1 ? this.enableNotifications(channels, id, index) :
this.disableNotifications(channels, id, index);
}
}
Loading

0 comments on commit 2cc3c04

Please sign in to comment.