Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What is going on with this article?
@nedew

Overwatchのプレイヤーデータを返すDiscord Bot

More than 1 year has passed since last update.

N高等学校 一期生のnedewです。

若干の今更感はありますが、一時期の盛り上がり程ではないにしろマッチングに困らない程度には熱のあるOverwatchネタになります。

N高等学校アドベントカレンダー2018の6日目の記事です。

前提

  • Node.js環境でのdiscord.jsを用いたDiscord Bot開発を想定
    • 本記事ではNode.js v10.4.1を使用
  • discord.jsはJavaScript(Node.js)でDiscordのBot開発をするためのnpmパッケージ
    • この記事ではdiscord.jsの詳細は省略するため、触ったことの無い方は先にドキュメント読むことを推奨
    • discord.js Documents

準備

Botアカウントの準備とTokenの取得

1.DEVELOPER PORTAL に行く
2.「Create an application」をクリックし、アイコンや名前等任意のものに変更
3.左側にある「SETTING」メニューから「Bot」を選択、次の画面で「Add Bot」をクリックしBotを作成
4.「Click to Reveal Token」をクリックするとTokenが表示されるのでメモっておく
5.再度左側の「SETTING」メニューから「OAuth2」を選択
6.「SCOPES」欄で「bot」を選択
7.「BOT PERMISSIONS」欄で適切な権限を割り当てる(ここでは「Administrator」を付与)
8.手順6で「bot」を選択した際に「SCOPES」欄下部に表示されたURLにアクセスし、自身のサーバーへBotアカウントを追加

参考: 【Discord】Node.jsでBOTを作成する

新規ディレクトリの作成と諸々のインストール

必要なフォルダ、ファイルを作成します

$ mkdir overwatch-bot
$ cd overwatch-bot
$ touch app.js

今回使用するパッケージをインストールします。
Overwatchのプレイヤーデータ自体はここから見れるのでスクレイピングして持ってくるのも一つですが、その辺りを代わりにやってくれるoverwatch-api(非公式)というものが提供されてるので利用させてもらいましょう。

$ npm init
$ npm i discord.js --save
$ npm i overwatch-api --save

とりあえず書いてみる

Botのいるチャンネルにoverwatch <battletag> <region> <platform>という形での投稿がされた時に指定されたプレイヤーの名前、レベル、レートをBotに返信させるサンプルコードを書いてみます。

なお<battletag>, <region>, <platform>にはそれぞれ以下の値が入ります。

  • <battletag>にはexample#1234といったBattleTag
  • <region>にはゲームサーバus, eu, kr, cn, globalのいずれか
  • <platform>には指定したプレイヤーがプレイしている媒体pc, xbl, psnのいずれか
app.js
'use strict';

/*
Copyright (c) 2016-2018 Alfred Gutierrez
Released under the MIT license
https://github.com/alfg/overwatch-api/blob/master/LICENSE
*/
const overwatch = require('overwatch-api'); // overwatch-apiはMITライセンスで提供されています

const Discord = require('discord.js');
const client = new Discord.Client();
const token = 'ここに先程作成したBotアカウントのTokenを貼り付ける';

// ready
client.once('ready', () => {
  console.log('ready...');
});

// Botの参加しているチャンネル(DM含む)に発言があったとき
client.on('message', message => {

  // Bot自身の発言を無視
  if (message.author.bot) return;

  // メッセージをスペースで分割
  const args = message.content.split(/ +/);

  // メッセージの始まりが'overwatch'であれば
  if (args[0] === 'overwatch') {

    let tag = args[1].replace('#', '-'), // "#"を"-"に置き換える(example#1234 → example-1234)
        region = args[2],
        platform = args[3];

    // Profileデータを取得
    overwatch.getProfile(platform, region, tag, (err, data) => {

      if (err) {
        // Errorを出力
        console.error(err);
      } else {
        // 取得したデータからネーム、レベル、レートを抽出してBotに発言させる
        message.channel.send(
          `Name: **${data.username}**\n` +
          `Level: **${data.level}**\n` +
          `Rank: **${data.competitive.rank}**`
        );
      }

    });
  }
});

// Discordに接続
client.login(token);

実行して試してみましょう

$ node app

スクリーンショット 2018-12-05 22.33.15.png
ちゃんと取得できているようです。

