🔪

GASを使ってたすけて!もなふわすい~とる~むを集計してみた

2020/12/20に公開

前置き

「もなふわすい~とる~む Advent Calendar 2020」20日目の記事です。

前回はニワカ先輩さんの巻乃もなかとの1年を振り返ってでした。
1年間を通して巻乃もなかさんとの出会いから現在に至るまでのことが記事になっていて推しへの愛が伝わってきてとてもよかったです…!
https://note.com/kirame1011/n/naf06c5ad6146

このアドベントカレンダーに参加している他の皆さんも素敵な記事ばかりなので、是非色んな記事を見ていただいて巻乃もなかさんを知っていただけると幸いです。
https://adventar.org/calendars/5183

余談ですが昨日のツイートで僕はやられました…。かわいい…(昇天)

概要

今回はいつもお世話になっている「たすけて!もなふわすい~とる~む」をTwitter上で一日何回つぶやかれているか集計し、どれだけのMDCがたすけを求めているのか可視化できたら面白いんじゃないかと思い、挑戦してみました。

プログラム多めなので、プログラムわからん…の方々には大変申し訳無いですが、よろしくおねがいします…!

この記事は以下の記事を参考にしています。8割方はこの記事の内容をもとにしてあります。
この記事では今回やりたいことに対して増やした部分を説明していこうと思います。
https://qiita.com/nobu09/items/c940fc6e0d67ef1cbc85

環境

今回はデータの集計とスクリプトを同時に行えるGoogleAppScript(GAS)を採用しました。
開発環境以下です。

ライブラリ:clasp
IDE:webStorm
言語:TypeScript

筆者はインテリジェンスと型定義がないと不安になる人種のため、JavaScriptではなくTypeScriptからJavaScriptに変換するように方針を決め、claspを採用しました。
IDEは普段はC#のRiderを使っているため、同じJetBrainsであり使用感が変わらないWebStormを使いました。

Twitter API準備

ツイッターの検索APIを使うためには、TwitterDevelopに登録する必要があります。
まだ登録していないけどAPIを使いたい方は申請したらすぐ使えるわけではないので、早めの登録をおすすめします(筆者は以前登録していたので特に待たずにTwitterAPIを作成できたのでラッキーでした)

Create Twitter Appを押すと、作成するアプリについての説明を記入する画面が出ます。
ウェブサイトはGASでアプリケーションとして公開をした際に表示されるURLを入力しました。
このアプリはどうゆうアプリなのかを説明する項目は、最低100文字で説明する必要があります、
筆者はGoogle翻訳を使って!やりたいこと日本語から英語に翻訳して記載しました。

うまく申請が通れば、このように追加されます。

追加されたアプリを選択して、「Key Token」は、後述する設定のところで使います。

GASスクリプト

ここからは実際にスクリプトを書いていきます。

検索設定

概要

検索設定を設定していきます。
今回は1日分(0時00分から23時59分まで)のツイートを対象にしたかったので、sinceとuntilを追加しました。
これはTwitterの検索オプションであるsince(開始日)とuntil(終了日)のことです。
これを入れることで、該当の時間内に含まれているTweetを対象に検索するようにしています。

since,untilともに文字列で'YYYY-MM-DD_hh_mm_ss'の表記になります。
hh:mm:ssを入れることで、同じ日を入れても検索できるようにしておきます。
https://qiita.com/kai_kou/items/336a9d1290c13abba7e2

表記 対応
YYYY 2020
MM 12
DD 19
hh 23
mm 59
ss 59

プログラム

まずはDate型を用いて当日の0時00分00秒から23時59分59秒までのデータを作ります。

    //開始時刻
    const firstDate = new Date();
    firstDate.setHours(0);
    firstDate.setMinutes(0);
    

    //最終時刻
    const now = new Date();
    now.setHours(23);
    now.setMinutes(59);

Date型は「toLocaleDateString()」を呼ぶことで時間を文字列として取得できますが、HH/MM/SSになっているため、/を-に入れ替えます。
TypeScriptではstring.replaceは一つの文字しか置き換えられないため、すべての文字を変えるためにsplitで「/」を区切り文字とした結果を配列にし、それを「join(-)」にすることで目的であったHH-MM-SSに変換されます。

    //'00-00-00'に変換する
    const firstTimeNotation = firstDate.toLocaleDateString().split('/').join('-');
    //'23-59-59'に変換する
    const lastTimeNotation = now.toLocaleDateString().split('/').join('-');

