Mastodonのタイムラインを眺めていると、インスタンスにもよるだろうが、文字ばかりで少し寂しい。Tootの詳細表示では、記事やTwitter、Instagram、YouTubeなど、リンクへのサムネイル=Cardが表示できるので、これをタイムラインにも欲しいところ。そこでコードを追ってみた。
タイムラインに手を加えるには
ログイン後の3カラムの画面、あれは全て自らのAPIで値を取得し、JavaScriptで書いている。例えば、
連合タイムライン
インスタンスURL/api/v1/timelines/public
ローカルタイムライン
インスタンスURL/api/v1/timelines/public?local=true
をブラウザへ入力すると、文字の羅列が表示されると思う。JSONと呼ばれるフォーマットだ。画面描画はこれを元にしている(iOSやAndroidのアプリも同様)。ホームはTokenが必要なので少し細工が必要になるものの、出てくるフォーマット自体は同じだ。
となると、このJSONの中にCardの情報が入っていない場合、タイムラインには表示できない。が、残念ながら添付画像情報のmedia_attachmentsはあるものの、必要とするpreview_cardは無い(どちらもDBのTable名)。
従って改造その1はJSONにpreview_cardを加える作業となる。当然JSONの中身が変わるので、アプリなどの互換性が気になると思うが、(例外もあるかも知れないが)一般的に知らない項目が増えても無視するだけで作動には問題無い。
ただデータ転送量が若干増えるので、微妙に反応が鈍くなる可能性はある。
JSONへpreview_cardを追加する
タイムラインAPIのcontrollerは、
app/controllers/api/v1/timelines_controller.rb
これだ。Status.as_public_timeline()など、modelsのstatus.rbにあるclassを呼んでいる。しかしAPIに関しては、status.rbの中を見てもDBのstatuses Table(Tootの中身)しか実質扱っていない。
別Tableになっているmedia_attachmentsをどこで引っ張ているのか…と、しばし悩んだところ該当箇所を発見。
app/views/api/v1/statuses/_show.rabl
ここで出す直前にmedia_attachmentsをJSONへマージしている。つまりここへpreview_cardを追加すればいい。
1 2 3 4 |
# app/views/api/v1/statuses/_show.rablへ追加 child :preview_card, object_root: false do extends 'api/v1/statuses/_card' end |
実体は新規で_card.rablを作り中は以下のようになる。
1 2 3 4 5 6 |
# app/views/api/v1/statuses/_card.rablを新規作成 attributes :url, :title, :description, :type, :author_name, :author_url, :provider_name, :provider_url, :html, :width, :height node(:image) { |card| card.image? ? full_asset_url(card.image.url(:original)) : nil } |
これで一旦build/assets:precompileをし直し、先のAPIをブラウザで入力すると、
1 |
"preview_card":{"url":"https://www.youtube.com/watch?v=2tvooAF67z0","title":"BABYMETAL Kami Band Intro Awadama Fever Columbia SC 2017","description":"BABYMETAL Kami Band Intro Awadama Fever Opening for RHCP Columbia SC 2017","type":"link","author_name":"","author_url":"","provider_name":"","provider_url":"","html":"","width":0,"height":0,"image":"http://192.168.11.205:3000/system/preview_cards/images/000/000/016/original/maxresdefault.jpg?1492667166"}, |
preview_cardが追加されているはずだ(Cardが無い時は”preview_card:null”のみ)。これで準備は完了。改造その2は、画面側の追加プログラムとなる。
タイムラインへCardを表示する
詳しい話を書くと長くなるので単刀直入に(笑)。詳細表示のCardはここで書いている。
app/assets/javascripts/components/features/status/containers/card_container.jsx
app/assets/javascripts/components/features/status/components/card.jsx
二番目が実際に表示している部分。調べたところタイムラインにも共通で使えそうだ。従ってタイムラインからcard.jsxを呼ぶcard_container_tl.jsxを新規で作成する。
1 2 3 4 5 6 7 8 9 |
# app/assets/javascripts/components/features/status/containers/card_container_tl.jsxを新規作成 import { connect } from 'react-redux'; import Card from '../components/card'; const mapStateToProps = (state,{status}) => ({ card: status.getIn(['preview_card'], null) }); export default connect(mapStateToProps)(Card); |
次に実際タイムラインを描画している部分
app/assets/javascripts/components/components/status.jsx
へ、以下を追加する。一番目はコードの初めにimportの山があるので、そこへ挿入。二番目は、コードの最後の方に{media}だけ書かれた行があるので、その横に追記する。
1 2 3 |
import CardContainer from '../features/status/containers/card_container_tl'; <CardContainer status={status} /> |
これで準備が出来たので、再度build/assets:precompileすればOK。タイムラインにCardが表示されたら正解だ。
現状の問題点は2つ。
- ローカルインスタンス内でしかCardが表示されない(但し、これは非改造のv1.3.2でも外部インスタンスは詳細に出ないので同じ症状)
- リンク先の情報を取得するタイミングが詳細を表示した時なので、誰かが詳細を見ない限り、タイムラインにCardが表示されない
1)はもともと外インスタンスのCardは詳細でも出ないので、システム側の問題だと思われる。2)は投稿した時に自分で確認しつつ詳細を見るのが一番手っ取り早い(笑)。一度詳細を表示すれば情報がDBへ入る。
Card用のデータをFetchするコードは
app/services/fetch_link_card_service.rb
ここにある。以降はまだコードを追ってないので不明。sidekiqのWorkerにもlink_crawl_worker.rbが入っている。
いずれにしてもこれで少しはタイムラインが賑やかになる。ただこれらの改造はブラウザからのアクセスのみ有効で、残念ながら勝手改造を関知しないアプリは無関係だ。
またこの改造によって何か不都合が発生しても責任は持てないので自己責任でお願いしたい。本家に同等の機能が付けば嬉しいのだが…。
追記1:1)の件はBUGのようだ。”cards only get created for local posts”
追記2:v1.4系では後半の表示系PATHが変わった(内容は同じ)。
app/javascript/mastodon/features/status/containers/card_container_tl.js
app/javascript/mastodon/components/status.js