LoginSignup
3
1

Slack Platformを使いワークフローからnotionのページ作成

Last updated at Posted at 2023-10-04

概要

新しいSlack Platformを使いワークフローからNotionのページを作成します。
notionのAPIを使う部分はCustom Function(カスタムステップ)を使います。
Notion APIの使い方についてはこの記事では扱いません。
https://developers.notion.com/ を参考にしてからAPI用のシークレットトークンとデータベースのIDを取得しておいてください。

はじめに

クイックスタートからサンプルのアプリ実行まで紹介しているので、一通り実行するとわかりやすいと思います。
https://api.slack.com/automation/quickstart

  1. Slack CLIの導入, 承認
    https://api.slack.com/automation/quickstart#install-cli
    詳しくは省略します。slack auth listでワークスペースの情報が表示されるまで確認してください。

  2. スターターテンプレートと共にアプリ作成

$ slack create slack-to-notion --template https://github.com/slack-samples/deno-starter-template

作成するとfunctions, triggers, workflowsのフォルダがあると思いますが、これら3つがSlack アプリの主な構成要素となります。

各機能のざっくりとした説明は下記の通り

functions

Custom functions: https://api.slack.com/automation/functions/custom
自作のfunctionを作成する際はこちらのディレクトリの下で作成します。
Inputs(入力), outputs(出力)を定義して、SlackFunction内でやりたいことを実装します。
ワークフローのカスタムステップを作成する場合はこのCustom functionsを利用します。

triggers

https://api.slack.com/automation/triggers
ワークフローの起動方法をこちらで決めます。
サンプルはLink triggerでパプリックチャンネルからワークフローを起動します。
日付指定で実行する Scheduled triggerもあります。

workflows

https://api.slack.com/automation/workflows
functionを組み合わせて順次実行するワークフローを作成します。
カスタムステップを使う場合もこのワークフローにステップを追加します。

他の機能

datastores

https://api.slack.com/automation/datastores
アプリ内のデータを保存するデータベース。
今回は使用しません。

環境変数

https://api.slack.com/automation/environment-variables
.envがデフォルトで使えるためnotionのシークレットトークンとデータベースのIDを記述しておきます。

DATABASE_ID=XXX
NOTION_TOKEN=XXX

デプロイ

slack deploy でデプロイ
環境変数を使っている場合下記のように追加する必要がある。

$ slack env add MY_ENV_VAR asdf1234

ローカル実行

slack runでローカル環境の動作が確認できます。
ワークスペースを選択したら、ショートカット用のURLが表示されるので、slackチャンネルの上部の「+ 関連ページを追加する」から表示されたリンクにURLを入力します。

https://slack.com/shortcuts/XXXX
スクリーンショット 2023-10-04 13.50.23.png
スクリーンショット 2023-10-04 13.50.30.png

作成したもの

ワークフロー上でモーダルを表示してステータス、タイトル、概要、内容を入力します。
ボタンを押すとnotionのDBにページが作成され、成功したらSlackのチャンネルにメッセージを送信します。

実際のコード

notion_workflow.ts
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { NotionFunctionDefinition } from "../functions/notion_function.ts";
import Status from "../status.ts";

/// Workflow定義
const notionWorkflow = DefineWorkflow({
  callback_id: "request_workflow",
  title: "notionページ作成",
  description: "notionページ作成",
  input_parameters: {
    properties: {
      interactivity: {
        type: Schema.slack.types.interactivity,
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
      },
      user_id: {
        type: Schema.slack.types.user_id,
      },
    },
    required: ["user_id", "interactivity"],
  },
});

const inputForm = notionWorkflow.addStep(
  // モーダル表示
  Schema.slack.functions.OpenForm,
  {
    title: "notionへの投稿",
    interactivity: notionWorkflow.inputs.interactivity,
    submit_label: "作成",
    fields: {
      elements: [
        {
          name: "status",
          title: "ステータス",
          type: Schema.types.string,
          enum: Object.values(Status),
          choices: [
            {
              value: Status.New,
              title: Status.New,
              description: "",
            },
            {
              value: Status.InProgress,
              title: Status.InProgress,
              description: "",
            },
            {
              value: Status.Closed,
              title: Status.Closed,
              description: "",
            },
          ],
        },
        {
          name: "title",
          title: "タイトル",
          type: Schema.types.string,
          description: "記事のタイトル",
        },
        {
          name: "summary",
          title: "概要",
          type: Schema.types.string,
          long: true,
          description: "記事の概要",
        },
        {
          name: "content",
          title: "内容",
          type: Schema.types.string,
          long: true,
          description: "記事の内容",
        },
      ],
      required: ["status", "title"],
    },
  },
);

/// notionに送信
const requestFunctionStep = notionWorkflow.addStep(NotionFunctionDefinition, {
  user_id: notionWorkflow.inputs.user_id,
  status: inputForm.outputs.fields.status,
  title: inputForm.outputs.fields.title,
  summary: inputForm.outputs.fields.summary,
  content: inputForm.outputs.fields.content,
});

// Slackメッセージ送信
notionWorkflow.addStep(
  Schema.slack.functions.SendMessage,
  {
    channel_id: notionWorkflow.inputs.channel_id,
    message: requestFunctionStep.outputs.result_message,
  },
);

export default notionWorkflow;

タイトルのプロパティはnotionのDBのtitleと同じプロパティ名にする必要があります

notion_trigger.ts
import { Trigger } from "deno-slack-sdk/types.ts";
import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts";
import notionWorkflow from "../workflows/notion_workflow.ts";

