rubyでredmineのAPIkeyを取得してみる

### 経緯
　仕事でrubyを使ってredmineのAPIkeyを取得できるようにしろと仰せつかった。
　ひとまずの完成を見たので、自分の備忘録代わりにまとめておく。
　
　記事としては独学で勉強しただけでプログラマに就職した社会人一年目初心者プログラマが検索とコピペを繰り返して作ったものでしかない。もし自分と同じような人間がいたら今度はこの記事だけで一発成功することを祈って記述する。

### 方法
　まずは検索して見たが、該当する記述は見つからなかった。APIkeyはredmineの個人設定画面から受け取ってコードに入力するのが前提で、APIkeyをわざわざプログラムで取得するなどという方法は全然見つからなかった。が、rubyという条件を除外して検索した結果、以下の記事を発見した。

RedmineのJavaScriptから各種データを取得する方法
https://qiita.com/forenoonM/items/7f42701b2ea40353a820

　この記事内にはJavaScriptを使ってAPIkeyを取得する方法が記述されている。ざっくりいえばスクレイピング を使った方法だ。

### スクレイピング 実践

　なるほど、スクレイピング か。さて、恥ずかしながら私はスクレイピング がなんたるかすら知らなかった。なにせrubyを独学で勉強していただけの初心者なので。
　というわけで、rubyでスクレイピング する方法を検索し、以下のサイトにたどり着いた。

【5分で理解】Rubyでスクレイピングの基礎を解説！
https://www.sejuku.net/blog/13230

　こうして作ったコードがこれ

```ruby
equire 'open-uri'
require 'nokogiri' #APIキー取得に使う
url = "http://192.168.33.11/my/api_key"

charset = nil

html = open(url) do |page|
 charaset = page.charset
 page.read
end

contents = Nokogiri::HTML.parse(html,nil,charset)
apikey = contents.search("div[@class='box']").search('pre')
puts apikey
```
　さて、実際に動かすと動かない。
　色々調べた結果、ログイン画面で止まっていることに気づいた。

　という事はログインの処理を作らねばならない

### ログイン処理作成
　で、例によって検索でたどり着いたのがこのページ。

Mechanizeでログインしてスクレイピングする
https://qiita.com/katsuyuki/items/1a78360988d96eec1d54

　なるほど完全に理解した。

```ruby
agent = Mechanize.new
agent.user_agent = 'Mac Safari'
agent.get("http://#{adress}/my/api_key") do |page|
  response = page.form_with(:action => '/login') do |form|
    formdata = {
       :mail => mail,
       :password => password,
    }
    form.field_with(:name => 'username').value = formdata[:mail]
    form.field_with(:name => 'password').value = formdata[:password]
  end.submit
 end
 url = "http://#{adress}/my/api_key"
 html = agent.get("#{url}").content.toutf8
 contents = Nokogiri::HTML(html, nil, 'utf-8')
 apikey = contents.search("div[@class='box']").search('pre')
 puts apikey
```
　追加部分だけを抜き出した。

　ちなみに#{adress}については「今はローカルでテストしているが、この後会社のredmineとクライアントのredmineでも動かすんだから」と

```ruby
require 'yaml' #コンフィグを読むのに使う

set = YAML.load_file("config.yml")
puts set
adress = set["train"]["adress"]
puts adress
```
　こんな感じでconfig.ymlにアドレスを入力することで指定できるようにしたものだ。

結果

```
> <pre>[APIkey]</pre>
```

　これではダメだ。

```ruby
stringkey = "#{apikey}"
stringkey.slice!("</pre>")
stringkey.slice!("<pre>")
puts "APIKey = #{stringkey}"
```
　これを直後に加えて、ひとまずAPIkeyを取得する処理は完成。

### もしログイン済みだったら？

　実はこっちを先に取り掛かっていたのだが、もしログイン画面ではなかったらどうだろう？　そんなことがあるのかわからないが、もしあったら逆にログインする処理でエラーを起こしてしまう。

```ruby
page = RedmineController.new
pagetitle = page.Myaccount_access(adress)
puts "pagetitle = #{pagetitle}"

if pagetitle == "個人設定 - redmine" then # ログイン済みかどうかチェックする
     apikey = page.GetAPIkey(adress)
     puts "APIKey = #{apikey}"
else
     apikey = page.Login(adress,mail,password) 
end
```
　ログイン後の個人設定画面はタイトルが「個人設定 - redmine」になっていたので、これに対応した。

　ここでRedmineContorollerなるクラスが作られているが、このクラスについては後述する。

　この後、会社のredmineで試して気づいた。うちの会社のredmineは「個人設定 - [会社名]redmine 」になっている。
　調べたところ、トップページなども全て「[会社名]redmine」だ。つまり、「個人設定 - [ページ名]」というスタイルのようだ。対応しよう。

```ruby
   page = RedmineController.new
   toptitle = page.toptitle(adress)
   pagetitle = page.Myaccount_access(adress)
   puts "pagetitle = #{pagetitle}"

   if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする
     apikey = page.GetAPIkey(adress)
     puts "APIKey = #{apikey}"
   else
     apikey = page.Login(adress,mail,password) 
   end
```
　こうだ。つまり、トップのタイトルを先に把握しておく作戦だ。
　さて、ではクラス「RedmineController」を公開する。そう、toptitleメソッドのことを紹介しておかないと二度手間なので紹介した次第である。

