Building a Social Competition & Social listening Tool for Twitter with PHP

Social media marketing is a multi-dimensional task.

Two key aspects to focus on include building engagement with new and existing customers, and growing brand visibility. A third is conversions.

As part of a multi-pronged social strategy, Social marketers should be consitently running social marketing initiatives which deliver measurable leads & conversions, and not just focusing on metrics (which are sometimes vanity metrics) which don’t directly map to business goals (eg retweets. reach).

Enter Social competitions – the magic bullet which – when executed well – is the trinty of engagement, visiblity and conversions.

Social Media Competition – Campaign Goals

Let’s consider some S,M.A.R.T Campaign Goals (Specific, Measurable, Achievable, Realistic, Time-based). What do we want to achieve, and how do we do it ?

Engagement – We will make the user perform some action which makes them interact with our Twitter account.
Conversions – We will make the user take action then prove it (Giving us their customer ID, username, etc.).
Visiblity – We will make the user post the status openly (not an @reply), and mention our brand and / or hashtags.

Competition – The prize must be something relevant and of value to our audience / target demographic.

Social Competition Case Study: 888Poker’s $888 Freeroll Promotion

A Freeroll is a poker tournament with no entry fee, but with a real-money prize-pool.

@888Poker runs a monthly $888 Freeroll. In order to get enter / get your seat in the tournament, you must first Tweet to enter (888 gets engagement, brand visiblity) and you must also tweet your 888 userID (so if you don’t already have one, you need to go to 888Poker and create an account.

New accounts = leads which can be nurtured to conversion [depositing customer] via smart CRM + email marketing, while for existing account holders, this initiative can also play a small part in an retention strategy.

The Competition Landing Page

888’s Landing page for the promotion is free of distractions, and well optimised for conversions and frictionless sharing:

  • No distractions – menu items hidden
  • No cost – “FREE”
  • Extra value – $88 Free
  • Clear calls to action
  • Use of Urgency – Countdown Timer.
  • Frictionless Entry – Click a button and a pre-populated (and optimized) tweet is created for the user.

If we do a search on Twitter advanced search for ‘I want to play in the $888 Twitter Freeroll’, it seems like there’s no shortage of people entering the competition, 24/7.


This is a superb example of a viral social competition, geared towards conversion, engagement and viibility by @888Poker.

Data Anaysis Show me the Money

Let’s try to quantify how effective this campaign actually is for 888Poker by doing using some graph theory a powerful social nework analysis tool (Nodexl Pro) to perform a social network analytis on Twitter interaction data we extract from the Twitter API (Tweets matching the search query ‘I want to play in the $888 Twitter Freeroll).

Twitter Sociogram – 7 days of social competition Tweets


(you can also view the Graph on the NodeXL Graph Gallery here)

The Campaign by the Numbers

High Engagementin just seven days 1361 Twitter accounts engaged / opted in to this promotion.
Reach & Visibility – potential reach of 107,489 (based on sum of followers of each vertext except the 888Poker vertext in the graph).
In fact, a separate analysis we performed of >1.5 Million ‘poker’related tweets (1st – 31st March 2017) showed 888 Poker in the top 10 most engaged Twitter accounts in the uber-competitive poker Niche.
Conversions – Each entrant is either an existing customer, or has created a new account and is entered into an email conversion funnel.
Cost – $888

How to create a social Competition Lead-Generation Tool

So wouldn’t it be great if you could run competitions via on your own Twitter account, which delivered leads, customer engagement and brand visiblily – all on autopilot? Based on the data above, it seems like a high-ROI, low cost to win (as part of a balanced strategy of course).

We will :

  1. Create a twitter app (in order to search the Twitter API via our own code + scripts),
  2. create a free social media listening app to listen to all @mentions our Twitter account receives (the account connected to the Twitter app), and where appropriate we will do something with the data we extract from the tweet texts we receive.
  3. Set up a scheduler task to trigger our listening script every at regular intervals.

Firstly We will use the PHP TwitterAuth API by Abraham library to interact with the Twitter API (once you have supplied it with your access tokens from your Twitter APP.)

We also need to set up and use a ‘Twitter app’ to get credentials to enable use to use the Twitter API. (Read our guide to setting up a Twitter app here).

We can use the Twitter API to get Tweet data from tweets that @mention our Twitter account.

Upon inspecting the Twitter API Documentation for Mentions it is evident that we can get 200 results at a time, and that we can use a since_id parameter to only get @mention tweets which were posted after the last time we used the API for a @mention search of our Twitter handle.

Our script will need access to storage (either a database or a csv file) in order to save data (Tweets, entries, usernames) and our since_id counter (which refers to the last tweet we returned via the API when our scheduler runs).

The returned data can be interpreted, and where apprporiate, we can save the data (for picking a winner, or for allocating entry tickets) or perform some other dynamic interaction (such as confirmation tweet, or DM).

Step 1 – Monitoring @mentions

Some Code:

function getMentions()
	{
		global $connection, $filename;
		$params =[
			'count' => 200,
		];

		$line=readlastline($filename); //Read last line of our tweets CSV, pull out the ID so we can use it in our API query
		if ($line !="")
		{
			$aRow = explode(',',$line);
			$params['since_id'] = $aRow[2];	
			echo "Retrieving all Tweet mentions since Tweet id: ".$aRow[2]."<br/>";
		}
		 
		$result = $connection->get('statuses/mentions_timeline',$params,true);
		//echo '<pre>';  print_r($result); echo '</pre>'; 	
		if (sizeof($result) >0)
		{
			if (!isset($result['errors']))
			{
				storeInteractionData($result);
				echo "[OK] data processed. <br/>";
			}
			else
			{
				echo "[ERROR] Something went wrong.. Surprise attack killed him in his sleep that night.<br/>";
				echo '<pre>';  print_r($result); echo '</pre>'; 	
			} 
		}
		else
			echo '[WARNING] No data to process. Do you not have any friends?<br/>';
	}

We will store things to csv files, and we need to know the last tweetID we processed, which will be on the last line of the file:

//=============================================================================================================== 
	function readlastline($fileName)
	{
		$t = "";
	 	try
	    {
	       $fp = @fopen($fileName, "r");

	      if ( !file_exists($fileName) ) 
	      {
	        throw new Exception('File not found.');
	      }

	       $begining = fseek($fp, 0);      
	       $pos = -1;
	       $nlcount = 0;	//csv file ends in a \n so we need to parse to 2nd one.. 

	       while ($nlcount <2) 
	       {
	             fseek($fp, $pos, SEEK_END);
	             if(ftell($fp) == $begining)
	             {
	              break;
	             }
	             $t = fgetc($fp);
	             if ($t == "\n")
	             	$nlcount ++;
	             $pos = $pos - 1;
	       }
	       $t = fgets($fp);
	       fclose($fp);

	    }catch ( Exception $e )
	    {
	    	//echo $e;
	    	return '';
	    } 

	    return $t;
	}
//=============================================================================================================== 

Step 2 – Storing Social Interactions

Storing the data we need in a structured format to a CSV file.


function storeInteractionData($aTweetObjs)
{
	$aTweetObjs= array_reverse($aTweetObjs);
	global $filename;
	$aTweets = [];
	foreach ($aTweetObjs as $oTweet)
	{
		$a = [];
		$a['created'] = $oTweet->created_at;
		$a['timestamp'] = strtotime($oTweet->created_at);
		$a['id'] =  $oTweet->id;
		$a['text'] = 	$oTweet->text;
		$a['in_reply_to_tweet_id'] = $oTweet->in_reply_to_status_id_str;
		$a['name'] = $oTweet->user->name;
		$a['user_screen_name'] = $oTweet->user->screen_name;
		$a['followers_count"'] = $oTweet->user->followers_count;
		
		$aTweets[]=$a;
	}
	appendToCsv($aTweets, $filename);
  
}
 
	function appendToCsv($aTweetObjs, $filename)
	{
	    $fp = fopen($filename, 'a');
	    foreach($aTweetObjs as $values){
	        fputcsv($fp, $values);
	    }
	    fclose($fp);
	}

Step 3 – Parsing the data & storing results

Let’s update the above function to handle this, in a way that lets us add multiple filters, and specify output files for each filter, in addition to extracting data from the correct offset in any Tweet.

//=============================================================================================================== 
function storeInteractionData($aTweetObjs)
{
	$aTweetObjs= array_reverse($aTweetObjs);
	global $filename;
	$aTweets = [];
	foreach ($aTweetObjs as $oTweet)
	{
		$a = [];
		$a['created'] = $oTweet->created_at;
		$a['timestamp'] = strtotime($oTweet->created_at);
		$a['id'] =  $oTweet->id;
		$a['text'] = 	$oTweet->text;
		$a['in_reply_to_tweet_id'] = $oTweet->in_reply_to_status_id_str;
		$a['name'] = $oTweet->user->name;
		$a['user_screen_name'] = $oTweet->user->screen_name;
		$a['followers_count"'] = $oTweet->user->followers_count;
		
		$aTweets[]=$a;
	}
	appendToCsv($aTweets, $filename);
 

	$aFilters = [
		['file'=>  getcwd().'/'.'888-freeroll.csv', 'search'=> 'i want to play in the $888 Twitter Freeroll', 'delimiter' =>'My @888poker username is '] 
	];

	foreach ($aFilters as $aFilter)
	{
			foreach ($aTweetObjs as $oTweet)
			{
				$aResults=[];
				if (strpos($oTweet->text, $aFilter['search']) !== FALSE )
				{
					$s= explode($aFilter['delimiter'], $oTweet->text);
					$str = trim($s[1]);
					$achunks= explode(" ", $str);
					$id = $achunks[0];
					//echo 'ID: '.$id.'<br/>';
					$aResults[]= array ($ts =strtotime($oTweet->created_at), $twitID=$oTweet->user->screen_name, $id);
 				}
 				if (sizeof($aResults)>0)
 				{
 					appendToCsv($aResults, $aFilter['file']);
 				}
			}
	}
}
 
//=============================================================================================================== 

Executing this code at regular intervals (cron job) and all tweets to @yourhandle will get saved for future reference and analysis.
sample csv data

Time, Timestamp, Tweet_ID, text, inreplyto_tweet_id, display_name, screen_name, follower_coun
"Wed Apr 26 18:07:19 +0000 2017",1493230039,857295039042637833,"@marketinghack3r looks good!",8572666295257088,"hutlersInc",hustlersinc123231,5375
"Wed Apr 26 23:51:11 +0000 2017",1493250671,857381574341533696,"My @888Poker Username is pokergirl2001 I want to play in the $888 Twitter Freeroll. ",,pokergirl,pokergirl12001,6252

If you added a filter like the one above, the competition data will be stored in the csv file specified (containing usernames, twitter IDs of competition entrants).

Timestamp, twitterID, 888_ID
1313250572, pokergirl2001, pokergirl2001
1469325901, jack, brokesoon
1493252922, marketinghack3r, bbv4l

Finally we need to set up a cron job to trigger the script at regular intervals. We can usually do this via hosting control panel.

Twitter Social Listening & Competition Tool in PHP – Full Code:

<?php
 	/** 
 		Social competition data extraction & Social Listening app for Twitter + PHP  
 		(c) 2017 -  https://marketinghacker.blog  |  twitter.com/marketinghack3r
 	**/
	global $connection, $cwd, $filename;
	require_once('twitteroauth/twitteroauth.php'); 
//===============================================================================================================
/**  Fill in your Twitter APP Data https://marketinghacker.blog/blog/guide-creating-a-twitter-app-for-marketers/  **/

	$cwd = getcwd();
	
	require_once($cwd.'/twitteroauth-master/autoload.php'); //https://github.com/abraham/twitteroauth is your friend
	use Abraham\TwitterOAuth\TwitterOAuth;
	global $connection; 
	$filename = $cwd.'/'.'tweets.csv';	 
	$connection = new TwitterOAuth(
		$CONSUMER_KEY="INSERT-CONSUMER-KEY-HERE", 
    	$CONSUMER_SECRET="INSERT-CONSUMER-SECRET-KEY-HERE", 
    	$access_token="INSERT-ACCESS-TOKEN-HERE", 
    	$access_token_secret="INSERT-ACCESS-TOKEN-SERET-HERE"
    );
	   
	getMentions(); //Do Amazing Shit

//=============================================================================================================== 
	function getMentions()
	{
		global $connection, $filename;
		$params =[
			'count' => 200,
		];

		$line=readlastline($filename);
		if ($line !="")
		{
			$aRow = explode(',',$line);
			$params['since_id'] = $aRow[2];	
			echo "Retrieving all Tweet mentions since Tweet id: ".$aRow[2]."<br/>";
		}
		 
		$result = $connection->get('statuses/mentions_timeline',$params,true);
		//echo '<pre>';  print_r($result); echo '</pre>'; 	
		if (sizeof($result) >0)
		{
			if (!isset($result['errors']))
			{
				storeInteractionData($result);
				echo "[OK] data processed. <br/>";
			}
			else
			{
				echo "[ERROR] Something went wrong.. Surprise attack killed him in his sleep that night.<br/>";
				echo '<pre>';  print_r($result); echo '</pre>'; 	
			} 
		}
		else
			echo '[WARNING] No data to process. Do you not have any friends?<br/>';
	}
 
//=============================================================================================================== 
function storeInteractionData($aTweetObjs)
{
	$aTweetObjs= array_reverse($aTweetObjs);
	global $filename, $cwd;
	$aTweets = [];
	foreach ($aTweetObjs as $oTweet)
	{
		$a = [];
		$a['created'] = $oTweet->created_at;
		$a['timestamp'] = strtotime($oTweet->created_at);
		$a['id'] =  $oTweet->id;
		$a['text'] = 	$oTweet->text;
		$a['in_reply_to_tweet_id'] = $oTweet->in_reply_to_status_id_str;
		$a['name'] = $oTweet->user->name;
		$a['user_screen_name'] = $oTweet->user->screen_name;
		$a['followers_count"'] = $oTweet->user->followers_count;
		
		$aTweets[]=$a;
	}
	appendToCsv($aTweets, $filename);
 

	$aFilters = [
		['file'=>  $cwd.'/'.'888-freeroll.csv', 'search'=> 'i want to play in the $888 Twitter Freeroll', 'delimiter' =>'My @888poker username is '] 
	];

	foreach ($aFilters as $aFilter)
	{
			foreach ($aTweetObjs as $oTweet)
			{
				$aResults=[];
				if (strpos($oTweet->text, $aFilter['search']) !== FALSE )
				{
					$s= explode($aFilter['delimiter'], $oTweet->text);
					$str = trim($s[1]);
					$achunks= explode(" ", $str);
					$id = $achunks[0];
					//echo 'ID: '.$id.'<br/>';
					$aResults[]= array ($ts =strtotime($oTweet->created_at), $twitID=$oTweet->user->screen_name, $id);
 				}
 				if (sizeof($aResults)>0)
 				{
 					appendToCsv($aResults, $aFilter['file']);
 				}
			}
	}
}
 
//=============================================================================================================== 
	function appendToCsv($aTweetObjs, $filename)
	{
	    $fp = fopen($filename, 'a');
	    foreach($aTweetObjs as $values){
	        fputcsv($fp, $values);
	    }
	    fclose($fp);
	}
//=============================================================================================================== 
	function readlastline($fileName)
	{
		$t = "";
	 	try
	    {
	       $fp = @fopen($fileName, "r");

	      if ( !file_exists($fileName) ) 
	      {
	        throw new Exception('File not found.');
	      }

	       $begining = fseek($fp, 0);      
	       $pos = -1;
	       $nlcount = 0;	//csv file ends in a \n so we need to parse to 2nd one.. 

	       while ($nlcount <2) 
	       {
	             fseek($fp, $pos, SEEK_END);
	             if(ftell($fp) == $begining)
	             {
	              break;
	             }
	             $t = fgetc($fp);
	             if ($t == "\n")
	             	$nlcount ++;
	             $pos = $pos - 1;
	       }
	       $t = fgets($fp);
	       fclose($fp);

	    }catch ( Exception $e )
	    {
	    	//echo $e;
	    	return '';
	    } 

	    return $t;
	}
 
   
//===============================================================================================================

?>

Leave a Reply

Your email address will not be published. Required fields are marked *

Marketing Hacker

Online Marketing Tips, Hacks & Code Snippets