【誰でも導入可】ShadowverseのnoteをDiscordに投げてくれるbotを作ってみた。※追記,機能追加済

はじめまして。Shadowverse初心者の是祈途です。
今回はShadowverseの情報が多く投げられているnoteに記事が投稿された際にDiscordに投げてくれるbotを作ってみました。
完全無料で動きます。

少し手間ですが誰でもコピペだけでサーバーに実装できるので気になる方はぜひ読んでみてください!!(最悪自分に連絡いただければ実装まで行います)
それではどうぞ!!

追記 20240409:ミュートするユーザーについての追加やミュートするハッシュタグなどについて修正、追加を行いました。

稼働イメージ

こんな感じでdiscordに投稿してくれます

実際にどのように動いてるか見たい方はこちらからどうぞ。


実装!!※コードについて変更済

※PCでの作業をお勧めします!
まずはhttps://script.google.com/home

に飛んでみてください。
その後左上の新しいプロジェクトをクリックします


何もやったことのない方であれば表示されるプロジェクトはないはずです。

その後、歯車マークを押し"「appsscript.json」マニフェスト ファイルをエディタで表示する"のチェックボックスをオンにします。


タイムゾーンが画像と違う表示でも問題ないです

その後左側の<>マークを押しエディタを開きappsscript.jsonを開きます

マウスむずかしい

その後表示されているコードをすべて削除し以下のコードをコピーし貼り付けてください。

{
  "timeZone": "Asia/Tokyo",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "Parser",
        "version": "8",
        "libraryId": "1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw",
        "developmentMode": true
      }
    ]
  }
}

次にappsscript.jsonの下のコード.gsをクリックし表示されているコードをすべて削除した後以下のコードをコピーし貼り付けます。

function myFunction(){
  const cache = CacheService.getScriptCache();
  const discordWebHookURL = "URL";//ここにwebhookURLを貼る

  mute_user_list = ["grudgeso"];//ミュートしたいユーザーネームをまとめるリスト
  mute_hashtag_list = ["#シャドウバースエボルヴ","#Shadowverse_EVOLVE"];//ミュートしたいハッシュタグをまとめるリスト


  let send_list = new Array;
  let bf_send_list = cache.get("send_list")
  console.log(bf_send_list)
  if(bf_send_list != null && bf_send_list != ""){
    bf_send_list = bf_send_list.split(",")
    bf_send_list.forEach(item =>{
      send_list.push(item)
    }
    );    
  } 
  
  getNote("https://note.com/hashtag/shadowverse?f=new&paid_only=false","key1").forEach(item => {//URL,keyを()の中にかくこの3行を増やすと反応する#が増える。
      send_list.push(item);
    });
  
  getNote("https://note.com/hashtag/%E3%82%B7%E3%83%A3%E3%83%89%E3%83%90?f=new&paid_only=false","key2").forEach(item => {
      send_list.push(item);
    });
  getNote("https://note.com/hashtag/2pick?f=new&paid_only=false","key3").forEach(item => {
      send_list.push(item);
    });
  getNote("https://note.com/hashtag/%E3%82%B7%E3%83%A3%E3%83%89%E3%82%A6%E3%83%90%E3%83%BC%E3%82%B9?f=new&paid_only=false","key4").forEach(item => {
      send_list.push(item);
    });
    
  let last_list = send_list.filter((item, index) => send_list.indexOf(item) === index);
  console.log(send_list)
  console.log(last_list)
  for (i = 0; i < 10;i++){
    if (last_list.length > 0){
        send_switch = 0
        var article = last_list.pop()
        send_url = "https://note.com" + article
        const message = {
            "content": send_url,
            "tts": false
        }


        const param = {
            "method": "POST",
            "headers": { 'Content-type': "application/json"},
            "payload": JSON.stringify(message)
        }
        var user_name = article.substring(1,article.indexOf("/",2));
        var article_id = article.substring(article.lastIndexOf("/")+1);

        mute_user_list.forEach((value) => {
          if (user_name == value){
            send_switch += 1
          }
        })

        var hashtag_url = `https://note.com/api/v3/notes/${article_id}`;
        let jsonArticleInfo = UrlFetchApp.fetch(hashtag_url, {'method':'get'});
        var parsedData = JSON.parse(jsonArticleInfo);
          
        var hashtags = parsedData.data.hashtag_notes;
        var hashtagNames = hashtags.map(function(item) {
        return item.hashtag.name;
          }
        )

        mute_hashtag_list.forEach((value) => {
          hashtagNames.forEach((value2) => {
            if (value == value2){
              send_switch += 1
            }
          })
        })

        if (send_switch == 0){
          UrlFetchApp.fetch(discordWebHookURL,param);
      }
  }}
  cache.put("send_list",last_list,60 * 60 * 4);

}


