見出し画像

AWS Lambdaを使ってmicroCMSのコンテンツ更新通知をX(Twitter)に投稿したい


0.microCMS使ってる

自前のサイトでmicroCMSを使って(ほとんど)毎日日記を書いています。ちまちまと。
microCMSは国産のヘッドレスCMS、、、?らしいです、、、?もうよく分かんないけどなんか書いてます!調べて!各々!
Webサイト自体はお金をかけたくないのでAWSのS3の静的ホスティング機能とか使って構築してます。「S3 静的ホスティング 独自ドメイン」とかでググったらやり方は出てきます。たぶん…

作ってみたかっただけの構成図

フロントエンドのことはマジでさっぱり分からんなので、日記ページの構築については一切触れません。本当にずぶの素人。

1.IFTTTを用いた更新通知(旧)

さて。microCMSにはコンテンツ更新時にwebhook通知をしてくれる機能があります。ありがたいですね。
microCMS側で用意されている通知先としては、Slack,Chatworkとかとか。
僕は、コンテンツ更新のお知らせ先としてDiscordを使ってました。

こんな感じ

Discordってwebhookを使った投稿が簡単にできて。つってもmicroCMS側で送信するwebhookのフォーマットを(Discordの仕様に合ったものに)変えられるわけではないので、
microCMS->IFTTT->Discordって感じでお知らせを送っていました。
「IFTTTって何ぞや」とか、IFTTTでwebhookを受信する方法とか、IFTTTからDiscordへwebhook送信する方法とかは割愛です。ググったら死ぬほど情報が出てきます。

ここで問題点がいくつか。

IFTTT -> Discordの連携がときたまうまくいかない

こんなんがたまに出る

IFTTTからDiscordにwebhook投げるとき謎にratelimitに引っかかっているようでたまにDiscordに投稿されません。症状で調べると過去に解決された問題の様ですが、ほんとか?って感じです。

通知内容が毎日同じ

全部これ

「IFTTT側でmicroCMSから受け取ったwebhookの中身(json)を読み取って、Discordの投稿内容に反映させる」みたいなことができない(課金したらできる?)ので、投稿内容が毎回同じになってしまいます。いつの何のお知らせか全くわからないし、日記の個別ページにリンクが張れない。

更新したときも通知が出る

microCMS側で設定できるのは「コンテンツの公開・更新時」のwebhookなので、誤字を直しただけの更新時にも通知が飛んでしまいます。消すのめんどっちいですね。

webhookだけではX(Twitter)でポスト(ツイート)できない

Discordなんて少数の人間しか見てないのでXでも告知しちゃいたい。承認欲求の塊くん

2.そうだ、Lambdaを使おう

AWSの認定資格を何個か取っていた(expireした)くせにAWS Lambdaを使ったことがありません。無料で構築できそうだしやってみましょう。
したいことは以下。

要件

  1. webhookをトリガーにして日記更新通知をDiscordとX(Twitter)で行う

  2. 新規作成時のみ通知し、更新時には通知をしない

  3. 投稿内容にタイトルと個別ページへのリンクを含める

必要か分からない構成図

「API Gatewayは?」とお思いの方いませんか?特に認証とかしないなら、なくても作れるらしいです。

microCMSから送られるwebhookの中身(json)を知る

まずはmicroCMSから送信されるwebhookの中身を見ておきましょう。
microCMSの公式ドキュメントにも書いてある通り、devhookを使うとよさそうです。使い方は公式ドキュメントを見てくださいね

devhookで調べると、コンテンツ新規作成時に送られてくるjsonが以下の感じ。

{
  "service": "hoge",
  "api": "diary",
  "id": "1234abdcef",
  "type": "new",
  "contents": {
    "old": null,
    "new": {
      "id": "1234abdcef",
      "status": [
        "PUBLISH"
      ],
      "draftKey": null,
      "publishValue": {
        "id": "1234abdcef",
        "createdAt": "2023-12-23T14:21:47.985Z",
        "updatedAt": "2023-12-23T14:21:47.985Z",
        "publishedAt": "2023-12-23T14:21:47.985Z",
        "revisedAt": "2023-12-23T14:21:47.985Z",
        "title": "タイトル",
        "content": "<p>投稿内容</p>"
      },
      "draftValue": null
    }
  }
}