const notionTrigger: Trigger<typeof notionWorkflow.definition> = {
  type: TriggerTypes.Shortcut,
  name: "notionのページ作成",
  description: "notionのページ作成トリガー",
  workflow: `#/workflows/${notionWorkflow.definition.callback_id}`,
  inputs: {
    interactivity: {
      value: TriggerContextData.Shortcut.interactivity,
    },
    channel_id: {
      value: TriggerContextData.Shortcut.channel_id,
    },
    user_id: {
      value: TriggerContextData.Shortcut.user_id,
    },
  },
};

export default notionTrigger;
notion_function.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const NotionFunctionDefinition = DefineFunction({
  callback_id: "notion_function",
  title: "notionへの投稿",
  description: "notionへの投稿",
  source_file: "functions/notion_function.ts",
  input_parameters: {
    properties: {
      user_id: {
        type: Schema.slack.types.user_id,
        description: "ユーザーID",
      },
      status: {
        type: Schema.types.string,
        description: "ステータス",
      },
      title: {
        type: Schema.types.string,
        description: "タイトル",
      },
      summary: {
        type: Schema.types.string,
        description: "概要",
        long: true,
      },
      content: {
        type: Schema.types.string,
        description: "内容",
      },
    },
    required: ["user_id", "status", "title"],
  },
  output_parameters: {
    properties: {
      notion_page_id: {
        type: Schema.types.string,
        description: "作成したnotionページのID",
      },
      result_message: {
        type: Schema.types.string,
        description: "結果のメッセージ",
      },
    },
    required: ["result_message"],
  },
});

function postNotion(notionToken: string, blockJson: string) {
  const url = "https://api.notion.com/v1/pages";
  const options = {
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + notionToken,
      "Notion-Version": "2022-06-28",
    },
    "body": blockJson,
  };
  return fetch(url, options);
}

/// notionページに反映する内容
interface Request {
  userName: string;
  status: string;
  title: string;
  summary?: string;
  content?: string;
}

type SlackUser = {
  id: string;
  real_name: string;
};

type NotionResponse = {
  id: string;
  url: string;
};

export default SlackFunction(
  NotionFunctionDefinition,
  async ({ inputs, env, client }) => {
    let userName: string;

    // Slackユーザー情報取得
    try {
      const info = await client.users.info({
        user: inputs.user_id,
      });

      if (!info.ok) {
        throw new Error(info.error);
      }

      // Slackユーザー名設定
      const user = info.user as SlackUser;
      userName = user.real_name;
    } catch (error) {
      const resultMessage =
        `Slack APIからのエラーメッセージ:\n${error.message}`;
      return { outputs: { result_message: resultMessage } };
    }

    const request: Request = {
      userName: userName,
      status: inputs.status,
      title: inputs.title,
      summary: inputs.summary,
      content: inputs.content,
    };

    const blockJson = createBlockObjects(env["DATABASE_ID"], request);

    let resultMessage: string;

    try {
      // notionへの投稿
      const response = await postNotion(env["NOTION_TOKEN"], blockJson);
      if (!response.ok) {
        throw new Error(response.statusText);
      }

      const json = await response.json() as NotionResponse;
      resultMessage = `notionのページ: ${json.url}`;
    } catch (error) {
      resultMessage = `notionAPIからのエラーメッセージ:\n${error.message}`;
    }

    return {
      outputs: { result_message: resultMessage },
    };
  },
);

function createBlockObjects(databaseId: string, request: Request): string {
  const propertyBlock = (
    propertyName: string,
    type: string,
    content: string,
  ) => {
    return {
      [propertyName]: {
        [type]: [
          {
            "text": {
              "content": content,
            },
          },
        ],
      },
    };
  };

  const objectBlock = (type: string, content: string) => {
    return {
      "object": "block",
      "type": type,
      [type]: {
        "rich_text": [
          {
            "type": "text",
            "text": {
              "content": content,
            },
          },
        ],
      },
    };
  };

  const payload = {
    "parent": {
      "type": "database_id",
      "database_id": databaseId,
    },
    "properties": {
      ...propertyBlock("タイトル", "title", request.title),
      "ステータス": {
        "select": {
          "name": request.status,
        },
      },
    },
    "children": [
      objectBlock("heading_2", "概要"),
      objectBlock("paragraph", request.summary ?? ""),
      objectBlock("heading_3", "内容"),
      objectBlock("paragraph", request.content ?? ""),
    ],
  };
  return JSON.stringify(payload);
}

manifest.ts

import { Manifest } from "deno-slack-sdk/mod.ts";
import notionWorkflow from "./workflows/notion_workflow.ts";

export default Manifest({
  name: "slack-to-notion",
  description: "Slackからnotionページ作成",
  icon: "assets/default_new_app_icon.png",
  workflows: [notionWorkflow],
  outgoingDomains: ["api.notion.com"],
  botScopes: [
    "commands",
    "chat:write",
    "chat:write.public",
    "users:read",
  ],
});

outgoingDomainsに"api.notion.com"を指定しないとnotionのAPIにアクセスできないので注意

status.ts

enum Status {
  New = "新規",
  InProgress = "進行中",
  Closed = "完了",
}

export default Status

動作について

モーダル

スクリーンショット 2023-10-04 13.01.15.png

Slackメッセージ

スクリーンショット 2023-10-04 13.20.18.png

作成したnotionページ

スクリーンショット 2023-10-04 13.04.41.png

Custom funtionについて

上記の例ではワークフローごと作成して、最後にSlackメッセージを送信するものまで作成しましたが
Custom funtionではnotionの接続部分だけ作成することもでき、自作のワークフローに組み込むことができます。

スクリーンショット 2023-10-04 15.32.06.png

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1