How to make big Facebook API requests faster? - php

I am working on an Facebook application in PHP that fetches a large amount of location information of the user's friends. The application gets increasingly slow as the number of friends of the users increases. But the more friends' information I retrieve, the more accurate is the result.
I have tried to use the following ways to speed up the query:
$facebook->api('/locations?ids=uid1,uid2,uid3,...')
And I used this together with batched requests:
$batched_request = array(
array('method' => 'GET', 'relative_url' => '/locations?ids=uid1,uid2,uid3,...'),
array('method' => 'GET', 'relative_url' => '/locations?ids=uid11,uid12,uid13,...'),
array('method' => 'GET', 'relative_url' => '/locations?ids=uid21,uid22,uid23,...'),
...
);
$batch = $facebook->api('/?batch='.json_encode($batched_request), 'POST');
But still it takes at least 20 seconds to get the location information from a random set of 100 friends of the user.
Actual Code Used
This part is fine. It gets done in just a few seconds.
$number_of_friends = "100"; // Set the maximum number of friends from which their location information is retrieved
$number_of_friends_per_request = 10; // Set the number of friends per request in the batch
$access_token = $facebook->getAccessToken();
// This is the excerpt of another batched request to get the friend ids
$request = '[{"method":"POST","relative_url":"method/fql.query?query=SELECT+uid,+name+FROM+user+WHERE+uid+IN(SELECT+uid2+FROM+friend+WHERE+uid1+=+me()+order+by+rand()+limit+'.$number_of_friends.')"}]';
$post_url = "https://graph.facebook.com/" . "?batch=" . urlencode($request) . "&access_token=" . $access_token . "&method=post";
$post = file_get_contents($post_url);
$decoded_response = json_decode($post, true);
$friends_json = $decoded_response[0]['body'];
$friends_data = json_decode($friends_json, true);
if (is_array($friends_data)) {
foreach ($friends_data as $friend) {
$selected_friend_ids[] = number_format($friend["uid"], 0, '.', ''); // Since there are exceptionally large id numbers
}
}
But this is problematic. It takes too long to receive a response from Facebook.
// Retrieve the locations of the user's friends using batched request
$i = 0;
$batched_request = array();
while ($i < ($number_of_friends/$number_of_friends_per_request)) {
$i++;
$friend_ids_variable_name = 'friend_ids_part_'.$i;
$$friend_ids_variable_name = array_slice($selected_friend_ids, ($i-1)*$number_of_friends_per_request, $number_of_friends_per_request);
if (!empty($$friend_ids_variable_name)) {
$api_string_ids_variable_name = 'api_string_ids_'.$i;
$$api_string_ids_variable_name = implode(',', $$friend_ids_variable_name);
$batched_request[] = array('method' => 'GET', 'relative_url' => '/locations?ids='.$$api_string_ids_variable_name);
}
}
$batch = $facebook->api('/?batch='.json_encode($batched_request), 'POST');
foreach ($batch as $batch_item) {
$body = $batch_item["body"];
$partial_friends_locations = json_decode($body, true);
foreach ($partial_friends_locations as $friend_id => $friend_locations_data) {
$friend_locations = $friend_locations_data["data"];
foreach ($friend_locations as $friend_location) {
// Process location information...
}
}
}
}
Is there a way to make the above request faster? I placed some codes to check the response time of the request and it is pretty slow.
For 100 friends, it takes > 20 seconds on average.
For 200 friends, it takes > 40 seconds on average.
For 400 friends, it takes > 80 seconds on average, and I sometimes receive an error message: "Error Code: 1 Message: An unknown error occurred"
To make things faster, it means:
Getting the same amount of information in less time, or
Getting more information for the same amount of time.

