Compare commits

..

No commits in common. "6b9d9d862ed726707c13ca2286255ad96e23ac73" and "038d32386d201a5da7824ca5c15155b43897c2d4" have entirely different histories.

12 changed files with 1129 additions and 1497 deletions

0
.gitignore vendored
View File

View File

@ -1,3 +0,0 @@
{
"direnv.path.executable": "/usr/bin/direnv"
}

152
README.md
View File

@ -1,98 +1,66 @@
=== Share On Bluesky === # Bluesky Publisher for WordPress
Contributors: eugenewebdoctor
Tags: bluesky, social media, cross-posting, atproto
Requires at least: 5.0
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 1.0.0
License: MIT
License URI: https://opensource.org/licenses/MIT
A simple WordPress plugin for automatically sharing your posts to Bluesky with support for featured images and customizable formatting. Automatically share your WordPress posts to Bluesky with customizable formatting, image handling, and queue management.
== Description == ## Description
Share On Bluesky is a lightweight WordPress plugin that enables automatic cross-posting to Bluesky. When you publish a post, it automatically shares it to your Bluesky account with proper formatting and image support. Bluesky Publisher for WordPress enables automatic cross-posting of your WordPress content to Bluesky. With customizable post formatting, image support, and reliable queue management, you can ensure your content looks great on Bluesky.
= Key Features = ### Features
* One-click connection to Bluesky using your handle and app password * Automatic post sharing to Bluesky
* Automatic post sharing when you publish
* Featured image support with auto-resizing
* Customizable post format with title and excerpt options
* Manual post/repost controls from post editor
* Secure token management with automatic refresh
== Installation ==
There are two ways to install the Share On Bluesky plugin:
= From WordPress Dashboard (Recommended) =
1. Go to your WordPress Dashboard > Plugins > Add New
2. Search for "Share On Bluesky"
3. Click "Install Now" next to the Share On Bluesky plugin
4. After installation completes, click "Activate"
5. Go to Settings > Bluesky to configure your connection
= Manual Installation =
1. Download the 'share-on-bluesky' plugin from WordPress.org
2. Go to your WordPress Dashboard > Plugins > Add New > Upload Plugin
3. Choose the downloaded zip file and click "Install Now"
4. After installation completes, click "Activate"
5. Go to Settings > Bluesky to configure your connection
= After Installation =
1. Enter your Bluesky handle (username.bsky.social)
2. Generate and enter an app password from your Bluesky account settings
3. Choose your preferred post format options
4. Test by publishing a new post
== Frequently Asked Questions ==
= Where do I find my Bluesky app password? =
You can generate an app password in your Bluesky account settings under "App Passwords". Never use your main account password.
= How are images handled? =
The plugin automatically uploads your post's featured image to Bluesky when sharing. Images are resized if needed to meet Bluesky's size limits.
= Can I manually control what gets posted? =
Yes! Each post has a Bluesky status box where you can manually share, retry, or repost content.
== Screenshots ==
1. Settings page showing connection and format options
2. Post editor integration with Bluesky status and controls
== Changelog ==
= 1.0.0 =
* Initial release
* Automatic post sharing with featured images
* Customizable post formatting * Customizable post formatting
* Manual post controls * Featured image support with automatic resizing
* Secure token management * Queue management for reliable posting
* Post meta box for post status and manual controls
* Support for post titles, excerpts, and links
* Proper spacing and formatting of posts
* Automatic token refresh
== Privacy Policy == ## Requirements
This plugin connects to Bluesky's servers (bsky.social) to share your posts. It stores: - WordPress 5.0 or higher
* Your Bluesky handle - PHP 7.4 or higher
* Authentication tokens (securely encrypted) - Bluesky account
* Post sharing status metadata
No other personal data is collected or shared. ## Installation
== Credits == 1. Upload 'bluesky-connector' folder to the '/wp-content/plugins/' directory
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Go to Settings > Bluesky Publisher to configure your connection
Developed by [Eugene Web Doctor](https://eugenewebdoctor.com) ## Configuration
== License == 1. Get your Bluesky app password
2. Enter your Bluesky handle and app password in the settings
3. Choose your preferred post format
4. Start publishing!
## Frequently Asked Questions
### Where do I find my Bluesky app password?
You can generate an app password in your Bluesky account settings under "App Passwords".
### What happens if an error occurs during posting?
Posts are added to a queue and the plugin will automatically retry failed posts. You can also manually retry posts from the post editor.
## Changelog
### 1.0.0
* Initial release
* Customizable post formatting
* Image support with automatic resizing
* Queue management system
* Post meta box controls
* Token refresh functionality
## License
This project is licensed under the MIT License - see below for details:
```
MIT License MIT License
Copyright (c) 2024 Eugene Web Doctor Copyright (c) 2024 Eugene Web Doctor
@ -114,13 +82,23 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
```
== Support Development == ## Credits
Developed by [Eugene Web Doctor](https://eugenewebdoctor.com)
## Support Development
If you find this plugin useful, consider supporting its development: If you find this plugin useful, consider supporting its development:
Lightning Network: ### Lightning Network
`enki@zap.sovbit.host` ```
enki@zap.sovbit.host
```
On-Chain Bitcoin: ### On-Chain Bitcoin
`bc1pe60ykxhl6h8j6w7dpwrn7qzcyay6l52dkfeulkgg72eezgmms3wss3ul42` ```
bc1pe60ykxhl6h8j6w7dpwrn7qzcyay6l52dkfeulkgg72eezgmms3wss3ul42
```

View File

@ -1,3 +1,4 @@
.bluesky-post-status { .bluesky-post-status {
padding: 10px; padding: 10px;
} }
@ -36,21 +37,3 @@
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
/* Button States */
.updating-message {
position: relative;
padding-left: 24px !important;
}
.updating-message:before {
content: '';
position: absolute;
top: 50%;
left: 8px;
margin-top: -8px;
width: 16px;
height: 16px;
background: url(../images/spinner.gif) no-repeat center;
background-size: 16px 16px;
}

View File

@ -1,145 +1,62 @@
jQuery(document).ready(function($) { jQuery(document).ready(function($) {
// Post status container element // Retry posting to Bluesky
const $statusContainer = $('.bluesky-post-status');
// Helper function to display status messages
function updateStatus(message, type = 'info') {
const $status = $(`<div class="notice notice-${type} is-dismissible"><p>${message}</p></div>`);
$statusContainer.prepend($status);
// Add dismiss button functionality
$status.find('.notice-dismiss').on('click', function() {
$status.fadeOut(300, function() { $(this).remove(); });
});
// Auto dismiss after 5 seconds for success messages
if (type === 'success') {
setTimeout(() => {
$status.fadeOut(300, function() { $(this).remove(); });
}, 5000);
}
}
// Handle immediate posting
$('.bluesky-share-post').on('click', function(e) {
e.preventDefault();
const $button = $(this);
const postId = $button.data('post-id');
const nonce = $button.data('nonce');
// Disable button and show loading state
$button.prop('disabled', true)
.addClass('updating-message')
.text(blueskyAdmin.strings.publishing);
$.ajax({
url: blueskyAdmin.ajaxUrl,
type: 'POST',
data: {
action: 'bluesky_post_now',
post_id: postId,
nonce: nonce
},
success: function(response) {
if (response.success) {
updateStatus(response.data.message, 'success');
location.reload();
} else {
updateStatus(response.data.message, 'error');
$button.prop('disabled', false)
.removeClass('updating-message')
.text(blueskyAdmin.strings.retry);
}
},
error: function(xhr, status, error) {
updateStatus(blueskyAdmin.strings.error, 'error');
$button.prop('disabled', false)
.removeClass('updating-message')
.text(blueskyAdmin.strings.retry);
}
});
});
// Handle retry posting
$('.bluesky-retry-post').on('click', function(e) { $('.bluesky-retry-post').on('click', function(e) {
e.preventDefault(); e.preventDefault();
const $button = $(this); var button = $(this);
const postId = $button.data('post-id'); var postId = button.data('post-id');
const nonce = $button.data('nonce');
// Disable button and show loading state button.prop('disabled', true);
$button.prop('disabled', true)
.addClass('updating-message')
.text(blueskyAdmin.strings.retrying);
$.ajax({ $.ajax({
url: blueskyAdmin.ajaxUrl, url: ajaxurl,
type: 'POST', type: 'POST',
data: { data: {
action: 'bluesky_post_now', action: 'bluesky_retry_post',
post_id: postId, post_id: postId,
nonce: nonce nonce: blueskyAdmin.nonce
}, },
success: function(response) { success: function(response) {
if (response.success) { if (response.success) {
updateStatus(response.data.message, 'success');
location.reload(); location.reload();
} else { } else {
updateStatus(response.data.message, 'error'); alert(response.data.message || 'Error retrying post');
$button.prop('disabled', false) button.prop('disabled', false);
.removeClass('updating-message')
.text(blueskyAdmin.strings.retry);
} }
}, },
error: function(xhr, status, error) { error: function() {
updateStatus(blueskyAdmin.strings.error, 'error'); alert('Network error. Please try again.');
$button.prop('disabled', false) button.prop('disabled', false);
.removeClass('updating-message')
.text(blueskyAdmin.strings.retry);
} }
}); });
}); });
// Handle reposting // Share post to Bluesky
$('.bluesky-repost').on('click', function(e) { $('.bluesky-share-post').on('click', function(e) {
e.preventDefault(); e.preventDefault();
const $button = $(this); var button = $(this);
const postId = $button.data('post-id'); var postId = button.data('post-id');
const nonce = $button.data('nonce');
if (!confirm(blueskyAdmin.strings.confirmRepost)) { button.prop('disabled', true);
return;
}
$button.prop('disabled', true)
.addClass('updating-message')
.text(blueskyAdmin.strings.reposting);
$.ajax({ $.ajax({
url: blueskyAdmin.ajaxUrl, url: ajaxurl,
type: 'POST', type: 'POST',
data: { data: {
action: 'bluesky_post_now', action: 'bluesky_share_post',
post_id: postId, post_id: postId,
nonce: nonce, nonce: blueskyAdmin.nonce
repost: true
}, },
success: function(response) { success: function(response) {
if (response.success) { if (response.success) {
updateStatus(response.data.message, 'success');
location.reload(); location.reload();
} else { } else {
updateStatus(response.data.message, 'error'); alert(response.data.message || 'Error sharing post');
$button.prop('disabled', false) button.prop('disabled', false);
.removeClass('updating-message')
.text(blueskyAdmin.strings.repost);
} }
}, },
error: function(xhr, status, error) { error: function() {
updateStatus(blueskyAdmin.strings.error, 'error'); alert('Network error. Please try again.');
$button.prop('disabled', false) button.prop('disabled', false);
.removeClass('updating-message')
.text(blueskyAdmin.strings.repost);
} }
}); });
}); });

BIN
bluesky-connctor.zip Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,15 @@
<?php <?php
class Bluesky_API { class Bluesky_API {
private $api_url = 'https://bsky.social/xrpc'; private $api_url = 'https://bsky.social/xrpc'; // Replace with the actual API endpoint
private $api_key; private $api_key;
private $did; private $did;
public function __construct($api_key, $did) { public function __construct($api_key, $did) {
$this->api_key = $api_key; $this->api_key = $api_key;
$this->did = $did; $this->did = $did;
error_log('[Bluesky Connector] Initializing API with DID: ' . $did);
} }
public function create_post($post_data) { public function create_post($post_data) {
error_log('[Bluesky Connector] Creating post with data: ' . wp_json_encode($post_data));
$headers = array( $headers = array(
'Authorization' => 'Bearer ' . $this->api_key, 'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
@ -20,96 +17,54 @@ class Bluesky_API {
$response = wp_remote_post($this->api_url . '/com.atproto.repo.createRecord', array( $response = wp_remote_post($this->api_url . '/com.atproto.repo.createRecord', array(
'headers' => $headers, 'headers' => $headers,
'body' => wp_json_encode(array( 'body' => json_encode(array(
'repo' => $this->did, 'repo' => $this->did,
'collection' => 'app.bsky.feed.post', 'collection' => 'app.bsky.feed.post',
'record' => $post_data, 'record' => $post_data,
)), )),
'timeout' => 30,
)); ));
if (is_wp_error($response)) { if (is_wp_error($response)) {
error_log('[Bluesky Connector] Create post error: ' . $response->get_error_message());
return array('error' => $response->get_error_message());
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
error_log('[Bluesky Connector] Create post response code: ' . $response_code);
error_log('[Bluesky Connector] Create post response: ' . $response_body);
return json_decode($response_body, true);
}
public function resolve_handle($handle) {
error_log('[Bluesky Connector] Resolving handle: ' . $handle);
$response = wp_remote_get($this->api_url . '/com.atproto.identity.resolveHandle', array(
'query' => array(
'handle' => $handle,
),
'timeout' => 30,
));
if (is_wp_error($response)) {
error_log('[Bluesky Connector] Resolve handle error: ' . $response->get_error_message());
return array('error' => $response->get_error_message()); return array('error' => $response->get_error_message());
} }
return json_decode(wp_remote_retrieve_body($response), true); return json_decode(wp_remote_retrieve_body($response), true);
} }
public function upload_blob($file_path_or_data, $mime_type) { public function resolve_handle($handle) {
error_log('[Bluesky Connector] Starting blob upload'); $response = wp_remote_get($this->api_url . '/com.atproto.identity.resolveHandle', array(
error_log('[Bluesky Connector] Mime type: ' . $mime_type); 'query' => array(
'handle' => $handle,
),
));
if (is_wp_error($response)) {
return array('error' => $response->get_error_message());
}
return json_decode(wp_remote_retrieve_body($response), true);
}
public function upload_blob($file_path, $mime_type) {
$headers = array( $headers = array(
'Authorization' => 'Bearer ' . $this->api_key, 'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => $mime_type, 'Content-Type' => $mime_type,
); );
// Determine if input is a file path or raw data $file_contents = file_get_contents($file_path);
if (is_string($file_path_or_data) && file_exists($file_path_or_data)) {
error_log('[Bluesky Connector] Reading from file path: ' . $file_path_or_data);
$file_contents = file_get_contents($file_path_or_data);
if ($file_contents === false) { if ($file_contents === false) {
error_log('[Bluesky Connector] Failed to read file'); return array('error' => 'Failed to read file.');
return array('error' => 'Failed to read file');
}
} else {
error_log('[Bluesky Connector] Using provided data directly');
$file_contents = $file_path_or_data;
}
// Check content size
$content_size = strlen($file_contents);
error_log('[Bluesky Connector] Content size: ' . $content_size . ' bytes');
if ($content_size > 1000000) {
error_log('[Bluesky Connector] Content size exceeds 1MB limit');
return array('error' => 'File size exceeds 1MB limit');
} }
$response = wp_remote_post($this->api_url . '/com.atproto.repo.uploadBlob', array( $response = wp_remote_post($this->api_url . '/com.atproto.repo.uploadBlob', array(
'headers' => $headers, 'headers' => $headers,
'body' => $file_contents, 'body' => $file_contents,
'timeout' => 30,
)); ));
if (is_wp_error($response)) { if (is_wp_error($response)) {
error_log('[Bluesky Connector] Upload blob error: ' . $response->get_error_message());
return array('error' => $response->get_error_message()); return array('error' => $response->get_error_message());
} }
$response_code = wp_remote_retrieve_response_code($response); return json_decode(wp_remote_retrieve_body($response), true);
$response_body = wp_remote_retrieve_body($response);
error_log('[Bluesky Connector] Upload blob response code: ' . $response_code);
error_log('[Bluesky Connector] Upload blob response: ' . $response_body);
if ($response_code !== 200) {
return array('error' => 'Upload failed with status ' . $response_code);
}
return json_decode($response_body, true);
} }
} }

View File

@ -20,74 +20,33 @@ class Bluesky_Auth {
// Ensure proper URL format // Ensure proper URL format
$this->api_domain = rtrim($domain, '/') . '/'; $this->api_domain = rtrim($domain, '/') . '/';
// Log configuration details if debugging is enabled // Debug logs
$this->log_debug('Auth Configuration', array( error_log('Bluesky Auth - Using domain setting: ' . get_option('bluesky_domain'));
'domain_setting' => get_option('bluesky_domain'), error_log('Bluesky Auth - Processed domain: ' . $this->api_domain);
'processed_domain' => $this->api_domain, error_log('Bluesky Auth - Identifier: ' . $this->identifier);
'identifier' => $this->identifier
));
} }
public function get_access_token() { public function get_access_token() {
// First check if we have a valid access token
$access_token = get_option('bluesky_access_jwt');
$token_created = get_option('bluesky_token_created');
if (!empty($access_token) && $token_created > (time() - 7200)) {
$this->log_debug('Using existing valid token');
return $access_token;
}
// If we have a refresh token, try to use it
if ($this->should_refresh_token()) { if ($this->should_refresh_token()) {
$refresh_result = $this->refresh_access_token(); return $this->refresh_access_token();
if (!isset($refresh_result['error'])) {
return $refresh_result;
}
}
// If we got here, we need to create a new session
if (empty($this->identifier) || empty($this->password)) {
// Try to get credentials from settings if not provided
$settings = get_option('bluesky_connector_settings', array());
if (!empty($settings['identifier'])) {
$this->identifier = $settings['identifier'];
}
if (!empty($settings['password'])) {
$this->password = $settings['password'];
}
// If still empty, try individual options
if (empty($this->identifier)) {
$this->identifier = get_option('bluesky_identifier');
}
if (empty($this->password)) {
$this->password = get_option('bluesky_password');
}
if (empty($this->identifier) || empty($this->password)) {
$this->log_debug('Missing credentials', array(
'has_identifier' => !empty($this->identifier),
'has_password' => !empty($this->password)
));
return array('error' => 'Missing credentials - please check settings');
}
} }
$token_url = $this->api_domain . 'xrpc/com.atproto.server.createSession'; $token_url = $this->api_domain . 'xrpc/com.atproto.server.createSession';
$this->log_debug('Attempting connection', array('url' => $token_url)); // Debug log
error_log('Bluesky Auth - Attempting connection to: ' . $token_url);
$headers = array( $headers = array(
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
); );
$body = wp_json_encode(array( $body = json_encode(array(
'identifier' => $this->identifier, 'identifier' => $this->identifier,
'password' => $this->password, 'password' => $this->password,
)); ));
$this->log_debug('Request prepared', array('body' => str_replace($this->password, '[REDACTED]', $body))); // Debug log
error_log('Bluesky Auth - Request body: ' . $body);
$wp_version = get_bloginfo('version'); $wp_version = get_bloginfo('version');
$user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')); $user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url'));
@ -96,50 +55,38 @@ class Bluesky_Auth {
'headers' => $headers, 'headers' => $headers,
'user-agent' => "$user_agent; Bluesky Connector", 'user-agent' => "$user_agent; Bluesky Connector",
'body' => $body, 'body' => $body,
'timeout' => 30, 'timeout' => 30, // Increase timeout
)); ));
if (is_wp_error($response)) { if (is_wp_error($response)) {
$error_message = $response->get_error_message(); $error_message = $response->get_error_message();
$this->log_debug('Auth Error', array('error' => $error_message)); error_log('Bluesky Auth Error: ' . $error_message);
return array('error' => $error_message); return array('error' => $error_message);
} }
// Log response details // Debug response
$status_code = wp_remote_retrieve_response_code($response); $status_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response); $response_body = wp_remote_retrieve_body($response);
$this->log_debug('Response received', array( error_log('Bluesky Auth - Response status: ' . $status_code);
'status' => $status_code, error_log('Bluesky Auth - Response body: ' . $response_body);
'body' => $response_body
));
$data = json_decode($response_body, true); $data = json_decode($response_body, true);
if (!empty($data['accessJwt']) && !empty($data['refreshJwt']) && !empty($data['did'])) { if (!empty($data['accessJwt']) && !empty($data['refreshJwt']) && !empty($data['did'])) {
// Save credentials in both locations
$settings = get_option('bluesky_connector_settings', array());
$settings['identifier'] = $this->identifier;
update_option('bluesky_connector_settings', $settings);
// Save all necessary tokens and details
update_option('bluesky_identifier', sanitize_text_field($this->identifier));
update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt'])); update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt']));
update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt'])); update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt']));
update_option('bluesky_did', sanitize_text_field($data['did'])); update_option('bluesky_did', sanitize_text_field($data['did']));
update_option('bluesky_token_created', time()); update_option('bluesky_token_created', time());
delete_option('bluesky_password'); // Don't store password
// Store password for reauth
update_option('bluesky_password', $this->password);
$this->log_debug('Authentication successful', array('did' => $data['did']));
return $data['accessJwt']; return $data['accessJwt'];
} }
// More detailed error reporting
$error_message = isset($data['error']) ? $data['error'] : 'Failed to get access token'; $error_message = isset($data['error']) ? $data['error'] : 'Failed to get access token';
if (isset($data['message'])) { if (isset($data['message'])) {
$error_message .= ' - ' . $data['message']; $error_message .= ' - ' . $data['message'];
} }
$this->log_debug('Auth Failed', array('error' => $error_message)); error_log('Bluesky Auth - Error: ' . $error_message);
return array('error' => $error_message); return array('error' => $error_message);
} }
@ -147,6 +94,7 @@ class Bluesky_Auth {
$token_created = get_option('bluesky_token_created'); $token_created = get_option('bluesky_token_created');
$refresh_token = get_option('bluesky_refresh_jwt'); $refresh_token = get_option('bluesky_refresh_jwt');
// Refresh if token is older than 23 hours or doesn't exist
return !empty($refresh_token) && ($token_created < (time() - 82800)); return !empty($refresh_token) && ($token_created < (time() - 82800));
} }
@ -157,7 +105,9 @@ class Bluesky_Auth {
} }
$refresh_url = $this->api_domain . 'xrpc/com.atproto.server.refreshSession'; $refresh_url = $this->api_domain . 'xrpc/com.atproto.server.refreshSession';
$this->log_debug('Token refresh attempt', array('url' => $refresh_url));
// Debug log
error_log('Bluesky Auth - Attempting token refresh at: ' . $refresh_url);
$wp_version = get_bloginfo('version'); $wp_version = get_bloginfo('version');
$user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url')); $user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url'));
@ -173,18 +123,22 @@ class Bluesky_Auth {
if (is_wp_error($response)) { if (is_wp_error($response)) {
$error_message = $response->get_error_message(); $error_message = $response->get_error_message();
$this->log_debug('Token Refresh Error', array('error' => $error_message)); error_log('Bluesky Token Refresh Error: ' . $error_message);
return array('error' => $error_message); return array('error' => $error_message);
} }
$data = json_decode(wp_remote_retrieve_body($response), true); // Debug response
$status_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
error_log('Bluesky Auth Refresh - Response status: ' . $status_code);
error_log('Bluesky Auth Refresh - Response body: ' . $response_body);
$data = json_decode($response_body, true);
if (!empty($data['accessJwt']) && !empty($data['refreshJwt'])) { if (!empty($data['accessJwt']) && !empty($data['refreshJwt'])) {
update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt'])); update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt']));
update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt'])); update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt']));
update_option('bluesky_token_created', time()); update_option('bluesky_token_created', time());
$this->log_debug('Token refresh successful');
return $data['accessJwt']; return $data['accessJwt'];
} }
@ -192,23 +146,7 @@ class Bluesky_Auth {
if (isset($data['message'])) { if (isset($data['message'])) {
$error_message .= ' - ' . $data['message']; $error_message .= ' - ' . $data['message'];
} }
$this->log_debug('Token Refresh Failed', array('error' => $error_message)); error_log('Bluesky Auth Refresh - Error: ' . $error_message);
return array('error' => $error_message); return array('error' => $error_message);
} }
private function log_debug($message, $data = array()) {
if (defined('WP_DEBUG') && WP_DEBUG) {
$log_message = sprintf(
'[Bluesky Connector] %s | Data: %s',
$message,
wp_json_encode($data, JSON_PRETTY_PRINT)
);
if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
error_log($log_message);
}
do_action('bluesky_connector_debug', $message, $data);
}
}
} }

