概要
約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
実装
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にレコードができることを確認した。