macOSでお手軽Mastodon開発

せっかくの連休なので、さっくりと、Mastodonの改造をやってみたいと思います。こっちには連休無いけどな！この記事では、macOSでざっくりと開発環境を整え、Mastodonのコードを取得してmacOSで走らせられるようにして、特にフロントエンドに改造を加えて、GitHubにプルリクエストを送ってみます。macOSでビルドの難しいライブラリへの依存を取り除くことで、依存しない部分の改造を簡単にできるようにします。

この記事は、[Mastodon Golden Week Calendar](https://gw-advent.9wick.com/calendars/16)の4月30日の記事です。平成最後の日だ！昨日は、[にし](https://matitodon.com/@westantenna) さんによる「[おかげさまで、まちトドンは2周年](https://westantenna.com/%e6%8a%80%e8%a1%93/mastodon/3034/)」、明日は、[ライスには塩を](https://gorone.xyz/@squid999) さんによる「[ちいさなMastodonサーバの素人管理人が考える、いくつかのこと](https://le-mal-du-pay.goat.me/hnJmfuwpDG)」です。

この記事の内容は2019年4月下旬にmacOS 10.13.6 High Sierraで確認しました。

# 開発環境の準備
Mastodonのビルドと稼働に必要なパッケージ類を用意しておきます。手元の使い込んだ環境で執筆していますので、この項には書いていないけれども必要なものもあるかもしれません。足りないものに気づいた方はお知らせいただけるとうれしいです。

Mastodonに必要なサービスとしてPostgreSQLとRedisがあります。ローカルに入れちゃいましょう。手元では、[Postgresp.app](https://postgresapp.com/) 2.1.6でPostgreSQL 10.7が走っています。RedisはHomebrewで入れたredis-server 5.0.4が走っています。

Mastodonのビルドと稼働に必要なパッケージは[Homebrew](https://brew.sh/)で入れてしまいます。

```sh
$ brew install git rbenv yarn redis
```

rbenvのために下記の行を`~/.bash_profile`に追加しておきます。

```sh
$ cat <<_END >> ~/.bash_profile
PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
_END
```

必要なRubyのバージョンはMastodonの`.ruby-version`ファイルに書いてありますので、コードをcloneしてきてからインストールします。

# コードの準備
個人的には、アップストリームのレポジトリをGitHubでforkしてきて自分のレポジトリからクローンしてくるのが好みです。自分のforkのmasterはアップストリームのmasterをおっかけ、ブランチで自分用の改造をします。

GitHubにログインして https://github.com/tootsuite/mastodon から右上のForkボタンを押し、自分のレポジトリとしてforkして、自分のforkからClone or downloadボタンを押してSSHあるいはHTTPSのURLをコピーしてきてcloneします。さらに、アップストリームを`upstream`リモートとして設定しておくと後々便利です。`fetch`してリリース版のタグも手元に置いておきます。

```sh
$ git clone git@github.com:GitHubユーザー名/mastodon.git
$ cd mastodon
$ git remote add upstream https://github.com/tootsuite/mastodon.git
$ git fetch upstream
```

次に、macOS上でMastodonを起動できるようにするため、ファイルを書き換えます。ブランチを作成して作業を進めましょう。ここでは`master`あるいはリリースタグから`local-devel-macos`というブランチを作成します。ブランチ名は好みで決めちゃってください。リリース版から改造を始めたい場合には、タグをチェックアウトしてからブランチを作成します。

(Masterから改造をする場合)

```sh
$ git checkout -b local-devel-macos
```

(リリース版をもとに改造をする場合)

```sh
$ git checkout v2.8.0
$ git checkout -b local-devel-macos
```

今回は`development`環境で作業を進めます。環境変数を設定するファイルを作っておきます。ここではcommitしていませんが、commitしておいて別の環境にクローンして使い回すのも良いかもしれません。

```sh
$ cat <<_END > .env.dev
RAILS_ENV=development
NODE_ENV=development
_END
```

`cld3` gemは`protobuf-compiler`パッケージを必要として、macOSではビルドが面倒です。今回はトゥートの言語の自動検出はあきらめて、利用しないようにします。

https://github.com/mastodon/mastodon/pull/17478 で`cld3` gemへの依存が取り除かれました。これ以降のMastodonでは下記のパッチは必要なくなりそうです。

```sh
$ patch -p1 << _END
diff --git a/Gemfile b/Gemfile
index a2d851491..f7dd259ff 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,7 +34,6 @@ gem 'browser'
 gem 'charlock_holmes', '~> 0.7.7'
 gem 'iso-639'
 gem 'chewy', '~> 5.1'
-gem 'cld3', '~> 3.2.6'
 gem 'devise', '~> 4.7'
 gem 'devise-two-factor', '~> 3.1'

_END
$ patch -p1 << _END
diff --git a/Gemfile.lock b/Gemfile.lock
index cdb6fa48c..0b88803de 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -160,8 +160,6 @@ GEM
       elasticsearch (>= 2.0.0)
       elasticsearch-dsl
     chunky_png (1.3.11)
-    cld3 (3.2.6)
-      ffi (>= 1.1.0, < 1.12.0)
     climate_control (0.2.0)
     cocaine (0.5.8)
       climate_control (>= 0.0.3, < 1.0)
@@ -683,7 +681,6 @@ DEPENDENCIES
   capybara (~> 3.30)
   charlock_holmes (~> 0.7.7)
   chewy (~> 5.1)
-  cld3 (~> 3.2.6)
   climate_control (~> 0.2)
   concurrent-ruby
   connection_pool
_END
$ patch -p1 << _END
diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb
index 302072bcc..14e31a93e 100644
--- a/app/lib/language_detector.rb
+++ b/app/lib/language_detector.rb
@@ -6,10 +6,6 @@ class LanguageDetector
   WORDS_THRESHOLD        = 4
   RELIABLE_CHARACTERS_RE = /[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}]+/m

-  def initialize
-    @identifier = CLD3::NNetLanguageIdentifier.new(1, 2048)
-  end
-
   def detect(text, account)
     input_text = prepare_text(text)

@@ -19,7 +15,7 @@ class LanguageDetector
   end

   def language_names
-    @language_names = CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }.uniq
+    []
   end

   private
@@ -51,9 +47,7 @@ class LanguageDetector
   end

   def detect_language_code(text)
-    return if unreliable_input?(text)
-    result = @identifier.find_language(text)
-    iso6391(result.language.to_s).to_sym if result.reliable?
+    return nil
   end

   def iso6391(bcp47)
_END
$ git commit -am 'Remove dependency to cld3'
```

`idn-ruby` gemは`libidn11`パッケージを必要とします。開発時には国際化ドメイン名の取り扱いは不要ですので、利用しないようにします。

```sh
$ patch -p1 <<_END
diff --git a/Gemfile b/Gemfile
index f7dd259ff..d5c879aca 100644
--- a/Gemfile
+++ b/Gemfile
@@ -59,7 +59,6 @@ gem 'http', '~> 4.3'
 gem 'http_accept_language', '~> 2.1'
 gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
 gem 'httplog', '~> 1.3'
-gem 'idn-ruby', require: 'idn'
 gem 'kaminari', '~> 1.1'
 gem 'link_header', '~> 0.0'
 gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0b88803de..26bc5b16a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -295,7 +295,6 @@ GEM
       rails-i18n
       rainbow (>= 2.2.2, < 4.0)
       terminal-table (>= 1.5.1)
-    idn-ruby (0.1.0)
     ipaddress (0.8.3)
     iso-639 (0.2.8)
     jaro_winkler (1.5.4)
@@ -708,7 +707,6 @@ DEPENDENCIES
   http_parser.rb (~> 0.6)!
   httplog (~> 1.3)
   i18n-tasks (~> 0.9)
-  idn-ruby
   iso-639
   json-ld
   json-ld-preloaded (~> 3.0)
_END
$ git commit -am 'Remove dependency to idn-ruby'
```

# アプリケーションのビルド
Mastodonで利用するバージョンのRubyを用意します。

```sh
$ rbenv install $(cat .ruby-version)
$ gem install bundler
```

`foreman` Gemがあるとアプリケーションの起動が楽です。

```sh
$ gem install foreman
$ bundle rehash
```

`RAILS_ENV`と`NODE_ENV`環境変数を設定しておいて、gemsとnodeモジュールをインストールします。

```sh
$ source .env.dev
$ bundle install --path=vendor/bundle --with=development
$ yarn install --pure-lockfile
```

Postgres.appでPostgreSQLが起動しているのを確かめたら、データベースを初期化してアセットを用意します。

```sh
$ bundle exec rails db:setup
$ bundle exec rails assets:precompile
```

# 赤いMastodonの起動
PostgreSQLが起動しているのを確認したらRedisも起動して、

```sh
$ redis-server
```

他の端末でMastodonを起動します。

```sh
$ foreman start -e .env.dev -f Procfile.dev
```

http://localhost:3000/ からMastodonにアクセスします。`development`環境のMastodonはfaviconが赤いんですよ！

![Screen Shot 2019-04-27 at 4.06.13 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/28cd2587-3214-38dd-4ac2-b595325d289f.png)

この状態で、すでにadminとしてログインできるようになっています。メールアドレスは、`admin@localhost:3000`、パスワードは、`db/seeds.rb`ファイルから探してみてください。

```sh
$ grep password db/seeds/04_admin.rb 
```

# 改造してみる
このようにして`foreman start`で起動した`webpack-dev-server`が、Reacut/Reduxの変更については、ファイルの更新とともにブラウザがリロードして表示に反映してくれます。便利な時代だねえ！リロードしてくれないような変更は、`foreman`を`Ctrl-C`で停止して再起動、そして、ブラウザでリロードするなどして確かめます。

あとは心の赴くままにコードを触って遊びましょう！例えば下記のような変更を施すと団子をクリックして絵文字を選択できるようになります。

![Screen Shot 2019-04-27 at 4.29.34 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/b3b3b93a-9e9b-a8f6-4cbe-12bfa04cdbfb.png)



```sh
$ patch -p1 <<_END
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dr
opdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dro
pdown.js
index c1429c756..1ab1df137 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.
js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.
js
@@ -357,8 +357,8 @@ class EmojiPickerDropdown extends React.PureComponent {
         <div ref={this.setTargetRef} className='emoji-button' title={title} ari
a-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
           <img
             className={classNames('emojione', { 'pulse-loading': active && loading })}
-            alt='🙂'
-            src={`${assetHost}/emoji/1f602.svg`}
+            alt='🍡'
+            src={`${assetHost}/emoji/1f361.svg`}
           />
         </div>
_END
$ git commit -am 'Use dango for emoji picker button'
```

Happy Hacking!

# プルリクエストを送る
改造がうまくいったら、アップストリームに反映してみんなに使ってもらえるようにするのもうれしいです。でも、今回の改造では、うれしいのは筆者だけなのでプルリクエスト先は自分のレポジトリにしておきますね。

ローカルのMastodonは`Ctrl-C`で停止しておきます。


筆者のぼっちインスタンスは`zunda-ninja-master-on-heroku`というブランチで動いています。今回はこのブランチにマージできるようにレポジトリを準備していきます。

まず開発に使っていたブランチからプルリクエスト用の新しいブランチを作ります。

```sh
$ git checkout -b use-dango-for-emoji-picker
```

ブランチの分岐元をマージ先に変更します。エディタが起動するので、macOSで起動できるようにするなどプルリクエストに不要なコミットは`drop`し、必要なコミットは`squash`してまとめてしまうのが良いでしょう。

```sh
$ git rebase -i zunda-ninja-master-on-heroku
```

今回は下記のようになりました。

```
drop c8786e988 Remove dependency to cld3
drop 3a6a65a8a Remove dependency to idn-ruby
pick 63e4d15af Use dango for emoji picker button
```

GitHubにpushしたらプルリクエストを作成できます。

```sh
$ git push -u origin use-dango-for-emoji-picker
```

GitHubでの操作は直感的ですが、プルリクエストを送る先を間違えないように注意します。Compare & pull requestボタンを押して、

![Screen Shot 2019-04-27 at 5.02.29 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/ac52f72d-1a18-f474-bf8a-db81e3ebd0cb.png)

マージ先のレポジトリを選択し、

![Screen Shot 2019-04-27 at 5.04.08 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/4a1a7828-6552-7adf-81d0-0a3e403d23b6.png)

マージ先のブランチを選択し、

![Screen Shot 2019-04-27 at 5.05.23 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/2e1f7bb8-47ff-b88a-efa2-8530f3ce3879.png)

プルリクエストの内容を書いてCreate pull requestボタンを押します。

![Screen Shot 2019-04-27 at 5.06.32 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105088/809aa791-6459-97e4-aef9-c964f03a461e.png)

これで、[プルリクエスト](https://github.com/zunda/mastodon/pull/23)のできあがり！マージしてデプロイすれば、いつものMastodonにも今回の改造が反映されます。楽しいな♪

なお、特にバックエンドの改造の場合には、[テストを走らせて](https://gist.github.com/zunda/aa6870d0f9f68e705d54d1fa882b0cc0)通るのを確認しておくのも大事かもしれません。

以上、せっかくの連休なのでMastodonを改造したくなった方はご参考まで！