【GAS×Twitter API】自作ツールで相互フォロー解除を監視する

自作ツールで相互フォロワー解除を監視せよ 生産性アップ

フォローしてくれたのでフォロバしたら速攻フォローを外される、そんな経験ありませんか?気付きにくいのであまり気付いてない人もいるでしょうが、巷では結構その手口が横行しているようです。

このような自身のフォロワー数を増やす目的で、多数のアカウントをフォローした後でフォロー解除することをフォローチャーンと呼ぶそうです。ツイッターのヘルプでも禁止事項として挙げられております(こちら)。

詳しくは後程語りますが、なんだか気に食わないのでGoogle Apps ScriptとTwitter APIを利用してフォロワー、フォロー中の状況を監視するためのツールを作成してみました。

このツールにより以下の2つのアカウントが分かるようになります。

  • フォローを解除したアカウント
  • フォロー解除により相互フォローではなくなったアカウント

ツール作りましたんで使ってくださいというものではなく、Google Apps ScriptとTwitter APIを使ったツール自作の方法を伝える記事となっております。自作に興味がない方は、登録するだけで同様のことができるツールがありますのでネットで検索してみてください。

フォロバしたら即座にリムる人々

そもそもこのツールを作ってみようと思ったきっかけは、フォローをしてくれた人にフォローをし返す(通称「フォロバ」)をしたら即座にフォローを解除してくる輩がまあまあ存在するからです。

自分でも経験してますし、他の方がそのようなツイートをしているのを見ることもあります。

輩の目的は、フォロワー数をフォロー数より大きく見せることです。そのステータスを見せつけることでなんとなくこの人はみんなから選ばれているいいアカウントだと思わせる。こすいですね。

ツイッターではフォロワーではなくなったことに対する通知はありません。なのでフォローを外し(通称「リムる」)をされてもほぼ気付かない点を突いた手口です。

やり口が気に食いません。心意気が気に食いません。

ぶっちゃけフォロー数、フォロワー数を見て、ツイートの中身を見たら臭うアカウントは結構分かります。しかし、中にはよいことを呟いているにも関わらずこすい手口を使う輩もいます。普通にやってればいいのに残念で仕方ありません。

今まで面倒なので目をつむっていましたが、今回ツールを作ったので炙り出します。炙りカルビぐらい炙ります。覚悟はイイですか?

ちなみに私が気に食わないのはフォロバを貰ってリムすることを狙って活動している輩です。相互フォローだったのにフォローを外されることについては、受け入れます。

フォローしているアカウントの整理、思っていたのと違った、アイコンがコロコロ変わって気持ち悪い、色々あるでしょう。分かります。

なんだあか政治家の演説のようになってしまいましたね。ここから先はツールの作り方です★

必要なもの

  1. Googleアカウント
    Googleアカウントが1つあれば、サーバーレスでプログラムを定期的に実行する環境が手に入ります。
  2. ツイッター開発者アカウント
    フォロワー情報、フォロー中情報を取得するためにツイッターAPIを使います。
    登録は以下のページを参考にしてください。
    https://moripro.net/gas-twitter-developer-api/#Twitter_Developer

実現できること

1日1回指定した時間に24時間で検出された「減ったフォロワー」「相互だったフォロワー」情報をメールで通知します。

通知メール
通知メール

プログラム(スクリプト)がやってること

毎時0分に以下の処理を走らせています。指定した時間になるとリストをメールで飛ばして、内容をクリアします。

  1. フォロワーとフォロー中のユーザーIDを取得
  2. 1時間前のフォロワーと現在のフォロワーの差分を取る
    フォロワーではなくなっているアカウントを検出
  3. 検出されたアカウントをフォローしているかをチェック
    【フォローしている場合】相互だったフォロワーとしてリストに追加
    【フォローしていない場合】減ったフォロワーとしてリストに追加