```ruby
class RedmineController

 def toptitle(adress)
	url = "http://#{adress}/"

	charset = nil

	html = open(url) do |page|
	 charaset = page.charset
	 page.read
	end

	contents = Nokogiri::HTML.parse(html,nil,charset)
 return contents.title
 end

 def Myaccount_access(adress)
	url = "http://#{adress}/my/account"

	charset = nil

	html = open(url) do |page|
	 charaset = page.charset
	 page.read
	end

	contents = Nokogiri::HTML.parse(html,nil,charset)
 return contents.title
 end

 def Login(adress,mail,password)
	agent = Mechanize.new
	agent.user_agent = 'Mac Safari'
	agent.get("http://#{adress}/my/api_key") do |page|
	  response = page.form_with(:action => '/login') do |form|
	    formdata = {
	       :mail => mail,
	       :password => password,
	    }
	    form.field_with(:name => 'username').value = formdata[:mail]
	    form.field_with(:name => 'password').value = formdata[:password]
	  end.submit
	 end
	 url = "http://#{adress}/my/api_key"
	 html = agent.get("#{url}").content.toutf8
         contents = Nokogiri::HTML(html, nil, 'utf-8')
         apikey = contents.search("div[@class='box']").search('pre')
         stringkey = "#{apikey}"
	 stringkey.slice!("</pre>")
	 stringkey.slice!("<pre>")
	 puts "APIKey = #{stringkey}"
	 return stringkey
 end

 def GetAPIkey(adress)
        url = "http://#{adress}/my/api_key"

        charset = nil

        html = open(url) do |page|
         charaset = page.charset
         page.read
        end

        contents = Nokogiri::HTML.parse(html,nil,charset)
	apikey = contents.search("div[@class='box']").search('pre')
        stringkey = "#{apikey}"
	stringkey.slice!("</pre>")
	stringkey.slice!("<pre>")
	puts "APIKey = #{stringkey}"
	return stringkey
 end

end
```

### そして完成へ

　で、この後APIを使ってなんやかんやする処理を作る必要があるので、APIkeyを得るための処理はメソッドに押し込もう。

```ruby
class Getter
 def APIKey_Getter(adress,mail,password)
   page = RedmineController.new
   toptitle = page.toptitle(adress)
   pagetitle = page.Myaccount_access(adress)
   puts "pagetitle = #{pagetitle}"

   if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする
     apikey = page.GetAPIkey(adress)
     puts "APIKey = #{apikey}"
   else
     apikey = page.Login(adress,mail,password) 
   end
   return apikey
 end
```
　ちなみにこの後、今はこのゲッタークラスに各種APIを呼び出すメソッドを追加している。

### 全容

　以上で持って完成。全容はこんな感じである。

```ruby
require 'open-uri'
require 'nokogiri' #APIキー取得に使う
require 'mechanize' #ログインに使う
require 'yaml' #コンフィグを読むのに使う
require 'io/console' #パソワードの伏字に使う

class RedmineController

 def toptitle(adress)
	url = "http://#{adress}/"

	charset = nil

	html = open(url) do |page|
	 charaset = page.charset
	 page.read
	end

	contents = Nokogiri::HTML.parse(html,nil,charset)
 return contents.title
 end

 def Myaccount_access(adress)
	url = "http://#{adress}/my/account"

	charset = nil

	html = open(url) do |page|
	 charaset = page.charset
	 page.read
	end

	contents = Nokogiri::HTML.parse(html,nil,charset)
 return contents.title
 end

 def Login(adress,mail,password)
	agent = Mechanize.new
	agent.user_agent = 'Mac Safari'
	agent.get("http://#{adress}/my/api_key") do |page|
	  response = page.form_with(:action => '/login') do |form|
	    formdata = {
	       :mail => mail,
	       :password => password,
	    }
	    form.field_with(:name => 'username').value = formdata[:mail]
	    form.field_with(:name => 'password').value = formdata[:password]
	  end.submit
	 end
	 url = "http://#{adress}/my/api_key"
	 html = agent.get("#{url}").content.toutf8
         contents = Nokogiri::HTML(html, nil, 'utf-8')
         apikey = contents.search("div[@class='box']").search('pre')
         stringkey = "#{apikey}"
	 stringkey.slice!("</pre>")
	 stringkey.slice!("<pre>")
	 puts "APIKey = #{stringkey}"
	 return stringkey
 end

 def GetAPIkey(adress)
        url = "http://#{adress}/my/api_key"

        charset = nil

        html = open(url) do |page|
         charaset = page.charset
         page.read
        end

        contents = Nokogiri::HTML.parse(html,nil,charset)
	apikey = contents.search("div[@class='box']").search('pre')
        stringkey = "#{apikey}"
	stringkey.slice!("</pre>")
	stringkey.slice!("<pre>")
	puts "APIKey = #{stringkey}"
	return stringkey
 end
end

class Getter
 def APIKey_Getter(adress,mail,password)
   page = RedmineController.new
   toptitle = page.toptitle(adress)
   pagetitle = page.Myaccount_access(adress)
   puts "pagetitle = #{pagetitle}"

   if pagetitle == "個人設定 - #{toptitle}" then # ログイン済みかどうかチェックする
     apikey = page.GetAPIkey(adress)
     puts "APIKey = #{apikey}"
   else
     apikey = page.Login(adress,mail,password) 
   end
   return apikey
 end
end


#ここから処理開始。実際にはsinatraでgetかpostで情報を受け取る
puts "アカウント名を入力してください"
mail = gets
mail.chomp!
puts "パスワードを入力してください（表示されませんが入力できています)"
password = STDIN.noecho(&:gets)
password.chomp!
#実際にはgetsを使って取得する訳ではないが、一応設定。最終的にはこの範囲は全て消える

set = YAML.load_file("config.yml")
puts set
adress = set["train"]["adress"]
puts adress
f = Getter.new
apikey = f.APIKey_Getter(adress,mail,password) 
puts "APIKey = #{apikey}"
```

以上、お目汚し失礼しました。