経緯
仕事で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
こうして作ったコードがこれ
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
なるほど完全に理解した。
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でも動かすんだから」と
require 'yaml' #コンフィグを読むのに使う
set = YAML.load_file("config.yml")
puts set
adress = set["train"]["adress"]
puts adress
こんな感じでconfig.ymlにアドレスを入力することで指定できるようにしたものだ。
結果
> <pre>[APIkey]</pre>
これではダメだ。
stringkey = "#{apikey}"
stringkey.slice!("</pre>")
stringkey.slice!("<pre>")
puts "APIKey = #{stringkey}"
これを直後に加えて、ひとまずAPIkeyを取得する処理は完成。
もしログイン済みだったら?
実はこっちを先に取り掛かっていたのだが、もしログイン画面ではなかったらどうだろう? そんなことがあるのかわからないが、もしあったら逆にログインする処理でエラーを起こしてしまう。
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」だ。つまり、「個人設定 - [ページ名]」というスタイルのようだ。対応しよう。
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メソッドのことを紹介しておかないと二度手間なので紹介した次第である。
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を得るための処理はメソッドに押し込もう。
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を呼び出すメソッドを追加している。
全容
以上で持って完成。全容はこんな感じである。
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}"
以上、お目汚し失礼しました。