フォローしてくれたのでフォロバしたら速攻フォローを外される、そんな経験ありませんか?気付きにくいのであまり気付いてない人もいるでしょうが、巷では結構その手口が横行しているようです。
このような自身のフォロワー数を増やす目的で、多数のアカウントをフォローした後でフォロー解除することをフォローチャーンと呼ぶそうです。ツイッターのヘルプでも禁止事項として挙げられております(こちら)。
詳しくは後程語りますが、なんだか気に食わないのでGoogle Apps ScriptとTwitter APIを利用してフォロワー、フォロー中の状況を監視するためのツールを作成してみました。
このツールにより以下の2つのアカウントが分かるようになります。
- フォローを解除したアカウント
- フォロー解除により相互フォローではなくなったアカウント
ツール作りましたんで使ってくださいというものではなく、Google Apps ScriptとTwitter APIを使ったツール自作の方法を伝える記事となっております。自作に興味がない方は、登録するだけで同様のことができるツールがありますのでネットで検索してみてください。
フォロバしたら即座にリムる人々
そもそもこのツールを作ってみようと思ったきっかけは、フォローをしてくれた人にフォローをし返す(通称「フォロバ」)をしたら即座にフォローを解除してくる輩がまあまあ存在するからです。
自分でも経験してますし、他の方がそのようなツイートをしているのを見ることもあります。
輩の目的は、フォロワー数をフォロー数より大きく見せることです。そのステータスを見せつけることでなんとなくこの人はみんなから選ばれているいいアカウントだと思わせる。こすいですね。
ツイッターではフォロワーではなくなったことに対する通知はありません。なのでフォローを外し(通称「リムる」)をされてもほぼ気付かない点を突いた手口です。
やり口が気に食いません。心意気が気に食いません。
ぶっちゃけフォロー数、フォロワー数を見て、ツイートの中身を見たら臭うアカウントは結構分かります。しかし、中にはよいことを呟いているにも関わらずこすい手口を使う輩もいます。普通にやってればいいのに残念で仕方ありません。
今まで面倒なので目をつむっていましたが、今回ツールを作ったので炙り出します。炙りカルビぐらい炙ります。覚悟はイイですか?
ちなみに私が気に食わないのはフォロバを貰ってリムすることを狙って活動している輩です。相互フォローだったのにフォローを外されることについては、受け入れます。
フォローしているアカウントの整理、思っていたのと違った、アイコンがコロコロ変わって気持ち悪い、色々あるでしょう。分かります。
なんだあか政治家の演説のようになってしまいましたね。ここから先はツールの作り方です★
必要なもの
- Googleアカウント
Googleアカウントが1つあれば、サーバーレスでプログラムを定期的に実行する環境が手に入ります。 - ツイッター開発者アカウント
フォロワー情報、フォロー中情報を取得するためにツイッターAPIを使います。
登録は以下のページを参考にしてください。
https://moripro.net/gas-twitter-developer-api/#Twitter_Developer
実現できること
1日1回指定した時間に24時間で検出された「減ったフォロワー」「相互だったフォロワー」情報をメールで通知します。
プログラム(スクリプト)がやってること
毎時0分に以下の処理を走らせています。指定した時間になるとリストをメールで飛ばして、内容をクリアします。
- フォロワーとフォロー中のユーザーIDを取得
- 1時間前のフォロワーと現在のフォロワーの差分を取る
フォロワーではなくなっているアカウントを検出 - 検出されたアカウントをフォローしているかをチェック
【フォローしている場合】相互だったフォロワーとしてリストに追加
【フォローしていない場合】減ったフォロワーとしてリストに追加
注意点
- 「フォローされる→フォロバする→リムされる」の一連の動作が情報更新をまたがないで行われた場合には、フォロー解除が検出されません。フォロバをする際はフォローされてから時間を置いて行うようにしてください。
- フォロワーとフォロー中がどちらも5000以下の場合のみ正常に監視できます。
- 一度フォローを解除したアカウントが再度フォローをしても減ったフォロワーとしてリストに残ります。
作成手順
- Twitter Developers App作成
以下の記事を参考にさせていただきました。
https://moripro.net/gas-twitter-developer-api/#API - スプレッドシート スプレッドシートを作成し、スクリプトエディタを開く
- GAS ライブラリを追加
- Twitter Developers AppでコールバックURL設定
- GAS アプリの連携を認証
3~5は以下の記事を参考にさせていただきました。
https://moripro.net/gas-twitter-bot/#i-2 - Twitter ユーザーIDを確認
ツイッターにログインし、
「もっと見る」→「設定とプライバシー」→「Twitterデータ」→「アカウント」
で[ユーザー名]の欄にユーザーIDが記載されています。 - スプレッドシート リストのタイトル、パラメータ記入
- [A1セル] 「1時間前のフォロワー」と入力
- [B1セル] 「減ったフォロワー」と入力(通知メールで使用)
- [C1セル] 「相互だったフォロワー」と入力(通知メールで使用)
- [F2セル] ユーザーIDを入力(パラメータ)
- [F3セル] レポート送信時間を入力(パラメータ)
- [F4セル] レポート送信先メールアドレスを入力(パラメータ)
- [シート名]「List」に変更
- 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件)を利用しています。
26~50行目
1時間前のフォロワーと現在のフォロワーで一致するIDがあるかの比較をしています。
フォロワーが1時間でそこまで大きく減ることはないという前提でループの初期値を0ではなく、現在値-5としています。
これにより比較をする回数をかなり抑えることができ、実行時間を削減できます。
ちなみに、この部分は以下のようなもっとスマートな書き方もあったのですが、実行時間がかなりかかったので単純な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時間により日付が変わったりする場合も自動で調整してくれるのでプログラム側では対応不要です。
- GAS スクリプト実行
follow_exchange_checkerを実行するとチェックの実行および1時間後のトリガーの設定が行われます。
あとがき
私の中でのTwitter API活用は、PHP×Twitter APIとGAS×Twitter APIがあります。PHPは環境がないとだめな一方GASはGoogleアカウントさえ持っていればOKです。
サーバーレスで定期的にTwitter APIを操作できるので神レベルの便利さなのですが、とっつきにくさが唯一の問題です。
とっつきやすいレベルに解説ができてない(していない)私にも問題があるのですが、つまずきながら成長するもんなのでプログラム勉強中の方にはちょうどよいのかもしれません(棒)
Twitter APIは攻略できてもツイッターそのものは全然攻略できてません。良かったらフォローしてみてください。
Follow @munokuno私がフォローバックすれば、そこから監視が始まります👀
(フォロバしないこともありますのであしからず)
コメント