howto

Pulling avatars from slack

Basic bot integration

tags
slack
nextjs
bot

Create a slack app

First we need to create a slack application.

Create a new app, and add the following scopes

  • users:read.email
  • users:read
  • users.profile:read
  • chat:write:bot

Manifest

We are going to use a manifest, so we can tweak things as we go.

1
2
3
4
5
6
7
8
9
  _metadata:
    major_version: 1
  display_information:
    name: HappyFunCorp App
  settings:
    org_deploy_enabled: false
    socket_mode_enabled: false
    is_hosted: false
    token_rotation_enabled: false

Create the database and schema

1
2
  npm install prisma
  npx prisma init

Setup the schema in prisma/schema.prisma

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  generator client {
    provider = "prisma-client-js"
  }

  datasource db {
    provider = "postgresql"
    url      = env("DATABASE_URL")
  }

  model SlackUser {
    id           String       @id
    name         String
    email        String?
    deleted      Boolean
    admin        Boolean?
    restricted   Boolean?
    bot          Boolean
    tz           String?
    title        String?
    skype        String?
    real_name    String?
    display_name String?
    status_text  String?
    status_emoji String?
    custom_image Boolean?
    original_image String?
    }

Start a database if you don't have one

1
  docker run -d -e POSTGRES_PASSWORD=awesome_password -p 5432:5432 postgres

And create the migrations

1
  npx prisma migrate dev --name init

Installing @slack/bolt

1
npm install @slack/bot dotenv

users-list.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
  const { App } = require('@slack/bolt');
  require('dotenv').config()
  const { PrismaClient } = require('@prisma/client')
  const prisma = new PrismaClient()

  const fs = require('fs')

  const app = new App({
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    token: process.env.SLACK_BOT_TOKEN,
  });

  (async () => {
      console.log( "Getting user list from slack" )
      const slackdata = await app.client.users.list()


      if( slackdata["ok"] ) {
          const data = []
          slackdata["members"].map( (e) => {            
              data.push({
                  id: e["id"],
                  name: e["name"],
                  email: e["profile"]["email"],
                  deleted: e["deleted"],
                  admin: e["is_admin"],
                  restricted: e["is_restricted"],
                  bot: e["is_bot"],
                  tz: e["tz"],
                  title: e["profile"]["title"],
                  skype: e["profile"]["skype"],
                  real_name: e["profile"]["real_name"],
                  display_name: e["profile"]["display_name"],
                  status_text: e["profile"]["status_text"],
                  status_email: e["profile"]["status_email"],
                  status_emoji: e["profile"]["status_emoji"],
                  custom_image: e["profile"]["is_custom_image"],
                  original_image: e["profile"]["image_original"],
              })
          })

          console.log( `Found ${data.length} accounts, updating database` );

          while( data.length > 0 ) {
              const e = data.pop();

              // console.log( `Updating/created ${e['name']}` )

              const user = await prisma.slackUser.upsert({
                  where: { id: e['id'] },
                  update: e,
                  create: e
              })

          }

          console.log( "Done" );
      }
  })();

Create a nextjs app

Base

1
  npm install next react react-dom

Update package.json:

1
2
3
4
5
6
7
8
  {
      "scripts": {
          "dev": "next dev",
          "build": "next build",
          "start": "next start",
          "lint": "next lint"
      }
  }

And then create a sample pages/index.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  const { PrismaClient } = require("@prisma/client");
  const prisma = new PrismaClient();
  const { MessageSender } = require("../components/messageSender");

  function Home({ users }) {
    return (
      <div>
        <div className="d-flex flex-wrap justify-content-around">
          {users.map((u) => (
            <div key={u.id} className="card mt-3" style={{ width: "18rem" }}>
              <div className="card-body">
                {u.custom_image && (
                  <img src={u.original_image} className="card-img-top" />
                )}
                <h5 className="card-title">{u.name}</h5>
                <p className="card-text">
                  {u.real_name}
                  <br />
                  {u.title}
                  <br />
                  <a href={`mailto:${u.email}`}>{u.email}</a>
                </p>
                <MessageSender user={u} />
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  export async function getServerSideProps(context) {
    const users = await prisma.slackUser.findMany({
      where: {
        deleted: false,
        restricted: false,
      },
    });

    return {
      props: { users }, // will be passed to the page component as props
    };
  }
  export default Home;

And also the MessageSender component in components/messageSender.jsx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  export const MessageSender = ({ user }) => {
    return (
      <form action="/api/sendMessage" method="get">
        <input
          type="text"
          name="message"
          placeholder={`message to ${user.name}`}
        />
        <input type="hidden" name="id" value={user.id} />
        <button type="submit" className="btn-primary">
          Send
        </button>
      </form>
    );
  };

Sending a message

Lets create the api call to actually send the message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  const { App } = require("@slack/bolt");

  export default async function handler(req, res) {
    const app = new App({
      signingSecret: process.env.SLACK_SIGNING_SECRET,
      token: process.env.SLACK_BOT_TOKEN,
    });

    const { id, message } = req.query;

    console.log(`Sending ${message} to ${id}`);

    if (id && message) {
      try {
        // Call the chat.postMessage method using the WebClient
        const result = await app.client.chat.postMessage({
          channel: id,
          text: message,
        });

        console.log(result);

        res.status(200).json({ id, message, ok: result.ok });
      } catch (error) {
        console.error(error);
        res.status(500).json({ id, message, result: error.toString() });
      }

      res.status(200).json({ id, message, action: "Sent" });
    } else {
      res.status(404).json({ action: "Not Sent", reason: "Missing params" });
    }
  }

Styling

1
  npm install bootstrap

pages/_app.js:

1
2
3
4
5
6
7
8
  // pages/_app.js
  import 'bootstrap/dist/css/bootstrap.css'

  export default function MyApp({ Component, pageProps }) {
      return <div className="container">
          <Component {...pageProps} />
          </div>
  }

References

Previously

howto

Running SQLite in the browser using NextJS

Why not?

tags
sqlite
browser
nextjs

Next

howto

Using ActiveRecord outside of rails

just the rake

tags
activerecord
ruby