サービス・API名、公開されたコンテンツID、投稿の種類とかとか。投稿とか場合分けに必要そうなものがパラメータにそろっています。たぶん。(各パラメータの意味については公式ドキュメント参照)

Lambda関数の作成

とにもかくにもAWS Lambdaの関数をつくっちゃいましょう。細かい調整はあとからしたらええねん。

AWSコンソールのLambdaから、「関数」>「関数の作成」ポチ

Lambdaの画面

「一から作成」、テキトーな名前、「Python 3.11(このあとのレイヤーの都合です)」、「x86_64」にして「関数の作成」ポチ

Lambdaの画面

はい、とりあえず箱はできました

ここでコードとか書いたり、設定したり。

関数URLの作成

設定タブを選択してもらって、「関数URL」っていうのを選択してください
そして真ん中の「関数URLを作成」をポチ

関数URLの作成

んで「NONE」を選択して、表示された内容を確認して「保存」をポチ

ちゃんと確認してね

そうすると関数URLが作成されます。これがmicroCMSから受信するURLです。絶対に公開しないようにしましょう。とんでもない請求が来ることになると思います。
あとでmicroCMSに登録するURLなので安全なところにURLを保存しときましょう。

他言厳禁のURL

レイヤーの追加

このあとPythonでコードを書くわけですが、Discordへ投稿(webhook送信)するためにrequestsモジュール、X(Twitter)へ投稿するためにtweepyモジュールが必要なわけですが、lambdaをそのまま使うとインストールされておらずエラーになっちゃうのでレイヤーの追加で解決します。

1.requestsモジュール

Klayersを追加したらすぐ使えます。以下のサイトを参考に追加してください。(以下のページが参考にならない場合は「Klayers requests」等で調べてください)
KlayersにはPython3.11までしか用意されていなかったので3.11をつかっています。

2.tweepyモジュール

tweepyはKlayersにないので自分で何とかしましょう

↑のページがすごく簡単に手順説明されています。先人の知恵
tweepyのバージョンは最新(202312末時点の)でpython3.11で使えています。
「UNIX環境がなくてzipが作れないよ~」って人はEC2で無料のインスタンスを建てるとか、CloudShellでサクッとやっちゃいましょう

XのAPIを使えるようにする

↑のページが大体現状の申請方法に則しています。
API Key&Secret、Access Token&Secretを大事なところに保存できたらOKです。あとBearer Tokenももらっておきましょう。一応使います。一応。
X(Twitter)、開発者向けのいろいろが(本 当 に)しょっちゅう変わるのでもしかしたら上記のページも古いかもしれません。その際は各自調べてください。

Discord投稿用のwebhookURLを発行する

以下参考にどうぞ。ここで取得したURLも保存しておきましょう

環境変数を追加する

AWSコンソールのLambda関数をいじる画面まで戻りましょう。
「設定」タブから、「環境変数」を選択して、「編集」ポチ

環境変数

環境変数の編集画面に遷移するので、「環境変数の追加」を押下すると
「キー」と「値」が入力できるようになると思います。ならなかったら壊れてる

環境変数編集画面

以下の表通りにキーと値をぶっこんでいきましょう(「環境変数の追加」を押すとどんどん追加できます)

$$
\begin{array}{l|l}
\textbf{キー}  & \textbf{値} \\\hline
\textbf{DISCORD\_URL}        & \textbf{discordのwebhookURL} \\
\textbf{X\_API\_KEY}         & \textbf{XのAPI Key}          \\
\textbf{X\_API\_KEY\_SECRET} & \textbf{↑のSecret}           \\
\textbf{X\_BEARER\_TOKEN}    & \textbf{XのBearer Token}     \\
\textbf{X\_TOKEN}            & \textbf{XのAccess Token}     \\
\textbf{X\_TOKEN\_SECRET}    & \textbf{↑のSecret}
\end{array}
$$

