GitHub Actions + Node.jsを使ってmainブランチにpushしたらXへ投稿されるようにした

こんにちは、ツーナです！
この記事では、Markdown形式で書いたxへの呟きを
GitHub Actionsを使ってXに自動投稿する仕組みを作ったので、
その構築方法をまとめていきます。

最終的に「Markdownを書いて pushするだけでXに投稿される」ようにしており、
個人開発や技術メモの共有を効率化したい方にはおすすめです！

## 🎯 目的とゴール

* プロジェクト内の`posts/` ディレクトリ配下にMarkdownファイルを追加
* pushしたらGitHub Actionsが発火
* Node.jsでposts/配下の最新のMarkdownを読み込み、Xに投稿

これにより、
・ 開発ログや技術メモをX上で共有しやすくする
・ Xの画面を開かずともXでの発信が可能
などの恩恵がありm！

---

## 🧱 全体構成

```
├── posts/                    # 投稿内容をMarkdownで管理
│   └── 2025-05-20-00-00-00.md
├── post-to-x.js              # Node.jsによる投稿スクリプト
├── .github/
│   └── workflows/
│       └── post-to-x.yml     # GitHub Actions 定義
├── makefile                  # posts配下にmarkdownを生成するコマンド
├── .env                      # ローカル実行用（公開しない）
└── .env.example              # 必須環境変数のテンプレ
```

---

## 🛠 使用技術

