Slack

コミュニケーションチームチャットAPIBot開発ワークフロー自動化統合

コミュニケーションツール

Slack

概要

Slackは、チャンネルベースのチームコミュニケーションプラットフォームです。2,000以上のアプリ統合をサポートし、ワークフローの自動化とBot機能が充実しています。開発者フレンドリーなAPIにより、カスタム統合や自動化が容易に実現できます。

詳細

Slack(スラック)は、2013年にリリースされたビジネス向けコミュニケーションプラットフォームです。現在、1,000万人以上のデイリーアクティブユーザーを抱え、スタートアップから大企業まで幅広く採用されています。2021年にSalesforceによって買収されましたが、独立したサービスとして成長を続けています。

Slackの最大の特徴は、チャンネルベースの組織的なコミュニケーションと、豊富な外部連携機能です。REST APIやWebhook、リアルタイムメッセージング(RTM API)、最新のSocket Modeなど、多様な統合手法を提供しています。2024年には新しいWorkflow Builder機能が強化され、ノーコードでの自動化がより簡単になりました。また、AI機能(Slack AI)やAgentforce Agentsの導入により、インテリジェントな自動化も可能になっています。

Block Kit UIフレームワークにより、リッチなインタラクティブ要素を備えたメッセージやモーダルを作成でき、Bolt フレームワーク(JavaScript、Python)を使用することで、効率的なアプリ開発が可能です。

メリット・デメリット

メリット

  • 豊富な統合機能: 2,000以上のアプリ統合をサポート
  • 強力なAPI: REST API、WebSocket(Socket Mode)、Webhook等
  • 開発者フレンドリー: Bolt フレームワークによる効率的な開発
  • Block Kit UI: リッチなインタラクティブ要素を簡単に作成
  • ワークフロー自動化: Workflow Builderでノーコード自動化
  • AI機能統合: Slack AIとAgentforce Agentsによるインテリジェント化
  • セキュリティ: エンタープライズグレードのセキュリティ機能
  • スケーラビリティ: 小規模チームから大企業まで対応

デメリット

  • 価格: 高機能プランは比較的高価
  • 学習コスト: 高度な機能活用には習熟が必要
  • API制限: レート制限や利用制限が存在
  • ストレージ制限: 無料プランでは履歴とファイル容量に制限
  • 通知過多: 適切な設定なしでは通知が多すぎる場合も
  • 複雑性: 多機能すぎて小規模チームには過剰な場合も

主要リンク

書き方の例

Hello World Bot(Bolt.js)

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

// Slack Botの初期化
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

// "hello"を含むメッセージに反応
app.message('hello', async ({ message, say }) => {
  await say(`Hello <@${message.user}>! 👋`);
});

// アプリの起動
(async () => {
  await app.start(process.env.PORT || 3000);
  console.log('⚡️ Slack Bot is running!');
})();

Socket Mode設定

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

// Socket Modeでの初期化(開発時推奨)
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true, // Socket Modeを有効化
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

// WebSocket接続でリアルタイム通信
(async () => {
  await app.start();
  console.log('⚡️ Bolt app is running in Socket Mode!');
})();

インタラクティブなBlock Kit UI

// ボタン付きメッセージの送信
app.message('menu', async ({ message, say }) => {
  await say({
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: `Hey <@${message.user}>! What would you like to do?`
        },
        accessory: {
          type: "button",
          text: {
            type: "plain_text",
            text: "Click Me"
          },
          action_id: "button_click"
        }
      }
    ]
  });
});

// ボタンクリックの処理
app.action('button_click', async ({ ack, respond, client }) => {
  await ack();
  
  await respond({
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: "Button clicked! Here's your menu:"
        }
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: { type: "plain_text", text: "Option 1" },
            action_id: "option_1"
          },
          {
            type: "button",
            text: { type: "plain_text", text: "Option 2" },
            action_id: "option_2"
          }
        ]
      }
    ]
  });
});

スラッシュコマンド

// /hello コマンドの実装
app.command('/hello', async ({ command, ack, respond }) => {
  await ack();
  
  const user = command.user_name;
  const text = command.text;
  
  await respond({
    response_type: 'in_channel', // チャンネル内で表示
    text: `Hello ${user}! You said: "${text}"`
  });
});

// /weather [city] コマンドの実装
app.command('/weather', async ({ command, ack, respond }) => {
  await ack();
  
  const city = command.text;
  
  if (!city) {
    await respond({
      response_type: 'ephemeral', // 実行者のみに表示
      text: 'Please specify a city: `/weather Tokyo`'
    });
    return;
  }
  
  // 外部API呼び出し例
  try {
    const weather = await getWeatherInfo(city);
    await respond({
      response_type: 'in_channel',
      text: `Weather in ${city}: ${weather.description}, ${weather.temperature}°C`
    });
  } catch (error) {
    await respond({
      response_type: 'ephemeral',
      text: `Sorry, couldn't get weather for ${city}`
    });
  }
});

イベント処理(チーム参加時の歓迎メッセージ)

const WELCOME_CHANNEL = 'C12345'; // 歓迎チャンネルのID