View File

@ -1,71 +1,14 @@
<?php <?php
class Post_Formatter class Post_Formatter {
{
private $api; private $api;
private $max_length = 300; private $max_length = 300;
private function diagnose_thumbnail_support($post_id) { public function __construct($access_token, $did) {
error_log('[Bluesky Connector] Starting thumbnail support diagnosis');
// Check WordPress version
error_log('[Bluesky Connector] WordPress Version: ' . get_bloginfo('version'));
// Check if theme supports post thumbnails
$theme_support = current_theme_supports('post-thumbnails');
error_log('[Bluesky Connector] Theme supports post thumbnails: ' . ($theme_support ? 'yes' : 'no'));
// If theme doesn't support thumbnails, check if we can add support
if (!$theme_support) {
error_log('[Bluesky Connector] Attempting to add post thumbnail support');
add_theme_support('post-thumbnails');
$theme_support = current_theme_supports('post-thumbnails');
error_log('[Bluesky Connector] Post thumbnail support after attempt: ' . ($theme_support ? 'yes' : 'no'));
}
// Check post type support
$post_type = get_post_type($post_id);
$post_type_support = post_type_supports($post_type, 'thumbnail');
error_log('[Bluesky Connector] Post type ' . $post_type . ' supports thumbnails: ' . ($post_type_support ? 'yes' : 'no'));
// Check post meta directly
$post_meta = get_post_meta($post_id);
error_log('[Bluesky Connector] All post meta for debugging: ' . print_r($post_meta, true));
// Check attachment status
$thumbnail_id = get_post_thumbnail_id($post_id);
if ($thumbnail_id) {
$attachment = get_post($thumbnail_id);
if ($attachment) {
error_log('[Bluesky Connector] Attachment details: ' . print_r([
'ID' => $attachment->ID,
'post_type' => $attachment->post_type,
'status' => $attachment->post_status,
'mime_type' => $attachment->post_mime_type,
'attached_file' => get_attached_file($thumbnail_id),
'exists' => file_exists(get_attached_file($thumbnail_id))
], true));
} else {
error_log('[Bluesky Connector] Attachment post not found for ID: ' . $thumbnail_id);
}
}
return $theme_support && $post_type_support;
}
public function __construct($access_token, $did)
{
error_log('[Bluesky Connector] Initializing Post_Formatter with DID: ' . $did);
$this->api = new Bluesky_API($access_token, $did); $this->api = new Bluesky_API($access_token, $did);
} }
public function format_and_post($post, $formatted_content = null) public function format_and_post($post) {
{ $content = $this->get_formatted_content($post);
try {
error_log('[Bluesky Connector] Starting format_and_post for post ' . $post->ID);
// Use provided content if available, otherwise format it
$content = $formatted_content ?: $this->get_formatted_content($post);
error_log('[Bluesky Connector] Formatted content: ' . $content);
$post_data = array( $post_data = array(
'$type' => 'app.bsky.feed.post', '$type' => 'app.bsky.feed.post',
@ -76,62 +19,28 @@ class Post_Formatter
// Add facets for the URL // Add facets for the URL
$post_data['facets'] = $this->parse_facets($post); $post_data['facets'] = $this->parse_facets($post);
error_log('[Bluesky Connector] Added facets: ' . wp_json_encode($post_data['facets']));
// Debug thumbnail support // Handle image embed separately from text content
error_log('[Bluesky Connector] Checking for featured image...');
error_log('[Bluesky Connector] Post type: ' . $post->post_type);
error_log('[Bluesky Connector] has_post_thumbnail result: ' . (has_post_thumbnail($post->ID) ? 'true' : 'false'));
error_log('[Bluesky Connector] Theme supports thumbnails: ' . (current_theme_supports('post-thumbnails') ? 'true' : 'false'));
error_log('[Bluesky Connector] Post thumbnail meta check:');
error_log('[Bluesky Connector] _thumbnail_id: ' . get_post_meta($post->ID, '_thumbnail_id', true));
error_log('[Bluesky Connector] Post status: ' . get_post_status($post->ID));
// Handle image embed
if (has_post_thumbnail($post->ID)) { if (has_post_thumbnail($post->ID)) {
error_log('[Bluesky Connector] Processing featured image for post ' . $post->ID);
$image_data = $this->handle_featured_image($post->ID); $image_data = $this->handle_featured_image($post->ID);
if (!empty($image_data) && !isset($image_data['error'])) { if (!empty($image_data) && !isset($image_data['error'])) {
$post_data['embed'] = $image_data; $post_data['embed'] = $image_data;
error_log('[Bluesky Connector] Image data added to post: ' . wp_json_encode($image_data));
} else {
error_log('[Bluesky Connector] Image processing error: ' . ($image_data['error'] ?? 'Unknown error'));
} }
} }
error_log('[Bluesky Connector] Sending post data to API: ' . wp_json_encode($post_data)); return $this->api->create_post($post_data);
$response = $this->api->create_post($post_data);
error_log('[Bluesky Connector] API Response: ' . wp_json_encode($response));
return $response;
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in format_and_post: ' . $e->getMessage());
return array('error' => $e->getMessage());
}
} }
private function get_formatted_content($post) private function get_formatted_content($post) {
{
try {
error_log('[Bluesky Connector] Starting content formatting for post ' . $post->ID);
$format = get_option('bluesky_post_format', 'image-title-excerpt-link'); $format = get_option('bluesky_post_format', 'image-title-excerpt-link');
$include_title = get_option('bluesky_include_title', true); $include_title = get_option('bluesky_include_title', true);
error_log('[Bluesky Connector] Using format: ' . $format . ', Include title: ' . ($include_title ? 'yes' : 'no'));
// Get individual components // Get individual components
$title = $include_title ? $post->post_title : ''; $title = $include_title ? $post->post_title : '';
$excerpt = $this->get_excerpt($post); $excerpt = $this->get_excerpt($post);
$url = wp_get_shortlink($post->ID); $url = wp_get_shortlink($post->ID);
error_log('[Bluesky Connector] Content components:'); // Start building content with explicit line breaks
error_log('[Bluesky Connector] - Title: ' . $title);
error_log('[Bluesky Connector] - Excerpt length: ' . strlen($excerpt));
error_log('[Bluesky Connector] - URL: ' . $url);
// Start building content
$content = ''; $content = '';
// Add title with line break if it exists // Add title with line break if it exists
@ -141,71 +50,48 @@ class Post_Formatter
// Add excerpt if it exists // Add excerpt if it exists
if (!empty($excerpt)) { if (!empty($excerpt)) {
$content .= $excerpt . "\n\n"; $content .= $excerpt . "\n\n"; // Add double line break after excerpt
} }
// Add URL on its own line // Add URL on its own line
if (!empty($url)) { if (!empty($url)) {
$content .= "Continue Reading: " . $url; $content .= $url; // URL starts on new line due to previous \n\n
} }
error_log('[Bluesky Connector] Final formatted content: ' . $content);
return $content; return $content;
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in get_formatted_content: ' . $e->getMessage());
throw $e;
}
} }
private function get_excerpt($post) private function get_excerpt($post) {
{
try {
error_log('[Bluesky Connector] Getting excerpt for post ' . $post->ID);
$text = wp_strip_all_tags($post->post_excerpt); $text = wp_strip_all_tags($post->post_excerpt);
if (empty($text)) { if (empty($text)) {
$text = wp_strip_all_tags($post->post_content); $text = wp_strip_all_tags($post->post_content);
error_log('[Bluesky Connector] Using post content for excerpt (no excerpt found)');
} }
$url = wp_get_shortlink($post->ID); $url = wp_get_shortlink($post->ID);
$include_title = get_option('bluesky_include_title', true); $include_title = get_option('bluesky_include_title', true);
// Calculate available length // Calculate available length accounting for spacing and new lines
$available_length = $this->max_length; $available_length = $this->max_length;
$available_length -= strlen($url); $available_length -= strlen($url);
$available_length -= strlen("Continue Reading: "); // Account for the prefix $available_length -= 2; // Account for \n\n after excerpt
$available_length -= 4; // Account for \n\n before and after the URL
if ($include_title) { if ($include_title) {
$available_length -= strlen($post->post_title); $available_length -= strlen($post->post_title);
$available_length -= 2; // Account for \n\n after title $available_length -= 2; // Account for \n\n after title
} }
error_log('[Bluesky Connector] Available length for excerpt: ' . $available_length);
if (mb_strlen($text) > $available_length) { if (mb_strlen($text) > $available_length) {
$text = mb_substr($text, 0, $available_length - 3) . '...'; $text = mb_substr($text, 0, $available_length - 3) . '...';
error_log('[Bluesky Connector] Excerpt truncated to fit length limit');
} }
error_log('[Bluesky Connector] Final excerpt length: ' . mb_strlen($text));
return $text; return $text;
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in get_excerpt: ' . $e->getMessage());
throw $e;
}
} }
private function parse_facets($post) private function parse_facets($post) {
{
try {
error_log('[Bluesky Connector] Parsing facets for post ' . $post->ID);
$facets = array(); $facets = array();
$content = $this->get_formatted_content($post); $content = $this->get_formatted_content($post);
// Add link facet for the post URL
$url = wp_get_shortlink($post->ID); $url = wp_get_shortlink($post->ID);
$text_bytes = mb_convert_encoding($content, 'UTF-8'); $text_bytes = mb_convert_encoding($content, 'UTF-8');
$url_position = mb_strrpos($text_bytes, $url); $url_position = mb_strrpos($text_bytes, $url);
@ -223,71 +109,20 @@ class Post_Formatter
), ),
), ),
); );
error_log('[Bluesky Connector] Added URL facet for: ' . $url);
} }
error_log('[Bluesky Connector] Generated facets: ' . wp_json_encode($facets));
return $facets; return $facets;
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in parse_facets: ' . $e->getMessage());
throw $e;
}
} }
private function handle_featured_image($post_id) { private function handle_featured_image($post_id) {
try {
error_log('[Bluesky Connector] Starting featured image process for post ' . $post_id);
// Run diagnostics first
$thumbnail_support = $this->diagnose_thumbnail_support($post_id);
if (!$thumbnail_support) {
error_log('[Bluesky Connector] Thumbnail support is not properly configured');
}
// Get image ID with multiple fallbacks
$image_id = get_post_thumbnail_id($post_id); $image_id = get_post_thumbnail_id($post_id);
error_log('[Bluesky Connector] Initial image ID from get_post_thumbnail_id: ' . $image_id);
if (!$image_id) {
// Try direct meta approach
$image_id = get_post_meta($post_id, '_thumbnail_id', true);
error_log('[Bluesky Connector] Image ID from direct meta: ' . $image_id);
// If still no image, check for fallback
if (!$image_id) {
$image_id = get_option('bluesky_fallback_image');
error_log('[Bluesky Connector] Using fallback image ID: ' . $image_id);
}
}
if (!$image_id) {
return array('error' => 'No valid image ID found');
}
// Get the image file path
$image_path = get_attached_file($image_id); $image_path = get_attached_file($image_id);
error_log('[Bluesky Connector] Image path: ' . ($image_path ?: 'not found'));
// Verify file exists and is accessible if (!$image_path) {
if (!$image_path || !file_exists($image_path)) { return array('error' => 'Image file not found');
$upload_dir = wp_upload_dir();
error_log('[Bluesky Connector] Upload directory information: ' . print_r($upload_dir, true));
return array('error' => 'Image file not found or inaccessible');
} }
// Check file permissions
error_log('[Bluesky Connector] File permissions: ' . decoct(fileperms($image_path) & 0777));
// Verify mime type
$mime_type = get_post_mime_type($image_id); $mime_type = get_post_mime_type($image_id);
error_log('[Bluesky Connector] Mime type: ' . $mime_type);
// Validate mime type
$allowed_types = array('image/jpeg', 'image/png', 'image/gif');
if (!in_array($mime_type, $allowed_types)) {
return array('error' => 'Unsupported image type: ' . $mime_type);
}
// Get image data // Get image data
$image_data = file_get_contents($image_path); $image_data = file_get_contents($image_path);
@ -295,53 +130,29 @@ class Post_Formatter
return array('error' => 'Failed to read image file'); return array('error' => 'Failed to read image file');
} }
// Check and handle file size // Check file size (1MB limit for Bluesky)
$size = strlen($image_data); if (strlen($image_data) > 1000000) {
error_log('[Bluesky Connector] Original image size: ' . $size . ' bytes'); // If image is too large, attempt to resize it
if ($size > 1000000) {
error_log('[Bluesky Connector] Image exceeds size limit, attempting resize');
$resized = $this->resize_image($image_path); $resized = $this->resize_image($image_path);
if ($resized) { if ($resized) {
error_log('[Bluesky Connector] Image resized successfully'); $image_data = file_get_contents($resized);
$image_path = $resized; unlink($resized); // Clean up temporary file
// Verify new size
$new_size = filesize($resized);
error_log('[Bluesky Connector] New image size: ' . $new_size . ' bytes');
if ($new_size > 1000000) {
error_log('[Bluesky Connector] Resized image still too large');
return array('error' => 'Unable to reduce image size below 1MB');
}
} else { } else {
error_log('[Bluesky Connector] Image resize failed'); return array('error' => 'Image file size exceeds 1MB limit and resize failed');
return array('error' => 'Image resize failed');
} }
} }
// Upload to Bluesky // Upload image blob
error_log('[Bluesky Connector] Uploading image to Bluesky');
$response = $this->api->upload_blob($image_path, $mime_type); $response = $this->api->upload_blob($image_path, $mime_type);
if (isset($response['error'])) { if (isset($response['error'])) {
error_log('[Bluesky Connector] Upload failed: ' . $response['error']);
return $response; return $response;
} }
// Clean up temporary file if it exists // Get the alt text
if (isset($resized) && file_exists($resized)) {
unlink($resized);
error_log('[Bluesky Connector] Cleaned up temporary resized file');
}
// Get alt text
$alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: ''; $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: '';
error_log('[Bluesky Connector] Using alt text: ' . $alt_text);
// Prepare final image embed return array(
$image_embed = array(
'$type' => 'app.bsky.embed.images', '$type' => 'app.bsky.embed.images',
'images' => array( 'images' => array(
array( array(
@ -350,33 +161,19 @@ class Post_Formatter
), ),
), ),
); );
error_log('[Bluesky Connector] Image embed prepared successfully');
return $image_embed;
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in handle_featured_image: ' . $e->getMessage());
error_log('[Bluesky Connector] Stack trace: ' . $e->getTraceAsString());
return array('error' => $e->getMessage());
}
} }
private function resize_image($image_path) private function resize_image($image_path) {
{ // Only proceed if GD is available
try {
error_log('[Bluesky Connector] Starting image resize for: ' . $image_path);
if (!function_exists('imagecreatefrompng')) { if (!function_exists('imagecreatefrompng')) {
error_log('[Bluesky Connector] GD library not available');
return false; return false;
} }
$mime_type = mime_content_type($image_path); $mime_type = mime_content_type($image_path);
list($width, $height) = getimagesize($image_path); list($width, $height) = getimagesize($image_path);
error_log('[Bluesky Connector] Original dimensions: ' . $width . 'x' . $height);
// Calculate new dimensions // Calculate new dimensions while maintaining aspect ratio
$max_dimension = 1000; $max_dimension = 1000; // Reasonable size that should result in < 1MB file
if ($width > $height) { if ($width > $height) {
$new_width = $max_dimension; $new_width = $max_dimension;
$new_height = floor($height * ($max_dimension / $width)); $new_height = floor($height * ($max_dimension / $width));
@ -385,16 +182,17 @@ class Post_Formatter
$new_width = floor($width * ($max_dimension / $height)); $new_width = floor($width * ($max_dimension / $height));
} }
error_log('[Bluesky Connector] New dimensions: ' . $new_width . 'x' . $new_height); // Create new image
$new_image = imagecreatetruecolor($new_width, $new_height); $new_image = imagecreatetruecolor($new_width, $new_height);
// Handle different image types
switch ($mime_type) { switch ($mime_type) {
case 'image/jpeg': case 'image/jpeg':
$source = imagecreatefromjpeg($image_path); $source = imagecreatefromjpeg($image_path);
break; break;
case 'image/png': case 'image/png':
$source = imagecreatefrompng($image_path); $source = imagecreatefrompng($image_path);
// Preserve transparency
imagealphablending($new_image, false); imagealphablending($new_image, false);
imagesavealpha($new_image, true); imagesavealpha($new_image, true);
break; break;
@ -402,15 +200,14 @@ class Post_Formatter
$source = imagecreatefromgif($image_path); $source = imagecreatefromgif($image_path);
break; break;
default: default:
error_log('[Bluesky Connector] Unsupported image type: ' . $mime_type);
return false; return false;
} }
if (!$source) { if (!$source) {
error_log('[Bluesky Connector] Failed to create image resource');
return false; return false;
} }
// Resize
imagecopyresampled( imagecopyresampled(
$new_image, $new_image,
$source, $source,
@ -421,37 +218,26 @@ class Post_Formatter
$height $height
); );
// Use PHP's tempnam // Create temporary file
$temp_file = tempnam(sys_get_temp_dir(), 'bluesky_img_'); $temp_file = tempnam(sys_get_temp_dir(), 'bluesky_img_');
error_log('[Bluesky Connector] Created temp file: ' . $temp_file);
$success = false; // Save resized image
switch ($mime_type) { switch ($mime_type) {
case 'image/jpeg': case 'image/jpeg':
$success = imagejpeg($new_image, $temp_file, 85); imagejpeg($new_image, $temp_file, 85);
break; break;
case 'image/png': case 'image/png':
$success = imagepng($new_image, $temp_file, 8); imagepng($new_image, $temp_file, 8);
break; break;
case 'image/gif': case 'image/gif':
$success = imagegif($new_image, $temp_file); imagegif($new_image, $temp_file);
break; break;
} }
// Clean up
imagedestroy($source); imagedestroy($source);
imagedestroy($new_image); imagedestroy($new_image);
if ($success) {
error_log('[Bluesky Connector] Image resized successfully');
return $temp_file; return $temp_file;
} else {
error_log('[Bluesky Connector] Failed to save resized image');
return false;
}
} catch (Exception $e) {
error_log('[Bluesky Connector] Error in resize_image: ' . $e->getMessage());
return false;
}
} }
} }

