つくったもの
ブログ記事を新たに執筆してCMSに投稿すると、ホスティング先への反映を待ってからTwitterへ記事の公開について自動的にツイートする仕組みを作りました。
せっかくなので内容を記事として公開してみました。
対象としている読者
ある程度Webの開発に慣れている方、AWSの操作に慣れている方を想定して記事を記述しています。
それなりには詳細に記述しているので、それ以外の方でもある程度参考になると思います。
記事の投稿からツイートまでの流れ
- ブログの記事を書く
- 記事の 情報を管理しているCMSへアップロードする
- ホスティングしているサービスなどへ追加記事の反映を行う
- Twitterで記事の公開について自動でツイートする

仕組みの概要
ホスティングサービスなどでビルドが進んでいる間、CMSへのアップロードに連動してAWS上の処理が動きます。
API Gatewayで用意したREST APIにてリクエストを受け付け、Step Functionsにてホスティング用のビルド時間を待機します。
待機時間が過ぎると、リクエスト時に受け取った記事情報を使ってLambdaの処理でTwitterへの投稿が行われます。

Step Functionsを採用している理由
ホスティング先への反映が瞬時に完了する場合、API GatewayとLambdaだけを使って同期処理でTwitterへツイートすることも可能です。
しかし私のブログではGatsbyを使ったSSGを採用しているため、数分かけてブログのビルドを行う必要があります。
API GatewayやLambdaを単純に使うとSleepを使用した際にタイムアウトするため、Step Functionsを使って非同期処理にしています。
作成手順
流れ
- Twitterへの投稿を行うための認証情報の生成を行う
- Twitterへの投稿を行うLambda関数を作成する
- Step FunctionsのWorkflowを作成する
- API GatewayでREST APIを作成する
- CMSで新規記事公開時のWebhookを設定する
Twitterへの投稿を行うための認証情報の生成を行う
開発者アカウントの作成
投稿したいTwitterアカウントでログインした状態でTwitter開発者プラットフォームのDeveloper Portalにアクセスしてください。
表示される指示に従って開発者アカウントを作成します。
| 項目 | 内容 |
|---|---|
| Twitter Account | 初期状態で入力済み |
| 初期状態で入力済み | |
| What country are you based in? | Japan |
| What's your use case? | Build customized solutions in-house |
| Will you make Twitter content or derived information available to a government entity or a government affiliated entity? | No |

情報の入力をして進むとポリシーへの同意を求められるので確認の上同意します。
ポリシーに同意するとメールアドレス宛に認証メールが送信されるので、メールのリンクから認証を行います。
Appの作成と認証情報の生成
認証メールを開くとAppの作成を促されるので、Appの名前を入力します。
Blog published notificationとしておきますが、任意で設定してください。

Appを作成するとAPIを使うための各種認証情報が生成されるので、後で使えるようにメモしておきます。

