Slackのemojiを別のTeamに引っ越しするスクリプトをRubyで作ってみた（解説編）

#はじめに
この記事は前回投稿した記事の解説編です。
スクリプトを使いたい方は、前回の記事を参照してください。
[Slackのemojiを別のTeamに引っ越しするスクリプトをRubyで作ってみた](http://qiita.com/from_Unknown/items/e1f99d9a2c4d19ffed52 "Slackのemojiを別のTeamに引っ越しするスクリプトをRubyで作ってみた")

今回Slackのemoji引っ越しスクリプトを作成して、Mechanizeの便利さを知りました。
Mechanizeを使用することでAPIが用意されていないサービスでも色々自動化出来そうだなと思いましたので、簡単に作ったスクリプトの解説を書いてみます。
少しでもお役に立ちましたら幸いですm(_ _)m

#ソースについて
こちらにアップしております。
[slack_emoji_mover](https://github.com/from-unknown/slack_emoji_mover "slack_emoji_mover")
そのまんまでセンスのない名前ですね^^;

#解説
##概要
このスクリプトは以下の作業を自動化しています。

1. 引越し元のemojiリストを取得する
2. 引越し元のemojiリストから画像ファイルを取得する
3. 引越し先のSlackのTeamにログインする
4. Webページにemojiの情報を記入する
5. formデータをsubmitする（「Save New Emoji」のボタンを押す）
6. アップロードが完了していない場合4.に戻る

上記以外に設定ファイルの読み込み等を行っています。
色々やっているようですが、スクリプトは200行以下ですし、有効行だけなら150行もありません。
それでは、処理順に解説していきます。
※requireや変数宣言などは省略します。

##詳細
###設定ファイルの読み込み
以下の関数は、設定ファイルの読み込みを行っています。

```ruby:import_emoji.rb
  ConfigFile = 'emoji_conf.txt'

  def readConfigFile
    config = Array.new
    File.open(ConfigFile, 'r') do |file|
      file.each_line do |line|
        stripLine = line.strip
        if stripLine !~ /^#/ && !stripLine.empty?
          config.push(line.strip)
        end
      end
    end
    @@url = config[0] # target slack team URL
    @@email = config[1] # email to login
    @@password = config[2] # password
    @@token = config[3] # target slack team token
  end
```

上記関数で行っていることは単純で、ファイルを読み込んで一行づつArrayに追加した後に、設定保持用のローカル変数にセットしています。
工夫した点として、`if stripLine !~ /^#/ && !stripLine.empty?`で先頭が#か空行の場合は無視するようにした点です。
これでコメントを複数行書けるようにしました。
実際にコミットされている設定ファイルは以下の様になっています。

```emoji_conf.txt
# Slack team URL to import emoji
https://xxxx.slack.com/
# login mail address
xxxx@xxxx
# login password
xxxx
# Slack Token to get emoji list from team
xxxx-xxxx-xxxx 
```

以下の関数は、デフォルトでSlackの#内に登録されているemojiを弾くために作ったリストを読み込む関数です。
設定ファイルを読み込む関数を流用したため、ほぼ同じ構造です。
（書いていて思いましたが、共通化出来そうですね・・・。反省点です。）

```ruby:import_emoji.rb
  DefaultFile = 'default_emoji.txt'

  def readDefaultEmojiFile
    @@default = Array.new
    File.open(DefaultFile, 'r') do |file|
      file.each_line do |line|
        stripLine = line.strip
        if stripLine !~ /^#/ && !stripLine.empty?
          @@default.push(line.strip)
        end
      end
    end
  end
```

###引越し元のemojiの取得

以下の関数で、引越し元のemojiのリストを取得します。
Slackに用意されているAPIを使用し、返ってきたレスポンスをファイルに書き出します。

参考記事：
[slackに登録した custom emoji を取得する](http://qiita.com/nofrmm/items/ebe4397c728537e75917 "slackに登録した custom emoji を取得する")

```ruby:import_emoji.rb
  ApiUrl = 'https://slack.com/api/emoji.list?pretty=1&token='

  def getEmojiText
    open(EmojiFile, 'wb') do |output|
      open(ApiUrl + @@token) do |data|
        output.write(data.read)
      end
    end
  end
```

上記APIから、以下の様なJSONが取得できます。

```
{
    "ok": true,
    "emoji": {
        "bowtie": "https:\/\/emoji.slack-edge.com\/T1DKGLDQA\/bowtie\/f3ec6f2bb0.png",
        "squirrel": "https:\/\/emoji.slack-edge.com\/T1DKGLDQA\/squirrel\/465f40c0e0.png",
        "glitch_crab": "https:\/\/emoji.slack-edge.com\/T1DKGLDQA\/glitch_crab\/db049f1f9c.png",
...
```

emoji内にemojiの名前とその画像ファイルのURLがペアになって記載されています。
この情報があれば、引越し元のemojiの情報は十分ですね。

それでは、上記の情報から画像ファイルを取得しましょう。

まずは先程取得したJSONを読み込みます。
この時、一部のemojiは標準で登録されているため、最初に読み込んだデフォルトのemojiリストにヒットした場合は削除するようにしています。
また、aliasで始まる場合はエイリアスなので、こちらも削除しています。

```ruby:import_emoji.rb
  def readEmojiJSON
    File.open(EmojiFile, 'r') do |file|
      @@hash = JSON.load(file)
    end

    @@hash = @@hash['emoji']
    @@hash.each do |node|
      if !@@default.index(node[0]).nil? || node[1] =~ /^alias/
        @@hash.delete(node[0])
      end
    end
  end
```

上記関数で`@@hash`内にカスタマイズしたemojiのみ残りました。
画像ファイルを全てローカルにダウンロードしましょう。
`open`はファイルを開くだけでなく、URLを指定することで画像ファイルなどをダウンロードすることも出来ます。

参考記事：
[ファイルをWebからダウンロードして保存する](http://qiita.com/ima0123/items/a7c252a45f91eb807930 "ファイルをWebからダウンロードして保存する")

```ruby:import_emoji.rb
  def getEmojiData
    @@hash.each do |node| # loop each node
      name = File.basename(node[1]) # get file name from URL
      if name !~ /^alias/
        open(name, 'wb') do |output|
          open(node[1]) do |data|
            output.write(data.read) # write to local
          end
        end
      end
      sleep 3
    end
  end
```

画像をダウンロードする際に、サーバーの負担を軽減するために`sleep`を3秒入れています。
あまりサーバーに負荷をかけると攻撃と変わらないので、少し間を挟むようにしています。

（なお、aliasから始まっている場合スキップするようにしていますが、すでに`@@hash`から削除しているので不要かもしれないですね・・・。）

###emojiのアップロード

さて、画像は全てダウンロードしたので後はアップロードするだけです。
少し長いので、分けて解説します。

```ruby:import_emoji.rb
  def uploadEmoji()
    log_file = open(LogFile, 'wb')
    agent = Mechanize.new
    agent.user_agent = 'Windows Mozilla'

    # login to Slack team
    agent.get(@@url) do |page|
      response = page.form_with(:action => '/') do |form|
        formdata = {
          :email => @@email, # login mail address
          :password => @@password # login password
        }
        form.field_with(:name => 'email').value = formdata[:email]
        form.field_with(:name => 'password').value = formdata[:password]
      end.submit

      # if login fails, write error to log file and exit
      if response.code != '200' || response.body.include?(LoginFail)
        log_file.write("Login failed! Please check Slack url, email and password.\n")
        return -1
      end
      log_file.write("Login success!\n")

      # loop upload emoji
      @@hash.each do |node|
        name = File.basename(node[1])
        agent.get(@@url + 'customize/emoji') do |emoji|
          if File.exist?('./' + name)
            eresponse = emoji.form_with(:action => '/customize/emoji') do |eform|
              eform.field_with(:name => 'name').value = node[0]
              eform.radiobuttons_with(:name => 'mode')[0].check
              eform.file_upload_with(:name => 'img').file_name = './' + name
            end.submit
            # write result to log
            # check responce code and body to decide success or failer
            if eresponse.code != '200' # check response code
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Respose code is not 200. Failed to add emoji.\n")
            elsif eresponse.body.include?(AddSuccess) # add success log
              log_file.write("S Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Successfully added.\n")
            elsif eresponse.body.include?(DuplicateEmoji) # add error log - duplicate error
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Same emoji name already exist.\n")
            else # add error log - unknown error
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Unknown error occured.\n")
            end
          else # add error log - file not found
            log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
            log_file.write("File not exist.\n")
          end
        end
        sleep 3 # wait 3 seconds for slack server
      end
    end
    return 0 # success
  end
```

以下の部分はSlackのチームにログインしている箇所です。

参考記事：
[[Ruby]Mechanizeでログイン機能があるサイトにログインする](http://qiita.com/jun1_0803/items/9d3b26176b399e21fd61 "[Ruby]Mechanizeでログイン機能があるサイトにログインする")

```ruby:import_emoji.rb
    # login to Slack team
    agent.get(@@url) do |page|
      response = page.form_with(:action => '/') do |form|
        formdata = {
          :email => @@email, # login mail address
          :password => @@password # login password
        }
        form.field_with(:name => 'email').value = formdata[:email]
        form.field_with(:name => 'password').value = formdata[:password]
      end.submit

      # if login fails, write error to log file and exit
      if response.code != '200' || response.body.include?(LoginFail)
        log_file.write("Login failed! Please check Slack url, email and password.\n")
        return -1
      end
      log_file.write("Login success!\n")
```

まずMechanizeを使用して、Slackのチームのページを取得してきます。
`agent.get(@@url) do |page|`(`@@url`には引越し先のSlackのURLが入ります)で、`page`にページを読み込みます。
その後、`response = page.form_with(:action => '/') do |form|`でページ内のformを取得します。
※`page.form_with(:action => xx)`に設定する値は、html内のform要素に設定されている値となります。
このform内には`email`と`password`という記入項目があるので、設定した値をセットします。
こちらもinputのnameに設定されている値をブラウザの開発ツール等使用して調べることが出来ます。
または、`form_with`で取得してきた値を`p xxx`でdumpすれば、どのような要素があるか確認出来ます。
全ての値をセットしたら、`end.submit`で送信します。
結果は`response`内の`code`と`body`の値をチェックし、正しくログインされたかどうか確認します。
`code`はサーバーからのレスポンスコードになるので、200以外は失敗となります。

以下の部分で、ログインに成功した状態を保ったままemojiをアップロードしていきます。

```ruby:import_emoji.rb
      @@hash.each do |node|
        name = File.basename(node[1])
        agent.get(@@url + 'customize/emoji') do |emoji|
          if File.exist?('./' + name)
            eresponse = emoji.form_with(:action => '/customize/emoji') do |eform|
              eform.field_with(:name => 'name').value = node[0]
              eform.radiobuttons_with(:name => 'mode')[0].check
              eform.file_upload_with(:name => 'img').file_name = './' + name
            end.submit
```

まずループを開始し、全てのemojiを処理するようにします。
先程ログインした`agent`を使用して、emojiを設定するページを読み込み、formを取得します。
emojiの名前、チェックボックスのチェック、画像ファイルを設定し、送信します。

参考記事：
[RubyのMechanizeで画像をPOSTする方法](http://qiita.com/yumiyon/items/3d3e8e9b00e9bc53e17f "RubyのMechanizeで画像をPOSTする方法")

残りは結果のチェックです。

```ruby:import_emoji.rb
            # write result to log
            # check responce code and body to decide success or failer
            if eresponse.code != '200' # check response code
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Respose code is not 200. Failed to add emoji.\n")
            elsif eresponse.body.include?(AddSuccess) # add success log
              log_file.write("S Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Successfully added.\n")
            elsif eresponse.body.include?(DuplicateEmoji) # add error log - duplicate error
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Same emoji name already exist.\n")
            else # add error log - unknown error
              log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
              log_file.write("Unknown error occured.\n")
            end
          else # add error log - file not found
            log_file.write("F Name:[" + node[0] + "] FileName:[" + name + "] Result: ")
            log_file.write("File not exist.\n")
          end
        end
        sleep 3 # wait 3 seconds for slack server
      end
    end
    return 0 # success
  end
```

レスポンスコードが200でない場合、正常でないため失敗したことをログに残します。
コードが200で成功した場合は、返ってきたbody内に記載されている文言で判定します。
（nokogiriを使用しないようにするつもりでしたが、Mechanizeが依存しているということに後で気付きました・・・。nokogiriを使えばもう少し柔軟な判定が出来そうです。）
また、画像のダウンロードが失敗した場合を考慮し、アップする画像がローカルにない場合はエラーをログに残します。

後は3秒のsleepを挟み、emojiをアップし続けます。
放置しておけば、勝手にemojiが全てアップされます。

#最後に

そこそこ長い記事となってしまいました。
一気に書いてしまい結構端折ってしまった部分もあり、htmlの知識などが前提になってしまっている部分もあるため、分かりにくいかもしれません。
分かりにくい点があれば、コメントして頂けると幸いです。

また、今回の手法はURLなどが完全に決め打ちとなってしまっている部分もあるため、非常に仕様変更に弱いです。
SlackのWebページの仕様が変わった途端に動かなくなる可能性が大です。
頻繁にデザインが変わるページの場合は注意してください。

手作業では大変な作業を自動化したい時に、今回の記事が役立ちましたら幸いです。
次回はこちらのスクリプトを応用して作った、簡単にemojiをアップするスクリプトを解説できたらと思います。