forked from lolcat/4get
1
0
Fork 0
4get/scraper/facebook.php

810 lines
17 KiB
PHP

<?php
class facebook{
const get = 0;
const post = 1;
public function __construct(){
include "lib/nextpage.php";
$this->nextpage = new nextpage("fb");
}
public function getfilters($page){
return [
"sort" => [
"display" => "Sort by",
"option" => [
"relevance" => "Relevance",
"most_recent" => "Most recent"
]
],
"newer" => [
"display" => "Newer than",
"option" => "_DATE"
],
"older" => [
"display" => "Older than",
"option" => "_DATE"
],
"live" => [
"display" => "Livestream",
"option" => [
"no" => "No",
"yes" => "Yes"
]
]
];
}
private function get($url, $get = [], $reqtype = self::get){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
if($reqtype === self::get){
$headers = [
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:115.0) Gecko/20100101 Firefox/115.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"
];
$url .= "?" . $get;
}else{
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$headers = [
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:115.0) Gecko/20100101 Firefox/115.0",
"Accept: */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Content-Type: application/x-www-form-urlencoded",
"X-FB-Friendly-Name: SearchCometResultsPaginatedResultsQuery",
//"X-FB-LSD: AVptQC4a16c",
//"X-ASBD-ID: 129477",
"Content-Length: " . strlen($get),
"Origin: https://www.facebook.com",
"DNT: 1",
"Connection: keep-alive",
"Referer: https://www.facebook.com/watch/",
"Cookie: datr=__GMZCgwVF5BbyvAtfJojQwg; oo=v1%7C3%3A1691641171; wd=955x995",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"TE: trailers"
];
curl_setopt($curlproc, CURLOPT_POST, true);
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
}
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function video($get){
$search = $get["s"];
$npt = $get["npt"];
$this->out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
if($get["npt"]){
$nextpage =
json_decode(
$this->nextpage->get(
$npt,
"videos"
),
true
);
// parse next page
$this->video_nextpage($nextpage);
return $this->out;
}
// generate filter data
// {
// "rp_creation_time:0":"{\"name\":\"creation_time\",\"args\":\"{\\\"start_year\\\":\\\"2023\\\",\\\"start_month\\\":\\\"2023-08\\\",\\\"end_year\\\":\\\"2023\\\",\\\"end_month\\\":\\\"2023-08\\\",\\\"start_day\\\":\\\"2023-08-10\\\",\\\"end_day\\\":\\\"2023-08-10\\\"}\"}",
// "videos_sort_by:0":"{\"name\":\"videos_sort_by\",\"args\":\"Most Recent\"}",
// "videos_live:0":"{\"name\":\"videos_live\",\"args\":\"\"}"
// }
$filter = [];
$sort = $get["sort"];
$live = $get["live"];
$older = $get["older"];
$newer = $get["newer"];
if(
$older !== false ||
$newer !== false
){
if($older === false){
$older = time();
}
if($newer === false){
$newer = 0;
}
$filter["rp_creation_time:0"] =
json_encode(
[
"name" => "creation_time",
"args" =>
json_encode(
[
"start_year" => date("Y", $newer),
"start_month" => date("Y-m", $newer),
"end_year" => date("Y", $older),
"end_month" => date("Y-m", $older),
"start_day" => date("Y-m-d", $newer),
"end_day" => date("Y-m-d", $older)
]
)
]
);
}
if($sort != "relevance"){
$filter["videos_sort_by:0"] =
json_encode(
[
"name" => "videos_sort_by",
"args" => "Most Recent"
]
);
}
if($live != "no"){
$filter["videos_live:0"] = json_encode(
[
"name" => "videos_live",
"args" => ""
]
);
}
$req = [
"q" => $search
];
if(count($filter) !== 0){
$req["filters"] =
base64_encode(
json_encode(
$filter
)
);
}
$html =
$this->get(
"https://www.facebook.com/watch/search/",
$req
);
/*
$handle = fopen("scraper/facebook.html", "r");
$html = fread($handle, filesize("scraper/facebook.html"));
fclose($handle);*/
preg_match_all(
'/({"__bbox":.*,"sequence_number":0}})\]\]/',
$html,
$json
);
if(!isset($json[1][1])){
throw new Exception("Could not grep JSON body");
}
$json = json_decode($json[1][1], true);
foreach(
$json
["__bbox"]
["result"]
["data"]
["serpResponse"]
["results"]
["edges"]
as $result
){
$this->parse_edge($result);
}
// get nextpage data
if(
$json
["__bbox"]
["result"]
["data"]
["serpResponse"]
["results"]
["page_info"]
["has_next_page"]
== 1
){
preg_match(
'/handleWithCustomApplyEach\(ScheduledApplyEach,({.*})\);}\);}\);<\/script>/',
$html,
$nextpagedata
);
// [POST] https://www.facebook.com/api/graphql/
// FORM data, not JSON!
$nextpage = [
"av" => "0",
"__user" => null,
"__a" => null,
"__req" => "2",
"__hs" => null,
"dpr" => "1",
"__ccg" => null,
"__rev" => null,
// another client side token
"__s" => $this->randomstring(6) . ":" . $this->randomstring(6) . ":" . $this->randomstring(6),
"__hsi" => null,
// tracking fingerprint (probably generated using webgl)
"__dyn" => "7xeUmwlE7ibwKBWo2vwAxu13w8CewSwMwNw9G2S0im3y4o0B-q1ew65xO2O1Vw8G1Qw5Mx61vw9m1YwBgao6C0Mo5W3S7Udo5q4U2zxe2Gew9O222SUbEaU2eU5O0GpovU19pobodEGdw46wbS1LwTwNwLw8O1pwr86C16w",
"__csr" => $this->randomstring(null),
"__comet_req" => null,
"lsd" => null,
"jazoest" => null,
"__spin_r" => null,
"__spin_b" => null,
"__spin_t" => null,
"fb_api_caller_class" => "RelayModern",
"fb_api_req_friendly_name" => "SearchCometResultsPaginatedResultsQuery",
"variables" => [ // this is json
"UFI2CommentsProvider_commentsKey" => "SearchCometResultsInitialResultsQuery",
"allow_streaming" => false,
"args" => [
"callsite" => "comet:watch_search",
"config" => [
"exact_match" => false,
"high_confidence_config" => null,
"intercept_config" => null,
"sts_disambiguation" => null,
"watch_config" => null
],
"context" => [
"bsid" => null,
"tsid" => null
],
"experience" => [
"encoded_server_defined_params" => null,
"fbid" => null,
"type" => "WATCH_TAB_GLOBAL"
],
"filters" => [],
"text" => $search
],
"count" => 5,
"cursor" =>
$json
["__bbox"]
["result"]
["data"]
["serpResponse"]
["results"]
["page_info"]
["end_cursor"],
"displayCommentsContextEnableComment" => false,
"displayCommentsContextIsAdPreview" => false,
"displayCommentsContextIsAggregatedShare" => false,
"displayCommentsContextIsStorySet" => false,
"displayCommentsFeedbackContext" => null,
"feedLocation" => "SEARCH",
"feedbackSource" => 23,
"fetch_filters" => true,
"focusCommentID" => null,
"locale" => null,
"privacySelectorRenderLocation" => "COMET_STREAM",
"renderLocation" => "search_results_page",
"scale" => 1,
"stream_initial_count" => 0,
"useDefaultActor" => false,
"__relay_internal__pv__IsWorkUserrelayprovider" => false,
"__relay_internal__pv__IsMergQAPollsrelayprovider" => false,
"__relay_internal__pv__StoriesArmadilloReplyEnabledrelayprovider" => false,
"__relay_internal__pv__StoriesRingrelayprovider" => false
],
"server_timestamps" => "true",
"doc_id" => "6761275837251607" // is actually dynamic
];
// append filters to nextpage
foreach($filter as $key => $value){
$nextpage["variables"]["args"]["filters"][] =
$value;
}
$nextpagedata = json_decode($nextpagedata[1], true);
// get bsid
foreach($nextpagedata["require"] as $key){
foreach($key as $innerkey){
if(is_array($innerkey)){
foreach($innerkey as $inner_innerkey){
if(is_array($inner_innerkey)){
foreach($inner_innerkey as $inner_inner_innerkey){
if(
isset(
$inner_inner_innerkey
["variables"]
["args"]
["context"]
["bsid"]
)
){
$nextpage
["variables"]
["args"]
["context"]
["bsid"] =
$inner_inner_innerkey
["variables"]
["args"]
["context"]
["bsid"];
}
}
}
}
}
}
}
foreach($nextpagedata["define"] as $key){
if(isset($key[2]["haste_session"])){
$nextpage["__hs"] = $key[2]["haste_session"];
}
if(isset($key[2]["connectionClass"])){
$nextpage["__ccg"] = $key[2]["connectionClass"];
}
if(isset($key[2]["__spin_r"])){
$nextpage["__spin_r"] = (string)$key[2]["__spin_r"];
}
if(isset($key[2]["hsi"])){
$nextpage["__hsi"] = (string)$key[2]["hsi"];
}
if(
isset($key[2]["token"]) &&
!empty($key[2]["token"])
){
$nextpage["lsd"] = $key[2]["token"];
}
if(isset($key[2]["__spin_r"])){
$nextpage["__spin_r"] = (string)$key[2]["__spin_r"];
$nextpage["__rev"] = $nextpage["__spin_r"];
}
if(isset($key[2]["__spin_b"])){
$nextpage["__spin_b"] = $key[2]["__spin_b"];
}
if(isset($key[2]["__spin_t"])){
$nextpage["__spin_t"] = (string)$key[2]["__spin_t"];
}
}
preg_match(
'/{"u":"\\\\\/ajax\\\\\/qm\\\\\/\?__a=([0-9]+)&__user=([0-9]+)&__comet_req=([0-9]+)&jazoest=([0-9]+)"/',
$html,
$ajaxparams
);
if(count($ajaxparams) !== 5){
throw new Exception("Could not grep the AJAX parameters");
}
$nextpage["__a"] = $ajaxparams[1];
$nextpage["__user"] = $ajaxparams[2];
$nextpage["__comet_req"] = $ajaxparams[3];
$nextpage["jazoest"] = $ajaxparams[4];
/*
$handle = fopen("scraper/facebook-nextpage.json", "r");
$json = fread($handle, filesize("scraper/facebook-nextpage.json"));
fclose($handle);*/
$nextpage["variables"] = json_encode($nextpage["variables"]);
$this->video_nextpage($nextpage);
}
return $this->out;
}
private function video_nextpage($nextpage, $getcursor = false){
$json =
$this->get(
"https://www.facebook.com/api/graphql/",
$nextpage,
self::post
);
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode next page JSON");
}
foreach(
$json
["data"]
["serpResponse"]
["results"]
["edges"]
as $result
){
$this->parse_edge($result);
}
if(
$json
["data"]
["serpResponse"]
["results"]
["page_info"]
["has_next_page"] == 1
){
$nextpage["variables"] = json_decode($nextpage["variables"], true);
$nextpage["variables"]["cursor"] =
$json
["data"]
["serpResponse"]
["results"]
["page_info"]
["end_cursor"];
$nextpage["variables"] = json_encode($nextpage["variables"]);
//change this for second call. after, it's static.
// TODO: csr also updates to longer string
$nextpage["__dyn"] = "7xeUmwlEnwn8K2WnFw9-2i5U4e0yoW3q322aew9G2S0zU20xi3y4o0B-q1ew65xOfxO1Vw8G11xmfz81s8hwGwQw9m1YwBgao6C2O0B85W3S7Udo5qfK0EUjwGzE2swwwJK2W2K0zK5o4q0GpovU19pobodEGdw46wbS1LwTwNwLw8O1pwr86C16w";
// TODO: change this on third and 6th call
//$nextpage["__s"] = $this->randomstring(6) . ":" . explode(":", $nextpage["__s"], 2)[1];
$this->out["npt"] = $this->nextpage->store(json_encode($nextpage), "videos");
}
}
private function parse_edge($edge){
$append = "video";
$edge =
$edge
["relay_rendering_strategy"]
["view_model"];
if(
strtolower(
$edge
["video_metadata_model"]
["video_broadcast_status"]
)
== "live"
){
// handle livestream
$duration = "_LIVE";
$append = "livestream";
$timetext = null;
$views =
(int)$edge
["video_metadata_model"]
["relative_time_string"];
}elseif(
stripos(
$edge
["video_metadata_model"]
["video_broadcast_status"],
"vod"
) !== false
){
// handle VOD format
$timetext = null;
$views =
(int)$edge
["video_metadata_model"]
["relative_time_string"];
$duration =
$this->hms2int(
$edge
["video_thumbnail_model"]
["video_duration_text"]
);
}else{
// handle normal format
$timetext =
explode(
" · ",
$edge
["video_metadata_model"]
["relative_time_string"],
2
);
if(count($timetext) === 2){
$views = $this->truncatedcount2int($timetext[1]);
}else{
$views = null;
}
$timetext = strtotime($timetext[0]);
$duration =
$this->hms2int(
$edge
["video_thumbnail_model"]
["video_duration_text"]
);
}
if(
isset(
$edge
["video_metadata_model"]
["video_owner_profile"]
["uri_token"]
)
){
$profileurl =
"https://www.facebook.com/watch/" .
$edge
["video_metadata_model"]
["video_owner_profile"]
["uri_token"];
}else{
$profileurl =
$edge
["video_metadata_model"]
["video_owner_profile"]
["url"];
}
$this->out[$append][] = [
"title" =>
$this->limitstrlen(
str_replace(
"\n",
" ",
$edge
["video_metadata_model"]
["title"]
),
100
),
"description" =>
empty(
$edge
["video_metadata_model"]
["save_description"]
) ?
null :
str_replace(
"\n",
" ",
$this->limitstrlen(
$edge
["video_metadata_model"]
["save_description"]
)
),
"author" => [
"name" =>
$edge
["video_metadata_model"]
["video_owner_profile"]
["name"],
"url" => $profileurl,
"avatar" => null
],
"date" => $timetext,
"duration" => $duration,
"views" => $views,
"thumb" =>
[
"url" =>
$edge
["video_thumbnail_model"]
["thumbnail_image"]
["uri"],
"ratio" => "16:9"
],
"url" =>
"https://www.facebook.com/watch/?v=" .
$edge
["video_click_model"]
["click_metadata_model"]
["video_id"]
];
}
private function randomstring($len){
if($len === null){
$str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-";
$len = rand(141, 145);
$c = 61;
}else{
$str = "abcdefghijklmnopqrstuvwxyz123456789";
$c = 34;
}
$out = null;
for($i=0; $i<$len; $i++){
$out .= $str[rand(0, $c)];
}
return $out;
}
private function limitstrlen($text, $len = 300){
return explode("\n", wordwrap($text, $len, "\n"))[0];
}
private function hms2int($time){
$parts = explode(":", $time, 3);
$time = 0;
if(count($parts) === 3){
// hours
$time = $time + ((int)$parts[0] * 3600);
array_shift($parts);
}
if(count($parts) === 2){
// minutes
$time = $time + ((int)$parts[0] * 60);
array_shift($parts);
}
// seconds
$time = $time + (int)$parts[0];
return $time;
}
private function truncatedcount2int($number){
// decimal should always be 1 number long
$number = explode(" ", $number, 2);
$number = $number[0];
$unit = strtolower($number[strlen($number) - 1]);
$tmp = explode(".", $number, 2);
$number = (int)$number;
if(count($tmp) === 2){
$decimal = (int)$tmp[1];
}else{
$decimal = 0;
}
switch($unit){
case "k":
$exponant = 1000;
break;
case "m":
$exponant = 1000000;
break;
case "b";
$exponant = 1000000000;
break;
default:
$exponant = 1;
break;
}
return ($number * $exponant) + ($decimal * ($exponant / 10));
}
}