function getNote(url,key) {//スクレイピングしてnoteのURLをとってきている
  const cache = CacheService.getScriptCache();
  let bf_url_list = cache.get(key);
  if (bf_url_list != null){
      bf_url_list = bf_url_list.split(",");
  } else{
    bf_url_list = new Array()
  }
  let url_list = new Array();
  let response = UrlFetchApp.fetch(url);
  let content = response.getContentText("utf-8");
  let texts = Parser.data(content).from('<div class="m-largeNoteWrapper__card" data-v-1e6ad5ad>').to('</div>').iterate();
  texts.forEach((value, index) => {
      let i = 0
      url_temp_l = [];
      let url_temp = "";
      let html_list = value.split("");
      html_list.forEach((value2,index) => {
          if (value2 == '"'){
              i += 1
          }
          if (i == 1){
              url_temp_l.push(value2)
          }
      })
      for (i = 1; i < url_temp_l.length; i++){

          url_temp += url_temp_l[i]
      }
      url_list.push(url_temp);
  });
  

  last_url_list = url_list.filter(item => !bf_url_list.includes(item));
  cache.put(key,url_list,60 * 60 * 4);
  
  return(last_url_list);
}

次に少しDiscordに移ります。
Botを追加したいDiscordサーバーを開きます。
そして投稿してほしいチャンネルにカーソルを合わせ歯車ボタン(チャンネルの編集)を開きます


横のバーから連携サービスを開きウェブフックを確認を押す

新しいウェブフックと書かれた青いボタンを押す

出てきた>を押してウェブフックURLをコピー


”こ”って意外と書くの難しい


名前やアイコンは好きにいじって大丈夫です。

またまた先ほどまで開いていたGAS(Google Apps Script)のエディタに戻り上から3行目の""の中にウェブフックURLをペーストしてあげます

黒い縦棒がある位置です

最後に左のバーから目覚まし時計のマークをクリックしてトリガーの設定をします。


右下の青いトリガーを追加ボタンを押して設定を下記画像と同様にし(多分最初から同じだと思います)保存
時間の間隔は複数の理由により1時間おきが最適なので1時間にしておいてください。後ろに理由を書くので気になる方はどうぞ。

通知の頻度は自由です(エラーのメールを受け取った時に連絡いただければ対応します。)

そうすると下記のような画面が表示されると思うので自分の使うアカウントを選択し下にスクロール、許可を押します。

その後上のバーにある実行ボタンを押します

今度は下のような画面が現れるので"権限を確認"を押し、同意を押します。
この際にこのアプリはGoogleで確認されていませんと画面が表示される場合がありますがその場合は左下の灰色の詳細を押してさらに安全でないページに移動を押して許可をしてください。
仰々しいですが特に問題はありません。

これで完了です!!
実行ログに実行完了が表示されず赤かったりエラーと出てる場合は連絡ください。
サーバーに呼んでいただき必要な権限をいただければ実装いたしますので是非連絡ください!!

この後にこのbotをシャドバ以外にも使う方法や特定ユーザーのミュートなど、コードの簡単な説明、参考資料を書いていきます。

反応するハッシュタグをふやそう!!

  getNote("URL","str").forEach(item => {//URL,keyを()の中にかくこの3行を増やすと反応する#が増える。
      send_list.push(item);
    });

