Mastodonを読む
はじめに †
前回は、クライアント(画面)でフォローボタンを押したときにサーバに処理が飛んでいくところまでを確認しました。今回は引き続き、サーバ側の処理を見ていきます。
app/controllers/api/v1/accounts_controller.rb †
フォローボタンが押されると、サーバ側の「/api/v1/accounts/:id/follow」が呼び出されます。routes.rbを見てみると他にもフォロー処理をしている雰囲気のものがありますがパス的にはAccountsControllerのfollowメソッドが呼び出されるはずです。
1
2
3
4
5
| -
|
|
|
!
| def follow
FollowService.new.call(current_user.account, @account.acct)
set_relationship
render :relationship
end
|
@accountはbefore_actionを使用して設定されています。
1
2
3
4
5
|
-
|
!
| before_action :set_account, except: [:verify_credentials, :update_credentials, :suggestions, :search]
def set_account
@account = Account.find(params[:id])
end
|
app/models/account.rb †
Accountのacctはインスタンスも含めてアカウントを識別するための情報のようです。(domainはモデルの属性)
1
2
3
4
5
6
7
| -
|
!
-
|
!
| def local?
domain.nil?
end
def acct
local? ? username : "#{username}@#{domain}"
end
|
さて、ここで気づくことがあります。上のコードを考えると、別インスタンスにいるアカウントについてもaccountsテーブルに情報が格納されているということになります。
では、どうやって別インスタンスのアカウント情報を取ってくるのか?という疑問はありますが、今はaccountsテーブルにすでに情報があるという前提で先に進みましょう。
app/services/follow_service.rb †
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -
|
|
-
|
|
-
|
|
|
|
|
-
|
|
|
!
!
| class FollowService < BaseService
include StreamEntryRenderer
def call(source_account, uri)
target_account = FollowRemoteAccountService.new.call(uri)
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
if target_account.locked?
request_follow(source_account, target_account)
else
direct_follow(source_account, target_account)
end
end
|
FollowRemoteAccountServiceはaccountsテーブルにアカウントがあればそれを返す、ない場合はWebFingerを利用して指定されたドメインから情報を取得→Account作成→返すということをしています。これはこれでおもしろいですが話が発散してしまうので今回は省略。
といいつつ一言だけ。WebFingerでは特定のURLにアクセスすることでユーザの情報を取得するようです。MastodonではGoldFingerというgemを使って、って、中の人一緒ですね(笑)
以下のURLへのアクセスが行われ、情報が返されるようです。
1
|
| get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
|
さて、話を戻して、鍵付きかどうかで処理が分かれていますが、鍵付きでないとしてdirect_followを見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -
|
|
-
|
|
|
|
|
!
|
|
|
|
!
| def direct_follow(source_account, target_account)
follow = source_account.follow!(target_account)
if target_account.local?
NotifyService.new.call(target_account, follow)
else
Pubsubhubbub::SubscribeWorker.perform_async(target_account.id) unless target_account.subscribed?
NotificationWorker.perform_async(build_follow_xml(follow), source_account.id, target_account.id)
AfterRemoteFollowWorker.perform_async(follow.id)
end
MergeWorker.perform_async(target_account.id, source_account.id)
follow
end
|
フォロー処理を行った後、ローカルアカウントなのかリモートアカウントなのかによって処理が分かれています。全部見ていくと長くなるので各処理の概要だけ挙げると以下のことをしています。
- NotifyService
- メールの送信、Redisへの書き込み(ストリーミングが有効の場合はクライアント画面に飛んでいく)
- Pubsubhubbub::SubscribeWorker
- OStatus2を使いSubscriptionのリクエスト送信。相手側は「/api/push」で受け取ってSubscriptionを作成
- NotificationWorker
- Salmon経由でフォローを通知。受け取った相手インスタンスはフォローの処理を行う(呼ばれるのは、/api/salmon/:id」)
- AfterRemoteFollowWorker
- よくわからない。時間差で相手が鍵垢にしたときのやり直し用?
- MergeWorker
- フォローしたアカウントのトゥートを自分のホームに表示されるようにする処理(Redisへの書き込み)
おわりに †
今回はフォロー時のサーバ側の処理について見てきました。リモートの場合はいろいろ端折りましたが、WebFinger、PubSubHubbub、Salmonを利用してフォロー対象のアカウントがいるインスタンスとの通信が行われていました。フォローの流れがわかったので次回はリモートフォローについて詳細に見ていきたいと思います。