Open5
Google Cloudとn8nで複数SNSに自動投稿
n8nとは
- n8n
- 便利すぎる
動機
- ポッドキャストを始めた
- 各種SNSで更新を告知しないと誰も聴いてくれない
- とはいえ手作業は面倒
- たいていのSNS管理ツールはTwitter(現X)で有料プランを要求する
- OAuth 1.0認証での投稿はほとんどのツールが対応してない
- n8nは自分でコードを書ける(JavaScript / Python)のでなんとかなるのでは?
- いろいろ制約があって苦労はしたが実現できそう
n8nをGoogle Cloudで動かす
- だいたい以下の通り
- 日本語でコマンドもコピペできて便利
- 無料でn8nをGoogle Cloudにホスティングして、自動化ワークフローを構築|n8n自動化ラボ
- 設定テスト→再起動だけちょっと間違ってるので注意
- セミコロンを入れるか分割しましょう
sudo nginx -t
sudo systemctl restart nginx
-
sudo apt upgrade
したらめっちゃ時間かかった
Twitter(現X)に投稿する(失敗編)
- n8nのTwitter投稿ノードは有料プラン前提
- X (Formerly Twitter) node documentation | n8n Docs
- OAuth 1.0認証による投稿は非対応
- 個人的信条により課金したくない
- n8nは自分で書いたコードを実行するノードがある
- Using the Code node | n8n Docs
- JavaScript / Python
- よろしいならば開発だ
- Geminiありがとう
JavaScript
-
import
がうまくいかない- sandbox内で動かしてるから
import { createHmac, randomUUID } from 'crypto';
// →'import' and 'export' may only appear at the top level [line 1]
-
crypto
というノードもある- が、
SHA-1
は非対応(それはそう)
- が、
Python
-
HTTPSConnection
がいない- OpenSSLを有効にしたビルドではないと思われる
- そもそもsandbox環境だし
import urllib.parse
import http.client
API_URL = 'https://api.twitter.com/2/tweets'
parsed_url = urllib.parse.urlparse(API_URL)
conn = http.client.HTTPSConnection(parsed_url.netloc)
# →AttributeError: module 'http.client' has no attribute 'HTTPSConnection'. Did you mean: 'HTTPConnection'?
Twitter(現X)に投稿する(成功編)
- 何らかのトリガーをcode nodeで受け取る
- PythonでAuthentication Headerを作る
- Geminiが書いたコードがだいたいそのまま使えた
- HTTP Request nodeでポストする
投稿テスト
code node
import hmac
import hashlib
import base64
import time
import uuid
import urllib.parse
import os
import json
API_KEY = '********'
API_SECRET = '********'
ACCESS_TOKEN = '********'
ACCESS_TOKEN_SECRET = '********'
TWEET_TEXT = '投稿テスト'
API_URL = 'https://api.twitter.com/2/tweets'
def get_oauth_header(method, url, params):
"""
OAuth 1.0aの認証ヘッダーを生成する
"""
oauth_params = {
'oauth_consumer_key': API_KEY,
'oauth_token': ACCESS_TOKEN,
'oauth_signature_method': 'HMAC-SHA1',
'oauth_timestamp': str(int(time.time())),
'oauth_nonce': uuid.uuid4().hex,
'oauth_version': '1.0'
}
# すべてのパラメータを結合してソート
all_params = {**params, **oauth_params}
sorted_params = sorted(all_params.items())
# エンコードしてクエリ文字列を作成
encoded_params = urllib.parse.urlencode(sorted_params, quote_via=urllib.parse.quote)
# シグネチャベース文字列を作成
base_string = '&'.join([
method,
urllib.parse.quote(url, safe=''),
urllib.parse.quote(encoded_params, safe='')
])
# シグネチャキーを作成
signing_key = f"{urllib.parse.quote(API_SECRET)}&{urllib.parse.quote(ACCESS_TOKEN_SECRET)}"
# HMAC-SHA1で署名を生成
hashed = hmac.new(signing_key.encode('utf-8'), base_string.encode('utf-8'), hashlib.sha1)
signature = base64.b64encode(hashed.digest()).decode('utf-8')
oauth_params['oauth_signature'] = signature
# ヘッダー用のパラメータを整形
header_params = [f'{urllib.parse.quote(k)}="{urllib.parse.quote(str(v))}"' for k, v in oauth_params.items()]
auth_header = 'OAuth ' + ', '.join(header_params)
return auth_header
def get_headers():
method = 'POST'
return {
'Authorization': get_oauth_header(method, API_URL, {}),
'Content-Type': 'application/json'
}
def get_body():
return {'text': TWEET_TEXT}
return {
'header': get_headers(),
'body': get_body()
}
HTTP Request node
TODO
- RSSをトリガーにして起動する
- 「未投稿の1件を取る」のがちょっと面倒
- 投稿頻度を考えるとSlack投稿でトリガーとかでいいか
- 各種トークンがハードコード
- n8nにはCredentialという機能がある
- が、code nodeで値を引っ張るのは不可能っぽい
- Access saved credentials from "expressions" - Feature Requests - n8n Community
- これはいかんともしがたそう
- HTTP Request + OAuth 1というノードの組み合わせもありそう
- …だが、失敗したよ助けてがたくさん
- Can't connect Twitter with oauth1 or oauth2 - Questions - n8n Community
- n8nにはCredentialという機能がある
- mixi2への投稿
- APIはないのでヘッドレスブラウザとか
-
Browser Use
とかSkyvern
とか使えそうだけど未検証