※この記事におけるMastodonバージョンは v2.3.3 となります。また、コードの状態は記事執筆時のものとなります。
どうもお久しぶりです。
最近ずっと開発の方に専念していたので、ブログ側にまで手が回らずに放置していました…:( ;´꒳`;):
そんな怠慢の末路、恥ずかしい記事が残ってしまいましたので、挽回という形で新しく記事を投げようと思った次第です🙏
さて、前回の記事における 非公開トゥート ですが、今更ながら既存の公開範囲を書き換える必要性が無くなりましたので、
公開範囲を新たに追加する、という形で再製作を行いました。

名前も一新して 限定公開機能 となりました!!✌(´◉౪◉`)✌
という訳でソースはこちらです!
当初はリプの公開範囲をリプ元に合わせようという事をしていたのですが、Sidekiqの再試行に悩まされ、今の自分の知識では無理だと判断したので、結局実装を諦めました。
v2.3.3との差分をご覧に頂ければ分かるのですが、今回のファイル変更数は12件となっています。
その内ローカライズデータに3件、フロントエンド側に5件、そしてバックエンド側に4件の変更を加えています。
まだまだ駆け出しの初心者ですのでこれが大規模なのかどうか分かりませんが、実際にやってみて、比較的容易だったなと思っています。
ブログの方も未だにどういう風に書いていけばいいのか分かっておりませんが、とりあえず解説の様なものでもしていきたいと思います:( ;´꒳`;):
また、開発にあたって参考にさせて頂きました!!ありがとうございます!!🙏🙏🙏
フロントエンド側 - ローカライズ処理
ひとまず、追加したい項目の名前と表示名[表1]を決めます。そこから英語と日本語に翻訳すれば大体OKだと思います。
[表1]
ローカライズID | 日本語 | 英語 |
---|---|---|
privacy.limited.short | 限定公開 | Limited |
privacy.limited.long | 自分がフォローしているユーザーにだけ公開 | Post to followed users only |
compose_form.limited_disclaimer | このトゥートは限定公開です。非公開とは違い、自身がフォローしているユーザーにのみ公開されます。 | This post is for followed users. Only users you're following can view ones. |
これらを app/javascript/mastodon/locales 直下のローカライズデータに打ち込んでいきます。
改変後のソースはこちらをご覧下さい。
フロントエンド側 - privacy_dropdown.js
サーバー側より目に見える部分の方が実装していて気が楽になると思います。(precompileがあるのでちょっと微妙ですが…)という訳でこちらから改変していきます。
公開範囲を追加する という事なので、まず投稿UIを弄りに行きましょう。
app/javascript/mastodon の下を漁ると features/compose ディレクトリがあります。
ここが投稿時に表示される画面になっています。
試しに compose_form.js を触ってみると、 PrivacyDropdownContainer が参照されています。
そしてその中で PrivacyDropdown が参照されています。
明らかに名前からして怪しい……ですよね?(*´ω`*)
という訳で覗いてみると、 this.options へ代入している部分があります。
コードの感じからして、ここに追加すればアイテムも追加されそうです。
という訳でその部分を書き換えます。
まず、ローカライズに対応させるので最上部の defineMessages メソッドに、先ほど追加したローカライズIDを割り当てます。
ここで使うのは privacy.limited.short と privacy.limited.long の2つです。
次に this.options に項目を追加します。
既に追加されているものを参考にして記述しました。
改変後はこのような感じになりました。
フロントエンド側 - warning_container.js
とりあえずここでprecompileして再起動してみると、公開範囲のドロップダウンに項目が追加されている事が分かります。しかし、まだ見栄え的に足りない様な気もします。
わたしの場合は、非公開のように喚起文を付け加えたいなと思い立ったので、次はそこを改変していきました。
先ほどもあった通り、投稿UIは features/compose の中にあります。
index.js を見ると ComposeFormContainer があり、更にその中を覗くと ComposeForm が、そしてその中には WarningContainer があります。
props には既に2つ定義されているようです。
1つは公開範囲が非公開の時、そしてもう1つがハッシュタグを検知した時です。
今回は限定公開の時に喚起文を出すようにしたいので、新たに props に定義を加えます。
投稿にあたって使用する visibility は"limited"なので、とりあえず needsLockWarning をコピーして書き換えて定義しましょう。
次に WarningWrapper に実際に表示する喚起文の要素を書いていきます。
ローカライズに対応させるので先と同様に、コピーしてちょちょいと書き換えちゃいましょう👍
ローカライズIDは compose_form.limited_disclaimer です。
これを参照させて、一応 defaultMessage フィールドにも代入しておきましょう。
という訳で、改変後のソースはこのようになります。
フロントエンド側 - action_bar.js 及び status_action_bar.js
ここまでは投稿時の処理に注目してきましたが、実際投稿されたトゥートはどのように表示されるのでしょうか。限定公開トゥートがブーストされてしまっては困りますし、その他にも色々あるかもしれません。
(執筆途中にまた一つ不具合を見つけてしまいました…)
という訳で、次はそこら辺を弄っていきます。
まずは限定公開のトゥートのアイコンを決めてみました。
faiconsのパッケージ内で探してみて fa-low-vision が丁度いいかなと思いましたので、今回はこれを使っていきます。
トゥートされて表示されるときに「限定公開だよ!!」と表明していなければ、どの公開範囲なのかは分かりません。
という事なのでアイコンが表示されている領域の ActionBar と StatusActionBar を覗いてみます。
ここでは基本のアイコンが fa-retweet に設定されており、公開範囲によって変更される仕組みになっているようです。
ここを改変して、"limited"の時にアイコンが fa-low-vision に変化するようにします。
また ActionBar コンポーネントのみの変更となりますが、ブーストさせないようにさせなければなりません。
何故か ActionBar と StatusActionBar では処理の方法が異なるようです。
統一しちゃえばいいのに…と思うのはわたしだけでしょうか…?
まぁその話は置いておくとして reblog_disabled の取得方法を StatusActionBar の処理に合わせ、更にtrueとfalseを反転させました。
ここまでのソースはこことここを参照して下さい。
フロントエンド側 - reducers/compose.js
これがフロントエンド側で最後の変更となります。新しく公開範囲を追加した為か、返信時に公開範囲がリプ元のものに合わさってくれない、という問題が発生しました。
という事なので、返信時のReducer側の処理を見てみます。
なんやかんやあって compose.js に辿り着きました。
COMPOSE_REPLY の受信時に privacy を設定しているのですが、そこで参照されている privacyPreference では"limited"の設定がされていませんので、新しく条件分岐を作ります。
これでちゃんとリプ元の公開範囲に合わさるようになりました。
ここまでのソースはこちらです。
バックエンド側
フロントエンド側の実装は完了したので、次は実際に投稿したり配信する処理を見ていきましょう。APIは全て app/controller の下にあるらしく、漁ってみると statuses_controller.rb がありました。
この中では PostStatusService が呼ばれていますので、次は定義を探しに行きます。
PostStatusService → DistributionWorker → FanOutOnWriteService という風に配信処理部分まで辿り着くことが出来ました。
ここからが今回の最大の敵だと思います。Ruby超初心者にはかなりキツかったです:( ;´꒳`;):
ではここから更に細分化していきましょう。
バックエンド側 - models/status.rb
まず、Rubyの方でも公開範囲を追加します。Status モデルでおもむろに visibility の定義が行われています。
今回使っているのは"limited"です。追加しましょう。しましょう。(大事なことなので2度言いました)
ここからは完全にわたしの勘による改変になります。というか言ってしまうとほぼ全て勘ですが( '-' )
最初に permitted_for メソッドの改変を行いました。
改変してみて分かった事ですが、ここはプロフィール画面での配信に関わっているようです。
followed_by? メソッド(後述)が通る時にのみ limited をpushしています。
これで、フォローされていればプロフィール画面からトゥートが見える様になりました。
次に as_home_timeline メソッドの改変です。
ここは…やってみたのですが正直どこが変わったのか分かってません()
個人的にトゥート数とかの算出に関わりそうだな、って感じのレベルですね…
まぁここは改変なしでも大丈夫な気がします…:( ;´꒳`;):
ここまでのソースはこちらです。多分参考にならなそうな気がしますが(;^ω^)
バックエンド側 - models/concerns/account_interactions.rb
限定公開は 自分がフォローしているユーザー に対して配信します。実装にあたって、自分のフォローしているユーザーのリストが必要になります。
Account モデルには following? メソッドは既に定義されているのですが、今回取得したい followed_by? メソッドは見当たりません。
「見つからない…どうしよう…」となるぐらいなら実装してしまおう!という事で実装しました(白目)
Account モデルが参照している account_interactions.rb に following? の定義があります。
わたしの場合ですが、その下に followed_by? メソッドを新しく定義しました。
最初は where メソッドで target_account を指定していたのですが、上手く行かなかったので account にしてみたところ動作しました。
つまり偶然と勘です。勘って大事だね(爆)
ここまでのソースコードはこちらになります。
バックエンド側 - services/fan_out_on_write_service.rb
call メソッド部分でDMの時にのみ deliver_to_mentioned_followers メソッドが実行されています。ここで条件分岐して配信を分けているのだろうと思われます。
限定公開の時も処理が変わるように、ここに条件分岐を追加して deliver_to_followed_users メソッドを実行させます。
さて、では本命の deliver_to_followed_users メソッドを見ていきます。
ここで一旦配信条件を整理します。
自分のフォロワーであること 且つ 自分がフォローしていること が条件です。こうやって文字にすると結構良かったりします。
バラバラになって分からなくなった時に是非やってみると纏まるかと思いますよ(*´ω`*)
はい、という訳で条件の整理が出来ましたので、この条件で配信されるようにコードを実装していきます。
ここまでのコードはこちらです。
バックエンド側 - workers/activitypub/distribution_worker.rb
ここが最後の改変になります。限定公開をサポートしていない外部インスタンスには、有無を言わせず流れていってしまいます。
これはかなり困ります。限定の意味が全く無くなってしまいます。
という事なので最終手段で、そもそも 外部に流さない ようにしましょう。
そこで外部への配信処理を見ていきます。
先ほど辿った PostStatusService にて、複数の DistributionWorker が呼ばれています。
これらの一つ、ActivityPubのDistributionWorkerを確認してみます。
workers/activitypub/distribution_worker.rb は数十行程度のかなり短いコードです。
その中の skip_distribution? メソッドで、外部に流すか流さないか判断している模様です。
限定公開トゥート自体を流さない様にするので、条件を追加しましょう。
ここのソースはこちらになります。
実装してみて考察とかなんやら。
まずRubyやRailsなど、見たことも触った事もない所からここまで来るのに相当の時間を要してしまった気がします。基本的な所の理解が未だに出来ていなかったりしましたが、結構楽しかったです。
ただ、今までろくにブログを書いたことがなかったのでどうやって書いていいか迷いました:( ;´꒳`;):
この限定公開の実装にあたって、わたしを突き動かしてくれた一言がありました。
この場をお借りして感謝申し上げます。本当にありがとう🙏🙏🙏
これからも楽しく色々実装していけたらな、そして実装を通じて色んな知識を蓄えていけたらな、と思います。
長々と書き連ねてしまって本当にごめんなさい。ここまで見て頂いた方々、本当にありがとうございます。
そして前回の記事に関して、不十分な状態で広まってしまったことをお詫び申し上げます。
かなりの未熟者ですが、これからも見守ってて下さいませ。
ではまた(*・ω・)*_ _)