Why bother with batched requests? You can achieve everything with a single FQL multiquery:
{
"my_friends":
"SELECT uid, name FROM user WHERE uid IN
(SELECT uid2 FROM friend WHERE uid1 = me() ORDER BY rand() LIMIT 100)",
"their_locations":
"SELECT page_id, tagged_uids FROM location_post WHERE tagged_uids IN
(SELECT uid FROM #my_friends)",
"those_places":
"SELECT page_id, name, location FROM page WHERE page_id IN
(SELECT page_id FROM #their_locations"
}
In the API explorer, this runs in the 800-1200 ms range for me.
Another question: Why do you have the PHP SDK installed, but aren't using it to make these queries?

I too have noticed that facebook has a slow API response time. I have not actually developed a facebook application, but perhaps some of my learnings in dealing with the facebook like buttons and comment/share buttons will help you:
What I would suggest, is to lace your page with asynchronous api calls via javascript to get data. This way the page loads fast for your user, then it loads the facebook data in the background. You can accomplish this relatively easily with a library like jquery. Essentially you will break off the chunk of code that runs to process the data into another file and then run that with the jquery call.
Now that is the first part. The second part could be a little trick again to allow the user to perceive a faster application load time. Always load the first 100 friends first and display resulting data, and then query a second time (again using asynchronous calls), to finish querying for the rest of the users friends.
Again, this is speaking from more of a general perspective, but hopefully it will help!

Related

How can we make multiple php http request at the same time asynchronously?

I'm currently working on a project with my friends,
so let me explain:
We have a mySql database filled with english postcode from London, one table with universities, and one with hosts, what we want is to actually calculate the public transport travel time between all the host and the universities and save it into another table of the database that will have the host postcode, the university post code and the travel time between the both on one line, and etc...
For that we are doing http request to the tfl API that return to us a JSON with all the travel details (and of course the travel time), that we then decode and keep only what we want (travel time).
The problem is that we have a quite big database with almost 250 host and 800 universities that give us around 200 000 request and a too long process time to be used (with the api response time and the php treatment, around 19h)
We tried to see if we could use the cURL method to split the process between multiple loop so that we can divide the process time by the number of cURL we made but we can't manage to figure how we can do that...
The final goal is to make a small local app that when we select one university it give us the nearests 10 hosts in public transport.
Does anyone have any experience with that kind of things and can help us ?
Here is what we have right now :
//postCodeUni list contains all the universites objects
foreach ($postCodeUni as $uniPostCode) {
//here we take the postcode from the university object
$uni = $uniPostCode['Postcode'];
//postCodeHost list contains all the host objects
foreach ($postCodeHost as $hostPostCode) {
//here we take the postcode from the host object
$host = $hostPostCode['Postcode'];
//here we make an http request to the tfl api that return us a journey between the two post codes (a json with all the journey details)
$data = json_decode(file_get_contents('https://api.tfl.gov.uk/journey/journeyresults/' . $uni . '/to/' . $host . '?app_key=a59c7dbb0d51419d8d3f9dfbf09bd5cc'), true);
//here we save the multiple duration times (because there is different ways to travel between two point with public transport)
$duration = $data['journeys'];
$tableTemp = [];
foreach ($duration as $durations) {
$durationns = $durations['duration'];
array_push($tableTemp, $durationns);
}
//We then take the shorter one
$min = min($tableTemp);
echo "Shorter travel time : " . $min . " of travel between " . $uni . " and ". $host . " . <br>";
echo "<br>";
//We then save this time in a table that will contain the travel time of all the journeys to do comparaison
array_push($tableAllRequest, array($uni . " and ". $host => $min));
}
}
There are many ways to achieve this however the easiest imo would be to use Guzzle Async (cURL multi interface under the hood). Take a look at this answer - Guzzle async requests not really async? example below,
<?php
use GuzzleHttp\Promise;
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'http://httpbin.org/']);
// Initiate each request but do not block
$promises = [
'image' => $client->getAsync('/image'),
'png' => $client->getAsync('/image/png'),
'jpeg' => $client->getAsync('/image/jpeg'),
'webp' => $client->getAsync('/image/webp')
];
// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);
// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();
// Loop through each response in the results and fetch data etc
foreach($results as $promiseKey => $result) {
// Data response
$dataOfResponse = ($result['value']->getBody()->getContents());
// Status
echo $promiseKey . ':' . $result['value']->getStatusCode() . "\r\n";
}

having a predetermined ban list for telegram bot when added to a group