// 新メンバー参加時の処理
app.event('team_join', async ({ event, client, logger }) => {
  try {
    const result = await client.chat.postMessage({
      channel: WELCOME_CHANNEL,
      text: `Welcome to the team, <@${event.user.id}>! 🎉 Please introduce yourself in this channel.`,
      blocks: [
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: `Welcome to the team, <@${event.user.id}>! 🎉`
          }
        },
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "Please introduce yourself and don't hesitate to ask questions!"
          }
        },
        {
          type: "actions",
          elements: [
            {
              type: "button",
              text: { type: "plain_text", text: "View Team Guide" },
              action_id: "view_guide"
            }
          ]
        }
      ]
    });
    logger.info('Welcome message sent:', result);
  } catch (error) {
    logger.error('Error sending welcome message:', error);
  }
});

Webhook統合

// 外部サービスからのWebhook受信
app.post('/webhook/github', async (req, res) => {
  const payload = req.body;
  
  if (payload.action === 'opened' && payload.pull_request) {
    const pr = payload.pull_request;
    
    // プルリクエスト通知をSlackに送信
    await app.client.chat.postMessage({
      token: process.env.SLACK_BOT_TOKEN,
      channel: '#development',
      text: `New Pull Request: ${pr.title}`,
      blocks: [
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: `*New Pull Request*\n<${pr.html_url}|${pr.title}>`
          }
        },
        {
          type: "context",
          elements: [
            {
              type: "mrkdwn",
              text: `Created by ${pr.user.login} in ${payload.repository.full_name}`
            }
          ]
        }
      ]
    });
  }
  
  res.status(200).send('OK');
});

ワークフロー自動化(Custom Function)

// カスタムワークフロー関数の実装
app.function('create_jira_issue', async ({ inputs, complete, fail }) => {
  try {
    const { project, summary, description, priority } = inputs;
    
    // Jira APIに新しい課題を作成
    const jiraResponse = await fetch(`https://${process.env.JIRA_DOMAIN}/rest/api/latest/issue`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.JIRA_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        fields: {
          project: { key: project },
          summary: summary,
          description: description,
          issuetype: { name: 'Task' },
          priority: { name: priority }
        }
      })
    });
    
    const issue = await jiraResponse.json();
    
    // ワークフローに結果を返す
    await complete({
      outputs: {
        issue_id: issue.id,
        issue_key: issue.key,
        issue_url: `https://${process.env.JIRA_DOMAIN}/browse/${issue.key}`
      }
    });
  } catch (error) {
    console.error('Error creating Jira issue:', error);
    await fail({ error: error.message });
  }
});

スケジュールメッセージとリマインダー

// スケジュールメッセージの送信
app.message('remind me', async ({ message, say, client }) => {
  const text = message.text;
  const match = text.match(/remind me (.+) in (\d+) (minutes?|hours?|days?)/i);
  
  if (!match) {
    await say('Format: "remind me [message] in [number] [minutes/hours/days]"');
    return;
  }
  
  const [, reminderText, amount, unit] = match;
  
  // 時間計算
  const multipliers = {
    'minute': 60,
    'minutes': 60,
    'hour': 3600,
    'hours': 3600,
    'day': 86400,
    'days': 86400
  };
  
  const seconds = parseInt(amount) * (multipliers[unit] || 60);
  const scheduleTime = Math.floor(Date.now() / 1000) + seconds;
  
  try {
    const result = await client.chat.scheduleMessage({
      channel: message.channel,
      post_at: scheduleTime,
      text: `🔔 Reminder: ${reminderText}`
    });
    
    await say(`✅ Reminder set! I'll remind you in ${amount} ${unit}.`);
  } catch (error) {
    await say('Sorry, I couldn\'t schedule that reminder.');
  }
});

環境変数設定

# .env ファイル
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
SLACK_SIGNING_SECRET=your-signing-secret

# Socket Mode用(開発時)
SLACK_SOCKET_MODE=true

# 外部サービス統合用
JIRA_DOMAIN=your-company.atlassian.net
JIRA_TOKEN=your-jira-token
GITHUB_WEBHOOK_SECRET=your-webhook-secret

アプリマニフェスト例

{
  "display_information": {
    "name": "Development Assistant",
    "description": "A helpful bot for development teams",
    "background_color": "#2c2d30"
  },
  "features": {
    "bot_user": {
      "display_name": "DevBot",
      "always_online": true
    },
    "slash_commands": [
      {
        "command": "/hello",
        "description": "Say hello",
        "usage_hint": "[message]"
      },
      {
        "command": "/weather", 
        "description": "Get weather info",
        "usage_hint": "[city]"
      }
    ]
  },
  "oauth_config": {
    "scopes": {
      "bot": [
        "chat:write",
        "commands",
        "channels:read",
        "groups:read",
        "im:read",
        "mpim:read"
      ]
    }
  },
  "settings": {
    "event_subscriptions": {
      "bot_events": [
        "team_join",
        "message.channels",
        "message.groups",
        "message.im",
        "message.mpim"
      ]
    },
    "interactivity": {
      "is_enabled": true
    },
    "socket_mode_enabled": true
  }
}