noteで無理やり表作るとキモいな
最後に保存をお忘れなく。

コードを書く

コンソールの「コード」タブを選択するとコードが入力できる画面になると思います。
全然プログラマーじゃないですが、書きました

import json, sys, requests, os, tweepy
from datetime import datetime
from dateutil import tz

def lambda_handler(event, context):
    body = json.loads(event.get('body'))
    service = body["service"]
    api = body["api"]
    type = body["type"]
    id = body["id"]
    time_format = '%H'
    time_zone = tz.gettz('Asia/Tokyo')
    time_jst = datetime.now(tz=time_zone)
    hour_jst = int(time_jst.strftime(time_format))
    discord_url = os.environ['DISCORD_URL']
    X_API_KEY = os.environ['X_API_KEY']
    X_API_KEY_SECRET = os.environ['X_API_KEY_SECRET']
    X_BEARER_TOKEN = os.environ['X_BEARER_TOKEN']
    X_TOKEN = os.environ['X_TOKEN']
    X_TOKEN_SECRET = os.environ['X_TOKEN_SECRET']
    
    if service == "hoge" and api == "diary" and type == "new":
        title = body["contents"]["new"]["publishValue"]["title"]
    else:
        return {
            'statusCode': 400,
            'body': json.dumps('invalid parameter')
        }
        sys.exit()
    
    if hour_jst < 9:
        diary_date = "昨日"
    else:
        diary_date = "今日"
    
    diary_url = "https://hogehoge.jp/fuga.html?id=" + id
    message = diary_date + "の日記が更新されました。\n" + title + "\n" + diary_url
    
    headers_discord = {
        "Content-Type": "application/json",
        "User-Agent": "DiscordBot (private use) Python-urllib/3.11",
    }
    message_discord = {"content": message}
    
    requests.post(
        discord_url,
        json.dumps(message_discord).encode(),
        headers=headers_discord,
    )

    client = tweepy.Client(bearer_token=X_BEARER_TOKEN, consumer_key=X_API_KEY, consumer_secret=X_API_KEY_SECRET, access_token=X_TOKEN, access_token_secret=X_TOKEN_SECRET)

    client.create_tweet(text = message)
    
    return {
        'statusCode': 200,
        'body': json.dumps('OK')
    }

ちょっとずつ説明。
まずはモジュールのインポート

import json, sys, requests, os, tweepy
from datetime import datetime
from dateutil import tz

以下lambda_handler関数内の実行になります。(トリガーされたときに動く関数です)
変数の宣言です。microCMSから受け取ったjsonをbodyとして、そこから必要な情報をどんどん宣言しています。サービス名とか、API名とか。

def lambda_handler(event, context):
    body = json.loads(event.get('body'))
    service = body["service"]
    api = body["api"]
    type = body["type"]
    id = body["id"]

日本時刻の取得です。「時」だけとるためになんかめんどくさいことしてます。絶対もっとスマートに取得する方法があります。

    time_format = '%H'
    time_zone = tz.gettz('Asia/Tokyo')
    time_jst = datetime.now(tz=time_zone)
    hour_jst = int(time_jst.strftime(time_format))

先ほど環境変数に追加したものたちを変数に宣言しています。
os.environ['キー']で呼び出せるらしいです。

    discord_url = os.environ['DISCORD_URL']
    X_API_KEY = os.environ['X_API_KEY']
    X_API_KEY_SECRET = os.environ['X_API_KEY_SECRET']
    X_BEARER_TOKEN = os.environ['X_BEARER_TOKEN']
    X_TOKEN = os.environ['X_TOKEN']
    X_TOKEN_SECRET = os.environ['X_TOKEN_SECRET']

