6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

サーバーやサービスを使わないで定期的にTwitterの自動フォロー返ししてみる(2017)

Last updated at Posted at 2017-11-03

定期的にTwitterフォロー返しをする

いくつかサービスとか実装例があるのですが、情報が古かったり
痒いところに手が届かない感じだったので自作しました。
サーバ上でTwitter APIのスクリプトを起動する方法もありますが、
サーバを立てるのもめんどくさかったので
Google Apps Scriptで実装しました。

前準備

新規にスプレッドシートをGoogle Drive上で作成します。
スプレッドシートのツール→スクリプトエディタからGoogle Apps Script(.gs)を作成します。
スクリーンショット 2017-11-03 17.51.17.png

Google Apps ScriptのURLをコピーします。(あとで使います。)
スクリーンショット 2017-11-03 18.08.57.png

Twitter APIを利用するために
Twitter Application Managementで新規にアプリを作成します。
スクリーンショット 2017-11-03 18.01.28.png

新規にアプリを作成します。
Websiteがない場合は適当なURLでも大丈夫です。
Callback URLは先ほどコピーしたGoogle Apps ScriptのURLを指定します。
スクリーンショット 2017-11-03 18.13.25.png

Consumer KeyとConsumer Secretをメモしておきます(あとで使います。)
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f35353037372f34623962343961302d393430382d363237622d613333612d3731396230333362316532642e706e67.png

Twitter API利用にはOAuth1認証をするためGoogle Apps Scriptのライブラリを追加します。
リソース→ライブラリを開きます。

スクリーンショット 2017-11-03 18.26.45.png

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

1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
スクリーンショット 2017-11-03 18.27.13.png

実装

Google Apps Scriptに下記のスクリプトを記述します。

  • consumerKey
  • consumerSecret

は先程払い出したものを使用してください。

Twitterフォロー返し.gs
// 最初にこの関数を実行し、ログに出力された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関数を起動します。

スクリーンショット 2017-11-03 18.36.45.png

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

スクリーンショット 2017-11-03 18.36.55.png

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

スクリーンショット 2017-11-03 18.45.31.png

exec関数を実行するとフォローされていて未フォローのユーザを一括でフォローします。
フォローした結果のユーザはスプレッドシートに書き出しします。

定期的に実行する

編集→すべてのトリガーを選択します。

スクリーンショット 2017-11-03 18.47.32.png

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

スクリーンショット 2017-11-03 18.50.07.png
6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?