npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shitbot

v1.2.7

Published

Dynamic Shitposting Bot

Downloads

22

Readme

Shitbot

A simple bot for shitposting

| Quickstart | Matchers | Handlers | Message | Advanced | | ------------------------- | -------------------- | -------------------- | ------------------- | --------------------- |

In reality it's a bot that just tries to make it really fast and easy to respond to messages that are sent.

Right now it's mainly for handling messages live as they come in. You can see a bunch of contrived examples in src/_testbot.ts.

Quickstart

First install:

| npm install shitbot | yarn add shitbot | | --------------------- | ------------------ |

The most basic responder probably looks like this

import { Shitbot, all } from 'shitbot'
const bot = new Shitbot(token)

bot.handle(
  // Only if they do: `@bot hi` or `/msg @bot hi`
  all.directedAtBot.contains('hi'),
  msg => `hi to you too ${msg.userName} 🤘`,
)

Starting the bot connects it to the web and rtm slack connections and gets everything going.

The first argument is the "matcher" and the second argument is the "handler".

The matcher filters the messages so you only respond to applicable messages, the handler dictates how to respond to the message.

Matcher

Responders have a matcher, that filters what messages it'll respond to. They are a builder pattern that you can add more filters.

  • all - matches all messages, must be the seed for all matchers
  • startsWith('string') - only matches messages that start with the supplied string, provides everything after the prefix to the handler as an argument
  • contains('string') - matches any message that has the supplied string anywhere in it
  • matches(/^prefix: (.+)/i) - matches to a regex, any capture groups are then passed along to the handler
  • messageIs('string') - only matches if the whole message is only the supplied string (not counting mentions at beginning of string)
  • directedAtBot - only match if the user @mentions the bot or if they IM the bot
  • mentionsBot - matches only if the message starts with @shitbot (or whatever the bot is named)
  • isIM - only matches if message is sent via an IM to the bot
  • inChannels('new_york', 'random') - matches any of supplied channels
  • url(options) - Matches if the message contains this url, the urls themselves are added to the arguments list of the handler
  • url(options.host) - Specify if the host exactly matches the provided (note: it will match www.twitter.com and twitter.com for twitter.com)
  • url(options.path) - Specify if the path exactly equals the supplied string
  • url(options.pathLike) - Pass in a regex to match on the path only, will add the match to the url in the arguments
  • url(options.pathStartsWith) - Will match any path that starts with the supplied string

You can, and should, chain these to combine matchers like this:

all.isIM.startsWith('foo')

Matchers are defined in src/matcher.ts.

Logic Matchers

There are some matchers for doing more complex logic if you want

  • or(all.inChannel('random'), all.isIM)
  • and(all.inChannel('random'), all.startsWith('hi')) - mostly included for completeness, should probably just use normal chaining for and
  • not(all.isIM)

Handler

The second part of responding is the handler, this is a function that is called iff the matcher succeeds.

Handlers are just functions that take in a message and return a response, in their simplest form

msg => `echo ${msg.text}`

Message

The message supplies the following pieces of information

  • msg.text - the body of the message, if you @shitbot it will automatically remove that from the text
  • msg.userName - the user that sent the message
  • msg.channelName - the channel name that the message was posted in
  • msg.conversationId - the id of the channel the message was sent in
  • msg.isIM - if the message is an IM
  • msg.userId - the user id of the sender

Handler Result

The handler can return a string, that's a quick alias for just replying in the same channel that the original message was sent.

Otherwise you can have the handler respond using methods on the message itself.

  • msg.reply('text') - Same as plain text, replies in whatever channel.
  • msg.reply({ blocks: attachmentBlocks }) - Creates a new message with slack blocks.
  • msg.reply({ text: 'message', blocks: attachmentBlocks }) - Send both
  • msg.ephemeralResponse('message') - Sends the message to the person who wrote the original message, only that author can see this.
  • msg.replyThread('message') - replies but in a new thread on the existing message
  • msg.emojiWordReaction('word') - Adds emoji reaction that spells out the supplied word
  • msg.emojiReaction('emoji1','emoji2'...) - Adds any emoji reactions to the message that was sent

Attachments & Blocks

You can add richer content to your responses with Attachments (deprecated) and Blocks.

This is easy, for reply, replyThread, and ephemeralResponse you can just include them instead of the text:

bot.handle(all.isIM, msg => {
  const blocks = [
    {
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: 'You can add an image next to text in this block.',
      },
      accessory: {
        type: 'image',
        image_url:
          'https://api.slack.com/img/blocks/bkb_template_images/plants.png',
        alt_text: 'plants',
      },
    },
  ]

  return msg.reply({ blocks })
})

Fallthrough

If you want the bot to respond with if nothing was matched (eg to say that the bot didn't understand your command). You can do that with the following syntax. Otherwise it behaves just like any other handler.

bot.fallthrough(
  all.directedAtBot,
  msg => "Sorry I don't know how to handle that",
)

Advanced

Advanced handlers

Handlers can be async functions and so they can make network calls or similar from within the handler.

bot.add(
  all.directedAtBot.matches(/who is \<\@(\w+)\>/i),
  async (msg, match) => {
    const userId = match[1]
    const user = await bot.data.user(userId)

    return msg.reply(['```', JSON.stringify(user, null, 2), '```'].join(\n))
  },
)

Handlers can also return an array of responses if you want multiple actions to happen.

bot.add(all.directedAtBot.contains('basic multiple'), async msg => [
  'message one',
  'message two',
])
bot.add(all.directedAtBot.contains('advanced multiple'), async msg => [
  msg.replyThread('I can reply to threads too!'),
  msg.ephemeralResponse('only you can see this'),
])

Calling out to APIs manually

The bot has getters for both the rtm and web api. These are just the native clients provided by node.

bot.web is the Web API

bot.rtm is the RTM API

These are both used internally for the built in functionality. It's totally safe to call these within handlers to either get or send data.

Getting channel and user information

The bot helps provide a link between user/channel names and their IDs to make it easier to use. If you want to leverage that data store you can use the following methods:

  • await bot.data.channel(channelId) - returns a full channel object if exists
  • await bot.data.channelNamed(channelName) - returns a full channel object if exists
  • await bot.data.user(userId) - returns a full user object
  • await bot.data.userNamed(userName) - returns a full user object
  • await bot.data.channels - returns an array of all known channels
  • await bot.data.users - returns an array of all known channels

The internal data store is cached for 5 minutes, so thats how long you'd need to wait if you create a new user or channel and want it to be reflected. Or you can reset the data using await bot.data.resetAll().

Check out manager.ts to see all of the helper methods for getting data from slack.

Acting on others messages

If someone posts something that you want to act on you can have the bot act on that fairly magically. If you send the bot a link to a message with content in that body, the content provided will act on the shared post rather than the IM itself. Share it like this: share message ui

TODO

  • [x] Allow sending attachments (1.0.0)
  • [x] Add ability to send messages at any point, not just when inside a reply handler
  • [ ] Add scheduling to sending messages
  • [ ] Add web UI for adding rules