過去クールアニメ紹介botで黄金時代を懐古する。


#はじめに
小学4年生の頃、深夜アニメにハマった。
たまたま深夜に起きた時にやっていたあずまんが大王をきっかけに、あらゆる深夜アニメを見た。
夕方にやっているアニメとは少し違う。シュールな笑い、ダークな世界観、ドロドロ人間模様。
深夜アニメを見て多くのことを学んだ。
2000年代のアニメは少女期の私の情操教育に大きく寄与していたと思う。
(その代わり第二次成長と学校の成績は完全に終わった。)

あれから20年。
16時には学校が終わって、寝不足の疲れもなく元気に遊んで過ごしていた小学生は
22時まで仕事をして、帰ってすぐご飯をかきこんで泥のように寝る社会の歯車になった。
アニメを見る時間がない。新しいアニメなんてもう長いことリアタイしてない。
機械的に録画していた前期アニメも、見ることがないまま溜まっていく。

過去、アニメオタクだった自負はある。
だけど、今は新しいアニメを受け入れる時間とキャパがない。
老いた。

今は専ら「あの頃のアニメは楽しかったな」と過去を懐かしむばかり。
ふと、「当時って他にどんなアニメがやってたっけ」と思った。
そんな時に、こんなAPIを見つけた。

https://github.com/Project-ShangriLa/sora-playframework-scala

年度とクールに分けて当時放送していたアニメの情報を返してくれるAPI。
範囲は2014年以降かららしい。私の黄金期は2001年～2011年頃なので、ちょっと残念。
（今後随時データ登録予定ではあるとのこと。期待。）
面白そうなので、これを使って過去を懐古しようと思う。


#やったこと
・年度/クール別に当時放映していたアニメの情報を取得する
　〇取得する項目
　　-タイトル
　　‐タイトル略称
　　-公式サイトURL
　　-TwitterURL
・LINE BOTを作り、対話形式で応答させる

名付けて、<b>懐古厨向けアニメ紹介bot！！</b>(そのまま)

#環境
・Visual Studio Code
・JavaScript
・Node.js