注意点

  • 「フォローされる→フォロバする→リムされる」の一連の動作が情報更新をまたがないで行われた場合には、フォロー解除が検出されません。フォロバをする際はフォローされてから時間を置いて行うようにしてください。
  • フォロワーとフォロー中がどちらも5000以下の場合のみ正常に監視できます。
  • 一度フォローを解除したアカウントが再度フォローをしても減ったフォロワーとしてリストに残ります。

フォローが外れたアカウントをチェックするとリンクが飛べないことが多いです。おそらくアカウント削除とか凍結とかだと思われます。

作成手順

  1. Twitter Developers App作成
     以下の記事を参考にさせていただきました。
     https://moripro.net/gas-twitter-developer-api/#API
  2. スプレッドシート スプレッドシートを作成し、スクリプトエディタを開く
  3. GAS ライブラリを追加
  4. Twitter Developers AppでコールバックURL設定
  5. GAS アプリの連携を認証
     3~5は以下の記事を参考にさせていただきました。
     https://moripro.net/gas-twitter-bot/#i-2
  6. Twitter ユーザーIDを確認
     ツイッターにログインし、
      「もっと見る」→「設定とプライバシー」→「Twitterデータ」→「アカウント」
     で[ユーザー名]の欄にユーザーIDが記載されています。
  7. スプレッドシート リストのタイトル、パラメータ記入
スプレッドシート
スプレッドシート[クリックで拡大]
  • [A1セル] 「1時間前のフォロワー」と入力
  • [B1セル] 「減ったフォロワー」と入力(通知メールで使用)
  • [C1セル] 「相互だったフォロワー」と入力(通知メールで使用)
  • [F2セル] ユーザーIDを入力(パラメータ)
  • [F3セル] レポート送信時間を入力(パラメータ)
  • [F4セル] レポート送信先メールアドレスを入力(パラメータ)
  • [シート名]「List」に変更

  1. GAS スクリプト作成