サービス名、API名が想定している(最初の方で取得したjsonと同様の)ものか、投稿内容は「新規作成(new)」かチェックして、OKならタイトルを取得、なんか違うなら400吐いてそのまま終了です。一応変なリクエストは通らないように。一応。

    if service == "hoge" and api == "diary" and type == "new":
        title = body["contents"]["new"]["publishValue"]["title"]
    else:
        return {
            'statusCode': 400,
            'body': json.dumps('invalid parameter')
        }
        sys.exit()

朝の9時より前だったら一日前の日付で投稿されるので(UTCだから)、先ほど取得した時間が9時より前だったら「昨日」、9時以降なら「今日」を宣言しておきます。(投稿文面に反映します)

    if hour_jst < 9:
        diary_date = "昨日"
    else:
        diary_date = "今日"

記事URLと投稿文面の作成です。
僕の場合記事URLはidから生成可能なので生成しています。ここは各々の環境に合わせて実装してください。
投稿文面も良い感じになるように。

今日の日記が更新されました。
[タイトル]
[URL]

↑みたいになります

    diary_url = "https://hogehoge.jp/fuga.html?id=" + id
    message = diary_date + "の日記が更新されました。\n" + title + "\n" + diary_url

DiscordとX(Twitter)に投稿します。ここはもういろんな人が説明書いてくれてると思います

    headers_discord = {
        "Content-Type": "application/json",
        "User-Agent": "DiscordBot (private use) Python-urllib/3.11",
    }
    message_discord = {"content": message}
    
    requests.post(
        discord_url,
        json.dumps(message_discord).encode(),
        headers=headers_discord,
    )

    client = tweepy.Client(bearer_token=X_BEARER_TOKEN, consumer_key=X_API_KEY, consumer_secret=X_API_KEY_SECRET, access_token=X_TOKEN, access_token_secret=X_TOKEN_SECRET)

    client.create_tweet(text = message)
    

最後に200返して終わり

    return {
        'statusCode': 200,
        'body': json.dumps('OK')
    }

コードを編集したら「Deploy」押下をお忘れなく!

一回押し忘れて「なんで動かないんだ…」ってなりました

microCMSにwebhook送信先URLを登録する

公式ドキュメント
↑にほとんど手順書いてあります。
microCMSコンテンツ管理画面の右上「API設定」から、
「webhook」>「追加」>「カスタム通知」でURL入力欄が出てきますのでそこにlambdaの関数URLを入力しましょう。最後に画面下部の「設定する」押下をお忘れなく。

「通知タイミングの設定」はご自由に

動かしてみる

できちゃったらmicroCMSのコンテンツを追加して動くか確かめてみましょう。
ログはCloudWatch >「ロググループ」から見れると思います。

左メニュー

ロググループ名は「/aws/lambda/[関数名]」みたいな感じかと。
エラー吐いたときなんかはここからログを見て対処法をググりましょう。エラー文でググったらだいたい解決します。

ログ保持期間の設定

CloudWatchもタダではないのでログの保持期間を決めておきましょう。
ロググループを選択して、右上「アクション」から「保持設定を編集」を選択。

保持設定なのか保持期間の設定なのか…

保持期間の設定画面が出るので、お好みの期間を選択して「保存」を押下しておいてください。

デフォが「失効しない」なのおかしくない?

3.できたもの


discord通知
twitter通知

↑みたいな感じで通知されるようになりました。みんな見てくれるといいな。

多分Lambdaの無料の範囲で使えるはずです。うれしいね
正直思ったよりは簡単でした。他いろいろできそうだからまたなんか作りたいな~みたいな気持ちもあったりなかったり

4.今後

  • 通知先増やしてみたい

  • 絵文字つけたい

  • そもそも日記ページのコードがおわってる問題どうにかしたい

スーパー飽き性の僕なので、いろんな人に見てもらうことで続く気がします。たぶん。暇なときにでも読んでってください。それでは…


いいなと思ったら応援しよう!

なるさわ
セブンのハッシュポテト代になる