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


初めに

ハローワールド。

前回の記事でショートカットアプリを使ってマストドンに呟いてみました。
今回は更にショートカットアプリの可能性を感じて頂けたらと、読み上げクライアントを作ってみました。

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

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

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

マストドン読み上げショートカット
リバース

記事内の書き方のルール

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

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

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

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

まずは、前回の記事のアクションの追加のように、Web→URL→URLアクション、Web→URL→URLの内容を取得アクションを追加します。

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

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

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

社畜丼のローカルタイムラインのAPIのURLにアクセスしてみると、json形式の文字列がバーっと現れます。ちなみにローカルタイムラインを取得する場合は上の表のURLに?local=trueという文字列を書き加えればOKです(これをクエリストリング/クエリパラメータ/クエリ文字列と呼びます)。

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

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

それぞれで繰り返すアクションの中では、URLから取得してきたリストの中の値一つ一つを評価します。その中の値というのは、実は前回の記事の呟いた内容を喋らせてみようで扱ったデータと同じになります。
つまり、喋らせるだけなら前回と同じでいいというわけです。

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

項目名 内容
取得
キー content

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

一旦実行して見ると、どうでしょう、なんとなく取得できてますよね。

htmlタグを削除

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

ここで使えるのが正規表現。正規表現の使い方は詳しくは説明しませんが、これを使えば簡単にタグを削除することができます。

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

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

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

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

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

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

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

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

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

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

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

タイムライン読み上げ

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

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

リストを逆転させる

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

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

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

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

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

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

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

  1. ショートカットの入力変数をリスト化し、項目数を数える
  2. 1.でカウントした項目数を変数indexに設定する
  3. 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件を一旦削除して、新規項目を追加してください。
テキストのところをタップしてみるとキーボードが開きますが、その上に変数と書かれたバーがあります。
そこに変数ショートカットの入力がありますので、それをタップしてください。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

すなわち、Rubyで言うなら

index -= 1

ですね。

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

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

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

 テスト

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

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

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

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

まとめ

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

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

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

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

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

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

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

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

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

以上です。