同級生がdiscordのbotを作っているのを見て面白そうだと思い作成するに至った。
同級生がRubyで動かしていて、ネットで見かける情報もRubyのものが多かったためその路線で行くことに。
PC自体はWin8だったけども環境構築をめんどくさがったためcloud9を使用。Rubyのバージョンは後述の参考サイトに倣って2.3.3にアップデート。
目的
大乱闘スマッシュブラザーズ for WiiU/3DSのレーティング戦を行っているスマメイトという有志が運営しているサービスがある。
そこからレート戦ランキングの情報(順位、ユーザ名、レート)とユーザごとの情報(ユーザ名、ニンテンドーネットワークIDあるいはフレンドコード、勝利数、敗北数、勝率、レート、プロフィール)を取得してdiscord上に表示することが今回の目的。
参考サイト
- イチからDiscord Bot 。for Ruby
- Discord Botの作り方 #1
- ruby入門者がdiscordのbotで遊んだ話
- RubyでHTML解析が超余裕なんです
- Ruby製の構文解析ツール、Nokogiriの使い方 with Xpath
作成
discordのアカウントは作成済みと言う前提で、botの作成手順は参考サイト2が丁寧に書いてあるのでそちらを見ながらbotユーザを作成する。
今回作成したコードがこちら。
require 'discordrb'
require 'net/http'
require 'uri'
require 'open-uri'
require 'nokogiri'
bot = Discordrb::Commands::CommandBot.new token: 'トークン', client_id: クライアントID, prefix: '?'
bot.command :sumamate do |event, *code|
if ("#{code[0]}" == "wiiu") #ハードウェア判別
url = 'http://sumamate.com/'
hardwareName = "WiiU"
elsif ("#{code[0]}" == "3ds")
url = 'http://sumamate3ds.com/'
hardwareName = "3DS"
else
end
if ("#{code[1]}" == "ranking") #ランキング表示
requestUrl = url + "ranking/"
charset = nil
html = open(requestUrl) do |f|
charset = f.charset
f.read
end
doc = Nokogiri::HTML.parse(html, nil, charset)
ENV["TZ"]="Asia/Tokyo"
nowDate = Time.now
youbi = %w[日 月 火 水 木 金 土]
output = "```"
output += hardwareName + "スマメイトランキング\n["
output += nowDate.year.to_s + "年" + nowDate.month.to_s + "月" + nowDate.day.to_s + "日(" + youbi[nowDate.wday] + ")"
output += nowDate.hour.to_s + "時" + nowDate.min.to_s + "分" + nowDate.sec.to_s + "秒に取得]\n"
doc.css('div.row-battle').each do |node|
rankTmp = node.css('div.rank').inner_html.to_s.strip
nameTmp = node.css('p.name').css('a').inner_html.to_s.strip
rateTmp = node.css('div.rate').inner_html.to_s.strip
output += rankTmp + ":" + nameTmp + ":" + rateTmp + "\n"
end
output += "```"
event.send_message output
elsif ("#{code[1]}" == "user") #ユーザ検索機能
requestUrl = url + "search/?search=" + "#{code[2]}"
charset = nil
html = open(requestUrl) do |f|
charset = f.charset
f.read
end
doc = Nokogiri::HTML.parse(html, nil, charset)
hitAmount = doc.css('div.col-xs-8').css('h2').inner_html.gsub("検索結果","").gsub("件","").to_s.strip
if(hitAmount != "")
userUrl = doc.css('p.name').css('a').attribute('href').value.to_s #twitterIDで検索した結果から特定のユーザIDを含んだURL取得する
else
event.send_message ("ユーザが見つかりません")
break
end
nowDate = Time.now
youbi = %w[日 月 火 水 木 金 土]
html = open(userUrl) do |f|
charset = f.charset
f.read
end
userDoc = Nokogiri::HTML.parse(html, nil, charset)
userName = userDoc.css('table.user-table').css('span.user-name').inner_html.to_s.strip #ユーザ名
userNintendoNetworkId = userDoc.css('table.user-table').css('tr')[1].css('td').inner_html.to_s.strip #ニンテンドーネットワークIDあるいはフレンドコードを格納
userRate = userDoc.css('div.side').css('div.col-xs-6')[1].inner_html.to_s.strip #レートを格納
userVictoryOrDefeat = userDoc.css('div.side').css('div.col-xs-6')[3].inner_html.to_s.strip.gsub("勝 ","\n").gsub("敗","") #勝敗数(文字列)
userVictoryOrDefeatArray = userVictoryOrDefeat.split("\n") #勝敗数(配列)
userVictoryAmount = userVictoryOrDefeatArray[0].to_f
userDefeatAmount = userVictoryOrDefeatArray[1].to_f
userWinningPercentage = ((userVictoryAmount / (userVictoryAmount + userDefeatAmount)).round(4) * 100).to_s #勝率
userSeriesOfVictories = userDoc.css('div.side').css('span.red').inner_html.to_s.strip.gsub("現在","").gsub("連勝","").gsub("!","") #連勝数
userProfile = userDoc.css('div.col-xs-8').css('div.side').inner_html.to_s.strip.gsub("<br>","") #プロフィール
output = "```"
output += userName + "さんの" + hardwareName + "スマメイトデータ\n["
output += nowDate.year.to_s + "年" + nowDate.month.to_s + "月" + nowDate.day.to_s + "日(" + youbi[nowDate.wday] + ")"
output += nowDate.hour.to_s + "時" + nowDate.min.to_s + "分" + nowDate.sec.to_s + "秒に取得]\n\n"
if (hardwareName == "WiiU") #ハードウェア判別
output += "ニンテンドーネットワークID:" + userNintendoNetworkId + "\n\n"
elsif (hardwareName == "3DS")
output += "フレンドコード:" + userNintendoNetworkId + "\n\n"
else
end
if(userVictoryOrDefeat == "")
output += "今期はまだ対戦を行っていません\n"
else
output += "今期レート:" + userRate + "\n"
output += "今期対戦成績:" + userVictoryOrDefeatArray[0] + "勝 " + userVictoryOrDefeatArray[1] + "敗(勝率 " + userWinningPercentage + "%)\n"
end
if(userSeriesOfVictories == "")
else
output += "現在の連勝数:" + userSeriesOfVictories + "\n"
end
output += "\nプロフィール:\n" + userProfile
output += "```"
event.send_message output
else
end
end
bot.run
だいぶゴリ押ししたところが見えるコードとなった。
動作させるにはcloud9のターミナルで
$ ruby bot.rb
でbotを起動させた後にdiscordのチャットで
?sumamate {機種} ranking
とすれば機種ごとのランキングが表示される。(機種のところには3ds
かwiiu
のどちらかが入る)
また、ユーザごとの情報を見たい場合はチャットで
?sumamate {機種} user {TwitterID}
課題点
- コードが雑(変数名が雑)
- 取得した情報を一旦処理したくせに処理前と同じような文で出力している
- ユーザ検索がTwitterIDだけなため不便
特にユーザ検索が不便というのは重大な問題点である。
今回ユーザ検索に使用した方法がスマメイトの検索機能を利用したもので、TwitterIDで検索をした結果のページをスクレイピングし結果として出てきたユーザページへのURLからまたスクレイピングをし情報を取得する。といったものである。検索できるものとしてはTwitterIDの他にニンテンドーネットワークID、ユーザ名がある。
しかしユーザ名は短いものが多く、他のユーザ名の一部分である可能性が高い。その場合多数の検索結果が出てくるため求めるユーザにたどり着くことが難しくなる。(""で囲う完全一致検索は使用できなかった)
またニンテンドーネットワークIDで検索することもできるがサブアカウントを持つユーザは少なくない。そのためメインで使っているアカウントを出すことがいささか面倒である。(いっそのことサブも含めて検索結果に出すという手も)
ともかくユーザ検索が不便であるため利便性を高めたい。
感想
1日使って勢いで完成させた機能であったがひとまず動くものができたので良かった。
この機能を作る前にAPIを叩いてキャラクターの情報を引っ張ってくるという機能も実装していたがそれについては気が向いたらまた別の機会に記事にしようと思う。