false,'error'=>"could not connect to «{$host}»",'headers'=>null]; $res=@json_decode($res,true); if (is_null($res)) return ['ok'=>false,'error'=>"could not decode JSON data from «{$endpoint}» (".json_last_error().': '.json_last_error_msg().")",'headers'=>$http_response_header]; if (isset($res['error'])) return ['ok'=>false,'error'=>lcfirst($res['error']),'headers'=>$http_response_header]; /*print_r($http_response_header); preg_match('#^\S+\s+(\S+)\s+(\S+)#',$http_response_header[0],$matches); print_r($matches); $httpcode=$matches[1]+0; $httpcodetext=$matches[2]; if (($httpcode>=400 && $httpcode<=499) || ($httpcode>=500 && $httpcode<=599)) return ['ok'=>false,'error'=>"HTTP error: {$httpcodetext}"];*/ return ['ok'=>true,'data'=>$res,'headers'=>$http_response_header]; } function mastget($host,$token,$endpoint,$timeout) { $context=[ 'http'=>[ 'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n", 'method'=>'GET', 'ignore_errors'=>true, 'timeout'=>$timeout ] ]; if (!is_null($token)) $context['http']['header'].="Authorization: Bearer {$token}\r\n"; $res=mastreq($context,$host,$endpoint); return $res; } function mastpost($host,$token,$endpoint,$content,$timeout) { $content=http_build_query($content); $context=[ 'http'=>[ 'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n", 'method'=>'POST', 'ignore_errors'=>true, 'content'=>$content, 'timeout'=>$timeout ] ]; if (!is_null($token)) $context['http']['header'].="Authorization: Bearer {$token}\r\n"; $res=mastreq($context,$host,$endpoint); return $res; } function mastpostfile($host,$token,$endpoint,$content,$timeout) { $content=http_build_query($content); $context=[ 'http'=>[ 'header'=>"Content-type: multipart/form-data;boundary=\"boundary\"\r\nAccept: application/json\r\n", 'method'=>'POST', 'ignore_errors'=>true, 'content'=>$content, 'timeout'=>$timeout ] ]; if (!is_null($token)) $context['http']['header'].="Authorization: Bearer {$token}\r\n"; $res=mastreq($context,$host,$endpoint); return $res; } function mastdel($host,$token,$endpoint,$timeout) { $context=[ 'http'=>[ 'header'=>"Content-type: application/x-www-form-urlencoded\r\nAccept: application/json\r\n", 'method'=>'DELETE', 'ignore_errors'=>true, 'timeout'=>$timeout ] ]; if (!is_null($token)) $context['http']['header'].="Authorization: Bearer {$token}\r\n"; $res=mastreq($context,$host,$endpoint); return $res; } /* some endpoints get auth required verify app creds and get app info: /api/v1/apps/verify_credentials verify user creds and get account info: /api/v1/accounts/verify_credentials get a post: /api/v1/statuses/[id] post auth required post a status: /api/v1/statuses send follow request to an account: /api/v1/accounts/[id]/follow unfollow an account: /api/v1/accounts/[id]/unfollow */ function splitpost($post,$avchars,$cw,$pre,$cntup) { // decided use $matches[1] instead of $matches[0] // to stay safe, $avchars should be at least 30 (didn't test with less); // $pre can be used to list recipients (in this case it has to end with // a "\n" or " "), or for anything else $post=preg_replace('#[ \t\f\r]+\n#',"\n",$post); $post=rtrim($post); $postrlen=strlen($post); $postlen=postlength($post); $cwlen=mb_strlen($cw,'UTF-8'); $prelen=postlength($pre); if ($postlen+$prelen+$cwlen<=$avchars) return [['cw'=>$cw,'post'=>$pre.$post,'mastlen'=>$postlen+$prelen+$cwlen]]; // there is no way to know the total of posts before splitting, and its // string length modifies the total, so we roughly estimate it very // cautiosly to the decrease, just to spare cycles $tot=''; $gtot=ceil($postlen/($avchars-7-2-$prelen-$cwlen));// "7" is the min length of the counter ("\n\n[x/x]"); 2 counts for start and end "…" for ($i=0; $i$totlen) break;// do another cycle $cnt="__[{$i}/{$tot}]"; //$lastcons=substr($post,$off,40); preg_match('#(\S+)(\s+|$)#',$post,$matches,0,$off); //var_dump($matches); if (count($matches)==0) {// done, last post $spost[]=['cw'=>$cw,'post'=>rtrim($buf)]; break 2; } $offadd=strlen($matches[0]); ($off+$offadd>=$postrlen) ? $dotsaddlen=0 : $dotsaddlen=2;// if we are on the last word, we don't add "…" if ($prelen+$cwlen+postlength($buf.$matches[1].$cnt)+$dotsaddlen>$avchars) {// if current match would make buf+overhead overcome avchars //echo "LONGMATCH: «$matches[0]»\n"; $nxcntlen=$totlen+strlen($i+1)+5;// next cnt may be different, so we precalc its length ($i==1 || $dotsaddlen==0) ? $nxdotsaddlen=2 : $nxdotsaddlen=4;// if we are on first or last post, we add 1 "…"; otherwise we add 2 if ($prelen+$cwlen+postlength($matches[1])+$nxcntlen+$nxdotsaddlen>$avchars) {// if next match+overhead is by itself longer than avchars //echo "BLOCKMATCH: «$matches[0]»\n"; //$len=$avchars-$nxcntlen-$prelen-$nxdotsaddlen; $len=$avchars-postlength($buf.$cnt)-$prelen-$cwlen-$dotsaddlen; if ($len>0) { // deactivate possible links because they will be broken $matches[0]=preg_replace('#^http(s)?://#','zttp$1://',$matches[0]); $matches[0]=preg_replace('#^@([a-zA-Z0-9_]+@[a-z0-9-]+)#','+$1',$matches[0]); $matches[0]=mb_substr($matches[0],0,$len,'UTF-8'); //echo "SUBSTRING: «$matches[0]»\n"; $offadd=strlen($matches[0]); //echo "{$matches[0]}: OFF: {$off}; OFFADD: {$offadd}\n"; $buf.=$matches[0]; $matches[0]=''; } } $spost[]=['cw'=>$cw,'post'=>rtrim($buf).' …']; $buf='… '; $i++; }/* else { echo "NORMATCH: «$matches[0]»\n"; }*/ $buf.=$matches[0]; $off+=$offadd; } $tot.='x'; } //echo '
'.print_r($spost,true).'
'; if ($cntup) foreach ($spost as $key=>$post) { $spost[$key]['post']="{$pre}[".($key+1)."/{$i}]\n\n{$post['post']}"; $spost[$key]['mastlen']=postlength($spost[$key]['post'])+$cwlen; } else foreach ($spost as $key=>$post) { $spost[$key]['post']="{$pre}{$post['post']}\n\n[".($key+1)."/{$i}]"; $spost[$key]['mastlen']=postlength($spost[$key]['post'])+$cwlen; } //echo "CYCLES: {$c}\n"; //echo "LASTCONS: {$lastcons}\n"; return $spost; } function postlength($post) { global $retlds; // echo "-A-> |{$post}|\n"; // for some reason, mastodon seems to check tld existence only on http(s) links - see next regexp $res=preg_replace('#(^|\W)(@[a-zA-Z0-9_]+)@(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\b#u', '$1$2', $post); if (!is_null($res)) $post=$res; // $res=preg_replace('#(^|\W)https?://(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}(/\S*[\w=?_-])?#u', '$1HTTP://UUUUUUUUUUUUUUUU', $post); // on http(s) links mastodon checks if tld exists... $res=preg_replace('#(^|\W)https?://(([a-z0-9]([a-z0-9-]+[a-z0-9])?){1,63}\.)+('.$retlds.')(/\S*[\w/=_\-])?#u', '$1UUUUUUUUUUUUUUUUUUUUUUU', $post); if (!is_null($res)) $post=$res; // echo "-B-> |{$post}|\n"; return mb_strlen($post,'UTF-8'); } // this function requires these to be defined: // - an "evhandle" function to handle events // - an "eecho" function to handle output // - a "$doshut" global variable and a "shutdown" function that, since it's placed in secure places, can be used eg to safely shut down the program when "$doshut" is set to true by eg a function bound to a signal, like pcntl_signal(SIGTERM,'sighandler') // see ocrbot for an example function evlisten($host,$port,$endpoint,$token,$timeout) { global $doshut; while (true) { shutdown($doshut); $dispurl="tls://{$host}:{$port}"; eecho(1,"trying to connect to «{$dispurl}»."); $sh=@fsockopen("tls://{$host}",$port,$errno,$errstr,$timeout); if ($sh===false) { eecho(3,"could not connect to «{$dispurl}»: {$errstr} ({$errno}); will try again in 1 second."); sleep(1); } else { //stream_set_blocking($sh,false); stream_set_timeout($sh,1,0); eecho(1,"succesfully connected to «{$dispurl}»."); $req="GET {$endpoint} HTTP/1.1\r\nHost: {$host}\r\nUser-Agent: a_bot\r\nAuthorization: Bearer {$token}\r\n\r\n"; if (fwrite($sh,$req)===false) { eecho(3,"could not subscribe to user notifications on «{$dispurl}»; will try again in 1 second."); fclose($sh); unset($sh);// this is because shutdown can check if $sh is set and if it is, try to fclose it sleep(1); } else { eecho(1,"listening for user notifications on «{$dispurl}»."); //$lc=0; while (!feof($sh)) { shutdown($doshut); //$lc++; $line=rtrim(fgets($sh),"\r\n"); //echo "{$lc}> {$line}\n"; if (preg_match('#^event: #',$line)===1) { $event=['type'=>preg_replace('#^event: #','',$line),'data'=>'']; $line=rtrim(fgets($sh),"\r\n"); //echo "{$lc} DATA> {$line}\n"; if (preg_match('#^data: #',$line)===1) { $event['data'].=preg_replace('#^data: #','',$line); while ($line!='') { $line=rtrim(fgets($sh),"\r\n"); if ($line=='') break; //echo "{$lc} LENGTH> {$line}\n"; $line=rtrim(fgets($sh),"\r\n"); //echo "{$lc} DATA> {$line}\n"; $event['data'].=$line; } $event['data']=@json_decode($event['data'],true); if ($event['data']===false) { eecho(2,"could not decode data for event of type «{$event['type']}»."); } else { //print_r($event); evhandle($event); } } } } fclose($sh); unset($sh);// this is because shutdown can check if $sh is set and if it is, try to fclose it eecho(3,"lost connection to «{$dispurl}»; will try reconnecting in 1 second."); sleep(1); } } } } ?>