View File

@ -1,64 +1,58 @@
<div class="bluesky-post-status"> <div class="bluesky-post-status">
<?php wp_nonce_field('bluesky_post_meta_box', 'bluesky_post_meta_box_nonce'); ?> <?php wp_nonce_field('bluesky_post_meta_box', 'bluesky_post_meta_box_nonce'); ?>
<?php if (!empty($status)) : ?> <?php if (!empty($status)) : ?>
<p> <p>
<strong><?php esc_html_e('Status:', 'bluesky-connctor'); ?></strong> <strong><?php _e('Status:', 'bluesky-connector'); ?></strong>
<?php <?php
switch ($status) { switch ($status) {
case 'success': case 'success':
echo '<span class="bluesky-status-success">' . esc_html__('Posted', 'bluesky-connctor') . '</span>'; echo '<span class="bluesky-status-success">' . esc_html__('Posted', 'bluesky-connector') . '</span>';
break; break;
case 'error': case 'error':
echo '<span class="bluesky-status-error">' . esc_html__('Error', 'bluesky-connctor') . '</span>'; echo '<span class="bluesky-status-error">' . esc_html__('Error', 'bluesky-connector') . '</span>';
break; break;
case 'pending': case 'queued':
echo '<span class="bluesky-status-pending">' . esc_html__('Publishing...', 'bluesky-connctor') . '</span>'; echo '<span class="bluesky-status-pending">' . esc_html__('Queued', 'bluesky-connector') . '</span>';
break; break;
default: default:
echo '<span class="bluesky-status-unknown">' . esc_html__('Not Posted', 'bluesky-connctor') . '</span>'; echo '<span class="bluesky-status-unknown">' . esc_html__('Unknown', 'bluesky-connector') . '</span>';
} }
?> ?>
</p> </p>
<?php if ($posted_date) : ?> <?php if ($posted_date) : ?>
<p> <p>
<strong><?php esc_html_e('Posted:', 'bluesky-connctor'); ?></strong> <strong><?php _e('Posted:', 'bluesky-connector'); ?></strong>
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($posted_date))); ?> <?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($posted_date))); ?>
</p> </p>
<?php endif; ?> <?php endif; ?>
<?php if ($post_id) : ?> <?php if ($post_id) : ?>
<p> <p>
<strong><?php esc_html_e('Bluesky Post:', 'bluesky-connctor'); ?></strong> <strong><?php _e('Bluesky Post ID:', 'bluesky-connector'); ?></strong>
<a href="https://bsky.app/profile/<?php echo esc_attr(get_option('bluesky_identifier')); ?>/post/<?php echo esc_attr($post_id); ?>" <code><?php echo esc_html($post_id); ?></code>
target="_blank" rel="noopener noreferrer">
<?php esc_html_e('View Post', 'bluesky-connctor'); ?>
</a>
</p> </p>
<?php endif; ?> <?php endif; ?>
<?php if ($error) : ?> <?php if ($error) : ?>
<p class="bluesky-error-message"> <p class="bluesky-error-message">
<strong><?php esc_html_e('Error:', 'bluesky-connctor'); ?></strong> <strong><?php _e('Error:', 'bluesky-connector'); ?></strong>
<?php echo esc_html($error); ?> <?php echo esc_html($error); ?>
</p> </p>
<?php endif; ?> <?php endif; ?>
<div class="bluesky-actions"> <div class="bluesky-actions">
<?php if ($status === 'error' || !$post_id): ?> <?php if ($status === 'error' || empty($post_id)) : ?>
<button type="button" class="button bluesky-retry-post" data-post-id="<?php echo esc_attr($post->ID); ?>" <button type="button" class="button bluesky-retry-post" data-post-id="<?php echo esc_attr($post->ID); ?>">
data-nonce="<?php echo esc_attr(wp_create_nonce('bluesky_retry_post')); ?>"> <?php _e('Retry Post', 'bluesky-connector'); ?>
<?php esc_html_e('Retry Post', 'bluesky-connctor'); ?>
</button>
<?php else: ?>
<button type="button" class="button bluesky-repost" data-post-id="<?php echo esc_attr($post->ID); ?>"
data-nonce="<?php echo esc_attr(wp_create_nonce('bluesky_repost')); ?>">
<?php esc_html_e('Post Again', 'bluesky-connctor'); ?>
</button> </button>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php else : ?> <?php else : ?>
<p><?php esc_html_e('This post has not been shared to Bluesky.', 'bluesky-connctor'); ?></p> <p><?php _e('This post has not been shared to Bluesky yet.', 'bluesky-connector'); ?></p>
<button type="button" class="button button-primary bluesky-share-post" <button type="button" class="button bluesky-share-post" data-post-id="<?php echo esc_attr($post->ID); ?>">
data-post-id="<?php echo esc_attr($post->ID); ?>" <?php _e('Share to Bluesky', 'bluesky-connector'); ?>
data-nonce="<?php echo esc_attr(wp_create_nonce('bluesky_share_post')); ?>">
<?php esc_html_e('Share to Bluesky', 'bluesky-connctor'); ?>
</button> </button>
<?php endif; ?> <?php endif; ?>
</div> </div>