ダッシュボードから先程作成したPROJECT APPのApp settings(歯車アイコン)を選択して設定画面を開きます。
設定画面でKeys and tokensタブを選択します。
Authentication Tokensという項目のAccess Token and Secretで、Generateボタンをクリックします。
表示された情報は後で使えるようにメモしておきます。
投稿権限を設定する
- 設定画面の
Settingsタブを選択する User authentication not set upのSetupボタンをクリックするApp permissionsをRead and writeにするType of AppをWeb App, Automated App or BotにするApp infoのCallback URI / Redirect URLにブログのURLを末尾スラッシュ無しで入力する(例:https://blog.datsukan.me)App infoのWebsite URLにブログのURLを末尾スラッシュ有りで入力する(例:https://blog.datsukan.me/)SaveボタンをクリックするChanging permissions might affect your AppのダイアログでYesをクリックする
ここまででTwitterの設定は完了しました。
Twitterへの投稿を行うLambda関数を作成する
Goで処理を記述する
Lambda関数として動かす処理を作成します。
今回は言語にGoを採用します。
Twitter APIの操作にはmichimani/gotwiを採用しました。
package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/aws/aws-lambda-go/lambda"
"github.com/joho/godotenv"
"github.com/michimani/gotwi"
"github.com/michimani/gotwi/tweet/managetweet"
"github.com/michimani/gotwi/tweet/managetweet/types"
)
// Request は、リクエスト情報の構造体。
type Request struct {
Token string `json:"token"`
Slug string `json:"slug"`
Title string `json:"title"`
}
func main() {
t := flag.Bool("local", false, "ローカル実行か否か")
slug := flag.String("slug", "", "ローカル実行用の記事Slug")
title := flag.String("title", "", "ローカル実行用の記事Title")
flag.Parse()
isLocal, err := isLocal(t, slug, title)
if err != nil {
fmt.Println(err.Error())
return
}
if isLocal {
fmt.Println("local")
r := Request{
Slug: *slug,
Title: *title,
}
localController(r)
return
}
fmt.Println("production")
lambda.Start(controller)
}
// isLocal は、ローカル環境 の実行であるかを判定する。
func isLocal(t *bool, slug *string, title *string) (bool, error) {
if !*t {
return false, nil
}
if *slug == "" {
fmt.Println("no exec")
return false, fmt.Errorf("ローカル実行だがSlug指定が無いので処理不可能")
}
if *title == "" {
fmt.Println("no exec")
return false, fmt.Errorf("ローカル実行だがTitle指定が無いので処理不可能")
}
return true, nil
}
// localController は、ローカル環境での実行処理を行う。
func localController(r Request) {
if err := godotenv.Load(); err != nil {
fmt.Println(err)
return
}
if err := useCase(r); err != nil {
fmt.Println(err.Error())
}
fmt.Println("tweetしました。")
}
// controller は、API Gateway / AWS Step Functions / AWS Lambda 上での実行処理を行う。
func controller(r Request) error {
if r.Token != os.Getenv("API_TOKEN") {
return fmt.Errorf("unauthorized access")
}
if r.Slug == "" {
return fmt.Errorf("slug is empty")
}
if r.Title == "" {
return fmt.Errorf("title is empty")
}
if err := useCase(r); err != nil {
return err
}
return nil
}
// useCase は、アプリケーションのIFに依存しないメインの処理を行う。
func useCase(r Request) error {
in := &gotwi.NewClientInput{
AuthenticationMethod: gotwi.AuthenMethodOAuth1UserContext,
OAuthToken: os.Getenv("GOTWI_ACCESS_TOKEN"),
OAuthTokenSecret: os.Getenv("GOTWI_ACCESS_TOKEN_SECRET"),
}
c, err := gotwi.NewClient(in)
if err != nil {
return err
}
t := fmt.Sprintf("新しいブログ記事を投稿しました🐣\n\n「%s」\n%s%s", r.Title, os.Getenv("BLOG_URL"), r.Slug)
if _, err := tweet(c, t); err != nil {
return err
}
return nil
}
// tweet は、指定されたテキストをツイートする。
func tweet(c *gotwi.Client, text string) (string, error) {
p := &types.CreateInput{
Text: gotwi.String(text),
}
res, err := managetweet.Create(context.Background(), c, p)
if err != nil {
return "", err
}
return gotwi.StringValue(res.Data.ID), nil
}
- Request構造体に記述している通り、
{"token": "xxxx", "slug": "xxxx", "title": "xxxx"}というJsonで記事情報を受け取る想定- CMSのWebhook → API Gateway → Step Functions → Lambda とデータを受け渡す際にこの形式になるようにする
- 動作確認用にローカル環境でも実行できるように制御を用意している
新しいブログ記事を投稿しました🐣...となっている箇所は投稿メッセージを組み立てている- 必要なら任意の内容に変更して使 用する
実装のサンプル↓
ローカル環境で動作確認する
main.goと同じ階層に.envファイルを作成して、下記の通り環境変数を設定します。
なお、外部に漏洩するとまずい情報なので、.gitignoreに.envを設定することを推奨します。
GOTWI_API_KEY=[Twitter開発者プラットフォームでAppを作成した際に生成された`API Key`]
GOTWI_API_KEY_SECRET=[Twitter開発者プラットフォームでAppを作成した際に生成された`API Key Secret`]
GOTWI_ACCESS_TOKEN=[Twitter開発者プラットフォームの`Access Token and Secret`で、`Generate`した際の`Access Token`]
GOTWI_ACCESS_TOKEN_SECRET=[Twitter開発者プラットフォームの`Access Token and Secret`で、`Generate`した際の`Access Token Secret`]
BLOG_URL=[自分のブログのFQDN(例:`https://blog.datsukan.me/`)]
API_TOKEN=[認証用のトークンを独自に生成して設定する]
main.goがあるディレクトリでCLIからgo run main.go -local -slug=xxxx -title=hogeを実行します。
slugとtitleの値は任意の値で置き換えてください。
うまく行けば自身のTwitterアカウントでツイートが行われているはずです。
ここでは記事のURLがhttps://[ドメイン]/[記事のSlug]の形式であることを前提にしているので、異なる場合は環境変数のBLOG_URLの値、もしくはメッセージ部分を都合に合わせて書き換えてください。
AWS上にLambda関数を作成する
- AWSの管理コンソールへログインする
- Lambdaの関数のページへアクセスする
関数の作成ボタンをクリックする- 関数名を入力する(例:
blog-published-tweet) - ランタイムを
Go 1.xにする - ほかは変えずに
関数の作成ボタンをクリックする - 作成された関数のページから各種設定を行う
ランタイム設定を編集して、ハンドラをblog-published-tweetにする(任意の名称で良いが、Goの実行ファイル名に合わせる)設定の環境変数を編集して、「ローカルで動作確認する」の際に.envに設定した内容を登録する- 処理を登録する(
blog-published-tweetは任意の実行ファイル名でも可)Goで処理を記述するで作成した処理をgo build -o blog-published-tweet main.goでビルドする- 生成された
blog-published-tweetをZip圧縮する - 管理コンソールの関数のページで
コードソースのアップロード元を選択して、.zip ファイルから先程圧縮したファイルをアップロードして保存する
Lambda関数をテスト実行する
管理コンソールの関数のページで、テストタブを開いてイベント JSONを下記の値にしてテストボタンをクリックしてください。
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"slug": "xxxx",
"title": "hoge"
}
ローカルでの動作確認と同じようにTwitterへツイートが行われていれば成功です。
Step FunctionsのWorkflowを作成する
- AWSの管理コンソールでStep Functionsのページへアクセスする
ステートマシンの作成ボタンをクリックするワークフローを視覚的に設計を選択するタイプで標準を選択する次へボタンをクリックするWorkflow Studioが表示されるので、workflowを作成していく- 左上の検索欄で
Choiceと検索して、一番上に出てきたアイテムをworkflow上のStartの下にドラッグ&ドロップする - 右側の設定欄にある
Choice RulesでRule #1の編集ボタンをクリックする - 展開された中の
Add conditionsボタンをクリックする - 下記の通り入力して
条件を保存するボタンをクリックする項目 内容 Not 指定なし Variable $.revisionOperator is equal toValue (Type) Number constantValue 1 - 左上の検索欄で
Passと検索して、出てきたアイテムをworkflow上のChoiceのDefaultの分岐にドラッグ&ドロップする - 右側の設定欄にある
次の状態で最後に移動を選択する - 左上の検索欄で
Waitと検索して、出てきたアイテムをworkflow上のChoiceの$.revision == 1の分岐にドラッグ&ドロップする - 右側の設定欄にある
秒の設定値をブログのビルド時間より長くする(例:ビルドに4~6分かかるので、600secondsに設定) - 左上の検索欄で
Lambda Invokeと検索して、出てきた中からAWS Lambda Invokeのアイテムをworkflow上のWaitの下にドラッグ&ドロップする - 右側の設定欄にある
API パラメータのFunction nameから、先程作成したLambda関数を選択する(手順の記述通りならblog-published-tweet)
- 左上の検索欄で
- 右上の
次へボタンをクリックする 生成されたコードを確認のページで次へボタンをクリックする- ステートマシン名を入力する(例:
blog-published-tweet) ステートマシンの作成ボタンをクリックする