上記サンプルコードの様に、プレイヤーデータを取得するにはgetProfile()、またはgetStats()関数を使います。

getProfile()

主にユーザネームやレベル、レート等のデータをJSONで取得できます。

const overwatch = require('overwatch-api');

const tag = 'nedew-11506',
      region = 'us',
      platform = 'pc';

overwatch.getProfile(platform, region, tag, (err, data) => {
  if (err) {
    console.error(err)
  } else {
    console.log(data);
});
{ username: 'nedew',
  level: 105,
  portrait: 'https://d15f34w2p8l1cc.cloudfront.net/overwatch/029b39829039b61f438d164fc9f1b5373fdd560f7b30fa13cbbf82aee6f2d4a9.png',
  endorsement:
   { sportsmanship: { value: 0.21, rate: 21 },
     shotcaller: { value: 0.21, rate: 21 },
     teammate: { value: 0.58, rate: 58 },
     level: 3,
     frame: 'https://d3hmvhl7ru3t12.cloudfront.net/svg/icons/endorsement-frames-aa182c1f63b51afa951daec63595791283ab97ea3a07f8d47abf9dc7aeda5cc67c786041042de0b8e427194ed084f7cee6b56fa984532199e7ea95bc12bbd995.svg#_3',
     icon: //長すぎるので省略,
  private: false,
  games:
   { quickplay: { won: 147, played: undefined },
     competitive: { won: 42, lost: 35, draw: 3, played: 80, win_rate: 54.55 } },
  playtime: { quickplay: '37:11:55', competitive: '17:45:33' },
  competitive:
   { rank: 3340,
     rank_img: 'https://d1u1mce87gyfbn.cloudfront.net/game/rank-icons/rank-DiamondTier.png' },
  levelFrame: 'https://d15f34w2p8l1cc.cloudfront.net/overwatch/1055f5ae3a84b7bd8afa9fcbd2baaf9a412c63e8fe5411025b3264db12927771.png',
  star: 'https://d15f34w2p8l1cc.cloudfront.net/overwatch/8de2fe5d938256a5725abe4b3655ee5e9067b7a1f4d5ff637d974eb9c2e4a1ea.png' }

getStats()

主に使用ヒーローや記録等のデータをJSONで取得できます。

const overwatch = require('overwatch-api');

const tag = 'nedew-11506',
      region = 'us',
      platform = 'pc';

overwatch.getStats(platform, region, tag, (err, data) => {
  if (err) {
    console.error(err)
  } else {
    console.log(data);
});

{ username: 'nedew',
  level: 105,
  portrait: 'https://d15f34w2p8l1cc.cloudfront.net/overwatch/029b39829039b61f438d164fc9f1b5373fdd560f7b30fa13cbbf82aee6f2d4a9.png',
  endorsement:
   { sportsmanship: { value: 0.21, rate: 21 },
     shotcaller: { value: 0.21, rate: 21 },
     teammate: { value: 0.58, rate: 58 },
     level: 3,
     frame: 'https://d3hmvhl7ru3t12.cloudfront.net/svg/icons/endorsement-frames-aa182c1f63b51afa951daec63595791283ab97ea3a07f8d47abf9dc7aeda5cc67c786041042de0b8e427194ed084f7cee6b56fa984532199e7ea95bc12bbd995.svg#_3',
     icon: 'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjQwIiB3aWR0aD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxjaXJjbGUgcj0iMTUuOTE1NDk0MzA5MTg5NTQiIGZpbGw9IiMyYTJiMmUiIHN0cm9rZS1kYXNoYXJyYXk9IjIxIDc5IiBzdHJva2UtZGFzaG9mZnNldD0iMjUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlPSIjZjE5NTEyIiBjeD0iNTAlIiBjeT0iNTAlIj48L2NpcmNsZT48Y2lyY2xlIHI9IjE1LjkxNTQ5NDMwOTE4OTU0IiBmaWxsPSJ0cmFuc3BhcmVudCIgc3Ryb2tlLWRhc2hhcnJheT0iNTggNDIiIHN0cm9rZS1kYXNob2Zmc2V0PSIxMDQiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlPSIjYzgxYWY1IiBjeD0iNTAlIiBjeT0iNTAlIj48L2NpcmNsZT48Y2lyY2xlIHI9IjE1LjkxNTQ5NDMwOTE4OTU0IiBmaWxsPSJ0cmFuc3BhcmVudCIgc3Ryb2tlLWRhc2hhcnJheT0iMjEgNzkiIHN0cm9rZS1kYXNob2Zmc2V0PSI0NiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9IiM0MGNlNDQiIGN4PSI1MCUiIGN5PSI1MCUiPjwvY2lyY2xlPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkeT0iLjNlbSIgZm9udC1mYW1pbHk9ImNlbnR1cnkgZ290aGljLGFyaWFsLHNhbnMtc2VyaWYiIGZvbnQtd2VpZ2h0PSIzMDAiIGZvbnQtc2l6ZT0iMTYiIHN0cm9rZT0iI2Y2ZjZmNiIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSIjZjZmNmY2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4zPC90ZXh0Pjwvc3ZnPg==' },
  private: false,
  stats:
   { top_heroes: { quickplay: [Object], competitive: [Object] },
     combat: { quickplay: [Array], competitive: [Array] },
     match_awards: { quickplay: [Array], competitive: [Array] },
     assists: { quickplay: [Array], competitive: [Array] },
     average: { quickplay: [Array], competitive: [Array] },
     miscellaneous: { quickplay: [Array], competitive: [Array] },
     best: { quickplay: [Array], competitive: [Array] },
     game: { quickplay: [Array], competitive: [Array] } } }

使用例

試しにgetProfile()getStats()で取得したプレイヤーデータを利用し、整形したembedメッセージをBotに発言させるようにしてみます。

embed(埋め込み)メッセージについては別の記事で解説しているので興味ある方は覗いてみてください。
Discord.jsでembed (埋め込みメッセージ) を扱う

app.js
'use strict';

/*
Copyright (c) 2016-2018 Alfred Gutierrez
Released under the MIT license
https://github.com/alfg/overwatch-api/blob/master/LICENSE
*/
const overwatch = require('overwatch-api'); // overwatch-apiはMITライセンスで提供されています

const Discord = require('discord.js');
const client = new Discord.Client();
const token = 'ここに先程作成したBotアカウントのTokenを貼り付ける'

// ready
client.once('ready', () => {
  console.log('ready...');
});

// Botの参加しているチャンネル(DM含む)に発言があったとき
client.on('message', message => {

  // Bot自身の発言を無視
  if (message.author.bot) return;

  // メッセージをスペースで分割
  const args = message.content.split(/ +/);

  // メッセージの始まりが'overwatch'であれば
  if (args[0] === 'overwatch') {

    // "overwatch example#1234 us pc"
    let tag = args[1].replace('#', '-'),
        region = args[2],
        platform = args[3];

    // Get Profile Data
    const owGetProf = new Promise(function(resolve, reject) {
      overwatch.getProfile(platform, region, tag, (err, data) => {
        err ? reject() : resolve(data);
      });
    });

    // Get Stats Data
    const owGetStats = new Promise(function(resolve, reject) {
      overwatch.getStats(platform, region, tag, (err, data) => {
        err ? reject() : resolve(data);
      });
    });

    Promise.all([owGetProf, owGetStats])
    .then((result) => {

      const gottenProfile = result[0], gottenStats = result[1];
      // Battle Tag
      const playerName = tag.replace('-', '#');

      message.channel.send({embed: {
        color: 16751616,
        author: {
          name: 'OVERWATCH',
          icon_url: gottenProfile.portrait
        },
        title: 'Stats for ' + playerName,
        url: `https://playoverwatch.com/ja-jp/career/${platform}/${playerName.replace('#', '-')}`,
        timestamp: new Date(),
        thumbnail: {
          url: gottenProfile.portrait
        },
        image: {
          url: gottenProfile.competitive.rank_img
        },
        footer: {
          icon_url: message.client.user.avatarURL,
          text: '© Overwatch Bot'
        },
        fields: [
          {
            name: 'Account Stats',
            value: `Level: **${gottenProfile.level}**\n` +
                   `Rank: **${gottenProfile.competitive.rank}**\n` +
                   `Endorsement Level: **${gottenProfile.endorsement.level}**`,
            inline: true
          },
          {
            name: 'Medals in Quick Play',
            value: `Golden Medals: **${gottenStats.stats.match_awards.quickplay[2].value}**\n` +
                   `Silver Medals: **${gottenStats.stats.match_awards.quickplay[3].value}**\n` +
                   `Bronze Medals: **${gottenStats.stats.match_awards.quickplay[4].value}**`,
            inline: true
          },
          {
            name: 'Match Record',
            value: `__[QUICK PLAY]__\n` +
                   `Won: **${gottenStats.stats.game.quickplay[0].value}**\n` +
                   `Playtime: **${gottenStats.stats.game.quickplay[1].value.split(':')[0]}h**\n` +
                   `__[COMPETITIVE]__\n` +
                   `Won: **${gottenProfile.games.competitive.won}**\n` +
                   `Lost: **${gottenProfile.games.competitive.lost}**\n` +
                   `Draw: **${gottenProfile.games.competitive.draw}**\n` +
                   `Win Rate: **${Math.round(gottenProfile.games.competitive.win_rate)}%**`,
            inline: true
          },
          {
            name: 'Most Played Heroes',
            value: `__[QUICK PLAY]__\n` +
                   `1. **${gottenStats.stats.top_heroes.quickplay.played[0].hero}** - ${gottenStats.stats.top_heroes.quickplay.played[0].played}\n` +
                   `2. **${gottenStats.stats.top_heroes.quickplay.played[1].hero}** - ${gottenStats.stats.top_heroes.quickplay.played[1].played}\n` +
                   `3. **${gottenStats.stats.top_heroes.quickplay.played[2].hero}** - ${gottenStats.stats.top_heroes.quickplay.played[2].played}\n` +
                   `__[COMPETITIVE]__\n` +
                   `1. **${gottenStats.stats.top_heroes.competitive.played[0].hero}** - ${gottenStats.stats.top_heroes.competitive.played[0].played}\n` +
                   `2. **${gottenStats.stats.top_heroes.competitive.played[1].hero}** - ${gottenStats.stats.top_heroes.competitive.played[1].played}\n` +
                   `3. **${gottenStats.stats.top_heroes.competitive.played[2].hero}** - ${gottenStats.stats.top_heroes.competitive.played[2].played}`,
            inline: true
          }
        ]
      }});
    }).catch(() => {
      return message.reply('エラーが発生しました');
    });
  }
});

// Discordに接続
client.login(token);

app.jsを実行し、Botのいるチャンネルでコマンドを入力

$ node app

スクリーンショット 2018-12-06 1.59.18.png

注意すること

Overwatchではちょっと前から自身のプロフィールの公開範囲を制限することが可能になりました。
そのためプロフィールを公開していないプレイヤーの情報を取得しようとすると、ユーザネームやレベル等の常時公開データ以外のデータを取得することができません。

指定したプレイヤーの公開/非公開状態を知るにはgetProfile()またはgetStats()で取得したJSONのprivateプロパティで確認可能です(private: trueなら非公開、private: falseなら公開)

Overwatch League関連情報を取得(おまけ)

Overwatch Leagueの情報も取得できるらしいので一応
全てJSON形式で取得されます

owl.getLiveMatch()

記事執筆時にOWL配信がされていなかったので未確認ですが、関数名からして現在配信中のマッチの配信URL辺りを取得できるんだと思います

const overwatch = require('overwatch-api');

overwatch.owl.getLiveMatch((err, data) => {
  // 任意の処理
});

owl.getStandings()

Overwatch Leagueに参加しているそれぞれのチームデータを取得できます

const overwatch = require('overwatch-api');

overwatch.owl.getStandings((err, data) => {
  // 任意の処理
});

owl.getSchedule()

Overwatch Leagueのマッチスケジュールを取得できます

const overwatch = require('overwatch-api');

overwatch.owl.getSchedule((err, data) => {
  // 任意の処理
});

最後に

本記事で紹介しているプログラムは紹介用の簡略化されたものであり、見れば分かる通り想定しない入力があるとうまく動かないどころかエラー吐いて落ちるため、実際に運用する際は全ての入力に対応させて下さい。

OWから逃げるな

5
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
nedew
nnn-school
IT×グローバル社会を生き抜く“総合力”を身につける多様なスキルと多様な体験

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
5
Help us understand the problem. What is going on with this article?