見出し画像

【GAS】Twitterのリム通知を自作しよう

こんにちは、カキレモンです。今日はTwitterのリム通知を作ります。

リムというのは「リムーブ」、すなわちフォロワーからフォローを解除されることです。基本的に誰かにフォローされたときには通知が来ますが、逆に解除されたときには通知は来ません。プロフィール画面でフォロワー数が減っているのを見て初めて誰かにリムられたことに気づくわけです。

インターネットの力は偉大で、「リム通知」と検索すればリムられたときに通知を送ってくれるサービスがいくつも見つかります。ところが、それらのアプリ(サイト)の多くは利用するために自分のアカウントを連携させる必要があります。すべてのアプリが危険だというのはさすがに言いすぎですが、アカウントの連携を許可することは「知らない間に勝手にフォローしている・メッセージを送っている」といったトラブルに巻き込まれる可能性もあり、よく思わない人もいるかもしれません。

そこで今回は、GAS(Google Apps Script)を使って自家製リム通知を実装していきたいと思います。


ところで「誰にリムされたか」というのは本来不要な情報であって、わざわざ通知が来るというのもあまり愉快じゃないかもしれません。要するに、ご利用は自己責任でお願いします。

概要

大まかな流れは次のとおりです。

1. Twitter APIを使ってフォロワーのリストを取得
2. Googleスプレッドシートに記録されている前回取得時のフォロワーリストと比較して、減った(リムした)アカウントを判別
3. スプレッドシートの内容を今回取得したデータで更新
4. Slackにリムしたアカウントの一覧を送信

※GASのトリガー機能で1~4を定期的に実行

ちなみにGASからTwitter APIを利用したりSlackにメッセージを送ったりは以前に一度やっているので、今回は主にスプレッドシートを用いた処理を見ていこうと思います。

※以下、Slackにメッセージを送る準備(App作成)や、Twitterのdeveloperアカウント(Bearerトークン)取得済みが前提です。その辺は過去記事やGoogle検索をご利用下さい。

Googleスプレッドシートへの書き込み

リムった」(=前回取得時はフォロワーだったが今回はそうでない)アカウントを見つけるために、まずはフォロワーの一覧を記録しておくスプレッドシートを用意します。

スプレッドシートを作成し、次のように書き込みます(別に書き込まなくても動きますが、見やすさのために)。フォロワーのデータは2行目以降に書き込みます。

ここではアカウントを表す情報を3つ記録します。

id : 数字で管理されるアカウント固有の値(普段は見えない)
username : @以下の部分
name : 表示用の名前

アカウント識別のためならidとusernameはいずれか一方で十分ですが、両方記録しておきます。たぶん深い意味はありません。ちなみに自分のアカウントのIDは設定画面から調べられるそうです。あとで必要になるのでメモっておいて下さい。

また、GASからスプレッドシートにアクセスするためにスプレッドシートIDが必要なのでどこかにメモしておきます。スプレッドシートIDはスプレッドシートを開いているときのURLに入っています。

https://docs.google.com/spreadsheets/d/spreadsheetId/edit#gid=0
※spreadsheetId の部分に入っている文字列がID

https://developers.google.com/sheets/api/guides/concepts

試しにダミーのアカウント情報を書き込んでみましょう。GASで新しいプロジェクトを作成したら、以下のスクリプトを打ち込んで実行します。

function myFunction() {
  // 実際のスプレッドシートIDを入れる
  const ssid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

  const ss = SpreadsheetApp.openById(ssid);
  const sheet = ss.getSheets()[0];
  const range = sheet.getRange(2, 1, 1, 3);

  Logger.log(range.getValues());

  // 2次元配列なのでカッコが2重
  const dummy = [["12345678", "lemon_hoge4649", "ダミーレモン"]];
  range.setValues(dummy);

  Logger.log(range.getValues());
}

恐らく最初は権限の確認の画面が表示されるので、許可して下さい。実行後、スプレッドシートにダミーデータが書き込まれたのが確認できると思います。

基本的に参照する範囲を指定してgetValues()でデータ読み込み、setValues()でデータ書き込みができます。

スプレッドシートAPIの詳しい説明は割愛しますが、たぶんこの辺りが参考になるかと思います。

もしくは公式ドキュメントで。

リムを検知

スプレッドシートに前回取得したフォロワーリストが記録されているとして、新たに取得したフォロワーリストと比べたときに減っている人がリムーブしたということになります。

IDはアカウントごとの固有の値なので、IDを使って比較します。スプレッドシートのデータは単に羅列しただけの2次元配列なのでオブジェクトの配列に変換してから処理します。

const olddataRaw = /* スプレッドシートから取得 */

// 2次元配列からオブジェクトの配列に
const olddata = olddataRaw.map(row => 
  ({
    id : row[0], username : row[1], name : row[2]
  })
);
// newdataはオブジェクトの配列
const newdata = /* Twitter APIで取得(後述)*/

