1.0.0 • Published 4 years ago

@cherryblossom/discord-dynamic-messages v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

@cherryblossom/discord-dynamic-messages

The same as the original but using Discord.js v12.

npm i @cherryblossom/discord-dynamic-messages

Purpose

This library helps with creating messages that dynamically change their contents based on how people react on it. In other words, the message content acts as a screen, and the reactions act as input buttons.

import {Client} from 'discord.js'
import {DynamicMessage, OnReaction} from 'discord-dynamic-messages'
import type {Message} from 'discord.js'

export class CounterMessage extends DynamicMessage {
  private counter

  constructor(args: {initialCounterValue: number}) {
    super()
    this.counter = args.initialCounterValue
  }

  @OnReaction(':thumbsup:')
  public increment(user, channel, reaction) {
    this.counter += 1
  }

  @OnReaction(':thumbsdown:')
  public decrement(user, channel, reaction) {
    this.counter -= 1
  }

  public render() {
    return `Counter: ${this.counter}`
  }
}

const client = new Client()

client.on('message', ({channel}: Message) => new CounterMessage({initialCounterValue: 0}).sendTo(channel))

client.login(discordToken)

Demo image

Install

This library depends on typescript decorators, and will therefore not work properly unless used in a correctly configured typescript project.

  1. Install library: npm i @cherryblossom/discord-dynamic-messages.
  2. Enable experimentalDecorators and emitDecoratorMetadata in tsconfig.json.

If you are using VSCode you might need to set javascript.implicitProjectConfig.experimentalDecorators to true in the workspace settings.

Documentation

DynamicMessage

abstract class DynamicMessage {
  constructor(config: DynamicMessageConfig = {volatile: true})
}

The base class of the library. Every dynamic message must extend this class.

config: Optional configuration for the message.

interface DynamicMessageConfigTame {
  volatile: false
  onError: (error: Error) => void
}

interface DynamicMessageConfigVolatile {
  volatile: true
}

type DynamicMessageConfig = DynamicMessageConfigTame | DynamicMessageConfigVolatile

volatile: If true, errors will be thrown. If false, errors will be passed to onError.

  • Default: true

onError: An error handler for the errors if volatile is false.

message

message: Discord.Message | null

The Discord message that this is attached to.

render

abstract render(): string | Discord.MessageEmbed

Determines the contents of the message.

Returns: The content of the message.

reRender

reRender(): void

Used to manually trigger a re-render of the message content.

class Foo extends DynamicMessage {
  public doStuff() {
    // do some stuff
    this.reRender()
  }

  public render() {
    return 'stuff'
  }
}

addReactions

addReactions(emojiNames: string[]): void

Manually adds reactions to a message.

emojiNames: An array of the emojis to react with.
Returns: The last reaction or void if there were no reactions.

class Foo extends DynamicMessage {
  public addOneTwoThree() {
    this.addReactions([':one:', ':two:', ':three:'])
  }

  public render() {
    return 'stuff'
  }
}

sendTo

sendTo(channel: Discord.TextChannel | Discord.DMChannel): Promise<this>

Sends the dynamic message to the given channel.

channel: The channel to send the message to.
Returns: This dynamic message.

class Foo extends DynamicMessage {
  public render() {
    return 'stuff'
  }
}

client.on('message', (message: Discord.Message) => new Foo().sendTo(message.channel))

replyTo

replyTo(message: Discord.Message): Promise<this>

Sends the dynamic message as a reply to the given message.

message: The message to reply to.
Returns: This dynamic message.

class Foo extends DynamicMessage {
  public render() {
    return 'foo'
  }
}

client.on('message', (message: Discord.Message) => new Foo().replyTo(message))

attachTo

attachTo(message: Discord.Message, user?: Discord.User): this

Attaches an existing message to this instance, then call render on the instance and overwrite the content of the existing message.

message: The message to be attached.
user: The user to reply to.
Returns: This dynamic message.

class Foo extends DynamicMessage {
  render() {
    return 'foo'
  }
}

client.on('message', async (message: Discord.Message) => {
  const reply = await message.reply('tmp')

  // Attach in the same way as DynamicMessage#sendTo
  new Foo().attachTo(reply)

  // Attach in the same way as DynamicMessage#replyTo
  new Foo().attachTo(reply, message.author)
})

BaseReactionConfig

interface BaseReactionConfig {
  triggerRender?: boolean
  ignoreBots?: boolean
  ignoreHumans?: boolean
}

The configuration used for @OnReactionRemoved, @OnAnyReaction, and @OnAnyReactionRemoved.