View File

@ -1,22 +1,20 @@
<div class="wrap"> <div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1> <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<?php if (!empty($settings['connection_status'])) : ?>
<?php if ($settings['connection_status'] === 'connected') : ?> <?php if ($settings['connection_status'] === 'connected') : ?>
<div class="notice notice-success"> <div class="notice notice-success">
<p><?php _e('Successfully connected to Bluesky!', 'bluesky-connctor'); ?></p> <p><?php _e('Successfully connected to Bluesky!', 'bluesky-connector'); ?></p>
</div> </div>
<?php else : ?> <?php else : ?>
<div class="notice notice-error"> <div class="notice notice-error">
<!-- translators: %s: Error message --> <p><?php printf(__('Connection error: %s', 'bluesky-connector'), esc_html($settings['last_error'])); ?></p>
<p><?php printf(
esc_html__('Connection error: %s', 'bluesky-connctor'),
esc_html($settings['last_error'])
); ?></p>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php endif; ?>
<div class="card"> <div class="card">
<h2><?php esc_html_e('Connection Settings', 'bluesky-connctor'); ?></h2> <h2><?php _e('Connection Settings', 'bluesky-connector'); ?></h2>
<form method="post" action=""> <form method="post" action="">
<?php wp_nonce_field('bluesky_connector_settings'); ?> <?php wp_nonce_field('bluesky_connector_settings'); ?>
<input type="hidden" name="action" value="update_bluesky_settings"> <input type="hidden" name="action" value="update_bluesky_settings">
@ -24,122 +22,146 @@
<table class="form-table" role="presentation"> <table class="form-table" role="presentation">
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="bluesky_domain"><?php esc_html_e('Bluesky Domain', 'bluesky-connctor'); ?></label> <label for="bluesky_domain"><?php _e('Bluesky Domain', 'bluesky-connector'); ?></label>
</th> </th>
<td> <td>
<input name="bluesky_connector_settings[domain]" type="url" id="bluesky_domain" <input name="bluesky_domain"
value="https://bsky.social" class="regular-text" readonly> type="url"
id="bluesky_domain"
value="https://bsky.social"
class="regular-text"
readonly>
<p class="description"> <p class="description">
<?php esc_html_e('The Bluesky API domain (fixed to https://bsky.social)', 'bluesky-connctor'); ?> <?php _e('The Bluesky API domain (fixed to https://bsky.social)', 'bluesky-connector'); ?>
</p> </p>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="bluesky_identifier"><?php esc_html_e('Bluesky Handle', 'bluesky-connctor'); ?></label> <label for="bluesky_identifier"><?php _e('Bluesky Handle', 'bluesky-connector'); ?></label>
</th> </th>
<td> <td>
<input name="bluesky_connector_settings[identifier]" type="text" id="bluesky_identifier" <input name="bluesky_identifier"
value="<?php echo esc_attr($settings['identifier']); ?>" class="regular-text" type="text"
placeholder="username.bsky.social" required> id="bluesky_identifier"
value="<?php echo esc_attr($settings['identifier']); ?>"
class="regular-text"
placeholder="username.bsky.social"
required>
<p class="description"> <p class="description">
<?php esc_html_e('Your full Bluesky handle (e.g., username.bsky.social)', 'bluesky-connctor'); ?> <?php _e('Your full Bluesky handle (e.g., username.bsky.social)', 'bluesky-connector'); ?>
</p> </p>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="bluesky_password"><?php esc_html_e('App Password', 'bluesky-connctor'); ?></label> <label for="bluesky_password"><?php _e('App Password', 'bluesky-connector'); ?></label>
</th> </th>
<td> <td>
<input name="bluesky_connector_settings[password]" type="password" id="bluesky_password" <input name="bluesky_password"
class="regular-text" <?php echo empty($settings['identifier']) ? 'required' : ''; ?>> type="password"
id="bluesky_password"
class="regular-text"
<?php echo empty($settings['identifier']) ? 'required' : ''; ?>>
<p class="description"> <p class="description">
<?php esc_html_e('Your Bluesky app password (will not be stored)', 'bluesky-connctor'); ?> <?php _e('Your Bluesky app password (will not be stored)', 'bluesky-connector'); ?>
</p> </p>
</td> </td>
</tr> </tr>
</table> </table>
<?php submit_button(esc_html__('Save Connection Settings', 'bluesky-connctor')); ?> <?php submit_button(__('Save Connection Settings', 'bluesky-connector')); ?>
</form> </form>
</div> </div>
<?php if (!empty($settings['connection_status']) && $settings['connection_status'] === 'connected') : ?> <?php if (!empty($settings['connection_status']) && $settings['connection_status'] === 'connected') : ?>
<div class="card" style="margin-top: 20px;"> <div class="card" style="margin-top: 20px;">
<h2><?php esc_html_e('Post Format Settings', 'bluesky-connctor'); ?></h2> <h2><?php _e('Post Format Settings', 'bluesky-connector'); ?></h2>
<form method="post" action="options.php"> <form method="post" action="options.php">
<?php <?php settings_fields('bluesky_connector_settings'); ?>
settings_fields('bluesky-connector');
do_settings_sections('bluesky-connector');
?>
<table class="form-table" role="presentation"> <table class="form-table" role="presentation">
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="bluesky_post_format"><?php esc_html_e('Post Layout', 'bluesky-connctor'); ?></label> <label for="bluesky_post_format"><?php _e('Post Layout', 'bluesky-connector'); ?></label>
</th> </th>
<td> <td>
<select name="bluesky_connector_settings[post_format]" id="bluesky_post_format"> <select name="bluesky_post_format" id="bluesky_post_format">
<option value="title-excerpt-link" <?php selected($settings['post_format'] ?? 'title-excerpt-link', 'title-excerpt-link'); ?>> <option value="title-excerpt-link" <?php selected(get_option('bluesky_post_format'), 'title-excerpt-link'); ?>>
<?php esc_html_e('Title + Excerpt + Link (No Image)', 'bluesky-connctor'); ?> <?php _e('Title + Excerpt + Link (No Image)', 'bluesky-connector'); ?>
</option> </option>
<option value="image-title-excerpt-link" <?php selected($settings['post_format'] ?? 'title-excerpt-link', 'image-title-excerpt-link'); ?>> <option value="image-title-excerpt-link" <?php selected(get_option('bluesky_post_format'), 'image-title-excerpt-link'); ?>>
<?php esc_html_e('Image + Title + Excerpt + Link', 'bluesky-connctor'); ?> <?php _e('Image + Title + Excerpt + Link (Image will appear at top)', 'bluesky-connector'); ?>
</option> </option>
</select> </select>
<p class="description"> <p class="description">
<?php esc_html_e('bluesky-connctor'); ?> <?php _e('Note: When including images, Bluesky will always display them at the top of the post regardless of format selection.', 'bluesky-connector'); ?>
</p> </p>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="bluesky_include_title"><?php esc_html_e('Include Title', 'bluesky-connctor'); ?></label> <label for="bluesky_include_title"><?php _e('Title Options', 'bluesky-connector'); ?></label>
</th> </th>
<td> <td>
<input type="checkbox" name="bluesky_connector_settings[include_title]" <label>
id="bluesky_include_title" value="1" <?php checked($settings['include_title'] ?? true); ?>> <input type="checkbox"
name="bluesky_include_title"
id="bluesky_include_title"
value="1"
<?php checked(get_option('bluesky_include_title', true)); ?>>
<?php _e('Include post title when format includes title', 'bluesky-connector'); ?>
</label>
<p class="description"> <p class="description">
<?php esc_html_e('Include post title in the Bluesky post', 'bluesky-connctor'); ?> <?php _e('When enabled, the post title will be included at the beginning of the post text.', 'bluesky-connector'); ?>
</p> </p>
</td> </td>
</tr> </tr>
</table> </table>
<?php submit_button(esc_html__('Save Format Settings', 'bluesky-connctor')); ?> <?php submit_button(__('Save Format Settings', 'bluesky-connector')); ?>
</form> </form>
</div> </div>
<div class="card" style="margin-top: 20px;"> <div class="card" style="margin-top: 20px;">
<h2><?php esc_html_e('Debug Information', 'bluesky-connctor'); ?></h2> <h2><?php _e('Queue Management', 'bluesky-connector'); ?></h2>
<?php
$queue = get_option('bluesky_post_queue', array());
$queue_count = count($queue);
?>
<p>
<?php printf(
_n(
'There is %s post in the queue.',
'There are %s posts in the queue.',
$queue_count,
'bluesky-connector'
),
number_format_i18n($queue_count)
); ?>
</p>
<?php if ($queue_count > 0) : ?>
<form method="post" style="margin-top: 10px;">
<?php wp_nonce_field('bluesky_process_queue'); ?>
<input type="submit" name="process_queue_now" class="button button-primary"
value="<?php esc_attr_e('Process Queue Now', 'bluesky-connector'); ?>">
</form>
<?php endif; ?>
</div>
<?php if (WP_DEBUG) : ?>
<div class="card" style="margin-top: 20px;">
<h2><?php _e('Connection Status', 'bluesky-connector'); ?></h2>
<table class="form-table" role="presentation"> <table class="form-table" role="presentation">
<tr> <tr>
<th scope="row"><?php esc_html_e('Last Post Status', 'bluesky-connctor'); ?></th> <th scope="row"><?php _e('DID', 'bluesky-connector'); ?></th>
<td>
<?php
echo wp_kses(
sprintf(
/* translators: %s: Time of last post attempt */
esc_html__('Last post attempt: %s', 'bluesky-connctor'),
get_option('bluesky_last_post_time')
? esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), get_option('bluesky_last_post_time')))
: esc_html__('Never', 'bluesky-connctor')
),
array('b' => array(), 'span' => array('class' => array()))
);
?>
</td>
</tr>
<?php if (WP_DEBUG): ?>
<tr>
<th scope="row"><?php esc_html_e('DID', 'bluesky-connctor'); ?></th>
<td><code><?php echo esc_html(get_option('bluesky_did')); ?></code></td> <td><code><?php echo esc_html(get_option('bluesky_did')); ?></code></td>
</tr> </tr>
<tr> <tr>
<th scope="row"><?php esc_html_e('Connection Status', 'bluesky-connctor'); ?></th> <th scope="row"><?php _e('Last Token Refresh', 'bluesky-connector'); ?></th>
<td><?php echo esc_html(get_option('bluesky_connection_status')); ?></td> <td><?php echo esc_html(get_option('bluesky_token_created') ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), get_option('bluesky_token_created')) : __('Never', 'bluesky-connector')); ?></td>
</tr> </tr>
<?php endif; ?>
</table> </table>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php endif; ?>
</div> </div>