// 配列の差分をとる
const removed = olddata.filter(x => !newdata.some(y => x.id == y.id));

差分を取る処理はこのサイトを参考にしました。

こうしてリムーブしたユーザーの一覧を取得できました。Slackでメッセージを送る際はIDではなく表示名やユーザー名(@hogehoge)の情報で送ると見やすくていいと思います。

コード

というわけで試しに実装してみたのがこれです。

Twitter API部分

// twitter API のBearerトークン
const bearer = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// あなたの(リムを通知したい)TwitterアカウントのID(数字)
const userID = "0000000000000";

const option = {
  headers : {
    Authorization : "Bearer " + bearer
  }
};

// 一度のフォロワー取得の上限は1,000人(それ以上の場合はpaginationが必要だが未実装)
function getFollowers(max = 1000){
  const url = "https://api.twitter.com/2/users/" + userID + "/followers";
  const param = "?max_results=" + max;
  const response = UrlFetchApp.fetch(url + param, option);
  const result = JSON.parse(response);
  return result.data;
}

スプレッドシートAPI部分

// スプレッドシートID
const ssid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

let sheet = null;

function getSSData(){
  if(!sheet){
    sheet = SpreadsheetApp.openById(ssid).getSheets()[0];
  }
  const n = sheet.getLastRow() - 1;
  const range = sheet.getRange(2, 1, n, 3);
  return range.getValues();
}

function setSSData(followers){
  if(!sheet){
    sheet = SpreadsheetApp.openById(ssid).getSheets()[0];
  }
  const n = Math.max(sheet.getLastRow() - 1, followers.length);
  const range = sheet.getRange(2, 1, n, 3);

  const height = range.getHeight();
  const values = [];
  for(let i = 0; i < height; i++){
    const row = i < followers.length ? 
      [followers[i].id, followers[i].username, followers[i].name] :
      ["","",""];
    values.push(row);
  }
  range.setValues(values);
}

Slack API部分

// Slack Appのtoken
const token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 情報を送信するチャンネルのID
const channelID = "XXXXXXXXXX";

const apiUrl = "https://slack.com/api/chat.postMessage";
const twitterUrl = "https://twitter.com/";

function postSlackMessage(text){
  const payload = {
    token : token,
    channel : channelID,
    text : text,
    username : "Twitter Notifier",
    unfurl_links : false,
  };
  const params = {
    method : "post",
    payload : payload
  };

  UrlFetchApp.fetch(apiUrl, params);
}

function createMessage(remove){
  let mes = "";
  if(remove.length > 0){
    mes += "リムしたアカウント:\n";
    for(let i = 0; i < remove.length; i++){
      // リンク付きテキストに整形
      mes += "• <" + twitterUrl + remove[i].username + "|" + remove[i].name + ">\n";
    }
    mes = mes.trim();
    postSlackMessage(mes);
  }
}

実行する処理

function main(){
  const olddataRaw = getSSData();
  const olddata = olddataRaw.map(row => 
    ({
      id : row[0], username : row[1], name : row[2]
    })
  );
  const newdata = getFollowers();
  const remove = olddata.filter(x => !newdata.some(y => x.id == y.id));

  setSSData(newdata);
  createMessage(remove);
}

最後のmain()関数を実行するとすべての処理が行われます。「権限を承認」ダイアログが表示されたら承認して下さい。(正常に動けば)特にログは出ないので味気ないですが、最初の実行でスプレッドシートにフォロワーのリストが書き込まれているはずです。

トリガーの設定

GASの編集画面で左端にある時計のアイコンをクリックするとトリガーの設定画面が開きます。トリガーを使うと指定したタイミングで関数を自動的に実行することができます。

右下にある「トリガーを追加」を押します。「実行する関数」は「main」に設定します。それ以外はたぶんデフォルトのままで大丈夫です。もしくは時間間隔を好きなように設定して下さい。

これで、main()関数の処理が一定時間ごとに自動で実行されるようになります。

スプレッドシートに嘘のユーザーデータを追加してみたり、サブアカウントのフォローを解除したりして挙動をテストしてみて下さい。

おわりに

以上です。説明はわりと雑に端折った気がします。ちなみに今回検知されるのは必ずしもフォロー解除とは限らなくて、実際は例えばアカウント削除の可能性もあります(リンクを踏めば確認できる)。もっと厳密な判定もたぶん可能ですが、面倒なのでやりませんでした。

あと、トリガー実行でエラーが発生するとメールが送られてきます。GASの概要ページでエラー率が確認できますが、エラー率が低い場合はたぶんGAS側かTwitter側の一時的な問題だと思います(毎回エラーが発生するときはコードが悪い)。あんまり真面目にテストしてないので変な挙動があったら教えて下さい。

完全に余談ですけど最近wordleをやっています。wordleの結果を延々タイムラインに流し続けるのもどうかと思ったのでそれ用にサブアカウントを作りました。👉👉@kakilemon_w



はい。


それでは、また。


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