|
<?php |
|
|
|
// Load HTTP library |
|
require_once('Requests/src/Autoload.php'); |
|
WpOrg\Requests\Autoload::register(); |
|
|
|
|
|
/** |
|
* Get the API method |
|
* @return String The API method |
|
*/ |
|
function get_method () { |
|
return $_SERVER['REQUEST_METHOD']; |
|
} |
|
|
|
/** |
|
* Get data object from API data |
|
* @return Object The data object |
|
*/ |
|
function get_request_data () { |
|
return array_merge(empty($_POST) ? array() : $_POST, (array) json_decode(file_get_contents('php://input'), true), $_GET); |
|
} |
|
|
|
/** |
|
* Send an API response |
|
* @param array $response The API response |
|
* @param integer $code The response code |
|
*/ |
|
function send_response ($response, $code = 200) { |
|
http_response_code($code); |
|
die($response); |
|
} |
|
|
|
/** |
|
* Check bearer authorization token |
|
* @return boolean If true, request is valid |
|
*/ |
|
function is_request_valid () { |
|
|
|
$auth_header = $_SERVER['HTTP_AUTHORIZATION']; |
|
|
|
// If there's no auth header, fail |
|
if (empty($auth_header)) return false; |
|
|
|
// Get auth token |
|
$auth = explode('Bearer', $auth_header); |
|
$request_token = trim($auth[1]); |
|
|
|
// If there's no token, fail |
|
if (empty($request_token)) return false; |
|
|
|
// If token doesn't match |
|
$local_token = getenv('BSKY_LOCAL_TOKEN'); |
|
if ($request_token !== $local_token) return false; |
|
|
|
// Otherwise, return true |
|
return true; |
|
|
|
} |
|
|
|
/** |
|
* Get a BlueSky DID for the user handle |
|
* @@link https://docs.bsky.app/docs/advanced-guides/resolving-identities |
|
* @return string The DID |
|
*/ |
|
function get_bluesky_did () { |
|
|
|
// Call BlueSky API |
|
$handle = getenv('BSKY_HANDLE'); |
|
$request = WpOrg\Requests\Requests::get('https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle)); |
|
|
|
// If there was an error |
|
if (empty($request->success)) { |
|
return false; |
|
} |
|
|
|
// Otherwise, return the DID |
|
$response = json_decode($request->body, true); |
|
return $response['did']; |
|
|
|
} |
|
|
|
/** |
|
* Get a BlueSky API token |
|
* @return string The token |
|
*/ |
|
function get_bluesky_token ($did) { |
|
|
|
// Get the app password |
|
$app_pw = getenv('BSKY_APP_PW'); |
|
|
|
// Request options |
|
$options = json_encode([ |
|
'identifier' => $did, |
|
'password' => $app_pw, |
|
]); |
|
|
|
// Request headers |
|
$headers = ['Content-Type' => 'application/json']; |
|
|
|
// Call the API |
|
$request = WpOrg\Requests\Requests::post('https://bsky.social/xrpc/com.atproto.server.createSession', $headers, $options); |
|
|
|
// If there was an error |
|
if (empty($request->success)) { |
|
return false; |
|
} |
|
|
|
// Otherwise, return the DID |
|
$response = json_decode($request->body, true); |
|
return $response['accessJwt']; |
|
|
|
} |
|
|
|
/** |
|
* Get the BlueSky credentials |
|
* @return array The DID and session token |
|
*/ |
|
function get_bluesky_auth () { |
|
|
|
// Get the handle DID |
|
$did = get_bluesky_did(); |
|
if (empty($did)) return false; |
|
|
|
// Get the token |
|
$token = get_bluesky_token($did); |
|
if (empty($token)) return false; |
|
|
|
// Return the credentials |
|
return [ |
|
'did' => $did, |
|
'token' => $token, |
|
]; |
|
|
|
} |
|
|
|
/** |
|
* Get details about the post from the live page |
|
* @param string $url The post URL |
|
* @return array The post details |
|
*/ |
|
function get_post_details ($url) { |
|
|
|
// Get the URL |
|
$request = WpOrg\Requests\Requests::get($url); |
|
|
|
// If there was an error |
|
if (empty($request->success)) { |
|
return null; |
|
} |
|
|
|
// Otherwise, get post details |
|
libxml_use_internal_errors(true); |
|
$html = $request->body; |
|
$doc = new DomDocument(); |
|
$doc->loadHTML($html); |
|
$xpath = new DOMXPath($doc); |
|
$query = '//*/meta[starts-with(@property, \'og:\')]'; |
|
$metas = $xpath->query($query); |
|
$details = array(); |
|
foreach ($metas as $meta) { |
|
$property = $meta->getAttribute('property'); |
|
$content = $meta->getAttribute('content'); |
|
$details[$property] = $content; |
|
} |
|
|
|
return $details; |
|
|
|
} |
|
|
|
/** |
|
* Publish content on BlueSky |
|
* @param string $status The post status |
|
* @param string $url The post URL |
|
*/ |
|
function publish_to_bluesky ($status, $url = '') { |
|
|
|
// Get authorization credentials |
|
$auth = get_bluesky_auth(); |
|
if (empty($auth)) { |
|
send_response('BlueSky authorization failed', 400); |
|
} |
|
|
|
// Create request options |
|
$options = [ |
|
'collection' => 'app.bsky.feed.post', |
|
'repo' => $auth['did'], |
|
'record' => [ |
|
'text' => $status, |
|
'createdAt' => gmdate('Y-m-d\TH:i:s\Z'), |
|
], |
|
]; |
|
|
|
// If there's a URL, add a card |
|
if (!empty($url)) { |
|
$details = get_post_details($url); |
|
$options['record']['embed'] = [ |
|
'$type' => 'app.bsky.embed.external', |
|
'external' => [ |
|
'uri' => $url, |
|
'title' => empty($details['og:title']) ? $status : $details['og:title'], |
|
'description' => empty($details['og:description']) ? '' : $details['og:description'], |
|
], |
|
]; |
|
} |
|
|
|
// Request headers |
|
$headers = [ |
|
'Content-Type' => 'application/json', |
|
'Authorization' => 'Bearer ' . $auth['token'], |
|
]; |
|
|
|
// Call the API |
|
$request = WpOrg\Requests\Requests::post('https://bsky.social/xrpc/com.atproto.repo.createRecord', $headers, json_encode($options)); |
|
|
|
// If there was an error |
|
if (empty($request->success)) { |
|
send_response('Post failed', 400); |
|
// send_response($request->body, 400); |
|
} |
|
|
|
// Otherwise, success! |
|
send_response('ok'); |
|
|
|
} |
|
|
|
// Get the HTTP method |
|
$method = get_method(); |
|
|
|
// If not a post request |
|
if ($method !== 'POST') { |
|
send_response('Method not allowed', 405); |
|
} |
|
|
|
// If request not authorized |
|
if (!is_request_valid()) { |
|
send_response('Authorization failed', 400); |
|
} |
|
|
|
// Get the post data |
|
$data = get_request_data(); |
|
publish_to_bluesky($data['status'], $data['url']); |