This telegram bot tries to ban a predetermined user IDs each time added to a group. IDs are listed in a file. I'm using this telegram bot class and in webhook method.
$telegram = new Telegram($bot_id);
$chat_id = $telegram->ChatID();
$button = $telegram->text();
if ($button == "DoBan"){
$fn = fopen("ids.txt","r");
while(! feof($fn)) {
$result = fgets($fn);
$int_result = (int)$result;
$content = array('chat_id' => $chat_id, 'user_id' => "$int_result");
$telegram->kickChatMember($content);
}
fclose($fn);
$content2 = array('chat_id' => $chat_id, 'text' => "ban done! ");
$telegram->sendMessage($content2);
die();
}
But there is two problems here:
1. If IDs listed in our file haven't been banned before in the group bot is added to, the bot can't ban any of them. the point is if you ban IDs manually in any other group and perform a banning by bot there one time you can ban those IDs in any other group after that.
2. When bot receives the "DoBan" after it finishes the banning it get stuck in a some kind of loop and prints "ban done!" over and over like it's doing the banning all over again.
UPDATE:
Based on further researches there are two reasons for the two expressed problems
For the first problem: If your bot hasn't seen the user ID before it cannot ban it. So the bot has to see the user first somewhere, either by starting the bot or seeing that user inside the same group with bot
For the second problem: In case of an unsuccessful request telegram bot API thinks something is wrong with your server and requests again every few seconds so you have to add header("HTTP/1.1 200 OK"); to your script or do the increment on update_id
So this is the updated code
$telegram = new Telegram($bot_id);
header("HTTP/1.1 200 OK");
http_response_code(200);
$chat_id = $telegram->ChatID();
$result = $telegram->getData();
$text = $result['message'] ['text'];
$update = $result ['update_id'];
$result ['update_id'] = ++$update;
if ($button == "DoBan"){
$fn = fopen("ids.txt","r");
while(! feof($fn)) {
$result = fgets($fn);
$int_result = (int)$result;
$content = array('chat_id' => $chat_id, 'user_id' => "$int_result");
$telegram->kickChatMember($content);
}
fclose($fn);
$content2 = array('chat_id' => $chat_id, 'text' => "ban done! ");
$telegram->sendMessage($content2);
die();
}
So I need to know how should I exactly increase the update_id or what is the proper way to add header("HTTP/1.1 200 OK");becasue the problems still exist.
Based on further researches there are two reasons for the two expressed problems
for the first problem: If your bot hasn't seen the user ID before it cannot ban it. So the bot has to see the user first somewhere, either by starting the bot or seeing that user inside the same group with bot
for the second problem: In case of an unsuccessful request telegram bot API thinks something is wrong with your server and requests again every few seconds so you have to add header("HTTP/1.1 200 OK"); to your script or do the increment on update_id

i am using a rest API i need to parse a forever changing json result

Okay so here goes i am using a rest api called strichliste
i am creating a user credit payment system
i am trying to grab a users balance by username problems is
my restapi i can only get the blanace via its userid
I have created a bit of php that grabs all the current users and the corresponding id and balance using this below
function getbal(){
// Get cURL resource
$curl = curl_init();
// Set some options - we are passing in a useragent too here
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => 'https://example.io:8081/user/'
)
);
// Send the request & save response to $resp
$resp = curl_exec($curl);
// Close request to clear up some resources
curl_close($curl);
print_r($resp);
}
this is the resulting respinse i get after using this in my main php script
<? getbal(); ?>
result --- #
{
"overallCount":3,
"limit":null,
"offset":null,"entries":[
{"id":1,
"name":"admin",
"balance":0,
"lastTransaction":null
},
{"id":2,
"name":"pghost",
"balance":0,
"lastTransaction":null
},
{"id":3,
"name":"sanctum",
"balance":0,
"lastTransaction":null
}
]
}
as you can see there are only currently 3 users but this will grow everyday so the script needs to adapt to growing numbers of users
inside my php script i have a var with the currently logged in use so example
$user = "sanctum";
i want a php script that will use the output fro gatbal(); and only output the line for the given user in this case sanctum
i want it to output the line in jsondecode for the specific user
{"id":3,"name":"sanctum","balance":0,"lastTransaction":null}
can anyone help
$user = "sanctum";
$userlist = getbal();
function findUser($u, $l){
if(!empty($l['entries'])){
foreach($l['entries'] as $key=>$val){
if($val['name']==$user){
return $val;
}
}
}
}
This way, once you have the list, and the user, you can just invoke findUser() by plugging in the userlist, and the user.
$userData = findUser($user, $userlist);
However, I would suggest finding a way to get the server to return only the user you are looking for, instead of the whole list, and then finding based on username. But thats another discussion for another time.