#サンプルコード
ユーザーが入力するのは「年度とクールを合わせた数字」。
年度はyyyy、クールは01：春、02：夏：03：秋、04：冬としている。
つまり2016年夏クールのアニメを知りたい場合は、「201602」と入力する。
そのユーザーの入力値を分けて、パラメータを定義した。(冗長な書き方・・・）

<details><summary>長いのでセクションに</summary><div>

```php
'use strict';

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル（秘匿情報）です。
const config = {
    channelSecret: '省略',
    channelAccessToken: '省略'
};


    // ########## ▼▼▼ サンプル関数 ▼▼▼ ##########
    const sampleFunction = async (event) => {
        let yearseason = ''

        switch (event.message.text) {
            case '201401':
                yearseason = '2014/1';
                break;
            case '201402':
                yearseason = '2014/2';
                break;
            case '201403':
                yearseason = '2014/3';
                break;
            case '201404':
                yearseason = '2014/4';
                break;
            case '201501':
                yearseason = '2015/1';
                break;
            case '201502':
                yearseason = '2015/2';
                break;
            case '201503':
                yearseason = '2015/3';
                break;
            case '201504':
                yearseason = '2015/4';
                break;
            case '201601':
                yearseason = '2016/1';
                break;
            case '201602':
                yearseason = '2016/2';
                break;
            case '201603':
                yearseason = '2016/3';
                break;
            case '201604':
                yearseason = '2016/4';
                break;
            case '201701':
                yearseason = '2017/1';
                break;
            case '201702':
                yearseason = '2017/2';
                break;
            case '201703':
                yearseason = '2017/3';
                break;
            case '201704':
                yearseason = '2017/4';
                break;
            case '201801':
                yearseason = '2018/1';
                break;
            case '201802':
                yearseason = '2018/2';
                break;
            case '201803':
                yearseason = '2018/3';
                break;
            case '201804':
                yearseason = '2018/4';
                break;
            case '201901':
                yearseason = '2019/1';
                break;
            case '201902':
                yearseason = '2019/2';
                break;
            case '201903':
                yearseason = '2019/3';
                break;
            case '201904':
                yearseason = '2019/4';
                break;
            case '202001':
                yearseason = '2020/1';
                break;
            case '202002':
                yearseason = '2020/2';
                break;
            case '202003':
                yearseason = '2020/3';
                break;
            case '202004':
                yearseason = '2020/4';
                break;
            case '202101':
                yearseason = '2021/1';
                break;
            case '202102':
                yearseason = '2021/2';
                break;
            case '202103':
                yearseason = '2021/3';
                break;
            case '202104':
                yearseason = '2021/4';
                break;            
                            
                default:
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: '年代とクールの数字を入れてね' + '\n' + '春:01、夏:02、秋:03、冬:04'
                + '\n' + '例：2016年春アニメ→201601' });
            }
        
        // 「リプライ」を使って先に返事しておきます
            await client.replyMessage(event.replyToken, {
                type: 'text',
                text: 'その時期の人気アニメはこんな感じだったよ'
                });
        
            let pushText = '';
        
            try {
        // axiosAPIを叩きます（少し時間がかかる・ブロッキングする）
        const res = await axios.get('http://api.moemoe.tokyo/anime/v1/master/' + yearseason);
            for (let i = 0; i < 3; i++) {
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: imgurl + res.data[i].public_url,
        }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【タイトル】' + '\n' + res.data[i].title + '\n' + '【略称】' + '\n' + res.data[i].title_short1,
            }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【公式サイト】' + '\n' + res.data[i].public_url,
            }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【Twitter】' + '\n' +  'https://twitter.com/' + res.data[i].twitter_account,
            }); 
            }

        } catch (error) {
        pushText = '検索中にエラーが発生しました。';
        // APIからエラーが返ってきたらターミナルに表示する
        console.error(error);
        }
        
        } 

    // ########## ▲▲▲ サンプル関数 ▲▲▲ ##########

// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}

// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  
  // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します（なくてもよい）
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  } else {
    // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
    console.log('受信しました:', req.body.events);
  }

  // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
  // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
  Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

```
</div></details>


#挙動
実際の動きがこちら。背景とキーボードの主張が強くなってしまった・・・
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">どばば〜って出ちゃうの見にくいし改善したい。でも時間が本当にない...<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/linedc?src=hash&amp;ref_src=twsrc%5Etfw">#linedc</a> <a href="https://t.co/n8ATfFkPti">pic.twitter.com/n8ATfFkPti</a></p>&mdash; shigeyuri (@sgyr_Xedge) <a href="https://twitter.com/sgyr_Xedge/status/1451058967677861888?ref_src=twsrc%5Etfw">October 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#改善を模索
①画像を出したい
ばばっと出るんだけど、なんか見にくい。
画像が出せるとよいのではと思い、画像URLをどうにか作れないか試行錯誤した。

http://capture.heartrails.com/

↑URLを入力するとサイトのサムネイル画像を作ってくれるサイト

作成されたURL↓と画像(サンプル)
http://capture.heartrails.com/200x150/round?http://www.google.co.jp/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2155038/015969e0-efa3-6112-612b-9ff6973558f9.png)

このサイトURL＋公式サイトURLをつけるだけならいけるのでは？と思って以下を実装。
(一部抜粋)

```php
        // axiosAPIを叩きます（少し時間がかかる・ブロッキングする）
        const res = await axios.get('http://api.moemoe.tokyo/anime/v1/master/' + yearseason);
    ★追加→  const imgurl = 'https://capture.heartrails.com/200x150/cool?';
            for (let i = 0; i < 3; i++) {
        await client.pushMessage(event.source.userId, {
                type: 'image',
                previewImageUrl: imgurl + res.data[i].public_url,
        }); 

```

結果は…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2155038/60325c93-77c3-2754-bc14-197fe2e2ec17.png)
めっちゃ叱られた('-`)

https://developers.line.biz/ja/reference/messaging-api/#image-message
LINEのMessagingAPIの仕様として、画像を返す場合の指定はjpgかpng指定にしなきゃだから
当然こんなコードが受け入れられるわけがなかった。
そもそもサムネ画像自体勝手にとって出していいかも微妙だったので、あきらめた。


②量が多い。
期ごとに指定しても、アニメの数がすごく多い。
「当時の人気アニメはこれだよ」とか返してるけど、
実際は

```php
            for (let i = 0; i < 3; i++) {
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: imgurl + res.data[i].public_url,
        }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【タイトル】' + '\n' + res.data[i].title + '\n' + '【略称】' + '\n' + res.data[i].title_short1,
            }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【公式サイト】' + '\n' + res.data[i].public_url,
            }); 
        await client.pushMessage(event.source.userId, {
                type: 'text',
                text: '【Twitter】' + '\n' +  'https://twitter.com/' + res.data[i].twitter_account,
            }); 
            }

        } catch (error) {
        pushText = '検索中にエラーが発生しました。';
        // APIからエラーが返ってきたらターミナルに表示する
        console.error(error);
        }       
        } 
```
<b>実際は取得してきた一覧を上から3つ選んで出してるだけ。</b>
しょぼい…

同じ人が作ってるもので、対象アニメのTwitterアカウントの情報を取得できるAPIがあった。

https://github.com/Project-ShangriLa/sana_server

対象のTwitterアカウントのフォロワー数が取得できるので、
このフォロワー数によるソートで「当時の人気アニメ」を語れるのではないかと思う。
・・・けど、10/21現在、まだ実装途中なので、ここは追々更新予定。。。


#まとめ(感想)
他のAPIも使えばもっと情報量が多く、見やすい紹介ができる気がする。
YoutubeのAPIを使用して、タイトル検索で引っかかった動画をあわせて送るとか。

テストしている中で、「うわあああああ懐かしい！！！！」ってなったり
Twitterが細々と更新されているのを知れたり、当時の自分を思い返しながら主題歌とかも聞いちゃったりして、結構楽しかった。久しぶりにアニメ語りしたい気になった。


とにかく今回は本当に難産で、まとめるのにすっごく時間がかかってしまった…。
面白そうなAPIは沢山あったけど、それを使って何したいかっていうのが思いつかない。
やってみたいことを考える、ということ自体も中々難しいもんだなと思った。

久しぶりに人生の教科書、Gungrave(2003～2004放送　全26話)を見て寝よう。
Gungraveはいいぞ。