iOS12のショートカットアプリを使ってマストドン読み上げクライアントを作った

## 初めに

ハローワールド。

[前回の記事](https://qiita.com/sa2taka/items/229b47957a4e47e20848)でショートカットアプリを使ってマストドンに呟いてみました。
今回は更にショートカットアプリの可能性を感じて頂けたらと、読み上げクライアントを作ってみました。

今回はマストドンのアクセストークンを使わないのでマストドンにアカウントを持っていない方でも簡単に試して頂けます。

## 忙しい方のために(読む気が起きない方のために)

icloud上にショートカットを公開できたのでリンクを貼り付けておきます(中身は、この記事で作ったものに追加されている部分があります)。
一応今回作る2つのショートカットを貼り付けておきますが、上の1つだけでも大丈夫なのかもしれません(未確認)。動くかどうかも未確認です。
また [まとめ](#まとめ)の方にも今回作ったショートカットで使ったアクションなどをまとめておきます。

[マストドン読み上げショートカット](https://www.icloud.com/shortcuts/b040d32baaa448e5906dc40625d16c52)
[リバース](https://www.icloud.com/shortcuts/d5ee891018b14e00922a99883401c861)

## 記事内の書き方のルール
前回は書き方に統合性がなかった気がするので、今回は以下のようなルールを追加しました。

- アクション名は`[アクション名]`アクションと書きます。例えばURLアクションなら`URL`アクションという風にします。
- 変数名(後に出てくる)は変数`[変数名]`とします。マジック変数(後に出てくる)も同様にマジック変数`[マジック変数名]`とします。例えば変数`index`という風にします。
- アクションがどのコンテンツの中に入っているかを矢印を使って表します。例えば`URL`アクションはWebコンテンツの中のURLコンテンツの中にあるので、Web→URL→`URL`アクションという風にします。

## マストドンからタイムラインを取得

今回もWeb初心者の方でもできるだけわかりやすく説明するため、一つづつ段階的に説明していきます。

まずは、[前回の記事のアクションの追加](https://qiita.com/sa2taka/items/229b47957a4e47e20848#%E4%BB%8A%E5%9B%9E%E4%BD%BF%E3%81%86%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E8%BF%BD%E5%8A%A0)のように、Web→URL→`URL`アクション、Web→URL→`URLの内容を取得`アクションを追加します。

今回は次の表のような値を入力します。`URLの内容を取得アクション`は前回とは違い、特に入力しなくてもいいです。

アクション名 | 項目名 | 内容
---------|-------|----
URL      | URL   | [任意のマストドンインスタンスのURL]/api/v1/timelines/public
URLの内容を取得| 特になし | 特になし

ちなみに社畜丼の場合は、URLの内容が`https://mstdn-workers.com/api/v1/timelines/public`となります。

[社畜丼のローカルタイムライン](https://mstdn-workers.com/api/v1/timelines/public?local=true)のAPIのURLにアクセスしてみると、json形式の文字列がバーっと現れます。ちなみにローカルタイムラインを取得する場合は上の表のURLに`?local=true`という文字列を書き加えればOKです(これを[クエリストリング/クエリパラメータ/クエリ文字列](https://qiita.com/Sekky0905/items/dff3d0da059d6f5bfabf#%E3%82%AF%E3%82%A8%E3%83%AA%E3%82%B9%E3%83%88%E3%83%AA%E3%83%B3%E3%82%B0)と呼びます)。

このjsonはリスト型です。このリスト型もショートカットアプリでは簡単に扱えます。

スクリプティング→制御フロー→`それぞれで繰り返す`アクションを追加します。これは入力のリストの中の値一つ一つに対して動作を行う、Rubyのeach, JavaScriptのforEach, Pythonのforin文のようなものです。

それぞれで繰り返すアクションの中では、URLから取得してきたリストの中の値一つ一つを評価します。その中の値というのは、実は[前回の記事の呟いた内容を喋らせてみよう](https://qiita.com/sa2taka/items/229b47957a4e47e20848#%E5%91%9F%E3%81%84%E3%81%9F%E5%86%85%E5%AE%B9%E3%82%92%E5%96%8B%E3%82%89%E3%81%9B%E3%82%88%E3%81%86)で扱ったデータと同じになります。
つまり、喋らせるだけなら前回と同じでいいというわけです。

というわけで、ぱぱっとスクリプティング→辞書→`辞書の値を取得`アクションを追加して、次の表に従って項目を入力します。

項目名 | 内容
-------|--------
取得   | 値
キー   | content

ここまででショートカットは次の画像の様になってると思います。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/e859b6b3-cfe5-dc4f-88a0-bfa8b5d7bf62.jpeg" width="40%">

一旦実行して見ると、どうでしょう、なんとなく取得できてますよね。
<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/b6894bb9-122f-a8b8-fca7-50f8db9860e0.jpeg" width="40%">

## htmlタグを削除

ただ、前回も言った様に、&lt;p&gt;タグなど邪魔なタグが現れてしまいます。削除したいですね。

ここで使えるのが[正規表現](https://www.mnet.ne.jp/~nakama/)。正規表現の使い方は詳しくは説明しませんが、これを使えば簡単にタグを削除することができます。

正規表現を使ってhtmlタグを削除してみましょう。
テキスト→テキスト編集→`テキストを置き換える`アクションを追加してください。
そして次の表の様に入力してください。

項目名       | 内容
------------|-----
テキストを検索      | <.*?>
置き換え           | 空欄(うっすらworldが現れている状態)
大文字/小文字を区別 | どちらでも
正規表現          | **ON**

すると、次のようにhtmlタグが消えます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/f2757b70-3f40-54db-ed4f-3cac70a7ea07.jpeg" width="40%">

#### ちょっとだけ正規表現説明

ここからはちょっとだけ使用した正規表現の解説をします。正規表現に興味がなかったり既に知ってる方は飛ばして構いません。
が、とても便利なものなので知らない人はちょっと読んでみてもいいと思います。

さて、先程のようにhtmlタグが魔法の様に消えるのは、正規表現`/<.*?>/`にマッチしたものが空文字に置き換わるからです。
`<>`はタグの囲み文字、そして`.`が任意の一文字、`*`が直前の文字の0回以上繰り返しを表します(前後の`/`は、一般的に`/`に囲まれたものが正規表現であることを表すものなのでつけています)。
簡単に言えば&lt;&gt;に囲まれているものを空文字に変換するということをやっています。

ならば`/<.*>/`(上記と比べて?がない)で良いじゃないかと考えてしまいます。

ただその正規表現だと、`<p>hoge</p>`という入力に対して`<p>hoge</p>`すべてが正規表現にマッチしてしまいます。何故かと言うと、*はあまりにも強欲なので、マッチする値が長い方を選ぶからです。

どういう意味かというと、先程の正規表現では`<p>hoge</p>`という入力に対して`.*`(任意の一文字が0回以上)が`p`と`p>hoge</p`という2通りの値になり得ます。`.*`を`p`に変換すれば`<p>`になり、`.*`を`p>hoge</p`に変換すれば`<p>hoge</p>`になりますよね。
そのうち、最も長い方を選ぶので、`.*`は後者を選びます。

その強欲な`*`君を抑制するために`?`が登場します。
`.*?`というのは一般的に最短一致と呼ばれるもので、`.*`が最も短いものを選ぶ様になります(通常はそう考えてもらっていいです)。
そのため、先程の例では`.*`が`p`を選択して、当てはまる`<p>`を空文字に変換してくれるのです。

実は最短一致という名称はふさわしくない([参考ページ](https://qiita.com/anqooqie/items/191ad215e93237c77811))のですが(僕も一度ハマったことがある)、今回は心配しなくて大丈夫です。

## タイムライン読み上げ

これで怖いものはなくなりました。恐れることはありません、音声で読み上げてみましょう。
`テキストを置き換える`アクションの下に、テキスト→`テキストを読み上げる`アクションを追加してみましょう。

美しい声でマストドンのタイムラインが読み上げられますね。

## リストを逆転させる

ただ少し待ってください。順番がおかしくないですか?

普通「古い方から新しい方」へ向かって読み上げられるべきですが、あなたの耳には間違いなく「新しい方から古い方」へ向かって読み上げられたタイムラインが入っているはずです。
これは、マストドンのタイムラインAPIの戻り値のリストが「新しい方から古い方」に並んでいるからです。

つまり、逆転させてあげれば問題がなくなるはずです。

Rubyだったら`list.reverse`で事足りるのですが、ショートカットには逆転してくれる便利なアクションはありません。
が、しかし、無いならば作れば良いのです。

実は、ショートカットには「入力」と「出力」があります。
例えば`URLの内容を取得`アクションの入力はURL、出力はそのURLの内容でした。
同様に自分で作ったショートカットにも同様に入力と出力を作ることができます。
つまり「リストを逆転する」ショートカットを作れば良いのです。さながらプログラミングの関数ですね。

#### リストを逆転するアルゴリズム

今回のショートカットはちょっと長いので、まずリストを逆転するアルゴリズムをまとめてみました。
今後どういったことを行うかが想像しやすくなると思います。
今回のアルゴリズムはショートカットアプリ用になっています。

1. `ショートカットの入力`変数をリスト化し、項目数を数える
1. 1.でカウントした項目数を変数`index`に設定する
1. 1.でカウントした項目数回以下を行う

    1. ショートカットの入力をリスト化したものの変数`index`番目の項目を変数`retVal`の先頭に追加する
    2. 変数`index`の値から1を引き、変数`index`に再代入する

4. 変数`retVal`をショートカットの戻り値とする

ちなみにRubyのコードにするならこうでしょうね。かなり正確に再現しました。

``` リバース.rb
def reverse(ショートカットの入力)
  リスト = Array(ショートカットの入力)
  数 = リスト.length
  index = 数
  retVal = [] # ショートカットではここが不要
  数.times do
    retVal.append(リスト[index - 1]) # ショートカットではここの-1が不要
    index -= 1
  end
end
```

#### 1.ショートカットの入力をリスト化し、項目数を数える

まずは、新しくショートカットを作って、わかりやすいように「リバース」とでも名前をつけてください。

そしてまずはアルゴリズムの1.を実装します。と言ってもここは難しくありません。

まずはスクリプティング→リスト→`リスト`アクションを追加してください。
そして、すでにある1件, 2件を一旦削除して、新規項目を追加してください。
テキストのところをタップしてみるとキーボードが開きますが、その上に変数と書かれたバーがあります。
そこに変数`ショートカットの入力`がありますので、それをタップしてください。

<img width="40%" alt="IMG_2093.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/138959/f2d13be1-f481-b46b-24b4-c0bee3fd8440.png">

これで、ショートカットの入力をリスト化できました。
`変数を取得`アクションはあるのに、変数`ショートカットの入力`が取得できないみたいです(もっとスマートな方法を見つけたら教えてください)。

次にリストの項目数を数えるのですが、これはスクリプティング→コンテンツ→`カウント`アクションで容易に実装できます。
`カウント`アクションを追加し、カウント項目を項目にすればこれでもう項目数は取得できました。

#### 2. 1.でカウントした項目数を変数`index`に設定する

これはただ変数に突っ込むだけです。
スクリプティング→変数→`変数を設定`アクションを追加し、変数名を`index`(または任意の名前)にしてください。これで2.は完了です。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/da967e12-0b51-92d4-75f0-6e2bb3393e66.jpeg" width="40%">

#### 3.1.でカウントした項目数回以下を行う

これはつまり、繰り返しです。項目回数繰り返せば良いのです。
スクリプティング→制御フロー→`繰り返す`アクションを追加してください。

ここで項目数回繰り返したいのですが、どうしたら良いでしょう。

繰り返しの部分をタップすると次のような選択肢が現れます。
これで変数を選択できるようになります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/79ff5bd2-f53c-4007-39c7-fa22d090d16e.jpeg" width="40%">

ここで、マジック変数を選択をタップしてください。
すると、アクションとアクションの間の先に、例えば`リスト`や`数`といった表示が増えましたよね。
これは、アクションの出力値を変数として扱うことが出来る機能です(と思っています)。

ここで、マジック変数`数`を選択してください。
これで項目数回繰り返せます。

#### 3-1.ショートカットの入力をリスト化したものの変数`index`番目の項目を変数`retVal`の先頭に追加する

噛み砕いて言えば、入力の変数`index`番目を戻り値のリストに追加するですね。

ショートカットアプリでは通常のプログラミング言語とは違い、リストのインデックスが1から始まります。仮に項目数が20個とするなら、インデックスは0〜19ではなく1〜２０となります。

そのため、先程項目数をそのままindexに入れたので、最初は一番後ろの値を出力するリストの最初の項目になるということです。

リスト変数に追加する方法ですが、スクリプティング→変数→`変数に追加`アクションで簡単に再現できます。
このアクションは変数があれば、その値の最後尾に入力を追加、なければ新しく変数を作るというものです。

まずは　ショートカットの入力をリスト化したもの　を取ってきます。
スクリプティング→変数→`変数を取得`アクションを追加します。
変数を選択するところで、マジック変数を選択し、マジック変数`リスト`を選択してください。

次にindex番目の項目をリストから取得するため、スクリプティング→リスト→`リストから項目を取得`アクションを使ってindex番目のリストを取得します。
取得を項目のインデックス、インデックスを変数`index`してください。

最後に`変数に追加`アクションで変数名をretValにしましょう。

<img width="40%" alt="IMG_C53ADF1FC510-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/138959/59e35a19-71bb-a310-cb5d-aa5aa18690b5.jpeg">


#### 3-2. 変数`index`の値から1を引き、変数`index`に再代入する

すなわち、Rubyで言うなら

```
index -= 1
```
ですね。

スクリプティング→変数→`変数を取得`アクションで変数`index`を取得し、スクリプティング→計算`計算`で-1を行い、スクリプティング→変数→`変数を設定`アクションで変数`index`に再代入すればいいだけです。
つまり、画像のようになります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/863b1c5c-fc1f-a70a-45af-f40b54ef6437.jpeg" width="40%">

#### 4.変数`retVal`をショートカットの戻り値とする

ショートカットの戻り値はショートカットの最も下の出力であると考えられるため、一番下に`変数を取得`アクションで変数`retVal`の値を出力してあげましょう。

####　テスト

長々と説明したこのリバースショートカットも、完成したはずなので、一旦こんなショートカットを作って試してみましょう。
`ショートカットを実行`アクションはスクリプティング→Shortcutsにあります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/138959/be69d2c7-404a-3b09-52b7-07c68f2314cb.jpeg" width="40%"> 

これを再生してみると、下の方に4と表示されると思います。左にスワイプすると3, 2件, 1件と続々表示されます。
反転されてますね!

## タイムラインを反転させる

リバースショートカットを作った我々は無敵なので、ただただ、`URLの内容を取得`アクションと`それぞれで繰り返す`アクションの中間に`ショートカットを実行`アクションを追加して、リバースショートカットを呼んであげましょう。

## まとめ

長々と説明してきましたが、これでタイムラインが読み上げられました。

ただ少し待ってほしい、読み上げただけじゃないか。
これでは最初に取得した20件のtootが読まれるだけで、終わったら毎回毎回タップしなきゃいけないじゃないか。

というあなたのために、完全版が[こちら](https://www.icloud.com/shortcuts/b040d32baaa448e5906dc40625d16c52)に上がっています。
これは社畜丼のローカルタイムラインの読み上げショートカットで、実行中は無限に読み上げ、一度読み上げたものはもう一度読み上げない、といった工夫がなされてます。
と言っても4, 5アクション程度を追加しただけなんですけど。

今回のアクションを次の表にまとめました。

読み上げショートカットの表

アクション名         | 項目名         | 内容
---------------------|----------------|-------------------------------------------------------
URL                  | URL            | [任意のマストドンインスタンス]/api/v1/timelines/public
URLの内容を取得      | なし           | なし
ショートカットを実行 | ショートカット | リバース
それぞれで繰り返す   | なし           | なし
辞書の値を取得       | 値             | content
テキストを置き換える | 検索           | &lt;.*?&gt;
                     | 置き換え       | (何も入力しない)
                  | 正規表現       | ON
テキストを読み上げる | なし           | なし
繰り返しの終了       | なし           | なし

リバースショートカットの表

アクション名         | 項目名       | 内容
---------------------|--------------|---------------------
リスト               | テキスト     | ショートカットの入力
カウント             | カウント     | 項目
変数を設定           | 変数         | 変数`index`
繰り返す             | 繰り返し     | マジック変数`数`
変数を取得           | 変数         | マジック変数`リスト`
リストから項目を取得 | 取得         | 項目のインデックス
                     | インデックス | 変数`index`
変数に追加           | 変数         | 変数`retVal`
変数を取得           | 変数         | index
計算                 | 処理         | -
                     | オペランド   | 1
変数を設定           | 変数         | index
繰り返しの終了       | なし         | なし
変数を取得           | 変数         | retVal

再三になりますが、ぜひショートカットの可能性を感じて色々やってみてください。

以上です。