PropertyDescriptionDefault
triggerRenderWhether the bot will call the render method of the dynamic message after the reaction callback have executed.true
ignoreBotsWhether reactions from bots should be ignored and not trigger the callback.true
ignoreHumansWhether reactions from humans (non-bots) should be ignored and not trigger the callback.false

@OnInit

type OnInit = Decorator<InitHandler>

A decorator that tells the dynamic message what functions to call when a Discord message is attached to the dynamic message. Note that if the dynamic message is reused (when attached to another message after the first one) the init function will fire again.

class Foo extends DynamicMessage {
  @OnInit
  public initialize() {
    console.log(this.message?.content)
    // => stuff
  }

  public render() {
    return 'foo'
  }
}

@OnReaction

type OnReaction = (emoji: string, config?: OnReactionConfig) => Decorator<ReactionHandler>

A decorator that tells the dynamic message what functions to call in response to what emoji when a reaction is made on the message.

emoji: The emoji to listen for.
config: Optional configuration.

interface OnReactionConfig extends BaseReactionConfig {
  hidden?: boolean
  removeWhenDone?: boolean
  doRetroactiveCallback?: boolean
}
PropertyDescriptionDefault
hiddenWhether the bot shouldn't react with the given emoji to show the users what emoji the message is prepared to react to.false
removeWhenDoneWhether the bot will remove user reactions after the callback have executed.true
doRetroactiveCallbackWhether reactions made while the bot was offline or not setup trigger the callback.true
class Foo extends DynamicMessage {
  @OnReaction(':thumbsup:')
  public react(
    user: Discord.User,
    channel: Discord.TextChannel | Discord.DMChannel,
    reaction: Discord.MessageReaction
  ) {
    console.log('+1')
  }

  public render() {
    return 'foo'
  }
}

@OnReactionRemoved

type OnReactionRemoved = (emoji: string, config?: BaseReactionConfig) => Decorator<ReactionHandler>

A decorator that tells the dynamic message what functions to call in response to what emoji when a reaction removed from the message.

emoji: The emoji to listen for.
config: Optional configuration.

class Foo extends DynamicMessage {
  private toggle: boolean = false

  @OnReaction(':thumbsup:', {removeWhenDone: false})
  public on() {
    this.toggle = true
  }

  @OnReactionRemoved(':thumbsup:')
  public off() {
    this.toggle = false
  }

  public render() {
    return '```diff\n' + (this.toggle ? '+ on' : '- off') + '\n```'
  }
}

@OnAnyReaction

type OnAnyReaction = (config?: BaseReactionConfig) => Decorator<ReactionHandler>

A decorator that tells the dynamic message what functions to call when any reaction is made on the message.

config: Optional configuration.

class Foo extends DynamicMessage {
  private accumulator: string = ''

  @OnAnyReaction()
  public accumulate(
    user: Discord.User,
    channel: Discord.TextChannel | Discord.DMChannel,
    reaction: Discord.MessageReaction
  ) {
    this.accumulator += reaction.emoji.name
  }

  public render() {
    return `Accumulator: ${this.addAccumulator}`
  }
}

@OnAnyReactionRemoved

type OnAnyReactionRemoved = (config?: BaseReactionConfig) => Decorator<ReactionHandler>

A decorator that tells the dynamic message what functions to call when any reaction is removed from the message.

config: Optional configuration.

export class Foo extends DynamicMessage {
  private accumulator: string = ''

  @OnAnyReactionRemoved()
  public accumulate(
    user: Discord.User,
    channel: Discord.TextChannel | Discord.DMChannel,
    reaction: Discord.MessageReaction
  ) {
    this.accumulator += reaction.emoji.name
  }

  public render() {
    return `Accumulator: ${this.addAccumulator}`
  }
}

InitHandler

type InitHandler = () => void

A handler for @OnInit.

ReactionHandler

type ReactionHandler = (
  user: Discord.User,
  channel: Discord.Channel | Discord.DMChannel,
  reaction: Discord.MessageReaction
) => void

A handler for @OnReaction, @OnReactionRemoved, @OnAnyReaction, and @OnAnyReactionRemoved.

user: The user who reacted.
channel: The channel of the message.
reaction: The reaction.

Decorators

Decorators are documented here using Decorator<T>, where T is the function type that the decorator can be applied to. All decorators here can only be applied to methods of a class inheriting DynamicMessage.

Emojis

Emojis are resolved using node-emoji, so you can use things like ':one:' instead of having to use Unicode emojis.

Demos

See the demo folder.

Development

  1. (Optional) Install pnpm:
npm i -g pnpm

If you don’t use pnpm, replace pnpm with npm. 2. Create a file called .env with this:

TOKEN=yourDiscordBotTokenHere
  1. Install dependencies: pnpm i.
  2. Start demo: pnpm run demo.