【GAS】 X投稿+Discord通知を自動化する方法


## この記事について
この記事では、**Google Apps Script（GAS）初心者向け**に、X(旧:Twitter, 以降Xと記載)への自動投稿と、実行結果を**Discordに自動通知する方法**を記載します。
基本的な使い方から具体的なコード例、トリガー設定までカバーしています。

## 目次
1. [概要](#概要)
2. [目的](#目的)
3. [必要なもの](#必要なもの)
4. [スクリプト構成](#スクリプト構成)
   - [ステップ1. スプレッドシートの未投稿データをXに投稿する](#ステップ1-スプレッドシートの未投稿データをXに投稿する)
   - [ステップ2. 投稿の結果をDiscordに通知する](#ステップ2-投稿の結果をDiscordに通知する)
   - [ステップ3. ステップ1,2を実行する](#ステップ3-ステップ1,2を実行する)
5. [トリガー設定手順](#トリガー設定手順)
6. [まとめ](#まとめ)

## 概要
Google Apps Script（GAS）を使ってXに投稿し、その結果をDiscordへ自動通知する方法を紹介します。実際のユースケースとして、スプレッドシートのデータをもとにポストし、その投稿結果（成功・失敗）をDiscordのチャンネルにWebhookで通知します。

## 目的
- GASからXに投稿
- 実行結果（成功・失敗）をDiscordに自動通知

## 必要なもの
- Google スプレッドシート
  ※今回は以下の形式を使用します。
  | ID | タイトル       | 投稿文                             | 投稿ステータス |
  |----|----------------|----------------------------------|----------------|
  | 1  | サンプル投稿① | これは自動投稿テストです①。      | 未投稿         |
  | 2  | サンプル投稿② | これは自動投稿テストです②。      | 投稿済み       |
  | 3  | サンプル投稿③ | これは自動投稿テストです③。      | 未投稿         |
- Google Apps Script エディタ
- Discord Webhook URL
- XのAPI認証情報

---

## スクリプト構成

「スプレッドシートから投稿」→「投稿処理の中でX APIに通信」→「結果をDiscord通知」という構成になっています。

### ステップ1. スプレッドシートの未投稿データをXに投稿する
関数：postX()<br>
中で呼ばれる関数：postToX() → sendXRequest() → getOAuthHeader()

🧠 処理の流れ<br>
- スプレッドシートから「未投稿」のデータを探す
- 見つけたテキストをpostToX()でX（旧Twitter）に投稿
  - → この中で認証情報を元にAPIリクエストを送る関数が順に呼ばれます
- 投稿できたら「投稿済み」に更新
- 成否メッセージを返す（後でDiscord通知に使う）

コード(GAS)
```javascript
function postX() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  var lastMonth = new Date();
  lastMonth.setMonth(lastMonth.getMonth() - 1);

  var data = sheet.getDataRange().getValues();
  var itemToPost = null;
  var rowIndex = -1;

  for (var i = 1; i < data.length; i++) {
    if (data[i][3] === "未投稿") {
      itemToPost = {
        title: data[i][1],
        text: data[i][2],
      };
      rowIndex = i + 1;
      break;
    }
  }

  if (!itemToPost) {
    return "投稿できる新しいコンテンツがありません";
  }

  var postResponse = postToX(itemToPost.text);

  if (!postResponse || !postResponse.data || !postResponse.data.id) {
    return "ポスト失敗: " + JSON.stringify(postResponse);
  }

  sheet.getRange(rowIndex, 4).setValue("投稿済み");
  return "ポスト成功: " + itemToPost.title + "\n" + itemToPost.text;
}
```
---
```
function postToX(content) {
  var url = "https://api.twitter.com/2/tweets";
  var payload = { "text": content };
  return sendXRequest(url, "POST", payload, "application/json");
}
```
---
```javascript
/**
 * APIへリクエストを送る関数
 * @param {string} url - APIエンドポイントのURL
 * @param {string} method - HTTPメソッド (POST/GET)
 * @param {Object} payload - 送信するデータ (nullの場合は無し)
 * @param {string} contentType - Content-Type (JSON, multipart/form-data など)
 * @returns {Object} APIのレスポンス(JSON)
 */
function sendXRequest(url, method, payload, contentType) {
  var options = {
    "method": method,
    "headers": {
      "Authorization": getOAuthHeader(method, url),
      "Content-Type": contentType
    },
    "muteHttpExceptions": true
  };
  if (payload) {
    options.payload = (contentType === "application/json") ? JSON.stringify(payload) : payload;
  }
  var response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}
```
---

```javascript
/**
 * OAuth1.0aの署名を作成し、OAuth認証ヘッダーを生成する関数
 * @param {string} method - HTTPメソッド (POST/GET)
 * @param {string} url - APIのURL
 * @returns {string} OAuth認証ヘッダー
 */
function getOAuthHeader(method, url) {
  var credentials = X_CREDENTIALS;
  var timestamp = Math.floor(new Date().getTime() / 1000);
  var nonce = Math.random().toString(36).substring(2, 15);

  var parameterString = [
    "oauth_consumer_key=" + credentials.consumerKey,
    "oauth_nonce=" + nonce,
    "oauth_signature_method=HMAC-SHA1",
    "oauth_timestamp=" + timestamp,
    "oauth_token=" + credentials.accessToken,
    "oauth_version=1.0"
  ].sort().join("&");

  var signatureBaseString = method + "&" + encodeURIComponent(url) + "&" + encodeURIComponent(parameterString);
  var signingKey = encodeURIComponent(credentials.consumerSecret) + "&" + encodeURIComponent(credentials.accessTokenSecret);
  var signatureBytes = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, signatureBaseString, signingKey);
  var signature = Utilities.base64Encode(signatureBytes);

  return 'OAuth ' +
    'oauth_consumer_key="' + credentials.consumerKey + '", ' +
    'oauth_nonce="' + nonce + '", ' +
    'oauth_signature="' + encodeURIComponent(signature) + '", ' +
    'oauth_signature_method="HMAC-SHA1", ' +
    'oauth_timestamp="' + timestamp + '", ' +
    'oauth_token="' + credentials.accessToken + '", ' +
    'oauth_version="1.0"';
}
```
---

### ステップ2. 投稿の結果をDiscordに通知する
関数：sendToDiscord(message)

🧠 処理の流れ<br>
- ステップ①で取得した「成功」または「失敗」の結果メッセージをDiscordのWebhook URLにPOSTして通知

コード(GAS)
```javascript
function sendToDiscord(message) {
  const webhookUrl = "https://discord.com/api/webhooks/XXXXX/XXXXX"; // ここを自分のWebhookに置き換えてください
  const payload = { content: message };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(webhookUrl, options);
}
```

---

### ステップ3. ステップ1,2を実行する
関数：executeAndNotify()

🧠 処理の流れ<br>
- postX()を呼び出してXに投稿
- その結果をsendToDiscord()で通知

→ これひとつで全体が動きます！

コード(GAS)
```javascript
function executeAndNotify() {
  var result = postX();
  sendToDiscord(result);
}
```
---

## トリガー設定手順

初心者でも簡単に設定できます。以下の手順で、自動実行の仕組みを構築しましょう。

1. GASエディタ上部メニューから「トリガー」アイコンをクリック
2. 「＋ トリガーを追加」を選択
3. 実行関数として `executeAndNotify` を選択
4. イベントの種類（時間ベースなど）を選択し、実行頻度を設定
5. 保存して完了

---

## 参考
* [XのAPIについて](https://developer.x.com/ja/docs/authentication/oauth-1-0a)
* [Webhookについて](https://support.discord.com/hc/ja/articles/228383668-タイトル-Webhooksへの序章)

## まとめ
この構成により、スプレッドシートに登録された「未投稿」データを元にXへ投稿を行い、その処理結果をDiscordへ自動通知する仕組みが実現できます。**GAS初心者**でもすぐに始められる実用的な自動化テクニックです。

もし「XのAPIの使い方」や「Webhookの作成方法」などでつまずいた場合は、それぞれのドキュメントをチェックしてみてください。