X(Twitter)投稿自動化の記事を書いた振り返り

# 概要
先日、X（Twitter）を投稿するのをGASで自動化した[記事](https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435)を執筆したのですが、追加で書けてなかったハマりどころポイントとQiitaを書いたことで色々とフィードバックいただけのが嬉しかったのでこの年末にまとめとして書かせていただきます。

https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435

# 執筆後のフィードバック
## タイポを修正してもらいました。

@sinfuk1997 さん[修正リクエスト](https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435/patches?patch_id=125249)ありがとうございました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1985649/abbe499e-ed11-7111-bea0-7528954f90a9.png)

というか、Qiitaで編集リクエストなんて出来ることを知れたのが凄く良い収穫でした。

## コメントいただきました

@studiosimply さん
@Michio029 さん
コメントありがとうございました。励みになります！

# 自動化した後にハマったポイント（チーム開発だとなるかも）
## 原因
GASのプロジェクトを自動化してX(Twitter)に投稿できるようになったー！！
と思っていたのですが、いざ運用に乗せるとトリガーで実行したジョブは「Not Authorised」となり運用が開始できてなかったです。

https://qiita.com/yuzinet/items/ae4b9ca2b5cd989de435

これは、以前のQiita記事を書いた後に発覚したことです。

原因は、チームでプロジェクトを運用していたために、GASのスクリプトを自分自身で実行すると問題ないのですがトリガーを利用して実行すると実行ユーザーが変わることで環境変数が利用できず認証時に格納している
認証情報にアクアセスできず認証が突破できない事象です。

## 原因特定までの紆余曲折
この事象を調べてみると色々と参考になりそうな記事が。。。

と思ったら私が執筆したQiitaの記事を参照して実装してくれている記事が結構出てくる！！
（地味に嬉しい！！）
ですが同じ問題に当たっている人には遭遇できず。。。

https://prtn-life.com/blog/gas-twitter-api

https://zenn.dev/bs_kansai/articles/c79c6b84c536d1#gas-%E3%81%A7-twitter-api-%E3%81%8B%E3%82%89%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E6%8A%95%E7%A8%BF%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95

## 原因特定

Google Apps ScriptのPropertiesServiceには[スコープ](https://developers.google.com/apps-script/reference/properties/properties-service?hl=ja)がありこれが実行ユーザーによって利用できるものと出来ないことがあることが分かりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1985649/42b3eb85-bf84-350e-c415-4a7dcde8a2f1.png)

この、`PropertiesService.getUserProperties()` を利用していたことでプロジェクトの実行時（トリガー）を使って自動実行した場合にその作成ユーザーで実行されてしまってことが原因でした。

## 対処方法
PropertiesServiceの格納場所を変更することで対処しました。
`UserProperties`から`DocumentProperties`に変更することで対処しました。

<details><summary>対処時のスクリプト</summary><div>

```diff_javascript
function getService() {
  pkceChallengeVerifier();
-  const userProps = PropertiesService.getUserProperties();
+  const userProps = PropertiesService.getDocumentProperties();
  const scriptProps = PropertiesService.getScriptProperties();
  const clientId = scriptProps.getProperty('CLIENT_ID');
  const clientSecret = scriptProps.getProperty('CLIENT_SECRET');

  return OAuth2.createService('twitter')
    .setAuthorizationBaseUrl('https://twitter.com/i/oauth2/authorize')
    .setTokenUrl('https://api.twitter.com/2/oauth2/token?code_verifier=' + userProps.getProperty("code_verifier"))
    .setClientId(clientId)
    .setClientSecret(clientSecret)
    .setCallbackFunction('authCallback')
    .setPropertyStore(userProps)
    .setScope('users.read tweet.read tweet.write offline.access')
    .setParam('response_type', 'code')
    .setParam('code_challenge_method', 'S256')
    .setParam('code_challenge', userProps.getProperty("code_challenge"))
    .setTokenHeaders({
      'Authorization': 'Basic ' + Utilities.base64Encode(clientId + ':' + clientSecret),
      'Content-Type': 'application/x-www-form-urlencoded'
    })
}

function authCallback(request) {
  const service = getService();
  const authorized = service.handleCallback(request);
  if (authorized) {
    return HtmlService.createHtmlOutput('Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied.');
  }
}

function pkceChallengeVerifier() {
-  var userProps = PropertiesService.getUserProperties();
+  var userProps = PropertiesService.getDocumentProperties;
  if (!userProps.getProperty("code_verifier")) {
    var verifier = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";

    for (var i = 0; i < 128; i++) {
      verifier += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    var sha256Hash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, verifier)

    var challenge = Utilities.base64Encode(sha256Hash)
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '')
    userProps.setProperty("code_verifier", verifier)
    userProps.setProperty("code_challenge", challenge)
  }
}

function logRedirectUri() {
  var service = getService();
  Logger.log(service.getRedirectUri());
}
```

</div></details>


# まとめ
今年は、Qiitaの記事を昨年に比べて多く書きました。（それでも５記事くらい。）
記事を執筆したことで修正リクエストやコメントなど色々と反応頂けて嬉しかったです！
また、実際に利用して実装してくれていた方がいたのがもっと嬉しかった！
少しでも誰かの為になっていたら嬉しいです

来年も、備忘を兼ねた記事を執筆していこうと思います。

それでは、良いお年を〜！