Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- // 2023 by Thomas Nesges
- // (c) CC-BY
- class Bsky {
- private $username;
- private $password;
- private $xrpcbase;
- private $did;
- private $bearer;
- private $refreshToken;
- public $connected;
- public $handle;
- public $email;
- public $emailConfirmed;
- public $profile_url;
- function __construct($username, $password, $xrpcbase='https://bsky.social/xrpc') {
- $this->username = $username;
- $this->password = $password;
- $this->xrpcbase = $xrpcbase;
- }
- function connect() {
- $response = $this->xrpc_post('/com.atproto.server.createSession', [
- "identifier" => $this->username,
- "password" => $this->password,
- ]);
- $this->did = $response['did'];
- $this->bearer = $response['accessJwt'];
- $this->handle = $response['handle'];
- $this->email = $response['email'];
- $this->emailConfirmed = $response['emailConfirmed'];
- $this->refreshToken = $response['refreshJwt'];
- // todo: how do I find the profile url?
- $this->profile_url = 'https://bsky.app/profile/'.$this->handle;
- $this->connected = false;
- if($response['_curl']['http_code']==200) {
- $this->connected = true;
- }
- return $response;
- }
- function post($skeet_text, $languages=['de-DE']) {
- $postfields = [
- "repo" => $this->did,
- "collection" => "app.bsky.feed.post",
- "record" => [
- '$type' => "app.bsky.feed.post",
- 'createdAt' => date("c"),
- 'text' => $skeet_text,
- 'langs' => $languages,
- ]
- ];
- // find links and mark them as app.bsky.richtext.facet#link
- $start = 0; $end = 0;
- preg_match_all('/[$|\W](https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*[-a-zA-Z0-9@%_\+~#\/\/=])?)/', $skeet_text, $matches, PREG_SET_ORDER);
- foreach($matches as $match) {
- $link = $match[1];
- if($link) {
- $start = strpos($skeet_text, $link, $end);
- $end = $start + strlen($link);
- $postfields["record"]["facets"][] = [
- "index" => [
- 'byteStart' => $start,
- 'byteEnd' => $end,
- ],
- "features" => [[
- '$type' => "app.bsky.richtext.facet#link",
- 'uri' => $link,
- ]],
- ];
- // try to get a card for this link, if we don't already have one
- if(!isset($postfields["record"]["embed"])) {
- $card = $this->card($link);
- if($card) {
- $postfields["record"]["embed"] = $card;
- }
- }
- }
- }
- // *****START find hashtags
- // https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/richtext/facet.json
- $start = 0; $end = 0;
- preg_match_all('/(#[^\s#]+)/', $skeet_text, $matches, PREG_SET_ORDER);
- foreach($matches as $match) {
- $tag = $match[1];
- if($tag) {
- $start = strpos($skeet_text, $tag, $end);
- $end = $start + strlen($tag);
- $postfields["record"]["facets"][] = [
- "index" => [
- 'byteStart' => $start,
- 'byteEnd' => $end,
- ],
- "features" => [[
- '$type' => "app.bsky.richtext.facet#tag",
- 'tag' => substr($tag, 1),
- ]],
- ];
- }
- }
- // *****END find hashtags
- // find mentions and mark them as app.bsky.richtext.facet#mention
- $start = 0; $end = 0;
- preg_match_all('#[$|\W](@([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)#', $skeet_text, $matches, PREG_SET_ORDER);
- foreach($matches as $match) {
- $mention = $match[1];
- // check if bsky resolves mention as handle
- $response = $this->xrpc_get('/com.atproto.identity.resolveHandle', 'handle='.preg_replace('#@#', '', $mention));
- if($response['_curl']['http_code'] == 200) {
- $mentioned_did = $response['did'];
- $start = strpos($skeet_text, $mention, $end ?? 0);
- $end = $start + strlen($mention);
- $postfields["record"]["facets"][] = [
- "index" => [
- 'byteStart' => $start,
- 'byteEnd' => $end,
- ],
- "features" => [[
- '$type' => "app.bsky.richtext.facet#mention",
- 'did' => $mentioned_did,
- ]],
- ];
- }
- }
- # print "postfields: \n"; print json_encode($postfields); print json_last_error_msg(); print "\n"; exit;
- return $this->xrpc_post('/com.atproto.repo.createRecord', $postfields);
- }
- function card($url) {
- $doc = new DOMDocument();
- @$doc->loadHTMLFile($url);
- if($doc) {
- $xpath = new DOMXpath($doc);
- $xpath->registerNamespace('og', 'http://ogp.me/ns');
- $xpath->registerNamespace('fb', 'http://ogp.me/ns/fb');
- $xpath->registerNamespace('twitter', 'http://ogp.me/ns/twitter');
- $title = $this->nodeValue($xpath, ["//meta[@property='og:title']/@content", "//meta[@name='title']/@content", "//title", "//meta[@property='fb:title']/@content", "//meta[@property='twitter:title']/@content"]);
- $description = $this->nodeValue($xpath, ["//meta[@property='og:description']/@content", "//meta[@name='description']/@content", "//meta[@property='fb:description']/@content", "//meta[@property='twitter:description']/@content"]);
- // search for a possible image in this set of pathes
- $imgpaths = ["//meta[@property='og:image']/@content", "//meta[@property='og:image:url']/@content", "//meta[@property='og:image:secure_url']/@content", "//meta[@property='fb:image']/@content", "//meta[@property='twitter:image']/@content", "//link[@rel='icon']/@href"];
- $imgdata = false;
- foreach($imgpaths as $path) {
- foreach($xpath->query($path) as $node) {
- $imgurl = mb_convert_encoding($node->nodeValue, 'UTF-8', 'UTF-8');
- if($imgurl) {
- // load image
- $imgdata = @file_get_contents($imgurl);
- if($imgdata) {
- // stop after the first loadable image
- break;
- }
- }
- }
- if($imgdata) {
- break;
- }
- }
- if($imgdata) {
- // max filesize is 976.56 KB
- if(strlen($imgdata) > 976560) {
- // resize till it fits
- $quality = 90;
- $tmp = sys_get_temp_dir().'/'.basename($img);
- while(!file_exists($tmp) || filesize($tmp) > 976560 && $quality >= 0) {
- print "\nDEBUG resizing $img from ".strlen($imgdata)." with quality $quality ..\n";
- $this->_image_compress($img, $tmp, $quality);
- print "DEBUG new size: ".filesize($tmp)."\n";
- $quality -= 10;
- }
- $imgdata = file_get_contents($tmp);
- unlink($tmp);
- }
- if(strlen($imgdata) <= 976560) {
- // get mime type
- $headers = implode("\n", $http_response_header);
- if (preg_match_all("/^content-type\s*:\s*(.*)$/mi", $headers, $matches)) {
- $content_type = end($matches[1]);
- }
- // upload image to bsky
- $response = $this->xrpc_post('/com.atproto.repo.uploadBlob', $imgdata, $content_type);
- // attach blob data
- if(isset($response['blob'])) {
- $thumb = $response['blob'];
- }
- }
- } else {
- print "\nDEBUG no image found\n";
- }
- if(isset($thumb)) {
- return [
- '$type' => "app.bsky.embed.external",
- 'external' => [
- "uri" => $url,
- "title" => html_entity_decode(strip_tags(mb_convert_encoding($title, 'UTF-8', 'UTF-8'))),
- "description" => html_entity_decode(strip_tags(mb_convert_encoding($description, 'UTF-8', 'UTF-8'))),
- "thumb" => $thumb
- ]
- ];
- }
- }
- return false;
- }
- function nodeValue($xpath, $paths) {
- foreach($paths as $path) {
- $nodes = $xpath->query($path);
- if($nodes[0]) {
- return mb_convert_encoding($nodes[0]->nodeValue, 'UTF-8', 'UTF-8');
- }
- }
- return false;
- }
- function getProfile($actor_id) {
- return $this->xrpc_get('/app.bsky.actor.getProfile', 'actor='.$actor_id);
- }
- function xrpc_post($lexicon, $postfields=[], $content_type='application/json') {
- $httpheader = [ 'Content-Type: '.$content_type ];
- // if we have auth, send auth
- if(isset($this->bearer)) {
- $httpheader[] = 'Authorization: Bearer '.$this->bearer;
- }
- $curl = curl_init();
- curl_setopt_array($curl, [
- CURLOPT_URL => $this->xrpcbase.$lexicon,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_ENCODING => '',
- CURLOPT_MAXREDIRS => 10,
- CURLOPT_TIMEOUT => 0,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
- CURLOPT_POST => true,
- CURLOPT_CUSTOMREQUEST => 'POST',
- CURLOPT_POSTFIELDS => is_array($postfields) ? json_encode($postfields) : $postfields,
- CURLOPT_HTTPHEADER => $httpheader,
- CURLOPT_HEADER => true,
- ]);
- $response = curl_exec($curl);
- $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
- $header = substr($response, 0, $header_size);
- $response = substr($response, $header_size);
- $curl_info = curl_getinfo($curl);
- curl_close($curl);
- $response = json_decode($response, TRUE);
- $response['_curl'] = $curl_info;
- $response['_header'] = $header;
- return $response;
- }
- function xrpc_get($lexicon, $params='') {
- $httpheader = [ 'Content-Type: application/json' ];
- // if we have auth, send auth
- if(isset($this->bearer)) {
- $httpheader[] = 'Authorization: Bearer '.$this->bearer;
- }
- $curl = curl_init();
- curl_setopt_array($curl, [
- CURLOPT_URL => $this->xrpcbase.$lexicon.'?'.$params,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_ENCODING => '',
- CURLOPT_MAXREDIRS => 10,
- CURLOPT_TIMEOUT => 0,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
- CURLOPT_HTTPHEADER => $httpheader,
- ]);
- $response = curl_exec($curl);
- $curl_info = curl_getinfo($curl);
- curl_close($curl);
- $response = json_decode($response, TRUE);
- $response['_curl'] = $curl_info;
- return $response;
- }
- function _image_compress($file, $compressed, $quality) {
- $info = getimagesize($file);
- if ($info['mime'] == 'image/jpeg') {
- $image = imagecreatefromjpeg($file);
- } else if ($info['mime'] == 'image/gif') {
- $image = imagecreatefromgif($file);
- } else if ($info['mime'] == 'image/png') {
- $image = imagecreatefrompng($file);
- }
- imagejpeg($image, $compressed, $quality);
- return $compressed;
- }
- }
- ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement