PHP Download Twitter Friends and Follow Back

This continues my earlier tutorial PHP Download Twitter Followers and Save in MySQL Database by providing the functionality to:

  1. Download an account's friends and save this into a MySQL database
  2. Provide a discrepancy check between this list and the followers list, and delete all friends that are not also following
  3. Provide a discrepancy check between followers and friends, and follow back all those the account is not currently following

Theoretically, a script to do this should end up with the same amount of friends as followers, and providing this script and the previously mentioned script are run periodically, then they should remain in unison.

The database schema needs to be changed again - we need a copy of the friends list, but it is not important to also save the screen names so the table is very simple to create.

database schema

-- MySQL dump 10.13  Distrib 5.5.28, for Linux (x86_64)
--
-- Host: localhost    Database: xxxxxxxx
-- ------------------------------------------------------
-- Server version       5.5.28-log


--
-- Table structure for table `friends`
--

DROP TABLE IF EXISTS `friends`;

CREATE TABLE `friends` (
  `twitterid` bigint(20) NOT NULL,
  `update_dt` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY `twitterid` (`twitterid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- Dump completed on 2013-04-18 18:25:48

The next step is to create the PHP script. Unlike the followers script, this one needs authentication, so I used the same twitterauth.php script for this that I use for my retweeting script.

friends.php

<?php
// Copyright @badzillacouk <a href="http://www.badzilla.co.uk
//" title="http://www.badzilla.co.uk
//">http://www.badzilla.co.uk
//</a> Licence GPL. This program may be distributed as per the terms of GPL and all credits
// must be retained
//
// If you find this script useful, please consider a donation to help me fund my web presence
// and encourage me to develop more products to be placed under the terms of GPL
// To donate, go to <a href="http://www.badzilla.co.uk" title="http://www.badzilla.co.uk">http://www.badzilla.co.uk</a> and click on the donation button
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


set_include_path(get_include_path() . PATH_SEPARATOR . 'twitterauth');

require_once
'config.php';
require_once
'log.php';
require_once
'twitteroauth.php';




class
Friends {
   
    public
$db_dsn;
    public
$db_user;
    public
$db_password;
    public
$db_pdo;

    public
$oath;


    public function
__construct($db_dsn, $db_user, $db_password) {

       
$this->db_dsn = $db_dsn;
       
$this->db_user = $db_user;
       
$this->db_password = $db_password;

        try {
           
$this->db_pdo = new PDO($this->db_dsn, $this->db_user, $this->db_password);
        } catch (
PDOException $e) {
            echo
'Connection failed: ' . $e->getMessage();
            exit(
1);
        }

    }



   
/**
    * twitter_authenticate
    *
    * @param string $access_token The access token for this application
    * @param string $access_token_secret The access token secret for this application
    * @param string $consumer_key The consumer key for your account
    * @param string $consumer_secret The consumer key secret for your account
    */
   
public function twitter_authenticate($access_token, $access_token_secret, $consumer_key, $consumer_secret) {

       
$this->oauth = new TwitterOAuth($consumer_key, $consumer_secret, $access_token, $access_token_secret);
    }



   
/**
    * Verifies authentication - Fails silently if not authenticated
    * @return bool TRUE or FALSE
    */
   
public function verifyAccess() {

       
$credentials = $this->oauth->get('account/verify_credentials');

        return (
$this->oauth->http_code == 200) ? TRUE : FALSE;
    }



   
   
/**
    * getFriends - main loop to download list of those I am following and save in database
    * @param $user_account - name of the account
    * @return no return value
    */
   
public function getFriends($user_account) {

       
$log = new log($this->db_pdo);

       
$cursor = -1;
       
$friends = array();

       
// Main loop for download
       
do {

           
$accounts = $this->oauth->get('friends/ids', array('cursor' => $cursor, 'screen_name' => $user_account));

            if (isset(
$accounts->ids) and is_array($accounts->ids) and count($accounts->ids))
                foreach (
$accounts->ids as $value)
                   
$friends[] = $value;

           
$cursor = $accounts->next_cursor;

        } while (
$cursor > 0);

       
$log->logTwitterEvent('getFriends downloaded ' . count($friends) . ' friends');  

       
// Write into the database
       
foreach($friends as $value) {

           
// Do we update or do we insert?
           
$res = $this->db_pdo->prepare('SELECT COUNT(*) FROM friends WHERE twitterid = :id');
           
$res->execute(array('id' => $value));
           
$rows = $res->fetch(PDO::FETCH_NUM);

            if (!
$rows[0])
               
$this->db_pdo->prepare('INSERT INTO friends SET twitterid = :id, update_dt = NOW()')
                        ->
execute(array(':id' => $value));
            else
               
$this->db_pdo->prepare('UPDATE followers SET update_dt = NOW() WHERE twitterid = :id')
                        ->
execute(array(':id' => $value));
        }

       
$log->logTwitterEvent('getFriends completed db write');  
    }



   
/**
    * unfollowFriends - Unfollow all friends who aren't following me
    * @param $user_account - name of the account
    * @return no return value
    */
   
public function unfollowFriends($user_account) {
       
       
$log = new log($this->db_pdo);

       
$sql = 'SELECT twitterid FROM friends WHERE friends.twitterid NOT IN (SELECT followers.twitterid FROM followers)';

       
$res = $this->db_pdo->prepare($sql);
       
$res->execute();
       
$ids = $res->fetchAll();

        if (
$count = count($ids)) {
           
$log->logTwitterEvent('unfollowFriends attempting to unfollow ' . $count); 
            foreach(
$ids as $value) {
               
$this->oauth->post('friendships/destroy', array('user_id' => $value['twitterid']));
               
$res = $this->db_pdo->prepare('DELETE FROM friends WHERE twitterid = :id');
               
$res->execute(array('id' => $value['twitterid']));
            }
        }

       
$log->logTwitterEvent('unfollowFriends completed'); 
    }



   
/**
    * friendFollowers - follow back followers
    * @param $user_account - name of the account
    * @return no return value
    */
   
public function followBackFollowers($user_account) {
      
       
$log = new log($this->db_pdo);

       
$sql = 'SELECT twitterid FROM followers WHERE followers.twitterid NOT IN (SELECT friends.twitterid FROM friends)';

       
$res = $this->db_pdo->prepare($sql);
       
$res->execute();
       
$ids = $res->fetchAll();

        if (
$count = count($ids)) {
           
$log->logTwitterEvent('followBackFollowers attempting to follow ' . $count); 
            foreach(
$ids as $value) {
               
$this->oauth->post('friendships/create', array('user_id' => $value['twitterid']));
               
$this->db_pdo->prepare('INSERT INTO friends SET twitterid = :id, update_dt = NOW()')
                              ->
execute(array(':id' => $value['twitterid']));
            }
        }

       
$log->logTwitterEvent('followBackFollowers completed'); 

    }
}


$obj = new Friends($db_dsn, $db_user, $db_password);
$obj->twitter_authenticate($access_token, $access_token_secret, $consumer_key, $consumer_secret);
if (
$obj->verifyAccess()) {
   
$obj->getFriends($account_user);
   
$obj->unfollowFriends($account_user);
   
$obj->followBackFollowers($account_user);
}
?>

The final part to the jigsaw is to ensure that the retweeting script doesn't retweet any tweet that contains a mention to a non-follower. If that happens, the tweet is deleted from my cache without being retweeted. This strategy should ensure the bot remains active and without suspension for a little longer!

The consumer.php script now needs a new method - doesFollow to check whether a particular user is in the database, and there needs to be a logic check against all the mentions in a tweet.

Changes to consumer.php

<?php
           
// Worse case we'll need 2 x users + 1 API calls. Do we have enough headroom?
           
if (((count($users) * 2) + 1) >= $curr_limit)
                return;

           
// check each user to see if they are following. If not, bail
           
$bailout = FALSE;
            foreach(
$users as $user)
                if (
$user != $account_user)
                    if (!
$this->doesFollow($user)) {
                       
$bailout = TRUE;
                        break;
                    }

           
// Retweet unless already a retweet / one of mine
           
if (!$bailout and !isset($stat->retweeted_status) and $stat->user->screen_name != $account_user
               
$this->tweet($prefix . ' ' . '@' . $stat->user->screen_name . ': ' . $stat->text);

           
// check if friendship exists, if it doesn't, create one
//             foreach($users as $user)
//                 if ($user != $account_user)
//                     if (!$this->oauth->get('friendships/exists', array('screen_name_a' => $account_user, 'screen_name_b' => $user)))
//                         $this->oauth->post('friendships/create', array('screen_name' => $user));

            // Ruthlessly delete db record whether success or not
?>

<?php
   
/**
    * doesFollow - check whether a user follows you or not
    * @param $user - the screen name of the user
    * @return TRUE or FALSE
    */
   
private function doesFollow($user) {

       
$res = $this->db_pdo->prepare('SELECT COUNT(*) FROM followers WHERE name = :user');
       
$res->execute(array(':user' => $user));
       
$rows = $res->fetch(PDO::FETCH_NUM);

        return
$rows[0] ? TRUE : FALSE;       
    }
?>