Source: wpt-post-to-bluesky.php

<?php
/**
 * Send API queries for a post to Bluesky.
 *
 * @category Post from WordPress.
 * @package  XPoster
 * @author   Joe Dolson
 * @license  GPLv2 or later
 * @link     https://www.xposter.com
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly.

require_once plugin_dir_path( __FILE__ ) . 'classes/class-wpt-bluesky-api.php';

/**
 * Upload media to Bluesky API.
 *
 * @param object   $connection Bluesky connection.
 * @param int|bool $auth Connection context.
 * @param int      $attachment Attachment ID.
 * @param array    $status Array of posting information.
 * @param int      $id Post ID.
 *
 * @return array Image blob array to be posted with status.
 */
function wpt_upload_bluesky_media( $connection, $auth, $attachment, $status, $id ) {
	$request = array();
	if ( $connection ) {
		if ( $attachment ) {
			$allowed = wpt_check_mime_type( $attachment, 'bluesky' );
			if ( ! $allowed ) {
				wpt_mail( 'Media upload mime type not accepted by Bluesky', get_post_mime_type( $attachment ), $id );

				return $request;
			}
			$alt_text = get_post_meta( $attachment, '_wp_attachment_image_alt', true );
			/**
			 * Add alt attributes to uploaded images.
			 *
			 * @hook wpt_uploaded_image_alt
			 *
			 * @param {string} $alt_text Text stored in media library as alt.
			 * @param {int}    $attachment Attachment ID.
			 *
			 * @return {string}
			 */
			$alt_text  = apply_filters( 'wpt_uploaded_image_alt', $alt_text, $attachment );
			$path      = wpt_attachment_path( $attachment, 'large' );
			$mimetype  = mime_content_type( $path );
			$mimetypes = array( 'image/png', 'image/jpeg', 'image/webp' );
			$size      = filesize( $path );
			// Return without attempting if fails to fetch image object.
			if ( ! ( in_array( $mimetype, $mimetypes, true ) && (int) $size < 1000000 ) ) {
				return $request;
			}
			$attachment_data = wpt_image_binary( $attachment, 'bluesky' );
			if ( ! $attachment_data ) {
				return $request;
			}
			$request = array(
				'data'         => $attachment_data,
				'content-type' => $mimetype,
			);
			$blob    = $connection->call_api( 'https://bsky.social/xrpc/com.atproto.repo.uploadBlob', $request );
			$request = array(
				'$type'  => 'app.bsky.embed.images',
				'images' => array(
					array(
						'alt'   => $alt_text,
						'image' => $blob['blob'],
					),
				),
			);
			wpt_mail( 'Media Uploaded (Bluesky)', "$auth, $attachment" . PHP_EOL . print_r( $blob, 1 ), $id );

		}
	}

	return $request;
}

/**
 * Post status to Bluesky.
 *
 * @param object $connection Connection to Bluesky.
 * @param mixed  $auth Main site or specific author ID.
 * @param int    $id Post ID.
 * @param array  $status Array of information sent to Bluesky.
 * @param array  $image Array of image data to add to Bluesky post.
 *
 * @return array
 */
function wpt_send_post_to_bluesky( $connection, $auth, $id, $status, $image ) {
	$notice = '';
	/**
	 * Turn on staging mode. Staging mode is automatically turned on if WPT_STAGING_MODE constant is defined.
	 *
	 * @hook wpt_staging_mode
	 * @param {bool}     $staging_mode True to enable staging mode.
	 * @param {int|bool} $auth Current author.
	 * @param {int}      $id Post ID.
	 * @param {string}   $service Service being put into staging.
	 *
	 * @return {bool}
	 */
	$staging_mode = apply_filters( 'wpt_staging_mode', false, $auth, $id, 'bluesky' );
	if ( ( defined( 'WPT_STAGING_MODE' ) && true === WPT_STAGING_MODE ) || $staging_mode ) {
		// if in staging mode, we'll behave as if the update succeeded, but not send it.
		$connection = true;
		$http_code  = 200;
		$notice     = __( 'In Staging Mode:', 'wp-to-twitter' ) . ' ' . $status['text'];
		$status_id  = false;
	} else {
		/**
		 * Filter the approval to send a Bluesky Skeet.
		 *
		 * @hook wpt_do_skeet
		 * @param {bool}     $do_skeet Return false to cancel this Skeet.
		 * @param {int|bool} $auth Author.
		 * @param {int}      $id Post ID.
		 * @param {string}   $text Status update text.
		 *
		 * @return {bool}
		 */
		$do_post   = apply_filters( 'wpt_do_skeet', true, $auth, $id, $status['text'] );
		$status_id = false;
		$success   = false;
		// Change status array to Bluesky expectation.
		$status = array(
			'type'      => 'app.bsky.feed.post',
			'text'      => $status['text'],
			'createdAt' => gmdate( DATE_ATOM ),
		);
		if ( ! empty( $image ) ) {
			$status['embed'] = $image;
		}
		/**
		 * Filter status array for Bluesky.
		 *
		 * @hook wpt_filter_bluesky_status
		 *
		 * @param {array}    $status Array of parameters sent to Bluesky.
		 * @param {int}      $post Post ID being tweeted.
		 * @param {int|bool} $auth Authoring context.
		 *
		 * @return {array}
		 */
		$status = apply_filters( 'wpt_filter_bluesky_status', $status, $id, $auth );
		if ( $do_post ) {
			$return = $connection->post_status( $status );
			if ( isset( $return['cid'] ) ) {
				$success   = true;
				$http_code = 200;
				$status_id = $return['cid'];
				$notice   .= __( 'Sent to Bluesky.', 'wp-to-twitter' );
			} else {
				$http_code = 401;
				$notice   .= __( 'Bluesky status update failed.', 'wp-to-twitter' );
			}
		} else {
			$http_code = '000';
			$notice   .= __( 'Bluesky status update cancelled by custom filter.', 'wp-to-twitter' );
		}
	}

	return array(
		'return'    => $success,
		'http'      => $http_code,
		'notice'    => $notice,
		'status_id' => $status_id,
		'service'   => 'bluesky',
	);
}

/**
 * Establish a client to Bluesky.
 *
 * @param mixed int|boolean $auth Current author context.
 * @param array             $verify Array of credentials to validate.
 *
 * @return mixed array or false
 */
function wpt_bluesky_connection( $auth = false, $verify = false ) {
	if ( ! empty( $verify ) ) {
		$password = $verify['password'];
		$user     = $verify['identifier'];
	} else {
		if ( ! $auth ) {
			$password = get_option( 'wpt_bluesky_token' );
			$user     = get_option( 'wpt_bluesky_username' );
		} else {
			$password = get_user_meta( $auth, 'wpt_bluesky_token', true );
			$user     = get_user_meta( $auth, 'wpt_bluesky_username', true );
		}
	}
	$bluesky = false;
	if ( $password && $user ) {
		$bluesky = new Wpt_Bluesky_Api( $user, $password );
		if ( $verify ) {
			$verify = $bluesky->verify();

			return $verify;
		}
	}

	return $bluesky;
}