つくったもの
ブログ記事を新たに執筆して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 $.revision
Operator is equal to
Value (Type) Number constant
Value 1 - 左上の検索欄で
Pass
と検索して、出てきたアイテムをworkflow上のChoice
のDefault
の分岐にドラッグ&ドロップする - 右側の設定欄にある
次の状態
で最後に移動
を選択する - 左上の検索欄で
Wait
と検索して、出てきたアイテムをworkflow上のChoice
の$.revision == 1
の分岐にドラッグ&ドロップする - 右側の設定欄にある
秒
の設定値をブログのビルド時間より長くする(例:ビルドに4~6分かかるので、600
secondsに設定) - 左上の検索欄で
Lambda Invoke
と検索して、出てきた中からAWS Lambda Invoke
のアイテムをworkflow上のWait
の下にドラッグ&ドロップする - 右側の設定欄にある
API パラメータ
のFunction name
から、先程作成したLambda関数を選択する(手順の記述通りならblog-published-tweet
)
- 左上の検索欄で
- 右上の
次へ
ボタンをクリックする 生成されたコードを確認
のページで次へ
ボタンをクリックする- ステートマシン名を入力する(例:
blog-published-tweet
) ステートマシン
の作成ボタンをクリックする