これでツイートに時間を追加できるようになりました。

//実際は
//&since=2020-12-1_0_0_0&until=2020-12-1_23_59_59
//みたいになる
&since=' + firstTimeNotation + '&until=' + lastTimeNotation

検索結果保存

概要

取得した内容をスプレッドシートに保存していきます。

プログラム

まず今回は検索結果の内容を保存するために以下のクラスを用意しました。

class User{
    name:string;
}
class UserData{
    created_at: string;
    user: User;
    text:string;
}

検索結果はjsonで返ってくるので、パースした内容をもとにUserDataクラスに変換し、配列に保存していきます。
これで検索結果を保存することができました。

    const result = JSON.parse(search_response.getContentText());

    const userDatas:UserData[] = [];
    result.statuses.forEach(function (s:UserData){
        userDatas.push(s);
    });

スプレッドシート編

概要

スプレッドシートに保存していきます
まずテンプレートを用意します。
とりあえず名前とテキストとツイート日時だけ保存するようにテンプレートを用意します。
↓サンプル

プログラム

テンプレートのシート内容をコピーして、新しいシートを作って内容を書き込んでいきます。

const spreadSheet = SpreadsheetApp.getActive().getSheetByName('テンプレート');
    const targetSheet = spreadSheet.copyTo(SpreadsheetApp.getActive());
    targetSheet.setName(new Date(now.getFullYear(), now.getMonth(), now.getDate()).toLocaleDateString().split('/').join('-'));
    userDatas.forEach((value,index) => {
        targetSheet.getRange(2+index,1).setValue(value.user.name);
        targetSheet.getRange(2+index,2).setValue(value.text);
        targetSheet.getRange(2+index,3).setValue(new Date(value.created_at).toLocaleString());
    });

「助けて!もなふわすい~とる~む」を検索し、どなたかがつぶやいていると実際にスプレッドシートに保存されます。(プライバシーに配慮し、名前を伏せてあります)(ツイート内容でググれば出てきますが念の為…)

集計編

概要

せっかくスプレッドシートにツイート内容を保存できるようになったので、
一番たすけて!もなふわすい~とる~むが誰なのか集計してみました。
集計対象は活動を開始した11/21からアドカレ前日の12/18とします。

プログラム

集計用のユーザークラスと集計用の関数を用意します(即席なので設計ガバガバなのであしからず…)

class AggregateUser{
    name : string;
    tweetCount : number;
    constructor(name : string) {
        this.name = name;
        this.tweetCount = 0;
        this.AddTweet();
    }
    AddTweet(){
        this.tweetCount++;
    }
    IsSame(name : string) : boolean {
        return this.name == name;
    }
}

function Aggregate(){
    //全シートを対象に集計
    const activeSheetRoot = SpreadsheetApp.getActive();
    const AggregateSheets = activeSheetRoot.getSheets();
    let users : AggregateUser[] = [];
    AggregateSheets.forEach(sheet => {
        let lastRow = sheet.getLastRow();
        for (let index = 2; index <= lastRow; index++){
	    //ユーザー名は一番左なので、そこの値を見る
            const targetName = sheet.getRange(index,1).getValue();
            const targetUser = users.find(value1 => value1.IsSame(targetName));
            if(targetUser == null){
                const newUser = new AggregateUser(targetName);
                users.push(newUser);
            }else{
                targetUser.AddTweet();
            }
        }
    });
    users = users.sort((a, b) => b.tweetCount - a.tweetCount  );
    users.forEach(value =>
      console.log(value.name + "さん ツイート数:" +value.tweetCount)
    );
}

即席なのでAppScriptのコンソールのログに表示してみた結果…!

思ったより少ないような…?
正規表現を含んでなかったり、リプライが含まれていなかったりで少なく見えるだけで、実際はもっとつぶやかれている気がしますね…。
細かいところまで調べきれなかったので、今後の課題になりそうですね。

(余談ですが、IFTTTの存在を知らなかったのでこちらの記事も気になりました)
https://qiita.com/en129/items/cb17ba2056af29abad9a

実際の開発/デバッグの様子

プログラムの説明ばっかりのだったので、実際にやっていた際にスクショを撮っていたので、なんとなく雰囲気でも楽しんでいただければ幸いです。

