Misskeyクライアント「Miria」を作りました
この記事はMisskey Advent Calendar 2023 16日めの記事です。また、この記事とは別にMisskeyの仕様面や、Flutterでの実装についての記事を書きました。こちらについてもあわせてどうぞ。
Miriaについて
この記事を読む方には、Miriaを知らないよという方や、そもそもMisskeyを知らないよという方や、分散型SNSを知らないという方もいるかもしれないので、先に説明しようと思います。釈迦に説法という方は飛ばしてもらってだいじょうぶです。
まず分散型SNSというのは、「ソフトウェアとして公開されていて誰でも自分でそのSNSを運営できる」「それらのSNSはなにかしらのプロトコルをもって、相互に通信することができる」という特徴を持ったSNSです。
たとえばTwitterはかつてTwitterが運営していましたし、InstagramはMetaが運営していて、誰かが自分もTwitter(と同じもの)を運営する、といったことはできません。しかし、分散型のSNSであれば、自分でそのSNSをサーバーにインストールして構築すれば、自分でSNSを運営することができるというものです。
基本的にデータベースや画像ストレージ、SNSのソフトウェアのバッグエンド(サーバー側のソフトウェア)とブラウザでアクセスしてきたときのフロントエンドがセットで構築するようになっています。
このプロトコルには代表的なものにActivity Pub (仕様を策定しているのはHTMLの仕様も策定しているあのW3Cです)、AT Protocolなどが存在します。
このうちActivityPubプロトコルに対応した分散型SNSが、Misskeyです。このほかにMastodon(fedibird, kmyblueなどを含む) Misskeyをフォークしたもの(firefish, Sharkeyなど), そのほかにはPixelFed, Akkoma, Preloma, なかにはWordpressプラグインといったたくさんのソフトウェアやそのソフトウェアを改造(フォーク)したソフトウェアが存在するわけですが、Misskeyはそのうちのひとつということです。
難しいことを書きましたが、実際のところ分散型のSNSというのは、特にMisskeyの文化圏について言うのであれば、大昔の個人サイトにCGIの掲示板を立てるアレの延長線上と捉えてもらってもかまいません。そして昔と違うのはいろんな人が立てた同じ掲示板同士がなんかやり取りをして相互に通信しあっているということです。
もうちょっとMisskeyについて掘り下げていくと、Misskeyはsyuilo氏が開発されている(現在進行系)分散型SNSで、上記のほかのActivityPub対応の分散型ソフトウェアと比較すると多機能で、あまり強い連合志向を持っていないという特徴があります。また、Misskey自体が日本発ということもあって、Misskey全体のコミュニティも東アジア圏が根強く、日本語圏が特に圧倒的、続いて韓国語圏、簡体字・繁体字圏のユーザーが多いようです。
さてそんな分散型SNSですが、サードパーティクライアントというものがいくつか作られています。古い言い方をすれば「専ブラ(専用ブラウザ)」です。
マストドンやThreadsはこうしたサードパーティクライアントが活発に開発されているようで、特にマストドンは公式のネイティブアプリが貧弱という事情もあって多くの人がサードパーティクライアントを使用しているようです。
Misskeyは後述する理由でサードパーティクライアントは少なく、またそもそもMisskey Web(つまり標準の、ブラウザからアクセスしたときのクライアント)が多機能で優秀、それをPWAアプリとして使用できるので、あまり活発でもありませんでした。
そうしたなか、あえてサードパーティクライアントとしてつくりはじめて、今こうして存在しているのがMiriaです。
技術的な要素
MiriaはAGPLでライセンスされたオープンソースプロジェクトなので、ソースコードを公開しています。もうすぐ100 Starsを突破しそうです。突破しました!やったー!
簡単にMiriaのアーキテクチャなどに関して解説します。技術的な内容に興味が無いという方は飛ばしてもらってもかまいません。
まず、フレームワークとしてFlutterを採用しています。データ型などはfreezedで起こし、状態管理にはflutter_riverpodを使用しています。画面遷移にはauto_routerを使用しています(ちなみに50画面以上あるみたいです。ヤバい)
これらは比較的Flutterで作るアプリとしては一般的な構成かなと思います。
また、MFMのパーサと描画ウィジェットと、MisskeyのAPI呼び出しのラッパーについてはレポジトリを分け、それ単体で使えるようにしています。
misskey_dart DartによるAPIラッパー。misskeyという名前のパッケージがpub.devに存在していますが、中身が全く無くメンテナンスもされていなかったため、自分でAPIラッパーを作成する必要がありました。
mfm_parser MFMのパーサ実装。
mfm 実際にFlutterでMFMを使用したリッチテキストを配置するためのウィジェット。
デプロイとCI/CD(ユニットテスト)にはGitHub Actionを使用し、AndroidとWindows版のリリース用バイナリの作成と、iOSはApp Store Connectへのビルドのアップロードまでを自動化しています。iOSのApp Store Connectへのビルドアップロードはfastlaneを使用しています。
Miria自体のユニットテストにはflutter testで実行できる一般的なウィジェットテストを作成しており、codecovでカバレッジの変動等を管理しています。ただその規模に対してテストコードが追いついていない状態です(記事執筆時点で、12,000行あるトラック済コードのうち、通っているぶんでも4,600行程度)。またMiria自体の統合テストは作成していません。
misskey_dartに関しては、実際にMisseyをGitHub Actions上で起動してそこに対してリクエストをするテストを簡易的ではありますが作成しています。
Miriaのソースコードの規模を簡単に計測してみました。
SLOCSを今どき使うのかはわかりませんが、コメントや空白行を除いたソースコード行数は55,000行程度です。このうちの大部分は4月から8月上旬までの4ヶ月程度で書いたものだったと思います。
作ったきっかけ
作ったきっかけですが、経緯から書いてみます。
これまでびよーんったー、Lagopus、Hel2um on iOSといったTwitterのサードパーティクライアントを使ってTwitterを使い続けていました。そのなかでもとりわけ、いま見ているTLに直接シームレスにツイートできるびよーんったーやLagopusを特に愛用していていました。
しかしながら、これらのクライアントアプリは締め出されてしまい、それがきっかけでMisskey.ioに本格的に移住することになります。
Misskey WebではTLは自動更新されるし、デッキ表示であれば今見ているTLに直接ノートをすることもできます。ただやはり上記のTwitterサードパーティクライアントに慣れているので、あの操作感覚のアプリが欲しいという気持ちがありました。要するに手に馴染んだあの感覚が欲しいという気持ちです。
また、Misskey Webは十二分に強力なWebクライアントですが、必ずしもすべてのケースで使いやすいというわけではありませんでした。PCからのアクセスが基本に据え置かれており、スマートフォン(特にiOS)で使うと以下のような現象に悩まされることがあります。
デッキ表示がスマートフォンではとても使いにくい
なんかキーボードが出ないときがある、これがけっこう頻発してストレスになる(SafariのPWAの場合)
めちゃくちゃ通信量とバッテリーを食う
※現在ではデータセーバー機能がついていたり、UIのぼかしをオフにすることである程度の改善はしていますが、Miriaを含め他のネイティブのサードパーティクライアントと比べると消費は激しいようです。
通信環境が遅い環境では、読み込みにめちゃくちゃ時間がかかる
ドコモが都心部では非常に回線速度が遅く、特に作り始めた頃はイオンモバイルのMVNOを使っていたためさらに遅かったです。このような環境下ではMisskey.ioのチャンネルを開くところまで行き着くのに十数分かかることもまれではありませんでした。
SafariだとMFMが正しく動かないものもある
そもそもMisskeyについては後述する理由でサードパーティクライアントが少ないのですが、iOS向けはその中でもとりわけ少ないです(いまも)。
私自身は下記に述べるようなことが少なくともできてほしいという気持ちがありますが、こうしたサードパーティクライアントは作り始めた当時ありませんでした。
通信量やバッテリーの持ちなどの課題から、WebViewを使っていないこと
ノートやリアクションができ、ノートをするのにびよーんったーやLagopusのような簡易投稿欄があること
TLが自動更新されること、Misskey.ioのLTLの流速についていけること
チャンネルへの行き来がしやすいこと(そもそもチャンネルの機能がついていること)
不等幅のカスタム絵文字を不等幅で表示できること、リアクションピッカーでも不等幅で表示されること
このようなアプリがほしいと思い、作り始めたのがMiriaでした。
難しかったところ
MFM対応
MisskeyにはMFM (Makrup language For Misskey)というものがあり、これはマークアップ言語の一種ですが、文字装飾をさせたりするほか、アニメーションをつけたりといろんなことができます。
このMFMの実装をすることがむずかしいということは想像に難くないですが、最終的にMiriaはMisskeyの標準的なすべてのMFM構文を実装することができました、
これが難しいといわれる所以はいくつかありますが、
MFMのパースが難しい
普通のマークダウンを拡張したような仕様になっているので、パーサを自作する必要があります。Miriaの場合、mfm.jsをTypeScriptからDartに移植するという手段でなんとかしました。
アニメーションや変形処理が難しい
文字の色を変えるとか、中央揃えにするといった基本的なマークダウンの機能であればネイティブの一般的なフレームワークに乗っかる形でも可能ですが、Misskeyの場合「絵文字や文章の反転、回転、相対的な位置の変更、拡縮」「中の要素にぼかしをかける」「アニメーションさせる」「検索ボックスが出てくる」といったこともできるようになっています。
Androidを含め多くのネイティブのサードパーティクライアントではこれらの機能の多くが実装されていないか、簡易的な部分の実装のみにとどまります。また、ネイティブではないWebViewに頼っているものでも、マストドンと併用することを想定されたようなクライアントではほとんど実装されていません。単純に実装のコストが非常に高いうえ、さまざまな制約などによりそもそも実装ができない場合も多いようです。
今回の場合Flutterをフレームワークとして使用したわけですが、FlutterのリッチテキストにはWidgetSpanというものがあり、要するにリッチテキストの中になんでも入れれるようなスパンが存在します。このWidgetSpanを最大限活用することで、アニメーションや反転といった複雑なMFMを実装することができました。
むしろMiriaはWebViewやSafariに頼っていないからこそ、ルビを均等割付したり、$[spin.left]を使った等速直線運動などをどの環境でも同じように表現できるという、いかんともしがたい結果を得ることができました。
Miskeyの仕様変更追随や他のソフトウェアへの対応
Misskeyはそもそもがかなり大規模なソフトウェアであるうえに、とても活発に更新されています。そして容赦なく破壊的変更が加わります。
こうした面であまりサードパーティクライアントに優しくはなく、megalodonやSubway Tooterなど一時期にMisskeyのサポートをしていたものの、現在Misskeyへのサポートをやめたサードパーティクライアントはこれまでにも少なくありません。
実際Miriaもリリース後以降これに振り回されてきましたし、なかにはMisskeyに追従して実装した機能があとからMisskeyが撤回して削除されたことさえありました(ノートの編集機能。私自身もノート編集機能は懐疑的でしたが、それでもさすがにかなしい)。
開発初期からそういった雰囲気は感じ取っていたので、MiriaはFediverseの総合的なアプリにはしない(基本的にMisskeyの最新版にだけ追従し、Misskey以外の他のActivityPubプロトコルに対応したソフトウェアには対応しない)という方針を取ることにしました。要はMisskey以外には対応するつもりはないということです。
単純にMisskeyに対して莫大なコストを払うつもりで作っているのに、それ以外のソフトウェアのためにもコストを払うことはできないこと。
同じActivityPubプロトコルを使用しているとはいえ、そもそもソフトウェアが違うということは仕様や設計思想も異なるため、それらを同一に扱おうとすると必ず綻びや矛盾が生じざるを得ないであろうということ。
こうした点からMiriaはMisskey専属のアプリとし、Misskeyでの使い勝手を担保することができました。
アニメーション画像の対応
現状でもMiriaはSafariやChromeと比べてアニメーション絵文字の動く速度が異なったりしますが、開発当初はそもそも動かなかったものが多かったです(Misskey.ioの:ai_yay:や:ai_yay_superfast:は止まっていました)。アニメーションPNGがサポートされていなかったためです。
これはもうFlutterのフレームワークに左右される点で、どうしようもなかったのです。Flutter 3.10のアップデート(今年の5月に来たもの)でAPNGがサポートされ、動くようになりました。もしFlutterがAPNGのサポートをしないままだったら、Miriaは今よりずっと使いにくいアプリであったことは間違いないので、この対応は本当にありがたかったです。
作ってみてどうだったか
Miriaのアプリのニーズは想像していたよりはるかにあった
Miriaを正式リリースして以来、次第にMiriaが持つポテンシャルやMiriaが満たすニーズは私が想像してたよりずっと大きかったということがわかりました。
Miriaの需要は私が想像していた100倍はあったようです。(この100倍という数字は掛け値なしに、いまの累計ダウンロード数と当初想定していた数字との比較です。せいぜい多くても数百くらいだろうと想像していました。)
ストアの評価もApp Store, Play Storeともに他のクライアントと比べても高い評価をいただけているようで、とてもうれしいです。
Miriaが評価される点はいくつかありますが、しっかりとMisskeyのよく使う機能が実装されていること、そしてMisskeyの更新に対してスピーディーに追従している点かと思います。
これらを実現するには、MisskeyにいてMisskeyそのものや、Misskeyの使われ方をよく観察していないとできないのではないかなと思います。
また、次第にiOSでは他のサードパーティクライアントがアップデートされない状況が続いたりしているため、これらのクライアントがMisskeyの仕様変更に耐えることができなくなってしまっていて、こうした部分もMiriaがかなり吸収するようになっていったことも大きいようです。
(この記事を書いている現時点で、MissRiricaはPreview版こそたまに動いていますがProductionの最終リリースは2月22日から変わっておらず、MissCatの最終コミットも2月23日のままです。Kimisの最終リリースは比較的最近で11月16日です。)
Misskeyのクライアントアプリでおすすめされやすくなった
そうして更新を日々続けていると、次第に何かMisskeyを見るためのおすすめのアプリある?という文脈でMiriaが話題によく上がるようになりました。
私がアカウントを持ってないサーバーにもMiriaのアプリアイコンがカスタム絵文字に入っていることも少なくなく(たとえばふろすきー、お花すきー、かせいすきー、TKNGH、りんごぱい、みおきー、Misskey.lapy など)、こうしたサーバーでも日々使われていたり、たまに話題にのぼっていることを実感します。
果てはMiriaが対応していないfedibird.comにすらMiriaのアプリアイコンがカスタム絵文字として登録されるようになりました。FedibirdはMisskeyではなくマストドンのフォークで、当然Miriaは非対応なのですが、絵文字リアクションなどMisskeyの機能を持ったどちらかといえばマストドンというよりFedibirdというソフトウェアを使っているサーバーです。こうしたこともありご近所の文化圏ではあるのですが、とはいえMisskeyではないので、Misskeyから少し離れた場所でも話題になっていることに驚いたりします。
Misskey自体にも影響を与えていたらしい
これは最近になって気づいたことですが、MisskeyそのものもMiriaの使われ方や実装から影響を受けていそうということです。
ノート本文中の絵文字タップでリアクション
プロフィールのノート欄からRenoteを省く
タイムラインからRenoteを省く
通知欄のリアクショングルーピング
リアクションピッカー欄で長いカスタム絵文字を見切れさせない
これらの機能は、Miriaではもとから実装されていたものが、あとからMisskeyも近い仕様が追加されたり、実装されたものです。
偶然のものもあるかもしれませんが、直接的にせよ間接的にせよ影響を与えていたのかもしれません。
Misskeyがより便利になっていく過程にMiriaがいたなら、それはもちろんうれしいのですが、それ以上に私自身がMiriaがなんか遠くに行ってしまったような心持ちさえするときもあります。
アプリのアイコンかわいいって言ってもらえる
アプリのアイコンかわいい、アプリのアイコンかわいいから使っているというノートをエゴサでよく見かけます。このアイコンも自分で描いたもので要するにうちの子のようなものです。
とはいえ数千台のスマートフォンのホーム画面とかに私の描いた絵が表示されているっておかしいでしょ。なんでやねん。そんなことあるかいな。死ぬわ。いや死にはせんけど。
たまにそれが怖いって思うときもあるんですが、基本的にはうれしいです。
Flutterというフレームワークの強さを知れた
ちょっと技術よりな話になりますが、MiriaはFlutterというフレームワークを使用して開発しています。お仕事で2年ほど向き合っていて、今回Miriaでも採用したわけですが、ここまでMisskeyの複雑な(特にMFM周り)要求仕様をそれなりのところまで持ってこれるものだとは私自身も思っていませんでした。(というかSwiftやKotlinで直接実装していたら、パーサまではなんとかできても描画はたぶん私も実現できなかったと思います)
さらにこのFlutterというフレームワークを採用することで、iOSでもAndroidでもほぼ同じように動かすことができました。さらにWindowsやLinuxでも使用されるようにもなりました。
そしてWebViewで同じように作った場合と比べ、そこはネイティブで動くところの良さなのか、バッテリー消費が少なく済んでいます。画像などのキャッシュ戦略などもこちらで実装できるので、PWAより強いキャッシュを効かせることができ、最終的に通信量を削減することもできました。
自分自身の実装の強さを知れた
別にAtCoderのレートが高いわけでもない(むしろアルゴリズムや数学は苦手分野なのでAtCoderを少しでも触ったことのある人のなかでは低い方に位置する)し、Flutterは実務でたしかに触りましたがめちゃくちゃできるというわけでもないです。(上には上がいるということです)
でもMFMをWebViewを使わずに実装しきったり、これだけの規模のアプリを短い期間で仕上げることができたというのは、それ自体が私の実装の強さを示しているんじゃないかなあみたいなことでちょっと誇ってもいいかななんて思ったりします。
MFMに関してはほかのネイティブクライアントはもちろん、同じFlutterで実装されたクライアント(Kaitekiなど)ですら積まれている課題です。サードパーティクライアントでもできるということを示すことができたのは大きいのかもしれないですね。
あの頃のTwitterの感覚を取り戻せた
……いろいろ言ったものの、あの頃のTwitterクライアントを自分なりに咀嚼してMisskeyクライアントという形にできたことが、自分として一番作ってよかったかなと思います。そういう意味では、Miriaは亡くなったTwitterのサードパーティクライアントに囚われ続けています。
まだまだであるということ
わりと自分が何が苦手で何ができる人なのか、Miriaの開発を通して考えることになりました。
まず、そもそもオープンソースで開発することが初めてでした。自サイトになにか置くみたいなことはたしかにこれまでもやっていましたし、ちゃんとしたアプリの開発も業務でそれなりにやってきたので、何もかもというわけではありませんが、オープンソースとしては初めてのものです。なにかほかのオープンソースプロジェクトに参加したことも当然ありませんでした。
オープンソースであるとはいうものの、当初コントリビューターにプルリクを作成してもらうといったことは想像していませんでしたし、自分のアプリがそういったプロジェクトになるということはもっと想像していませんでした。
そしてコントリビューターも複数人いて、これまでにたくさんのプルリクエストを受理することになりました。本当にありがとうございます。この場を借りてお礼申し上げたいと思います。
Miskey自体にもコントリビュートするようになった
さらに最近は、Misskey自体にも私がプルリクエストを作成するようになりました。いずれの修正もMisskey Webにはあまり関係がない修正で、修正量も少ない軽微なものです。
自分の情報に含まれる未読のお知らせについて、お知らせの情報がスキーマの定義と合っておらず作成日が含まれていないのを修正 (#12092)
未読のお知らせの作成日はMisskey WebでもMiriaでも厳密には使っていないのですが、型が合っていないと型安全な言語ではパースに失敗してしまうためです。(TypeScriptはJSONからパースしたオブジェクトに対して型安全ではないので、パースに失敗しません)
ユーザーのノートの取得において、チャンネルのノートを含むオプションと画像のみのオプションを同時につけたとき、画像がないチャンネルのノートが含まれるのを修正(#12550)
現状のMisskey Webでは画像のみを選んだときにチャンネルに上げた画像が含まれませんし、そうした機能もないのですが、Miriaとしてはチャンネルに上げた画像も「画像のみ」に含めるようにしたかったためです。(なお、Misskey.ioはフォークで改造されているので異なる動きをしていました)
動機はMiriaとしてはこういう動きをさせたいがMisskeyの挙動が微妙…というところですが、こうして気づいたところをMiriaでなんとかするのではなく、Misskey自体を自分から修正していくことで、Miria以外のサードパーティもいずれ恩恵を受けられるのではないかと思っています。
おそらくMiriaを作っていなかったら、Misskeyにプルリクエストを作成するといったこともしていなかったと思います。その点でもMiriaはオープンソースというものに対してのわたし自身の向き合い方を大きく変えたように思います。
よく悩むこと
Miriaは誰のためのアプリなのか?
最近、よくMiriaは誰のためのアプリなのか考えることがあります。Miriaはもともと、自分が快適に低速な回線でもなんとかチャンネルとかを見たいといった需要のために作ったアプリでした。
しかしいまや、MiriaはMisskeyのサードパーティクライアントとして広く使われるようになっていて、またMiriaが取り巻く環境も作った当時と比べて大きく変わっています。
前述の通りiOS向けサードパーティクライアントはほぼMiria一択のような状態になってしまっていますし、Misskey 2023.11.0以降ではiOS16.4未満、iPod touchをはじめとするiOS 15からアップデートできない環境ではSafariからアクセスできなくなってしまいました。そこでまともに動作するMiriaをなんとかして使おうとするユーザーも観測下だけでも少なくないようになっていきました。
こうした環境で、Miriaはどうしていくべきなのか、いったい誰のためのアプリなのか、それはもともと思ってたところからずいぶんと離れてしまい……。私自身もわりとよく自問自答を繰り返すことがあります。
これからやりたいこと
Miriaはわりと(PWAやほかのサードパーティクライアントと比べて)軽量なアプリだと評されているので、そこは壊さないようにしたいです。もともと通信量やバッテリー消費を抑えながらMisskeyをしたいというのが動機のひとつでもあったわけです。
そのうえで、もっと便利な機能も増やしていきたいわけですが、その部分の駆け引きといいますか、難しいですね。
今後やりたいなあと思っている機能はこんなかんじです。
リアクションミュート
多言語対応
プロフィールの編集
ログインはできなくても、v12系やFirefishのサーバー情報詳細くらいは取得できるようにしてもいいかも
そして今後も続けないといけないのが、
Misskeyの最新版への追従
です。
最後に
正直全般的な感想を述べるとしたら、「こんなことになるとはまったく想定していなかった」というところです。
こういうのが作れたらいいなくらいの、はじめは比較的規模も小さいくらいのところから開発を始めたMiriaでした。それがいまやMisskeyのクライアントとして大きく広まるようになり、根強い人気を誇るようになりました。
ただ、ちょっと背負う荷が大きすぎるかな…と思うことも多いです。iOS 15.4以前の環境でMiriaが生命線のように扱われるのは私も想定していませんでしたし、そもそもiOS 15.4以前の端末だけではログインやAPIキーの取得もできないので、アカウント情報が何かしら消えたらログインできなくなってしまいます…
私としてはMiria以外のネイティブクライアントももっと増えてほしいと思っています(昔のTwitterのように、Misskeyでもサードパーティクライアントがたくさんある中から選べるようになったらいいなあとさえ思っています)。公式のネイティブクライアント開発(これはMisskeyなのかMisskey.ioなのかよくわかりませんが…)もとても期待しています。公式があれば背負う荷も軽くなりますし、もっとMiriaをMiriaらしくすることができるのではないかと思います。