ﾏﾂﾔ風に気温に対して感想を述べるbotを作ってmisskeyに生やした

# これ、は何⁈
Twitterで有名な謎のインド人こと[マツヤ](https://twitter.com/Matsya_)氏。
彼のような独特の文体で気温に対して感想を述べるbotを作ったので、その過程などを簡単に書く。

実装内容（ライブラリ部分）：https://github.com/Syuko4omi/matsya_temperature
bot：https://misskey.io/@matsya_temperature

# アー、⁇⁉︎、⁇マツヤの、。じこしようかい??。☆
- Twitterのアカウント：[मत्स्य](https://twitter.com/Matsya_)
- Twitterのフォロワー数20万人超え
- 日常のさもないツイートで毎回のように万バズする謎のインド人（日本在住）
- カレー屋（店名や所在地不明）
- 犬を飼っている


![スクリーンショット 2024-03-24 18.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/6cff57dc-88f7-6c28-c6d7-d44d3a77db5a.png)


# はぁ‼︎！？、botをつくるって。、⁇！なによ、、⁉︎
以下のような手順で作る。
1. マツヤ風ツイートを生成する部分を作る
2. 手順１で作ったライブラリをベースに、テキストを生成するアプリを作る
3. アプリをデプロイし、定期実行するスクリプトを走らせる

## 手順１：マツヤ風ツイートを生成する部分を作る
- Pythonで実装する
    - まず、[OpenWeatherMap](https://openweathermap.org)のAPIを使い、外部から天候の情報を取得する
    - その後、取得した天候の情報を元に、ツイートの核となる気温や季節の情報を生成し、特殊な文体に直す

### 💬ライブラリ部分の処理の具体例
- 日付や天候の情報 → 2024年3月24日　体感気温4度
- 上記の情報を元に判断した気温や季節　→ 寒い・冬
- 気温や季節を特殊な文体に変換　→ さﾑ！

### 🧑‍💻ライブラリ部分の実装の概要
<details><summary>非本質なので読み飛ばしてOK🚩</summary>

| 期間 | 体感気温 | 気温・季節 |
|:-----------:|:------------:|:------------:|
| 1月〜5月       | 18度以上        | 暑い・春         |
|     | 8度〜18度      | 暖かい・春       |
|    | 8度以下        | 寒い・冬・ふゆ         |
| 6月〜9月         | 28度以上 | 暑い・夏           |
|     | 24度〜28度 | 涼しい・秋       |
|   | 24度以下 | 寒い・秋      |
| 10月〜12月  | 28度以上 | 暑い・夏      |
|  | 18度〜28度 | すずしい・秋      |
|　　| 18度以下 | 寒い・冬・ふゆ      |
- ステップ１：気温は暑い・暖かい・涼しい・寒いの四種類から選ぶ。季節も春夏秋冬の四種類から選ぶ。気温や季節の肌感覚は、過去のマツヤ氏のツイートから独断と偏見で上記表のように決定した。その上で、マツヤ氏が普段どのようにそれぞれを形容しているのかを、直近のツイートを参考に集めた上で、核となる部分を生成する。
    - 季節
        - （これは（ね|ですねえ））＜春夏秋冬＞（ネ）
    - 寒暖
        - （これは）＜気温＞
            - 暖かい→ あたかい・あたたかい・あたたかネ・あったかくない？
            - 涼しい→ すじゆし・すじゆしとおもいますけど
            - 暑い→ あつい・あつくネ？・あつくない？・あついとおもいますけど
            - 寒い→ さむ・サむ・さム・さむい・サむい・さムい・さむくネ？・さむいとおもいますけど
- ステップ２：気温や季節を形容する部分（ツイートの核となる部分）を生成したら、それに対してマツヤ風な文章になるように調整を施す。以下はその例である
    - 形容詞や副詞を挿入する。例えば、季節の前に「かんぺきに（ねえ）・しょうみな話（ねえ）」といったフレーズを入れる
    - カタカナをランダムに半角にする
    - 文節の切れ目で、記号（句読点・!・?・!!・??・?!・☆）をランダムに挿入する
</details>

## 手順２：手順１で作ったライブラリをベースに、テキストを生成するアプリを作る
### 🔧アプリ部分の実装の概要
- Flaskで実装する
    - （※ライブラリ部分の実装では、pip installでライブラリがインストールできるような構造を心がけた。）アプリの実装では、ライブラリの上にapp.pyとしてアプリケーション部分を乗せるだけで完了した
    - アプリが担うのは、URLにアクセスしたら、ライブラリで生成したテキストを返却する役割である
    - ローカル環境で下記のapp.pyを実行して、自分自身を指すループバックアドレス（127.0.0.1）にアクセスすると、マツヤ風ツイートが生成される

```Python:app.py
import os
from flask import Flask

from matsya_temperature.generate_sentence import generate_sentence

app = Flask(__name__)


@app.route("/")
def matsya_report_temperature():
    return generate_sentence()


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
```

```Shell:example
> poetry run python3 app.py
かんぺきに!?‼︎あたかい!
```

## 手順３：アプリをデプロイし、定期実行するスクリプトを走らせる
- Cloud Runと、Google Apps Script (GAS)を用いる
    - [Cloud Run](https://cloud.google.com/run?hl=ja)は、サーバーなどの管理を考えずにアプリを実行できるプラットフォームを提供してくれる。クラウド上でアプリを立ち上げておけるので、任意のタイミングでマツヤ風ツイートが生成できるようになる
    - [Google Apps Script](https://www.google.com/script/start/)は、ブラウザ上で作成したプログラムをGoogleのクラウド上で実行するプラットフォームを提供してくれる。PCを立ち上げなくてもプログラムを定期的に実行してくれる機能などもある

### 🏗️Cloud Runでアプリをデプロイ
- デプロイ後に所定のURLにアクセスすると、手順２で実装したアプリが走り、以下のようにテキストが返却される

![スクリーンショット 2024-03-24 16.10.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/9947bee0-34cf-c3d8-d7fe-b231379d9a76.png)

<details><summary>Cloud Runでアプリをデプロイする詳しい手順。この記事の末尾にあるCloud Runの参考リンクを見れば大体分かるので、読み飛ばしてOK🚩</summary>

- まずは[このページ](https://cloud.google.com/run?hl=ja)からcloud runのアカウントを作る。それから、google-cloud-sdkを[こちら](https://cloud.google.com/sdk/docs/install)からダウンロードし、ホームディレクトリで解凍する
    - gcloudコマンドが使えるようにするためにパスを通す。.zshrcに以下を書く
        ```bash
        source '/Users/<user_name>/google-cloud-sdk/path.zsh.inc'
        source '/Users/<user_name>/google-cloud-sdk/completion.zsh.inc'
        ```

    - ターミナルを終了すると以下のようなエラーが出たので、[こちらのページ](https://qiita.com/ayihis@github/items/88f627b2566d6341a741)に従って修正した
        ```bash
        zsh compinit: insecure directories, run compaudit for list.
        Ignore insecure directories and continue [y] or abort compinit [n]? 
        ```
 
- gcloudコマンドが使えるようになったら、[gcloud init](https://cloud.google.com/sdk/docs/initializing?hl=ja)する。アカウントの紐付けとかやると、cloud project IDが発行される
    
- Dockerfileを書く。app.pyと同じディレクトリに置く

- 以下のコマンドを実行してビルドを行う。pip installする都合上、pyproject.tomlのpythonバージョンがDockerfileと対応していないとエラーが出るので注意（[ここ](https://hub.docker.com/_/python)にないバージョンでもワンチャンいける　3.9.13とか）。

```bash
gcloud builds submit --tag gcr.io/<cloud_project_id>/matsya_temperature
```

- 以下のコマンドを実行してデプロイする。[region](https://cloud.google.com/run/docs/locations?hl=ja)を選ぶように言われるので、東京に対応するasia-northeast1を選択。

```bash
gcloud run deploy --image gcr.io/<cloud_project_id>/matsya_temperature --platform managed
```

- アクセスに認証などいるかを聞かれるので、不要の方（y）を選ぶ。するとデプロイが完了し、Cloud Runに記載されている当該サービスのURLにアクセスできるようになる
```bash
Allow unauthenticated invocations to [matsyatemperature] (y/N)?
```

- [update-env-vars](https://cloud.google.com/sdk/gcloud/reference/run/services/update)などのオプションを使えば、ローカルの環境変数（例えば、今回はOWMのAPIキー）を渡すこともできる。詳しくは[こちら](https://cloud.google.com/run/docs/configuring/environment-variables?hl=ja#command-line)を参照

```bash
gcloud run services update matsyatemperature --update-env-vars OWM_API_KEY=xxx
```
</details>

### ⏰Google Apps Script (GAS)を使い、misskeyへの投稿を定期実行するスクリプトを書く
- Google Apps Script (GAS)を使い、定期的にURLにアクセスしてテキストを取得してmisskeyに投稿するスクリプトを作成した
- スクリプトを実行すると以下のようにテキストが生成され、これがmisskeyのタイムラインに投稿される

![スクリーンショット 2024-03-24 16.42.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/ee23ef9a-dca0-5f33-6783-ddd5bde1df7c.png)


<details><summary>botを作る部分の概要。この記事の末尾にある、misskeyのbotを作る記事とほとんど内容が同じなので、読み飛ばしてOK🚩</summary>

- misskeyでbot用のアカウントを作る。適当な権限を設定（例：投稿のみ許可など）してアクセストークン（APIキー）を発行する
- GASで新しいプロジェクトを立ち上げ、以下のようにテキストを投稿するコードをJavascriptで記述する
```javascript:bot.gs
function TLPost() {
  const url = 'https://<app-url>';
  
  try {
    const response = UrlFetchApp.fetch(url);
    const receivedText = response.getContentText();
    console.log(receivedText);
    let postURL = "https://misskey.io/api/notes/create"
    var headers = {"Content-Type": "application/json",}
    var data = {
      "method": "post",
      "headers": headers,
      'payload': JSON.stringify({
        "i" : "<access-token>",
        "text": receivedText
        })
        }
        let responses = UrlFetchApp.fetch(postURL, data)
  } catch (error) {
    console.error('Error:', error);
  }
}
```

- 上記のスクリプトを定期実行するように、トリガーを設定する

![スクリーンショット 2024-03-24 16.48.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/1a32da9b-5eb8-27a4-532d-a3a53edf3e13.png)

- 時間になると、以下のようにスクリプトが走り、タイムラインに投稿される

![スクリーンショット 2024-03-24 18.00.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/f6045865-fb89-a7ab-c7da-febe4dcc3db7.png)
![スクリーンショット 2024-03-24 18.00.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/22080737-1a88-c183-cb0e-76a64c50b80f.png)



</details>


# まとめﾈ
〜こうしてこの世界にまた一つ　新たなインターネットデブリが生まれた〜
![スクリーンショット 2024-03-24 0.28.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734300/e7fb56c2-3608-2dd6-2c23-ad990c39171f.png)

# 参考文献
## OpenWeatherMap
- [OpenWeatherMap(OWM)や、Python向けAPIのPyOWMの説明](https://dev.classmethod.jp/articles/openweather_-pyowm/)

## Cloud Run
- [Cloud Runのチュートリアル動画](https://www.youtube.com/watch?v=s2TIWIzCftM)
- [Cloud Runのチュートリアル動画に沿ってやってみたレポ](https://qiita.com/tomoyuki-suzuki/items/3f0ed0eb165c5e1979db)

## misskey
- [GASを用いてmisskeyのbotを作る](https://qiita.com/nekomimi_360/items/b1e12a8dce18b3a10e98)
- [misskeyに投稿するAPI](https://misskey.io/api-doc#tag/notes/operation/notes/create)
- [mo4jiから始めるMisskey時報Botの作り方 上編](https://zenn.dev/decl/articles/be573c57ea7773)

## Google Apps Script
- [エンジニアのための Google Apps Script 入門](https://qiita.com/nimzo6689/items/99ec2d627ab01a1e3924)