Windowsでスリープ前後にQiitadonへ「にゃ～ん🐈」とトゥートする

# 追悼 Qiitadon
⇒ [Misskey版](#windows%E3%81%A7%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E5%89%8D%E5%BE%8C%E3%81%ABmisskeydev%E3%81%B8%E3%81%AB%E3%82%83%E3%82%93%E3%81%A8%E3%83%8E%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B)

## できること
- QiitaのMastodonインスタンス「Qiitadon (β)」へ、Windows PCのスリープ前後に定型文を投稿(Toot)します。
- 「Windowsで」、「スリープの前後に」がポイントです。

#### 環境
- 初出時
  - Windows 10 Pro (20H2)
  - curl 7.55.1 (Windows) libcurl/7.55.1 WinSSL
- 最新
  - Windows 11 Pro (22H2)
  - curl 8.4.0 (Windows) libcurl/8.4.0 Schannel WinIDN

## 基本方針
- スリープ前
    - これまでは、単純なスリープを実現するショートカットファイルに、キー(`Ctrl + Shift + Alt + Down`)を割り当てて使っていました。
    - スクリプトを追加して、Tootしてからスリープするようにします。
- スリープ後
    - タスクスケジューラを使って、スリープ解除をトリガーにTootするようにします。

## curlでTootする
以下のようなシェルスクリプトで実現しました。

```bat:qiitadon_nyahn.bat
curl -X POST -sS https://qiitadon.com/api/v1/statuses --header "Authorization: Bearer 《アクセストークン》" -d "status=%%e3%%81%%ab%%e3%%82%%83%%ef%%bd%%9e%%e3%%82%%93%%f0%%9f%%90%%88"
```

- 以下の記事を参考にさせていただきました。どうもありがとうございました。
    - [Mastodon API から「未収載」でトゥートするサンプル（bash と PHP）](https://qiita.com/KEINOS/items/27d201afed4ed665cb11) (Qiita)
- Tootするメッセージは、URLエンコードが必要です。
    - バッチファイルでは、`%`が特殊文字として扱われるので、エスケープするために`%%`と書く必要があります。
- 「未収載」を指定する際は、` -d "visibility=unlisted"`を追加します。
    - バッチファイルでは、シングルクォートだと意図した通りになりません。
- `《アクセストークン》`は、Qiitadonの[`設定`>`開発`画面](https://qiitadon.com/settings/applications)で、`新規アプリ`ボタンを押すと生成できます。
    - 後で分かるように適当な`アプリの名前`を付けて、アクセス権を`write`だけにすれば、他はそのままでOKです。
    - 万が一漏洩した際は、「アクセストークンの再生成」を行って差し替えてください。

## スリープ前にTootする
以下のようなシェルスクリプトで実現しました。

```bat:Nyahn&SleepComputer.bat
start /wait /min cmd /C "qiitadon_nyahn.bat"
timeout /NOBREAK /T 10
powershell -WindowStyle Hidden -Command "Add-Type -Assembly System.Windows.Forms;[System.Windows.Forms.Application]::SetSuspendState('Suspend', $false, $false);"
```

- Tootした後、通信が完了するのを待つために、適当な時間をおいています。
    - 時間をおかないとTootされませんでした。
    - 必要な待ち時間は、PCや回線品質によって異なると思います。
- 最後の行は、私のPC向けのスリープですので、PCに合わせて適宜差し替えてください。
    - powershellを使わなくても、`rundll32.exe powrprof.dll,SetSuspendState 0,1,0`とかで可能です。
- 最初は、(後述の「復帰後」のように)イベントで実現しようと試みたのですが、処理中にスリープしてしまうため、スリープ復帰後にTootされてしまいました。
    - 試行で使用したトリガーは、「ログ: システム、ソース: Kernel-Power、イベントID: 42」です。

## スリープ復帰後にTootする
タスクスケジューラで、以下のようなタスクを作りました。

- タスク 'nyahn'
    - トリガー
        - ログ: システム
        - ソース: Power-Troubleshooter
        - イベントID: 1
    - 操作
        - プログラムの開始: qiitadon_nyahn.bat

## おまけ
- powershellを使っているのは、私のPC(BootCamp)では`rundll32`だと落ちるからです。
- 全てpowershellで書けば、もっとスマートにできそうですが、powershellの`curl`は`Invoke-WebRequest`のエイリアスで、`Invoke-WebRequest`は使い方が良く解りませんでした。

---

# Windowsでスリープ前後にmisskey.devへ「にゃ～ん🐈」とノートする
- QiitaのMastodonインスタンス「Qiitadon (β)」は失われて久しいので、(個人的な)引っ越し先のMisskeyサーバ、misskey.devで同じことができるようにしました。
- バッチ処理内容の変更のみで起動方法に変更はありません。
    - 3つのバッチファイルは同じフォルダに置かれることを想定しています。

## curlでNoteする
```bat:note.bat
echo off
chcp 65001
set TEMPJSON=%TEMP%\note.json
if exist "%TEMPJSON%" goto done
echo {"i":"《トークン》","text":%1,"visibility":"public","visibleUserIds":[],"localOnly":true}>%TEMPJSON%
curl -X POST -H "Content-Type: application/json" -d "@%TEMPJSON%" https://misskey.dev/api/notes/create
del %TEMPJSON%
:done
```

- 文字コードをUTF-8に設定します。
- 一時ファイルを使うので、環境変数にパスを設定します。
- 既に実行中(一時ファイルが既存)であれば終了します。
- 一時ファイルとして、postするjsonを作ります。
    - `《トークン》`は、[設定>API](https://misskey.dev/i/settings/api)の`Token`に置き換えてください。
    - `"visibility":"specified","visibleUserIds":["《自分のID(@なし)》"]`とすれば、自分だけに見えるように投稿してテストできます。
- 一時ファイルをapi `notes/create`へpostします。
- 一時ファイルを削除します。

### 一時ファイルが残留する可能性
- 何らかの理由でバッチ処理が中断すると、一時ファイルが消えずに残って、以降、機能しなくなる可能性があります。
- 随時、何らかの安全なタイミングで、一時ファイルをクリアすることが望ましいです。([後述](#一時ファイルを抹消する))

## スリープ復帰後にNoteする
```bat:Nyahn.bat
echo off
chcp 65001
start /wait /min cmd /C "chcp 65001&%~dp0note.bat "にゃ～ん🐈""
```
- 二重引用符の入れ子の部分を奇妙に感じるかも知れませんが、`cmd`は、`/c`の次の引数が`"`で始まると、以降の全てを対象に最初と最後の`"`を外すだけらしいです。


## スリープ前にNoteする
```bat:Nyahn&SleepComputer.bat
echo off
chcp 65001
start /wait /min cmd /C "chcp 65001&%~dp0note.bat "にゃ～ん🐈💤""
timeout /NOBREAK /T 10
powershell -WindowStyle Hidden -Command "Add-Type -Assembly System.Windows.Forms;[System.Windows.Forms.Application]::SetSuspendState('Suspend', $false, $false);"
```

### 一時ファイルを抹消する
- ここで、[前述](#一時ファイルが残留する可能性)の懸念を払拭するために、以下の1行を加えます。

```diff:Nyahn&SleepComputer.bat
~
  timeout /NOBREAK /T 10
+ del %TEMP%\note.json
```

## Windows Update で発火しないように
- イベントの起因をUSBデバイスや電源スイッチに限定します。
- タスクスケジューラのトリガーの編集で設定をカスタムにして、イベントフィルタをXMLに切り替え、手動でクエリを編集します。

```xml
<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">
      *[System[Provider[@Name="Microsoft-Windows-Power-Troubleshooter"] and (EventID='1')]]
       and
      *[EventData[(Data[@Name='WakeSourceType']='1' or Data[@Name='WakeSourceType']='5')]]
    </Select>
  </Query>
</QueryList>
```

- `Troubleshooter`と`EventID='1'`が、元のままの基本的な条件です。
- `WakeSourceType`の`1`が電源スイッチ、USBデバイスだと`5`です。
  - ネットワークアダプタなど、USB以外でも`5`の場合があるので、`(Data[@Name='WakeSourceType']='5' and Data[@Name='WakeSourceTextLength']='xx')`などとして、デバイス名を絞り込む必要があるかも知れません。