Handling unread posts in PHP / MySQL

For a personal project, I need to build a forum using PHP and MySQL. It is not possible for me to use an already-built forum package (such as phpBB).
I'm currently working through the logic needed to build such an application, but it's been a long day and I'm struggling with the concept of handling unread posts for users. One solution I had was to have a separate table which essentially holds all post IDs and user IDs, to determine if they've been read:
tbl_userReadPosts: user_id, post_id, read_timestamp
Obviously, if a user's ID appears in this table, we know they've read the post. This is great, except if we have thousdands of posts per day (which is more than possible in the system which is being proposed), and thousdands of users. This table would become huge within a matter of days, if not hours.
Another option would be to track the user's last activity as a timestamp, and then retrieve all posts made after their last activity was updated. This works in theory, but let's say a user is writing an extremely long post, and in the meantime several members also start new threads or reply to posts in other threads. When the user submits his new post, his last activity would be updated, and thus not match those made in the meantime.
Does anyone have experience with this, and how did you tackle it?
I've checked in phpBB and it seems that the system assigns a custom session to each user, and works on that basis, but the documentation is pretty sparse as to how this deals with unread posts.
Thoughts and opinions gratefully received, as always.
Sorry for the quick answer but I only have a second. You definitely do not want to store the read information in the database, as you've already deduced, this table would become gigantic.
Something in between what you've already suggested: Store the users last activity, and in conjunction with storing information of what they've seen in the cookie, to determine which threads/posts they've read already.
This offloads the storage to the client side cookie, which is far more efficient.
A table holding all user_ids and post_ids is a bad idea, as it grows exponentially. Imagine if your forum solution grew to a million posts and 50,000 users. Now you have 50 billion records. That'll be a problem.
The trick is to use a table as you said, but it only holds posts which have been read since the this login, of posts which were posted between the last login and this login.
All posts made prior to the last login are considered read.
IE, I last logged in on 4/3/2011, and then I log in today. All posts made before 4/3/2011 are considered read (they're not new to me). All posts between 4/3/2011 and now, are unread unless they are seen in the read table. The read table is flushed each time I log in.
This way your read posts table should never have more than a couple hundred records for each member.
Instead of having a new row for every post*user, you can have a field in the user-table that holds a comma-separated string with post-IDs that the user has read.
Obviously the user doesn't need to know that there are unread posts from 2 years ago, so you only display "New post" for posts made in the last 24 hours and is not in the comma-separated string.
You could also solve this with a session variable or a cookie.
This method stores the most recently-accessed postID separately for each forumID.
It's not as fine-grained as a solution that keeps track of each post individually, but it shrinks the amount of data that you need to store per user and still provides a decent way to keep track of a user's view history.
<?php
session_start();
//error_reporting(E_ALL);
// debug: clear session
if (isset($_GET['reset'])) { unset($_SESSION['activity']); }
// sample data: db table with your forum ids
$forums = array(
// forumID forumTitle
'1' => 'Public Chat',
'2' => 'Member Area',
'3' => 'Moderator Mayhem'
);
// sample data: db table with your forum posts
$posts = array(
// postID forumID postTitle
'12345' => array( 'fID'=>'1', 'title'=>'Hello World'),
'12346' => array( 'fID'=>'3', 'title'=>'I hate you all'),
'12347' => array( 'fID'=>'1', 'title'=>'Greetings!'),
'12348' => array( 'fID'=>'2', 'title'=>'Car thread'),
'12349' => array( 'fID'=>'1', 'title'=>'I like turtles!'),
'12350' => array( 'fID'=>'2', 'title'=>'Food thread'),
'12351' => array( 'fID'=>'3', 'title'=>'FR33 V1AGR4'),
'12352' => array( 'fID'=>'3', 'title'=>'CAPSLOCK IS AWESOME!!!!!!!!'),
'12353' => array( 'fID'=>'2', 'title'=>'Funny pictures thread'),
);
// sample data: db table with the last read post from each forum
$userhist = array(
// forumID postID
'1' => '12344',
'2' => '12350',
'3' => '12346'
);
// reference for shorter code
$s = &$_SESSION['activity'];
// store user's history into session
if (!isset($s)) { $s = $userhist; }
// mark forum as read
if (isset($_GET['mark'])) {
$mid = (int)$_GET['mark'];
if (array_key_exists($mid, $forums)) {
// sets the last read post to the last entry in $posts
$s[$mid] = array_search(end($posts), $posts);
}
// mark all forums as read
elseif ($mid == 0) {
foreach ($forums as $fid=>$finfo) {
// sets the last read post to the last entry in $posts
$s[$fid] = array_search(end($posts), $posts);
}
}
}
// mark post as read
if (isset($_GET['post'])) {
$pid = (int)$_GET['post'];
if (array_key_exists($pid, $posts)) {
// update activity if $pid is newer
$hist = &$s[$posts[$pid]['fID']];
if ($pid > $hist) {
$hist = $pid;
}
}
}
// link to mark all as read
echo '<p>[Read All]</p>' . PHP_EOL;
// display forum/post info
foreach ($forums as $fid=>$finfo) {
echo '<p>Forum: ' . $finfo;
echo ' [Mark as Read]<br>' . PHP_EOL;
foreach ($posts as $pid=>$pinfo) {
if ($pinfo['fID'] == $fid) {
echo '- Post: ' . $pid . '';
echo ' - ' . ($s[$fid] < $pid ? 'NEW' : 'old');
echo ' - "' . $pinfo['title'] . '"<br>' . PHP_EOL;
}
}
echo '</p>' . PHP_EOL;
}
// debug: display session value and reset link
echo '<hr><pre>$_SESSION = '; print_r($_SESSION); echo '</pre>' . PHP_EOL;
echo '<hr>[Reset Session]' . PHP_EOL;
?>
Note: Obviously this example is for demonstration purposes only. Some of the structure and logic may need to be changed when dealing with an actual database.
Phpbb2 has implemented this fairly simple. It just shows you all post since your last login. This way you don’t need to store any information about what the user actually has seen or read.

facebook api: count attendees for event (limit problem)

Okay normally I'm all fine about the facebook API but I'm having a problem which just keeps me wondering. (I think it's a bug (Check ticket http://bugs.developers.facebook.net/show_bug.cgi?id=13694) but I wanted to throw it here if somebody has an idea).
I'm usng the facebook PHP library to count all attendees for a specific event
$attending = $facebook->api('/'.$fbparams['eventId'].'/attending');
this works without a problem it correctly returns an array with all attendees...
now heres the problem:
This event has about 18.000 attendees right now.
The api call returns a max number of 992 attendees (and not 18000 as it should).
I tried
$attending = $facebook->api('/'.$fbparams['eventId'].'/attending?limit=20000');
for testing but it doesn't change anything.
So my actual question is:
If I can't get it to work by using the graph api what would be a good alternative? (Parsing the html of the event page maybe?) Right now I'm changing the value by hand every few hours which is tedious and unnecessary.
Actually there are two parameters, limit and offset. I think that you will have to play with both and continue making calls until one returns less than the max. limit.
Something like this, but in a recursive approach (I'm writting pseudo-code):
offset = 0;
maxLimit = 992;
totalAttendees = count(result)
if (totalAttendees >= maxLimit)
{
// do your stuff with each attendee
offset += totalAttendees;
// make a new call with the updated offset
// and check again
}
I've searched a lot and this is how I fixed it:
The requested URL should look something like this.
Here is where you can test it and here is the code I used:
function events_get_facebook_data($event_id) {
if (!$event_id) {
return false;
}
$token = klicango_friends_facebook_token();
if ($token) {
$parameters['access_token'] = $token;
$parameters['fields']= 'attending_count,invited_count';
$graph_url = url('https://graph.facebook.com/v2.2/' . $event_id , array('absolute' => TRUE, 'query' => $parameters));
$graph_result = drupal_http_request($graph_url, array(), 'GET');
if(is_object($graph_result) && !empty($graph_result->data)) {
$data = json_decode($graph_result->data);
$going = $data->attending_count;
$invited = $data->invited_count;
return array('going' => $going, 'invited' => $invited);
}
return false;
}
return false;
}
Try
SELECT eid , attending_count, unsure_count,all_members_count FROM event WHERE eid ="event"

Categories