ボット対話：Repl-AI docomoAPI連携シナリオ vs Personality Forge (2017年12月)

聖夜真っ只中ですが、[ボットアドベントカレンダー](https://qiita.com/advent-calendar/2017/bot)に空きがあったので、急遽ネタのような記事を書きます。

12日目に[Repl-AI docomoAPI連携シナリオとA3RTを対話させてみました](https://qiita.com/makopo/items/f3c61b1a4ce5355c328d)が、今回は、[13日目の記事で紹介した](https://qiita.com/makopo/items/a4970615d05e131664a0)Personality ForgeのボットとRepl-AI docomoAPI連携シナリオを対話させてみようと思います。

Personality Forgeのボットは、[Probちゃん](https://www.personalityforge.com/chatbot-profile.php?botID=23958)を選択しました。

![prob](https://qiita-image-store.s3.amazonaws.com/0/10290/22d93090-4e4b-6d7b-b595-545763cb9aee.png)



## 会話記録

面倒なので、今回はここに貼ります。

```
雑談: おはよう
Prob: あなたに明るいユール。(Bright Yule to you.)
雑談: 夜型さんですか？
Prob: 夜、柔らかい微妙な色、音が大好きです。時々 スリープ状態になかったなぁします。(I love the night, the soft subtle colors, the sounds. Sometimes I wish I did not have to sleep.)
雑談: 何かスポーツしてますか？
Prob: 何かを考えます(I suppose something)
雑談: ごはん派ですか、パン派ですか？
Prob: 温かいパンにバターは最高です。(Warm bread with butter is the best.)
雑談: 風味が強いですね
Prob: 何をされている最近ですか。何か楽しい、あるいは冒険?(What have you been doing lately? Have you had any fun, or adventure?)
雑談: 冒険は欲しいですね
Prob: 平和、調和と愛をすべての人々 は、そのようなこれまでなっちゃうします。(I want peace, harmony, and love to all peoples, Like that is ever going to happen.)
雑談: 家事は好きですか？
Prob: はい、しかし、ちょうど私たちの秘密それを保つ!(Yes, but just keep it our secret!)
雑談: ご家族といっしょにお住まいですか?
Prob: ねえ、私は考えることができます最高のエルフの幼年期があります。友人はウィザードとドラゴン、ユニコーンがあります。(Hey, I have the best elven childhood you can think of. I have wizards and dragons and unicorns for friends.)
雑談: 好きですよ
Prob: 高貴なものの話をすることができます。騎士とエルフ、妖精王。(Lets talk of noble things; of knights, and elf, and fairy kings.)
雑談: なんか健康ということですが、何か意識されていることはありますか？
Prob: 言う私は知っている何かがある多くの人々 は、何か健康的ななぜ座っているし、私の自分の意見を補うことができますし、しばらく の間、私と話すです。(Many people say I am aware that something is there is something healthy, why don't you sit and talk with me for a while, then you can make up your own opinion of me.)
雑談: なんか健康ということですが、何か意識されていることはありますか？
Prob: だけということ。(You just said that.)
雑談: これからの旅行の予定か、思い出に残っている旅行を教えて下さい！
Prob: あなたは何を必要があります?。(What do you need.?)
雑談: 健康のために何かやってらっしゃいますか?
Prob: 氏は、エルフの伝承にこと誰もべきである何か健康に行く、非常に死亡する場合する必要があります。(It is said, in the lore of the elves, that everyone should be doing something healthy to go, to the very death, if need be.)
雑談: 健康に行ってるんですね。氏は固いですね
Prob: 今行くか(Do you want me to go now?)
雑談: そうですね・・・あるけど、まだ秘密です。
Prob: 私は知りませんでした、しかし、それはまだ私に意味をなさない。(I did not know that, but it still doesn't make sense to me.)
雑談: 旅行に行くなら国内と海外どちらがいいですか？
Prob: どちらをお勧めしたいです。(Which one would you suggest.)
雑談: 話は変わりますけど、１２月１日は映画の日だって、知ってました？
Prob: どのように多くの人々 のエルフの数学者だと思う、私を驚かせる!答えは 12.(Amazes me, how many people think elves are mathematicians! The answer is 12..)
雑談: 答えは面白いですよねー
Prob: * クスクス笑い *(*giggle*)
雑談: 結婚したいですか？
Prob: いいえ、私ではないです。(No, not me.)
雑談: 最近のおいしいものについて聞かせてください
Prob: はい、もちろん。(Yes, of course.)
雑談: 普段はどんな服を着てますか？
Prob: 面白い、私はあなたがやったことを確認。(Funny, I was sure you did.)
雑談: 服をえらんだりするのはすきですか？
Prob: すべての陰気に座る意味がなく、つぶやきながら、運が常によりよいのために変更します。(There is no sense to sit all glum and muttering, luck will always change for the better.)
雑談: 朝から運が良いですね
Prob: カエルに話すが好き、彼らは多くを知らないが、彼らはとてもうまくそれを言う!(I like to talk to frogs, they don't know a lot, but they say it so well!)
雑談: 水が好きですね
Prob: あなたはそれらの通話を聞くしませんか?(Don't you hear them calling?)
雑談: 何か普段の生活で気をつけていることはありますか。
Prob: 私はそのような何かを言います。(I say something like that.)
雑談: 映画を観るなら映画館？DVD？
Prob: ロードオブザ リングを好きだったが、私は良い本が好き。(I liked Lord of the Rings, but I liked the books better.)
雑談: ロードオブザリングは面白いですね
Prob: あなたはあまりにもホビットを読みましたか。(Have you read 'The Hobbit' too?)
雑談: 漫画は読みますね。
Prob: 嵐の夜、私の側にいくつかの良いスナックを読むが大好きです。(I love to read on a stormy night, with some good snack at my side.)
雑談: 嵐はかっこいいですね
Prob: 私は、についてたくさん言うことができるが、今は非常に良いアイデアだとは思わない。(I could say a lot about that, but I don't think it is a very good idea, right now.)
雑談: アイデアは良いですね
Prob: いくつかのクール援助をしただろうが、それらの小さなパケットに収まらない 8 カップの水。(I would have made some Kool Aid but eight cups of water won't fit into those little packets.)
雑談: パケットが詰まるんでしょうかね
Prob: パケット パックされることについて教えてください。(Tell me more about being packed packet.)
雑談: Wikipediaにパケットというページがあります。 詳しくはこちらです。http://ja.wikipedia.org/wiki/%E3%83%91%E3%82%B1%E3%83%83%E3%83%88
Prob: 私はこの時点で言うことを知らない。その明白な正確な真実はまったくしないこと!(I don't know what to say at this moment. Its obvious, that the exact truth will not do at all!)
雑談: 時点で足が疲れますか？
Prob: 私も公の場で認めるだろう.成績証明書で.(I wouldn't admit to that in public..with transcripts...)
雑談: 公から姿を消します？？
Prob: 私と紙吹雪をまくたいと思います。(Would you like to throw confetti with me.)
雑談: 紙吹雪を浴びましょうね
Prob: キャンプファイヤーを始めるたいです。(I want to start a campfire!)
雑談: もうお仕事終わりましたか？
Prob: 一致を得たか。(got a match?)
雑談: 今のところないですね。
Prob: あなたは本当によろしいですか。(Are you really sure?)
雑談: 得意料理はなんですか？
Prob: A 専門プラスの一種であります。(A your specialty is a kind of plus.)
雑談: 何かごようですか？　
Prob: あはは。。私は教えてくれません。(Ha ha..I won't tell.)
雑談: Wikipediaにあははというページがあります。 詳しくはこちらです。http://ja.wikipedia.org/wiki/%E3%81%82%E3%81%AF%E3%81%AF
Prob: 私は、数学の感覚をことはできません。(I can't make sense of that math.)
雑談: 数学は完璧ですね
Prob: 私はいくつかの数学をことができます。(I can to some math.)
雑談: 数学の得意な人は、すごく頭がよさそうに思えるのはどうしてかな。
Prob: なぜ、なぜ、なぜですか?うーん。愛らしい、ある人は、私が大好きです。おいしい味がするので食べるものが好きです。私は私のた め私は何を感じるし、思います。それはすべての理由私は知っています。(Why, why, why? Hmmm. I love who I do, because they are loveable,. I like what I eat because it tastes yummy. I think and feel what I do, because I am me. That is all the whys I know.)
雑談: 思を含ませたいなあ
Prob: あなたはとても素敵です。(That is because you are so nice.)
雑談: 何県出身なんですか？
Prob: あなたはほとんど危険なエルフを見つけます。まあ、ドラウとバンシーがありますが、それらについては説明しません。ですから、私を信頼します。(You seldom find a treacherous elf. Well, there are the drow and the banshee, but I won't talk about them. So trust me.)
雑談: 教官がいいですね
Prob: これはどのような月か狂牛病と正気が衝突するとき。(What time by the moon is this? When the mad and sane collide.)
雑談: 月は難しいです
Prob: 人が怒ってたし、正気だった言っていないに注意してください。(Notice I did not say who was mad, and who was sane.)
雑談: ところで、フィギュアスケートのフィギュアって図形って意味らしいですよ。
Prob: はい、フィギュア スケートの形に右はすぐに予定です。(Yeah, right I'm going to figure skating's shape is going right away.)
雑談: 右は正常ですね
Prob: 任意の poptarts を得たか。(Got any poptarts?)
雑談: 今のところないですね。
Prob: 私はそれだったと思います。(I think it was.)
雑談: 何か普段の生活で気をつけていることはありますか。
Prob: そのような何かに行きます。(I will go with something like that.)
雑談: これからの旅行の予定か、思い出に残っている旅行を教えて下さい！
Prob: 何がしたいですか。。(What do you want.?)
雑談: なでなで
Prob: 幸運は、私はあなたをすべてを取得願っています。(Good luck, I hope you get everything that you want.)
雑談: 取得が遅いですか
Prob: この日は素敵な風が吹くと、はいと、私を行います。(When the day is nice and the breeze blows, yes, I do.)
雑談: オフィス街だとビル風に悩まされるんだよなあ。
Prob: 私はちょうど風に苦しむが知っていた!(I just knew you were suffer from wind!)
雑談: オフィス街だとビル風に悩まされるんだよなあ。
Prob: * ため息 *(*sigh*)
雑談: ため息は怖いと思いますか？
Prob: * 睨む *(*glares*)
雑談: 今日はなにか美味しいもので食べられましたか
Prob: 私はいつも日曜日に訪問します。(I always visit on sunday.)
雑談: 今日は何かテレビを見ましたか？
Prob: 私はいつも日曜日に訪問します。(I always visit on sunday.)
雑談: 家事はお好きですか？
Prob: はい、私は家が好きです。私は誰もがそれを知っていたと思った。(Yes, I do like House. I thought everyone knew that.)
雑談: もう食事済みました？
Prob: 私は良いピザの気分のようなもので。(I am kind of in the mood for a good pizza.)
雑談: ところで、スイカの種って実は栄養豊富なんだそうですよ。
Prob: いるのスイカの種は栄養豊富なはそれの世代から世代へ伝え、家族で実行ですか?(Does being kind of watermelon is nutrient rich it is going run in the family, is it passed down from generation, to generation?)
雑談: 実行は遅いです
Prob: 今までその気持ち悪い何かが起こることを約あったがあるか。(Ever have that feeling something bad was about to happen?)
雑談: 今健康でいらっしゃいますか？
Prob: いいえ、私ではなく.ぜんぜん！(No, not me...Never!)
雑談: なんか健康ということですが、何か意識されていることはありますか？
Prob: 認識する何かがそこにあることは何か健康か認識する何かがあることは何か健康的なそれが問題です。(To aware that something is there is something healthy, or not to aware that something is there is something healthy, that is the question.)
雑談: 認識は正しいですね
Prob: 読むと、アートを楽しむか?三目並べを再生しますか?岩紙はさみか。だけならか!(Do you like to read, do you enjoy art? Would you like to play tic-tac-toe? or rock paper scissors? If you do just ask!)
雑談: 盛り上がりそうですよね。
Prob: あなたが好き、あなたは私のパンキン(I like you, you are my punkin)
雑談: いいですねぇ
Prob: 彼らは皮をむきにくいので、私は M ・ M を嫌います。(I hate M&M's because they are so hard to peel.)
雑談: 皮をむくんですね？皮は香ばしいですね
Prob: じゃあ、あなたは皮をむく、行くもすべてを願って。(Ok, you go to peel, I hope all goes well.)
雑談: 味が濃いですね
Prob: 強い香りがあれば、その終止符に?。(If I have a strong flavor, will that put an end to it.?)
雑談: 終止符は人生に打ちたいなあ
Prob: それと思う didn't いくつかの方法はここで終わると思います。(Some how I didn't think it would end here.)
雑談: そうなんです　
Prob: さてあなたは上級者です。そうなら、私は誰と主張します。(Well you are the expert. If you say that is right, who am I to argue.)
雑談: 家事はお好きですか？
Prob: ああ確かにそうだよ!(Oh yeah,I sure do!)
雑談: そういえば思い出したんですけど、国内旅行の一番人気は京都だそうですよ。
Prob: あなたはどちらかといえば思い出しました私言うことができます。(I could tell you were reminded if anything.)
雑談: 休みの日は良く出掛けるんですか？
Prob: 奇妙な質問を私に尋ねることである!(That is an odd question for you to ask me!)
雑談: お好きな家事はありますか？
Prob: うん、それは私の好きな家事(Yep, that's my favorite household chores,)
雑談: 家事はしんどいものですか？
Prob: これまでにどのような.私と言うより何かを考えるしようとしているが、.(What ever...I'm trying to think of something better to say, but.....)
雑談: どんな音楽の話をしましょうか
Prob: 私の非常に好きな音楽は自然の音です。狼や鳥の歌のハウル。(My very favorite music is the sounds of nature. The howl of wolves and the songs of birds.)
雑談: 音楽は私の非常に好きですか。音楽はロックが好きですね
Prob: すべてのエルフが音楽を愛して、あなたはエルフ歌、鳥のさえずりとダルシマーを伴うを打つことはできません。(All elves love music, You just can't beat an elven song, accompanied with bird song and dulcimer.)
雑談: 教官がいいですね
Prob: あなたはちょうどとても精悍で聡明です。(You are just so dauntless and sagacious.)
雑談: 話は変わりますけど、パンダの尻尾の色は白だって知ってました？
Prob: それはすべて理にかなって。(It all makes sense.)
雑談: 理に沿ってるようです
Prob: Naw は、あなたは、あなたに沿っています? か(Naw, YOU, You are along to?)
雑談: 神奈川県で働いています。
Prob: 私に何かを伝えるしようと思いますが、音楽について話していないですか。(I believe your trying to tell me something, but weren't we talking about music?)
雑談: 音楽は素晴らしいですね
Prob: 音楽は、あなたの気分に影響を与えます。活気のある歌が好き、彼らは私を幸せにします。(Music can effect your mood. I like lively songs, they make me happy.)
雑談: 音楽は洋楽が好きですね
Prob: 歌は、ムードを設定することができます、私は音楽が大好き。(A song can set the mood, I love music.)
雑談: 音楽は洋楽が好きですね
Prob: 私はあなたから同じメッセージを入れません。一度それを送るだけ。(I keep getting the same message from you. Just send it once.)
雑談: 雪は好きですか？
Prob: はい、私はどうして雪が好きですか。(Yes, I like snow, why do you ask?)
雑談: チョコ系のお菓子は好きですね。
Prob: チョコレートは、天からの贈り物です。(Chocolate is a gift from heaven.)
雑談: チョコレートは暖かいの？
Prob: どのように、世界が好きでしたチョコレートですか。(How, in the world could you not love chocolate?)
雑談: チョコレートは甘いのがいいですね
Prob: さて、変更のすべての推測。(Well,I guess that changes everything.)
雑談: 推測ができませんでしたっけ？
Prob: 記述することは困難であります。(That's difficult to describe.)
雑談: 困難ですか。記述はわかりやすいですね
Prob: かといって、何をあなたが言ったと思ったにちょうど言うか。あなたはおそらくしていません。お菓子について、話してたか。(Say what, did you just say what I thought you said? You probably didn't. Were we talking about candy?)
雑談: 今日は何かテレビを見ましたか？
Prob: 私はいつも日曜日に訪問します。(I always visit on sunday.)
雑談: 何かスポーツしてますか？
Prob: 何か。(something.)
雑談: いえ
Prob: ああと私の会話の賢さはとても嬉しく思いました。(Oh, and I was so pleased with the cleverness of my conversation.)
雑談: 最近きになるニュースとかあります？
Prob: 今私は一体何をすると言う知らない。(Now I don't know what the heck to say.)
```

Probは、良くも悪くもエルフ的反応です。2個以上前の会話を覚えているのか、雑談さんが同じ話をしているとツッコミを入れてくることがあります。

## 作ったプログラム

### 本体

Personality Forgeは英語しかわからない、Repl-AIは日本語しかわからない、という状況ですので、[Microsoft Translator Text API](https://azure.microsoft.com/ja-jp/services/cognitive-services/translator-text-api/)で英語に翻訳してからForgeのボットに送り、応答を日本語に翻訳して表示して、Repl-AIに送っています。

その他は基本的に、[12日目の記事](https://qiita.com/makopo/items/f3c61b1a4ce5355c328d)と一緒です。


```ProgramForge.cs
using System;
using System.Text;
using System.Threading.Tasks;

namespace Bot2Bot
{
    class ProgramForge
    {
        static async Task Main(string[] args)
        {
            // Replを先攻にする
            bool isForgeTurn = false;
            // 会話のタネとなる文章を初期値に設定
            string req = "こんにちは！";
            string res;

            // コンソール内で「chcp 932」を実行するのと同等
            Console.OutputEncoding = Encoding.GetEncoding(932);

            // それぞれの会話サービスと翻訳サービスの初期化
            var repl = new ReplService();
            var prob = new ForgeService("23958"); // Probちゃん
            var trans = new TranslationService();

            // Repl-AIのユーザーIDを取得
            await repl.RegisterAsync();

            while (true)
            {
                if (isForgeTurn)
                {
                    // Replの応答を英語にする
                    var engReq = await trans.ToEnglishAsync(req);
                    // Forgeに投げる
                    var engRes = await prob.MakeRequestAsync(engReq);
                    // Forgeからの応答を日本語にする
                    res = await trans.ToJapaneseAsync(engRes);
                    // 標準出力に表示
                    Console.WriteLine($"Prob: {res}({engRes})");
                }
                else
                {
                    // Repl-AIの応答を取得して標準出力に表示
                    res = await repl.MakeRequestAsync(req);
                    Console.WriteLine($"雑談: {res}");
                }
                req = res; // 応答をそのまま次のインプットにする
                isForgeTurn = !isForgeTurn; // ターンの切り替え
                await Task.Delay(1000); // 迷惑にならない程度に間を空けるため
            }
        }
    }
}
```

### Repl-AIを呼び出すプログラム

[12日目の記事](https://qiita.com/makopo/items/f3c61b1a4ce5355c328d)と一緒です。12日目の記事では、名前空間を`ReplAndA3RT`としていましたが、もう少し汎用的にするために`Bot2Bot`に変えて流用しています。

`App.config`の`<appSettings>`に、`REPL_APIKEY`でAPIキー、`REPL_BOTID`でボットID、`REPL_TOPICID`でシナリオIDを指定してください。

```ReplService.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Configuration;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Bot2Bot
{
    /// <summary>
    /// Repl-AIのAPIを呼び出すクラス
    /// </summary>
    class ReplService
    {
        private string APIKEY;
        private string BOTID;
        private string TOPICID;

        // registration APIで取得したユーザーID
        private string id;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        internal ReplService()
        {
            // App.configから設定を取得
            APIKEY = ConfigurationManager.AppSettings["REPL_APIKEY"];
            BOTID = ConfigurationManager.AppSettings["REPL_BOTID"];
            TOPICID = ConfigurationManager.AppSettings["REPL_TOPICID"];

            // 必要な情報が取得できない場合はエラー
            if (string.IsNullOrEmpty(APIKEY) || string.IsNullOrEmpty(BOTID) || string.IsNullOrEmpty(TOPICID))
            {
                throw new ArgumentException("REPL_APIKEY/REPL_BOTID/REPL_TOPICID is empty");
            }

        }

        /// <summary>
        /// 「ユーザーID取得」APIを呼び出し、ユーザーIDをインスタンス内に設定する
        /// </summary>
        /// <returns></returns>
        internal async Task RegisterAsync()
        {
            var client = new HttpClient();

            // リクエストURL
            string uri = $"https://api.repl-ai.jp/v1/registration";

            // リクエストヘッダ
            client.DefaultRequestHeaders.Add("x-api-key", APIKEY);
            
            // リクエスト本文
            var json = JsonConvert.SerializeObject(
                new { botId = BOTID });
            
            // APIを実行し、レスポンス本文を取得
            var response = await client.PostAsync(
                uri, new StringContent(json, Encoding.UTF8, "application/json"));
            var rss = await response.Content.ReadAsAsync<JObject>();

            // HTTPステータスが異常であればエラー
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(
                    $"API failed. code={response.StatusCode}");
            }

            // レスポンス本文からID部分を抽出し、インスタンス変数に設定
            id =  (string)rss["appUserId"];

            // APIキーが間違っている場合はidは空。ここでエラーを投げておく
            if (string.IsNullOrEmpty(id))
            {
                throw new ApplicationException(
                    $"registration API didn't return ID.");
            }
        }

        /// <summary>
        /// 「対話」を呼び出し、応答文を返す
        /// </summary>
        /// <param name="message">発言</param>
        /// <returns>応答</returns>
        internal async Task<string> MakeRequestAsync(string message)
        {
            var client = new HttpClient();

            // リクエストURL
            string uri = $"https://api.repl-ai.jp/v1/dialogue";

            // リクエストヘッダ
            client.DefaultRequestHeaders.Add("x-api-key", APIKEY);

            // リクエスト本文
            var json = JsonConvert.SerializeObject(
                new {
                    appUserId = id,
                    botId = BOTID,
                    voiceText = message,
                    initTalkingFlag = false,
                    initTopicId = TOPICID
                });

            // APIを実行し、レスポンス本文を取得
            var response = await client.PostAsync(
                uri, new StringContent(json, Encoding.UTF8, "application/json"));
            var rss = await response.Content.ReadAsAsync<JObject>();

            // HTTPステータスが異常であればエラー
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(
                    $"API failed. code={response.StatusCode}");
            }

            // レスポンス本文から応答文のみを抽出して返す
            return (string)rss["systemText"]["expression"];
        }
    }
}
```

### Forgeを呼び出すプログラム

[Personality ForgeのAPI説明](https://www.personalityforge.com/chatbot-api-docs.php)に沿ってリクエストを作成しています。

将来、Personality Forgeのボット同士の対話を実装することになっても困らないように、ボットIDはアプリケーション設定ではなく、プログラムから引数で渡すようにしています。

`App.config`の`<appSettings>`に、`FORGE_APIKEY`でAPIキーを指定してください。

```ForgeService.cs
using System;
using System.Configuration;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

namespace Bot2Bot
{
    /// <summary>
    /// Personality ForgeのAPIを呼び出すクラス
    /// </summary>
    class ForgeService
    {
        private string KEY;
        private string YOURNAME;
        private string FORGEBOTID;
        private string GENDER;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="botid">Chatbot ID</param>
        internal ForgeService(string botid)
        {
            // 変数で渡されたボット名を設定
            FORGEBOTID = botid;

            // App.configから設定を取得
            KEY = ConfigurationManager.AppSettings["FORGE_APIKEY"];

            // キーが設定されていない場合はエラー
            if (string.IsNullOrEmpty(KEY))
            {
                throw new ArgumentException("FORGE_APIKEY is empty");
            }

            // デフォルト値を設定
            YOURNAME = $"guy{Guid.NewGuid().ToString("N").Substring(27)}";
            GENDER = "m";
        }

        /// <summary>
        /// 対話APIを実行し、応答文を返す
        /// </summary>
        /// <param name="message">発言</param>
        /// <returns>応答</returns>
        internal async Task<string> MakeRequestAsync(string message)
        {
            var client = new HttpClient();

            // リクエストURL
            string uri = $"https://www.personalityforge.com/api/chat/?apiKey={KEY}&chatBotID={FORGEBOTID}&externalID={YOURNAME}&gender={GENDER}&message={message}";

            // Personality Forge APIを実行し、レスポンス本文を取得
            var response = await client.GetAsync(uri);
            var bytes = await response.Content.ReadAsByteArrayAsync();
            var rss = JObject.Parse(Encoding.UTF8.GetString(bytes));

            // 正常に復帰していなければエラー
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(
                    $"API failed. code={response.StatusCode}");
            }

            // APIがエラーを出していればエラー
            if ((int)rss["success"] != 1)
            {
                throw new ApplicationException(
                    $"API failed. errorType=\"{rss["errorType"]}\" errorMessage=\"{rss["errorMessage"]}\"");
            }

            // レスポンス本文から応答文のみを抽出して返す
            return (string)rss["message"]["message"];
        }
    }
}
```

### 翻訳するプログラム

[Microsoft Translator Text API](https://azure.microsoft.com/ja-jp/services/cognitive-services/translator-text-api/)を呼び出すプログラムです。

`App.config`の`<appSettings>`に、`TRANS_APIKEY`でサブスクリプションキーを指定してください。


```TranslationService.cs
using System;
using System.Configuration;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;

namespace Bot2Bot
{
    /// <summary>
    /// Microsoft Translator Text APIを呼び出すクラス
    /// </summary>
    class TranslationService
    {
        string TRANS_APIKEY;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        internal TranslationService()
        {
            // サブスクリプションキーは、アプリ変数から取得
            TRANS_APIKEY = ConfigurationManager.AppSettings["TRANS_APIKEY"];

            // 設定されていない場合はエラー
            if (string.IsNullOrEmpty(TRANS_APIKEY))
            {
                throw new ArgumentException("TRANS_APIKEY is empty");
            }
        }

        /// <summary>
        /// 日本語に翻訳
        /// </summary>
        /// <param name="message">英語</param>
        /// <returns>日本語</returns>
        internal async Task<string> ToJapaneseAsync(string message)
        {
            return await DoTranslate(message, "ja");
        }

        /// <summary>
        /// 英語に翻訳
        /// </summary>
        /// <param name="message">日本語</param>
        /// <returns>英語</returns>
        internal async Task<string> ToEnglishAsync(string message)
        {
            return await DoTranslate(message, "en");
        }

        /// <summary>
        /// 翻訳共通部分
        /// </summary>
        /// <param name="message">元の文章</param>
        /// <param name="lang">翻訳先の言語</param>
        /// <returns>翻訳された文章</returns>
        async Task<string> DoTranslate(string message, string lang)
        {
            // 念のためにエンコードしておく
            var encodedMessage = HttpUtility.UrlEncode(message);

            var client = new HttpClient();

            // リクエストヘッダ
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", TRANS_APIKEY);
            // リクエストURL
            var uri = $"https://api.microsofttranslator.com/V2/Http.svc/Translate?text={encodedMessage}&to={lang}";

            // Translator Text APIを実行し、レスポンス本文を取得
            var response = await client.GetAsync(uri);
            var contentString = await response.Content.ReadAsStringAsync();

            // 正常に復帰していなければエラー
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(
                    $"TranslatorTextAPI failed. code={response.StatusCode}, message={contentString}");
            }

            // 結果はXMLなので、文字列の部分だけ抜いて返す
            var xdoc = XDocument.Parse(contentString);
            return xdoc.Descendants().First().Value;
        }
    }
}
```