GAS実行時にツイートの内容の取得がうまく行ったときの画像です。
うまくいくと楽しいですね!

この画像、何をしているかというと、「たすけて!もなふわすい~とる~む since:2020-11-27_0_0_0 until:2020-11-27_23_59_59」を日付毎に検索してちゃんと取得できているか確認してました。
これは後述のTipsでも説明しますが、実際につぶやかれているのに検索結果にヒットしないときに実際のTwitterで確認する際に使いました…。(これのおかげでsinceとuntilを覚えました)

Tips

ここからは本編とは関係ないですが、実装しながらやってためになったことを書いていきます。

タイムゾーン設定

自分がはまったのですが、タイムゾーンの設定を"Asia/Tokyo"にし忘れてUTC時刻で取得されてしまい、正確な時刻が取れなかったので、きちんと設定しておきましょう。

Clasp側
"timeZone":"Asia/Tokyo"を記述します。
これでGASプロジェクト側にも反映されます。
https://note.com/miraisouzoukan/n/n1dd76f67aaf9

appscript.json
{
  "timeZone":"Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

スプレッドシート側

画面上部のメニューバーから「ファイル」>「スプレッドシートの設定」を選択
「全般」タブの「タイムゾーン」の項目を「(GMT+09:00)東京」に設定

https://qiita.com/kawamurayuto/items/e98ce7f38cb572ae616a

検索

月ごとに集計しようと思ったら、ツイッター上だとつぶやいているのにAPIからだと取れない日付がありました。詳しく調べてみると、
「'https://api.twitter.com/1.1/search/tweets.json'」
だと一週間前のツイートしか取れないことがわかりました。
ここを見るとBANの可能性があるがsearch/tweets.jsonではなく、「search/universal.json」だとうまくいくらしい… 。
https://qiita.com/crane496/items/b59b6d4c401d221d80e6

デバッグ

デバッグ上に便利だったものを紹介します。

スプレッドシートを削除してくれるものです。
上記の月ごとの集計をしようとしたデータが取れなくて、これ一枚ずつ消すの…?と面倒くさくなったけどどうせ消す用のAPIがあるでしょ?と思ったらあったので使いました。
自動化最高…。
https://auto-worker.com/blog/?p=1366

また、null的扱いもtypescriptだとundefinedというものも知りました
https://typescript-jp.gitbook.io/deep-dive/recap/null-undefined

//指定した月のスプレッドシートを消す
function DeleteMonthSheet(){
    const now = new Date();
    const nowDate = now.getDate();
    for (let i = 1; i < nowDate; i++){
        const targetDate = new Date(now);
        targetDate.setDate(i);
        DeleteSheet(targetDate.toLocaleDateString().split('/').join('-'));
    }
}

//タイトルと一致したスプレッドシートを削除する
function DeleteSheet(title : string){
    const mySpreadSheet = SpreadsheetApp.getActive();
    const targetSheet = mySpreadSheet.getSheetByName(title);
    if(targetSheet == undefined){
        return;
    }
    if(targetSheet == null){
        return;
    }
    mySpreadSheet.deleteSheet(targetSheet);
}

まとめ

いかがだったでしょうか?(言いたかっただけ)

ここまでの流れをおさらいです。
以下の流れをGASを通して行いました。

  1. 日時を指定して「助けて!もなふわすい~とる~む」を検索
  2. 検索内容をスプレッドシートに保存
  3. 保存された内容を集計し、誰が何回つぶやいたかを表示

今回は「たすけて!もなふわすい~とる~む」でしたが、「#4時やんけ」でも、「俺がもなふわすい~とる~むだ!」でも、好きな言葉を集計することができるので、色々使えると思います。
実際筆者も推しのためにプログラムをかけるだけでめちゃくちゃ楽しかったので、この記事も読んでる方も自分が普段やっている活動が推しの不況にもなったりするので、おすすめです。

※私事で全然間に合わなかったのですが後日サンプルプロジェクトをgithubにあげようと思いますので、気になった方は参考になれば幸いです。

明日は「鷹乃みかんさん」の「もなふわ関係プロダクト」の記事です。
どんな記事になるのか楽しみですね。

ここまでお付き合いくださりありがとうございました。
よいもなふわすい~とる~むを。

参考

https://qiita.com/nobu09/items/c940fc6e0d67ef1cbc85

https://gaaaon.jp/blog/twitterapi

Discussion