Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# A simple Slack todo list app

This app is meant to walk through how to create a simple app using many of the features of the Slack API in a straightforward way. The app keeps dependencies to a minimum and tries to reduce complexity to keeping all of the business logic in a single file. As you will see, this isn't necessarily the most straightforward organization for an app of even modest complexity.

The app is broken down into multiple steps, each with its own branch. The `main` branch is the fully working, complete app.

## Step 0 — configure the app with Slack

1. Set up your app at [api.slack.com/apps](https://api.slack.com/apps). Create a new app and choose "From an app manifest".
2. Copy the manifest at `manifest.yaml`
3. Install the app on to your workspace
4. Generate an app-level token for Socket Mode
5. Name the token “TodoSocketMode” and request `connections:write`
6. Initialize the bolt project

```
npm init
npm install @slack/bolt
npm install dotenv
```
7. Create a `.env` file and add the following:

```
SLACK_APP_TOKEN=xapp...
SLACK_BOT_TOKEN=xoxb...
```

Copy the appropriate values from the app config page from when you created your app at api.slack.com/apps

## Step 1 — Set up the app for local development

1. Create `app.js` — loads the configuration and enables [Socket Mode](https://api.slack.com/apis/connections/socket)
2. Test that Socket Mode is working with `node app.js`

## Step 2 — Add a shortcut

1. Allows you to create a new todo
2. Install sequelize to manage data

```
npm install sequelize sql3
```

3. Add the data model (see `app.js`, starting at line 14)
4. Update your app's manifest at [api.slack.com/apps](https://api.slack.com/apps) to include support for message shortcuts (see `manifest.yml`)
5. Define the shortcut (see `app.js`, starting at line 21)

## Step 3 — Handle the user input from the shortcut

1. Save the todo information in the database (see `app.js`, starting at line 93)

## Step 4 — Add the [App Home](https://api.slack.com/tutorials/app-home-with-modal)

1. Add support for app home to your manifest (see `manifest.yml`)
2. Copy the new `manifest.yml` to your app's configuration at [api.slack.com/apps](https://api.slack.com/apps)
3. Show the todos on the App Home (see `app.js` starting at line 114)

## Next steps
That's it, a very simple todo app. You can probably already imagine how to improve it — you might add a [message shortcut](https://api.slack.com/interactivity/shortcuts/using#shortcut_types), allow people to assign todos to other team members, only show _your_ todos in your App Home space, [handle the todo checked event](https://api.slack.com/interactivity/handling), even add a [custom step that would allow your todo app to be used in Workflow Builder](https://api.slack.com/workflows/steps).

If you're looking for a more feature-filled app, check out the [Tasks App](https://github.com/slackapi/node-tasks-app), as well as the [corresponding video series](https://www.youtube.com/playlist?list=PLWlXaxtQ7fUb0B4uNTKirvrQ0JOTCBFae) on how it was built.
181 changes: 181 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// use the values stored in .env
require('dotenv').config();
const { Sequelize, Model, DataTypes } = require('sequelize');

const { App } = require('@slack/bolt');

//intialize the Bolt app
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
appToken: process.env.SLACK_APP_TOKEN,
socketMode: true,
});

// set up the data model
const sequelize = new Sequelize('sqlite::memory:');

class Todo extends Model {}
Todo.init({
todo: DataTypes.STRING,
due: DataTypes.DATE,
}, { sequelize, modelName: 'todo' });

// HANDLE THE APP INTERACTIONS
//
// Respond to the shortcut to create a modal for defining the todo
app.shortcut('new_todo', async ({ shortcut, ack, client, logger }) => {
try {
// Acknowledge shortcut request
await ack();

// Call the views.open method to create a modal
const result = await client.views.open({
trigger_id: shortcut.trigger_id,
view: {
type: "modal",
callback_id: "todo_view",
title: {
type: "plain_text",
text: "New Todo"
},
submit: {
type: "plain_text",
text: "Submit",
emoji: true
},
close: {
type: "plain_text",
text: "Close"
},
blocks: [
{
type: "input",
block_id: "todo_input",
element: {
type: "plain_text_input",
action_id: "todo-text"
},
label: {
type: "plain_text",
text: "What do you want to do?",
emoji: true
}
},
{
type: "input",
block_id: "todo_due_input",
element: {
type: "datepicker",
placeholder: {
type: "plain_text",
text: "Select a date",
emoji: true
},
action_id: "todo-datepicker"
},
label: {
type: "plain_text",
text: "When do you want to do it?",
emoji: true
}
}
]
}
});

// logger.info(result);
}
catch (error) {
logger.error(error);
}
});

// Handle the todo_view submission
app.view('todo_view', async ({ ack, body, view, client, logger }) => {
// Acknowledge the view_submission request
await ack();

// Get the specific values from the submitted view
const todo_description = view['state']['values']['todo_input']['todo-text']['value'];
const todo_due = view['state']['values']['todo_due_input']['todo-datepicker']['selected_date'];

// Create a new todo in the database
(async () => {
await sequelize.sync();
const new_todo = await Todo.create({
todo: todo_description,
due: todo_due,
});
console.log(new_todo);
})();
});


// Respond when someone opens your App Home
app.event('app_home_opened', async ({ event, client, logger }) => {
(async () => {
await sequelize.sync();
const todos = await Todo.findAll();
console.log("All todos:", JSON.stringify(todos, null, 2));

let todo_blocks = [];
todos.forEach(todo => {
todo_blocks.push({
text: {
type: "mrkdwn",
text: `*${todo.todo}*`
},
description: {
type: "mrkdwn",
text: `Due: ${todo.due}`
},
value: `value-${todo.id}`,
});
});

try {
// Call views.publish
const result = await client.views.publish({
// Use the user ID associated with the event
user_id: event.user,
view: {
"type": "home",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `*Hello, <@${event.user}>!*`
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "These are your todos:"
},
"accessory": {
"type": "checkboxes",
"options": todo_blocks,
"action_id": "checkboxes-action"
}
}
]
}
});
}
catch (error) {
logger.error(error);
}
})();


});

(async () => {
await app.start();
console.log('⚡️ Bolt app started');
})();
8 changes: 8 additions & 0 deletions manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ features:
bot_user:
display_name: YOURNAME Todos
always_online: false
shortcuts:
- name: Create a todo
type: global
callback_id: new_todo
description: Create a todo
oauth_config:
scopes:
bot:
Expand All @@ -19,6 +24,9 @@ oauth_config:
# Allows us to send task reminders via a DM from the bot
- chat:write
settings:
event_subscriptions:
bot_events:
- app_home_opened
interactivity:
is_enabled: true
org_deploy_enabled: false
Expand Down
Loading