上記コードを増やすことで反応するハッシュタグを増やせます。
URLの部分にnoteの投げてほしいハッシュタグを新着順で表示してる時のURLをいれ、strの部分に好きな単語を入れてください(この時ほかの単語と被るとうまく動きません)

ユーザーのミュート(変更済)

        if (value.includes("user_name")) {} else {//ユーザーネームを入力すると特定の人の記事は無効にする
          UrlFetchApp.fetch(discordWebHookURL,param);}

このuser_nameのところにユーザーネームを入れるとミュートができます(このユーザーがnoteを投稿してもDiscordには送られない)
増やすこともできます(多分ここまで読んでいる方なら自分でどうするかわかると思うので省略)
今回ある方をミュートしているのはその方が毎日ランダムなカードを紹介するという企画をやっていて自分の求める情報ではないのでミュートしています。
見たい方はuser_name部分を別の単語に置き換えておいてください

使いずらかったので仕組みを変えました
5行目のmute_user_listの中にミュートしたいユーザーネームを追加していただければミュートできます(ユーザーネームは記事のurlの/nの前部分です、この記事ならzekitoがユーザーネームとなっています)

ミュートするハッシュタグ(追加)

ユーザーネームと同じく6行目のmute_hashtag_listにミュートしたいハッシュタグを登録すれば流れないようになります。
エボルブ関係を標準でミュートしています。
2pickなども興味ない場合はミュートしておくといいと思います。

簡単な仕組み

かなりひどい無理やりなコードであり、もっときれいにかける方もいらっしゃると思うのですが現在の自分にとってはこれが最高なので許していただきたいです。(改善案等ありましたら教えていただけると幸いです)
当初はnoteの非公式apiを使用する予定だったのですがハッシュタグで検索するものが現在存在しなかったため直接スクレイピングし記事に飛ぶURLを持ってくるという荒業で実装しています。
そのためほかの検索ページなどで動くのかは試していないのもありよくわかっていません。
getNote関数内で上から20個の記事を読み取り、その中で前回読み取った中に存在しないURLのみを返しています。そのURLをDiscordBotが投稿してくれるという仕組みでできています。
GASは毎回変数等がリセットされてしまうためcacheに保存しています。保管期間は最長6時間らしいので60 * 60 * 4s(4時間)の保存期間で動かしています。そのためトリガーの頻度を下げすぎると毎回すべての記事を送ろうとしてうまくコードが作動しません。
また、WEBHook側にも制限があり短時間に多くの動作をさせると動かなくなってしまうので1時間に10記事までしか投稿しないようにしています。
これがトリガーの頻度を1時間にしている理由1です。これは守らなくてもただbotの動作が正常なものにならないだけなのですがもう1点あまりしてはいけない理由がありまして、それはスクレイピングをしているということです。スクレイピングのたびにページにアクセスしているのであまり頻度が高すぎるとサーバーに負荷をかけてしまうという問題も加味してトリガーを今回は1時間にしています。(正直1秒に1アクセス程度なら大丈夫な気はしますが)
appsscript.jsonはスクレイピングで使用するライブラリを自動で追加してくれるようにしています。

URLをもとにapiをたたけば投稿してくれるbotの名前とアイコンをnoteの投稿者に合わせるなども可能ですが特に意味がないのでやっていません。

最後に

シャドバを快適にプレイするためにこれからも自分の欲しいものを作っていきたいですね。今欲しいのは自分が使いやすい戦績保存アプリとratingを使いやすくする拡張機能(ホームレスさんが作っているようなもの)ですかね。これはビヨンドでレートがあるかでも作るモチベ変わってきますね(というか今期のレートさんは?)
次はポーカーの勉強用アプリを作る予定なので遠くなりそうですが

追記
思ったよりいろいろな人に使っていただき要望があったので少し仕様変更を加えました。
全然変更点に関係ないんですけどいまだにシャドバ勢の中にはhatenablogを使ってる方もいるのでhatenablogも対応させたいですね(誰かやってくれても)

参考文献(最終閲覧日省略)追記済


この記事が気に入ったらサポートをしてみませんか?