function follow_exchange_checker() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('List');

  //パラメータをシートから取得
  var user_id = sheet.getRange(2, 6).getValue(); //ユーザーID
  var report_time = sheet.getRange(3, 6).getValue(); //1日1回のレポートを送る時間を0~23で指定
  var mail_address = sheet.getRange(4, 6).getValue(); //レポート送信先

  //1時間前のフォロワーアカウントIDリストを配列prev_followersに格納
  var prev_followers = sheet.getRange(2, 1, sheet.getLastRow() - 1).getValues();
 
  //最新のフォロワーアカウントIDを配列followersに格納
  var service  = twitter.getService();
  var response = service.fetch('https://api.twitter.com/1.1/followers/ids.json?user_id=' + user_id + '&stringify_ids=true');
  json = JSON.parse(response);
  var followers = json['ids'];
   
  //1時間前に入っているのに最新に入っていないアカウントIDを抽出し、配列suspectsに格納
  //var new_suspects = prev_followers.filter(function(e){return followers.filter(function(f){return e.toString() == f.toString()}).length == 0});
  var suspects = [];
  var count_prev_followers = prev_followers.length;
  var count_followers = followers.length;
  var judge = 0;

  for(var i = 0; i < count_prev_followers; i++){
    //比較回数を減らすために5をマイナスしたところを初期値にする
    //(あまりずれが出ない前提)
    if(i < 5){
      for(var j = 0; j < count_followers; j++){
        if(prev_followers[i] == followers[j]){
          judge = 1;
          break;
        }
      }      
    } else {
      for(j = i - 5; j < count_followers; j++){     
        if(prev_followers[i] == followers[j]){
          judge = 1;
          break;
        }
      }
      if(judge == 0){
        for(j = i - 4; j >= 0; j--){
          if(prev_followers[i] == followers[j]){
            judge = 1;
            break;
          }
        }
      }
    }
    if(judge == 0){
      suspects.push(prev_followers[i]);
    } else {
      judge = 0;
    }
  }
  
  //フォロー中のアカウントIDを配列friendsに格納
  response = service.fetch('https://api.twitter.com/1.1/friends/ids.json?user_id=' + user_id + '&stringify_ids=true');
  json = JSON.parse(response);
  var friends = json['ids'];  
  
  //suspectsにもfriendsにも入っているアカウントを抽出
  var count_suspects = suspects.length;
  var count_friends = friends.length;
  var targets = [];
  for(var i = 0; i < count_suspects; i++){
    for(var j = 0; j < count_friends; j++){
      if(suspects[i] == friends[j]){
        targets.push(friends[j]);
        break;
      }
    }
  }
  
  //suspectsをB列に出力
  var last_row = 2;
  while(sheet.getRange(last_row, 2).getValue() !== ''){
    last_row++;
  }
  
  //ユーザーIDを利用してプロフィールへのリンクを作成
  for(i = 0; i < count_suspects; i++){
    sheet.getRange(i + last_row, 2).setValue('=HYPERLINK("https://twitter.com/intent/user?user_id=' + suspects[i]  + '","' + suspects[i] + '")');
  }
  
  //B列の最終行を格納
  var B_last_row = i + last_row - 1;
   
  //targesをC列に出力
  var last_row = 2;
  var count_targets = targets.length;

  while(sheet.getRange(last_row, 3).getValue() !== ''){
    last_row++;
  }

  //ユーザーIDを利用してプロフィールへのリンクを作成
  for(i = 0; i < count_targets; i++){
    sheet.getRange(i + last_row, 3).setValue('=HYPERLINK("https://twitter.com/intent/user?user_id=' + targets[i]  + '","' + targets[i] + '")');
  }

  //C列の最終行を格納
  var C_last_row = i + last_row - 1;

  var date = new Date();
  //B列に入力があり、現在時刻がレポート送信時刻の場合、メールを作成
  if(date.getHours() == report_time && B_last_row > 1){
    //htmlメールの本文を作成
    var html = '<h3>' + sheet.getRange(1, 2).getValue() + '</h3><p>';
    var url = '';
    var formula = '';
  
    for(i = 1; i < B_last_row; i++){
      formula = sheet.getRange(i + 1, 2).getFormula();
      url = formula.substring(formula.indexOf('(') + 2,formula.indexOf(',') - 1);

      html = html + '<a href="' + url + '">' + sheet.getRange(i+1,2).getValue() + '</a><br>';
    }
  
    html = html + '</p><h3>' + sheet.getRange(1, 3).getValue() + '</h3><p>';
  
    if(C_last_row > 1){
      for(i = 1; i < C_last_row; i++){
        formula = sheet.getRange(i + 1, 3).getFormula();
        url = formula.substring(formula.indexOf('(') + 2, formula.indexOf(',') - 1);

        html = html + '<a href="' + url + '">' + sheet.getRange(i + 1, 3).getValue() + '</a><br>';
      }
    } else {
      html += '該当なし'; 
    }
    
    html = html + '</p>';

    //メールを送信
    GmailApp.sendEmail(
      mail_address, //宛先
      '相互フォロー崩壊のお知らせ(' + Utilities.formatDate(date, 'Asia/Tokyo', 'M月d日') + ')', //件名
      'htmlメールが表示できませんでした', //本文
      {
        //from: 'xxxxx@gmail.com', //送り元(省略可)
        htmlBody: html
      }
    );
    
    sheet.getRange(2, 2, B_last_row, 2).clearContent();
  }
    
  //1時間前のフォロワーアカウントIDリストを更新  
  var ary=[];
  for (var i = 0; i < count_followers; i++) {
    ary.push([followers[i]]);
  }
  
  //1時間前のフォロワーアカウントIDの方が数が多い場合は差分を空白で埋める
  if(count_prev_followers>count_followers){
    for(i = 0; i < count_prev_followers - count_followers; i++){
      ary.push(['']);
    }
  }
  
  sheet.getRange(2, 1, ary.length, 1).setValues(ary);
  
}

