何かやってみるブログ

興味をもったこと、趣味のこと、技術について色々書きます。

Cloudflare Workersを使ってslackメッセージに特定のスタンプが押されたら、メッセージを変換してNotionのDBにINSERTするシンプルなbotを雑に作った。

概要

約2年前に下記の記事を書いたときに作った趣味のslack botを自分しかいないslackのワークスペースのあるチャンネルで動かしている。 www.takayasugiyama.com

このbotはとあるサイトのあるページを1日一回スクレイピングして動画と画像とテキストを引っ張ってきてslackに投稿してくれているので、気になるサムネだったら動画を見て楽しんでいる。 2年間も動かしていると、たまにお気に入りで保存しておきたい動画が流れてくるがslackのメッセージが消えてしまったりしていてスクレイピング元に探しに行かないといけない事態になったりする。 なので、slackのメッセージにハートのスタンプを押したらNotionのDBに動画のurlなどを保存するようにするbotを作成した。

ライブラリなど

botは使ったことがなかったCloudflare Workersで動かすことにした。またフレームワークにhonoを使った。 developers.cloudflare.com honojs.dev

NotionのDBの操作などはNotion APIのガイドの通りにnotionhq/clientを使った。 developers.notion.com

www.npmjs.com

実装

Slackの設定とかは書くのがだるいので省略する。 reaction_addedでリクエストを飛ばして、conversations.historyでスタンプを押されたメッセージを検索して特定してメッセージから要素を切り取ってNotionのDBにINSERTする流れになってる。

import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { addItem, NotionAddItemParams } from './notion'
import { searchStampedMessage, QueryParams } from './slack'

const app = new Hono()

app.use('*', poweredBy())

app.post('/', async(c) => {
  const req = await c.req.json()
  const reaction = req['event']['reaction']
  const item = req['event']['item']
  const channel = item["channel"]
  const ts = item["ts"]

  if(reaction !== "heart"){
    return c.json({ message: "heart以外のスタンプが押されました。" })
  }

  // get slack history api
  const params: QueryParams = { channel: channel, oldest: String(ts - 1), limit: 3 }
  const token: string = c.env.SLACK_BOT_TOKEN
  const searchResult = await searchStampedMessage(params, token, ts)
  if(!searchResult){
    throw new Error("メッセージの検索に失敗しました。")
  }

  // inser into record to notion database
  const notionParams: NotionAddItemParams = {...searchResult, auth: c.env.NOTION_AUTH, databaseId: c.env.DATABASE_ID}
  const response = await addItem(notionParams)
  if(!response){
    throw new Error("レコードの作成に失敗しました。")
  }

  return c.json({ status: 'success' })
})

export default app

メッセージを検索して要素を切り取ったりしている。

export type QueryParams = {
  channel: string
  oldest: string,
  limit: number
}

export type SlackSearchResult = {
  title: string,
  movieUrl: string
  girlUrl: string
  imageUrl: string
  timestamp: number
}

function encodeQueryData(data: QueryParams) {
  const ret = [];
  for (let d in data) {
    ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
  }
  return ret.join('&');
}

export async function searchStampedMessage(queryParams: QueryParams, token: string, timestamp: number): Promise<SlackSearchResult | null>{
  try {
    const queryParamsString = encodeQueryData(queryParams)
    const res = await fetch("https://slack.com/api/conversations.history?" + queryParamsString, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${token}`,
      },
    })
    const histories = await res.json()
    const history = histories["messages"].find ((message: any) => message["ts"] === String(timestamp))
    const elements = history['blocks'][0]['elements'][0]['elements']
    const movieUrl: string = elements.filter((element: any) => element["type"] == "link").find((element: any) => element["url"].match(/.+\.mp4/))['url']
    const imageUrl: string = elements.filter((element: any) => element["type"] == "link").find((element: any) => element["url"].match(/.+\.jpg/))['url']
    const girlUrl: string = elements.filter((element: any) => element["type"] == "link").find((element: any) => element["url"].match(/\/shop\/\d+\/girl\/\d+/))['url']
    const titleArr = elements.filter((element: any) => element["type"] == "text")[0]['text'].split("\n").map((item: any) => item.trim()).filter((item: any) => item !== '')
    const title: string = `${titleArr[1]} ${titleArr[2]}`

    return { title: title, movieUrl: movieUrl, imageUrl: imageUrl, girlUrl: girlUrl, timestamp: parseFloat(queryParams.oldest) + 1 }
  }catch(err){
    console.log(queryParams)
    console.log(err)
    return null
  }
}

NotionのDBにレコードをINSERTするなどしている。

import { Client } from "@notionhq/client"

export type NotionAddItemParams = {
  title: string
  movieUrl: string
  imageUrl: string
  girlUrl: string
  timestamp: number
  auth: string
  databaseId: string
}

export async function addItem({title, movieUrl, imageUrl, girlUrl, timestamp, auth, databaseId}: NotionAddItemParams) : Promise<any> {
  try {
    const notion = new Client({ auth: auth})
    const response = await notion.pages.create({
      parent: { database_id: databaseId },
      properties: {
        title: {
          title: [
            {
              text: {
                content: title
              },
            },
          ],
        },
        url: {
          url: movieUrl
        },
        image: {
          files: [
            {
              external: {
                url: imageUrl
              },
              type: "external",
              name: "サムネ"
            }
          ]
        },
        girl_link: {
          url: girlUrl
        },
        timestamp: {
          number: timestamp
        }
      }
    })
    console.log("Success! Entry added.")
    return response
  } catch (error) {
    console.error(error.body)
    return null
  }
}

動作確認

適当なメッセージにハートのスタンプを押したらNotionのDBにレコードができることを確認した。