定期的にTwitterフォロー返しをする
いくつかサービスとか実装例があるのですが、情報が古かったり
痒いところに手が届かない感じだったので自作しました。
サーバ上でTwitter APIのスクリプトを起動する方法もありますが、
サーバを立てるのもめんどくさかったので
Google Apps Scriptで実装しました。
前準備
新規にスプレッドシートをGoogle Drive上で作成します。
スプレッドシートのツール→スクリプトエディタからGoogle Apps Script(.gs)を作成します。
Google Apps ScriptのURLをコピーします。(あとで使います。)
Twitter APIを利用するために
Twitter Application Managementで新規にアプリを作成します。
新規にアプリを作成します。
Websiteがない場合は適当なURLでも大丈夫です。
Callback URLは先ほどコピーしたGoogle Apps ScriptのURLを指定します。
Consumer KeyとConsumer Secretをメモしておきます(あとで使います。)
Twitter API利用にはOAuth1認証をするためGoogle Apps Scriptのライブラリを追加します。
リソース→ライブラリを開きます。

次のScript IDでOAuth1 for Apps Scriptを追加します。
1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s

実装
Google Apps Scriptに下記のスクリプトを記述します。
- consumerKey
- consumerSecret
は先程払い出したものを使用してください。
// 最初にこの関数を実行し、ログに出力されたURLにアクセスしてOAuth認証する
function twitterAuthorizeUrl() {
Twitter.oauth.showUrl();
}
// OAuth認証成功後のコールバック関数
function twitterAuthorizeCallback(request) {
return Twitter.oauth.callback(request);
}
// OAuth認証のキャッシュをを削除する場合はこれを実行(実行後は再度認証が必要)
function twitterAuthorizeClear() {
Twitter.oauth.clear();
}
var Twitter = {
consumerKey: "(先程払い出したConsumer Key)",
consumerSecret: "(先程払い出したConsumer Secret)",
apiUrl: "https://api.twitter.com/1.1/",
oauth: {
name: "twitter",
service: function(screen_name) {
// 参照元:https://github.com/googlesamples/apps-script-oauth1
return OAuth1.createService(this.name)
// Set the endpoint URLs.
.setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
.setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
.setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// Set the consumer key and secret.
.setConsumerKey(this.parent.consumerKey)
.setConsumerSecret(this.parent.consumerSecret)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('twitterAuthorizeCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties());
},
showUrl: function() {
var service = this.service();
if (!service.hasAccess()) {
Logger.log(service.authorize());
} else {
Logger.log("認証済みです");
}
},
callback: function (request) {
var service = this.service();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput("認証に成功しました!このタブは閉じてかまいません。");
} else {
return HtmlService.createHtmlOutput("認証に失敗しました・・・");
}
},
clear: function(){
OAuth1.createService(this.name)
.setPropertyStore(PropertiesService.getUserProperties())
.reset();
}
},
api: function(path, data) {
var that = this, service = this.oauth.service();
if (!service.hasAccess()) {
Logger.log("先にOAuth認証してください");
return false;
}
path = path.toLowerCase().replace(/^\//, '').replace(/\.json$/, '');
var method = (
/^statuses\/(destroy\/\d+|update|retweet\/\d+)/.test(path)
|| /^media\/upload/.test(path)
|| /^direct_messages\/(destroy|new)/.test(path)
|| /^friendships\/(create|destroy|update)/.test(path)
|| /^account\/(settings|update|remove)/.test(path)
|| /^blocks\/(create|destroy)/.test(path)
|| /^mutes\/users\/(create|destroy)/.test(path)
|| /^favorites\/(destroy|create)/.test(path)
|| /^lists\/[^\/]+\/(destroy|create|update)/.test(path)
|| /^saved_searches\/(create|destroy)/.test(path)
|| /^geo\/place/.test(path)
|| /^users\/report_spam/.test(path)
) ? "post" : "get";
var url = this.apiUrl + path + ".json";
var options = {
method: method,
muteHttpExceptions: true
};
if ("get" === method) {
if (!this.isEmpty(data)) {
url += '?' + Object.keys(data).map(function(key) {
return that.encodeRfc3986(key) + '=' + that.encodeRfc3986(data[key]);
}).join('&');
}
} else if ("post" == method) {
if (!this.isEmpty(data)) {
options.payload = Object.keys(data).map(function(key) {
return that.encodeRfc3986(key) + '=' + that.encodeRfc3986(data[key]);
}).join('&');
if (data.media) {
options.contentType = "multipart/form-data;charset=UTF-8";
}
}
}
try {
var result = service.fetch(url, options);
var json = JSON.parse(result.getContentText());
if (json) {
if (json.error) {
throw new Error(json.error + " (" + json.request + ")");
} else if (json.errors) {
var err = [];
for (var i = 0, l = json.errors.length; i < l; i++) {
var error = json.errors[i];
err.push(error.message + " (code: " + error.code + ")");
}
throw new Error(err.join("\n"));
} else {
return json;
}
}
} catch(e) {
this.error(e);
}
return false;
},
error: function(error) {
var message = null;
if ('object' === typeof error && error.message) {
message = error.message + " ('" + error.fileName + '.gs:' + error.lineNumber +")";
} else {
message = error;
}
Logger.log(message);
},
isEmpty: function(obj) {
if (obj == null) return true;
if (obj.length > 0) return false;
if (obj.length === 0) return true;
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) return false;
}
return true;
},
encodeRfc3986: function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, function(char) {
return escape(char);
}).replace(/\*/g, "%2A");
},
init: function() {
this.oauth.parent = this;
return this;
}
}.init();
/********************************************************************
以下はサポート関数
*/
// ツイート検索
Twitter.search = function(data) {
if ("string" === typeof data) {
data = {q: data};
}
return this.api("search/tweets", data);
};
// 自分のタイムライン取得
Twitter.tl = function(since_id) {
var data = null;
if ("number" === typeof since_id || /^\d+$/.test(''+since_id)) {
data = {since_id: since_id};
} else if("object" === typeof since_id) {
data = since_id;
}
return this.api("statuses/home_timeline", data);
};
// ユーザーのタイムライン取得
Twitter.usertl = function(user, since_id) {
var path = "statuses/user_timeline";
var data = {};
if (user) {
if (/^\d+$/.test(user)) {
data.user_id = user;
} else {
data.screen_name = user;
}
} else {
var path = "statuses/home_timeline";
}
if (since_id) {
data.since_id = since_id;
}
return this.api(path, data);
};
// ツイートする
Twitter.tweet = function(data, reply) {
var path = "statuses/update";
if ("string" === typeof data) {
data = {status: data};
} else if(data.media) {
path = "statuses/update_with_media ";
}
if (reply) {
data.in_reply_to_status_id = reply;
}
return this.api(path, data);
};
// トレンド取得(日本)
Twitter.trends = function(woeid) {
data = {id: woeid || 1118108};
var res = this.api("trends/place", data);
return (res && res[0] && res[0].trends && res[0].trends.length) ? res[0].trends : null;
};
// トレンドのワードのみ取得
Twitter.trendWords = function(woeid) {
data = {id: woeid || 1118108};
var res = this.api("trends/place", data);
if (res && res[0] && res[0].trends && res[0].trends.length) {
var trends = res[0].trends;
var words = [];
for(var i = 0, l = trends.length; i < l; i++) {
words.push(trends[i].name);
}
return words;
}
};
Twitter.follower = function(screenName) {
var data = {
screen_name: screenName,
stringify_ids: true
};
return this.api("followers/ids",data);
};
Twitter.outgoing = function(screenName) {
var data = {
screen_name: screenName,
stringify_ids: true
};
return this.api("friendships/outgoing",data);
};
Twitter.friends = function(screenName) {
var data = {
"screen_name":screenName
};
return this.api("friends/list",data);
};
Twitter.follow = function(userId) {
var data = {
"follow":true,
"user_id":userId
};
return this.api("friendships/create",data);
};
// 定期実行でフォロワー返し
function exec(){
// フォロワー一覧
var follower = Twitter.follower("meetsmore");
Logger.log(follower)
// フォロー許可待ち一覧
var outgoings = Twitter.outgoing("meetsmore");
Logger.log(outgoings)
// フォロー済み一覧
var friends = Twitter.friends("meetsmore");
Logger.log(friends)
// フォロー済みとフォローリクエスト送信済みを除外
var follow_ids = []
for(var i = friends.users.length; i--;){
follow_ids.push(friends.users[i].id_str)
}
for(var i = outgoings.ids.length; i--;){
follow_ids.push(outgoings.ids[i])
}
// 未フォロワーにフォローリクエスト
for(var i = 0; i < follow_ids.length; i++){
var idx = follower.ids.indexOf(follow_ids[i]);
if(idx !== -1){
follower.ids.splice(idx,1);
}
}
var ids = follower.ids;
for(var i = ids.length; i--;) {
var id = ids[i];
// フォローする
var result = Twitter.follow(id);
// フォローしたユーザの情報を表示
Logger.log(result);
// シートに書き込み
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var Sheet = Spreadsheet.getSheetByName("Twitter");
if(Sheet){
var LastRow = Sheet.getLastRow();
Sheet.getRange(LastRow + 1,1,1,1).setValue('@' + result.screen_name);
}
}
}
OAuth認証をする必要があるので
まず、twitterAuthorizeURL関数を起動します。

表示→ログにてOAuth認証用URLのログが表示されます。

ブラウザにてURLにアクセスして
OAuth認証を完了させます。(TwitterアカウントとTwitterアプリ連携)

exec関数を実行するとフォローされていて未フォローのユーザを一括でフォローします。
フォローした結果のユーザはスプレッドシートに書き出しします。
定期的に実行する
編集→すべてのトリガーを選択します。

定期実行する関数をexec
イベントを時間主導型に設定します。
時間は適宜決めてください(私は1時間ごとにしました)
