-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from LartTyler/feature/help-command
Merge feature/help-command into master
- Loading branch information
Showing
17 changed files
with
887 additions
and
637 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.