function setTrigger(){
  //トリガーを全削除(時間指定トリガーが残るため)
  var triggers = ScriptApp.getProjectTriggers();
  for(var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }

  //1時間後のトリガーを作成
  var setTime = new Date();
  setTime.setHours(setTime.getHours() + 1);
  setTime.setMinutes(0);
  
  ScriptApp.newTrigger('follow_exchange_checker').timeBased().at(setTime).create();
}

ハイライト部分について、以下補足説明です。

14行目

APIを利用してフォロワーのユーザーIDを取得します。
1回の実行で取得できる量が段違いなのでGET followers/list(最大200件)ではなくGET followers/ids(最大5000件)を利用しています。

  • 本スクリプトの仕様のフォロワーもしくはフォロー中が最大5000までというのはここから来ています。複数回実行すれば5000x回数分取得できますが、5000以上なんて一部なので除外します。
  • stringify_idsをtrueにしないとIDが数字として取得されます。15桁を超える数字は指数で扱われ、IDが変わってしまうので文字列で取得してください。

26~50行目

1時間前のフォロワーと現在のフォロワーで一致するIDがあるかの比較をしています。

フォロワーが1時間でそこまで大きく減ることはないという前提でループの初期値を0ではなく、現在値-5としています。

これにより比較をする回数をかなり抑えることができ、実行時間を削減できます。

実績値としては、以下の差がでました。(施行するたびに数字は変わります)
0を初期値でループ:237,705回
現在値-5を初期値でループ:699回

ちなみに、この部分は以下のようなもっとスマートな書き方もあったのですが、実行時間がかなりかかったので単純なforループとしています。

suspects = prev_followers.filter(function(e){return followers.filter(function(f){return e.toString() == f.toString()}).length == 0});

84行目

ユーザーIDを使って以下のURLを作ることでプロフィールページへのリンクが作成できます。
https://twitter.com/intent/user?user_id=ユーザーID

178行目

現在時刻に+1時間を施しています。+1時間により日付が変わったりする場合も自動で調整してくれるのでプログラム側では対応不要です。

  1. GAS スクリプト実行
    follow_exchange_checkerを実行するとチェックの実行および1時間後のトリガーの設定が行われます。
    定期実行を止めるためにはトリガーの削除を行ってください。

■アプリが確認できてません
初回のスクリプト実行時には「このアプリは確認されていません」というポップアップが表示されます。

[詳細]を選択後、安全ではないページに移動してアプリの確認を行ってください。

確認されてませんのポップアップ
確認されてませんのポップアップ

■V8ランタイムによるエラー
2020/2/8よりGoogle Apps Scriptが「V8ランタイム」をサポートするようになりました。新規作成したスクリプトでは「V8ランタイム」が有効化されるのがデフォルトとなっているようです。

本記事で扱っているライブラリは「V8ランタイム」が有効になっていると「Property store is required. 」というエラーが出て動作しません。スクリプトエディタの実行より[Chrome V8を搭載した新しいApps Script ランタイムを無効にする]を選択し、無効化してください。

ランタイムを無効にする
ランタイムを無効にする

あとがき

私の中でのTwitter API活用は、PHP×Twitter APIとGAS×Twitter APIがあります。PHPは環境がないとだめな一方GASはGoogleアカウントさえ持っていればOKです。

サーバーレスで定期的にTwitter APIを操作できるので神レベルの便利さなのですが、とっつきにくさが唯一の問題です。

とっつきやすいレベルに解説ができてない(していない)私にも問題があるのですが、つまずきながら成長するもんなのでプログラム勉強中の方にはちょうどよいのかもしれません(棒)

Twitter APIは攻略できてもツイッターそのものは全然攻略できてません。良かったらフォローしてみてください。

私がフォローバックすれば、そこから監視が始まります👀
(フォロバしないこともありますのであしからず)

米国株ブログランキングに参加しています。応援のクリックお願いしますm(._.)m
にほんブログ村 株ブログ 米国株へ


生産性アップ
\この記事をシェアする/
\クノウをフォローする/
米国株にかける

コメント

タイトルとURLをコピーしました