* GitHub Actions
* Node.js (v18+)
* [twitter-api-v2](https://github.com/PLhery/node-twitter-api-v2) ライブラリ
* OAuth 2.0（User Context）によるX API認証

---

## 大まかな手順
大まかに以下の流れで進めていきます。
1. Twitter API側の準備（APIキーの作成や登録など）
2. node.jsを使った投稿処理用スクリプトを作成（このスクリプトをGithub Actionsで実行する想定）
3. Github Actionsの作成
4. 動作確認

まず、初めにTwitter API側の準備を進めていきます。

## 1. Twitter API側の準備

### ✅ 開発者アカウント作成

[X Developer Portal](https://developer.x.com/en/portal/dashboard) にアクセスし、Basicプラン（無料）を申請（私の場合は、Github ActionsからXへ投稿できるようにしたい的な内容を英語で書いて申請しました）

### ✅ APIキーを発行
以下の手順に従い、APIキー、APIシークレットキーなどを生成しました。

1. ダッシュボード画面の左側サイドメニューのProject&Appsから任意のIDを押下


2. Keys And TokensよりAPI Key and Secret、Access Token and Secretのgenerateを押して、キーを取得（コピーしておく）

### ✅ 発行したAPIキーをリポジトリ設定に登録
リポジトリのSettingsからSecret and Valiables/Actionsを押下し、New Repositoryでキーを登録（キー名は、プロジェクトファイル内の.github/workflows/post-to-x.ymlで定義された名前に設定、値は前項目２でコピーした値をペースト）



### ✅ スコープの有効化
これは結構分かりづらいんですが、以下の添付画像の場所にスコープを設定できる場所があります。

① 赤枠を押下

![スクリーンショット 2025-05-19 22.16.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4090435/4e6f9497-3921-405f-8d3b-e7a7558d24f1.png)

② 下の方に進んでいただき、赤枠を押下

![スクリーンショット 2025-05-19 22.19.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4090435/f67e6aad-4813-4e55-ab94-810253147994.png)

App permissionsっていう項目があるので、そこで
`tweet.read`, `tweet.write`のスコープを有効化するために
「Read and write」にチェックを入れます。

App infoの項目で、
コールバックURLやWebサイトURLを入力し、
Saveを押下します。
コールバックURLやWebサイトについては、`http://localhost:3000/callback` 
`http://example/com` 
など適当に設定しておけばOKです。

---

## 2. node.jsを使った投稿処理用スクリプトを作成
次にnode.jsを使って、投稿処理用スクリプトを作成していきます。

### 📦 依存パッケージのインストール

```bash
npm init -y
npm install twitter-api-v2 dotenv
```

### 📝 post-to-x.js の中身

```js
import fs from 'fs';
import twitterApiPkg from 'twitter-api-v2';
const { TwitterApi } = twitterApiPkg;
import dotenv from 'dotenv';

if (process.env.GITHUB_ACTIONS !== 'true') {
    dotenv.config();
  }

const {
  X_API_KEY,
  X_API_KEY_SECRET,
  X_ACCESS_TOKEN,
  X_ACCESS_TOKEN_SECRET,
  POST_FILE
} = process.env;

if (!POST_FILE) {
  console.error("POST_FILE is not defined.");
  process.exit(1);
}

const client = new TwitterApi({
  appKey: X_API_KEY,
  appSecret: X_API_KEY_SECRET,
  accessToken: X_ACCESS_TOKEN,
  accessSecret: X_ACCESS_TOKEN_SECRET,
});

 // 安全のため500文字に制限
const text = fs.readFileSync(POST_FILE, 'utf-8').slice(0, 500);

try {
  const result = await client.v2.tweet(text);
  console.log("Tweet posted:", result.data);
} catch (err) {
  console.error("Failed to post tweet:", err);
  process.exit(1);
}
```
ちなみに上記処理で、
```js
if (process.env.GITHUB_ACTIONS !== 'true') {
    dotenv.config();
}
```
としてる理由は、ローカルでテストする場合（Github Actionsからの実行でない場合）、.envからAPIキーを読み込みテストできるようにしておきたかったためです。

---

## 3. GitHub Actionsの作成

最後にGithub Actionsを作成していきます。

```yaml
name: Post to X

on:
  push:
    branches:
      - main
    paths:
      - 'posts/**.md'

jobs:
  tweet:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Find latest post file
        id: find_post
        run: |
          FILE=$(git log -1 --name-only --pretty=format: | grep '^posts/.*\.md$' | tail -n 1)
          echo "POST_FILE=$FILE"
          echo "POST_FILE=$FILE" >> $GITHUB_ENV

      - name: Post to X
        run: node post-to-x.js
        env:
          POST_FILE: ${{ env.POST_FILE }}
          X_API_KEY: ${{ secrets.X_API_KEY }}
          X_API_KEY_SECRET: ${{ secrets.X_API_KEY_SECRET }}
          X_ACCESS_TOKEN: ${{ secrets.X_ACCESS_TOKEN }}
          X_ACCESS_TOKEN_SECRET: ${{ secrets.X_ACCESS_TOKEN_SECRET }}
```

---

## できあがった自動投稿機能の使い方
自動投稿機能の使い方としてはローカル環境で検証する場合と実際にリモートで運用する場合があります。
それぞれ分けて説明していきます。

まず、以下コマンドまたは手動で投稿用のファイルをposts配下に生成します（例. posts/YYYY-MM-DD-M-S.mdを追加）
```
make create  
```


### ✅ ローカル確認の場合
.envファイルに以下の値を記載します。
```.env
POST_FILE=posts/2025-05-18-hajimete.md
X_API_KEY=your_api_key
X_API_KEY_SECRET=your_api_secret_key
X_ACCESS_TOKEN=your_access_token
X_ACCESS_TOKEN_SECRET=your_access_token_secret
```

記載が終わったら以下コマンドで送信テストしてみます。
```bash
node post-to-x.js
```

成功レスポンスが返されれば、うまくいってます。

### ✅ 本番確認の場合

push すれば自動投稿される想定なので、mainブランチにpushしてみましょう。
```
git add .
git commit -m "新しい開発ログ追加"
git push origin main
```

pushした結果、Xへ投稿されてればOK

---
## 💡 今後の展望

以下の対応についても追加でできるようにしていきたいなとは思ってるけど複雑な内容はXで直接書けばよさそう感あるので、どこまでの機能を今後詰め込むかは検討したい。

* 画像付き投稿
* スレッド投稿対応
* Qiita記事と自動リンク
* 投稿ログを一覧化して見やすく管理
* GPTとの連携

---

## 🚀 まとめ

今回の仕組みによって、日々の投稿をGitHubからXへ投稿できるようになりました。
開発者としての発信を少しでも効率的かつ継続的にしたい方はぜひ導入してみてください！

質問やフィードバックはX（[@toona](https://x.com/toona96x)）まで🍙！

---

## 🔗 サンプルリポジトリ

→ [https://github.com/toona-lab/post-to-x-action](https://github.com/toona-lab/post-to-x-action)

ここまで読んでいただきありがとうございました！