Initial commit
This commit is contained in:
commit
8dd4dd32ef
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Bluesky Connector Plugin for WordPress
|
||||||
|
|
||||||
|
## Description
|
||||||
|
This plugin connects your WordPress blog to Bluesky and allows you to format and publish posts to Bluesky whenever a new post is published. Users can log into their Bluesky account via the plugin, and the plugin will automatically generate and store the API key.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
1. Upload the `bluesky-connector` folder to the `/wp-content/plugins/` directory.
|
||||||
|
2. Activate the plugin through the 'Plugins' menu in WordPress.
|
||||||
|
3. Configure the plugin settings in the WordPress admin under 'Settings' > 'Bluesky Connector'.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
- **Client ID**: Your Bluesky client ID.
|
||||||
|
- **Client Secret**: Your Bluesky client secret.
|
||||||
|
- **Redirect URI**: Your Bluesky redirect URI.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
- Once configured, click on the "Login with Bluesky" button to authenticate and generate the API key.
|
||||||
|
- The plugin will automatically publish a formatted version of your new posts to Bluesky.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This plugin is licensed under the [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html).
|
2
admin/settings.php
Normal file
2
admin/settings.php
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
// This file is included in the main plugin file, so no need to include it separately
|
39
assets/css/admin.css
Normal file
39
assets/css/admin.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
.bluesky-post-status {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-status-success {
|
||||||
|
color: #46b450;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-status-error {
|
||||||
|
color: #dc3232;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-status-pending {
|
||||||
|
color: #ffb900;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-error-message {
|
||||||
|
color: #dc3232;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 5px;
|
||||||
|
background: #fff8f8;
|
||||||
|
border-left: 4px solid #dc3232;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-actions {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bluesky-retry-post,
|
||||||
|
.bluesky-share-post {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
63
assets/js/admin.js
Normal file
63
assets/js/admin.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
jQuery(document).ready(function($) {
|
||||||
|
// Retry posting to Bluesky
|
||||||
|
$('.bluesky-retry-post').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var button = $(this);
|
||||||
|
var postId = button.data('post-id');
|
||||||
|
|
||||||
|
button.prop('disabled', true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ajaxurl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'bluesky_retry_post',
|
||||||
|
post_id: postId,
|
||||||
|
nonce: blueskyAdmin.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(response.data.message || 'Error retrying post');
|
||||||
|
button.prop('disabled', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Network error. Please try again.');
|
||||||
|
button.prop('disabled', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Share post to Bluesky
|
||||||
|
$('.bluesky-share-post').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var button = $(this);
|
||||||
|
var postId = button.data('post-id');
|
||||||
|
|
||||||
|
button.prop('disabled', true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: ajaxurl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'bluesky_share_post',
|
||||||
|
post_id: postId,
|
||||||
|
nonce: blueskyAdmin.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert(response.data.message || 'Error sharing post');
|
||||||
|
button.prop('disabled', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Network error. Please try again.');
|
||||||
|
button.prop('disabled', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
BIN
bluesky-connctor.zip
Normal file
BIN
bluesky-connctor.zip
Normal file
Binary file not shown.
805
bluesky-connector.php
Normal file
805
bluesky-connector.php
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Plugin Name: Bluesky Publisher for WordPress
|
||||||
|
Description: A WordPress plugin for publishing posts to Bluesky with customizable formatting, image handling, and queue management.
|
||||||
|
Version: 1.0.0
|
||||||
|
Author: Eugene Web Doctor
|
||||||
|
Author URI: https://eugenewebdoctor.com
|
||||||
|
License: MIT
|
||||||
|
License URI: https://opensource.org/licenses/MIT
|
||||||
|
Text Domain: bluesky-connector
|
||||||
|
Domain Path: /languages
|
||||||
|
|
||||||
|
Copyright (c) 2024 Eugene Web Doctor
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
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
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Prevent direct file access
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define constants
|
||||||
|
define('BLUESKY_CONNECTOR_DIR', plugin_dir_path(__FILE__));
|
||||||
|
define('BLUESKY_CONNECTOR_URL', plugin_dir_url(__FILE__));
|
||||||
|
define('BLUESKY_CONNECTOR_VERSION', '1.0.0');
|
||||||
|
|
||||||
|
// Include necessary files
|
||||||
|
require_once BLUESKY_CONNECTOR_DIR . 'includes/bluesky-api.php';
|
||||||
|
require_once BLUESKY_CONNECTOR_DIR . 'includes/bluesky-auth.php';
|
||||||
|
require_once BLUESKY_CONNECTOR_DIR . 'includes/post-formatter.php';
|
||||||
|
require_once BLUESKY_CONNECTOR_DIR . 'includes/settings.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main plugin class
|
||||||
|
*/
|
||||||
|
class Bluesky_Connector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Plugin instance
|
||||||
|
*
|
||||||
|
* @var Bluesky_Connector|null
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin instance
|
||||||
|
*
|
||||||
|
* @return Bluesky_Connector
|
||||||
|
*/
|
||||||
|
public static function get_instance()
|
||||||
|
{
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
// Initialize plugin
|
||||||
|
add_action('init', array($this, 'init'));
|
||||||
|
add_action('init', array($this, 'ajax_init'));
|
||||||
|
|
||||||
|
// Admin hooks
|
||||||
|
if (is_admin()) {
|
||||||
|
add_action('admin_init', array($this, 'admin_init'));
|
||||||
|
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||||
|
add_action('admin_notices', array($this, 'admin_notices'));
|
||||||
|
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
|
||||||
|
add_filter(
|
||||||
|
'plugin_action_links_' . plugin_basename(BLUESKY_CONNECTOR_DIR . 'bluesky-connector.php'),
|
||||||
|
array($this, 'add_settings_link')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post hooks
|
||||||
|
add_action('publish_post', array($this, 'handle_post_publish'), 10, 2);
|
||||||
|
add_action('process_bluesky_post_queue', array($this, 'process_post_queue'));
|
||||||
|
add_action('add_meta_boxes', array($this, 'add_post_meta_box'));
|
||||||
|
add_action('save_post', array($this, 'save_post_meta_box'));
|
||||||
|
|
||||||
|
// Plugin activation/deactivation
|
||||||
|
register_activation_hook(BLUESKY_CONNECTOR_DIR . 'bluesky-connector.php', array($this, 'activate'));
|
||||||
|
register_deactivation_hook(BLUESKY_CONNECTOR_DIR . 'bluesky-connector.php', array($this, 'deactivate'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize plugin
|
||||||
|
*/
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
load_plugin_textdomain('bluesky-connector', false, dirname(plugin_basename(__FILE__)) . '/languages');
|
||||||
|
|
||||||
|
if (!wp_next_scheduled('process_bluesky_post_queue')) {
|
||||||
|
wp_schedule_event(time(), 'hourly', 'process_bluesky_post_queue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize AJAX handlers
|
||||||
|
*/
|
||||||
|
public function ajax_init()
|
||||||
|
{
|
||||||
|
add_action('wp_ajax_bluesky_retry_post', array($this, 'handle_retry_post'));
|
||||||
|
add_action('wp_ajax_bluesky_share_post', array($this, 'handle_share_post'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize admin settings
|
||||||
|
*/
|
||||||
|
public function admin_init()
|
||||||
|
{
|
||||||
|
// Register settings
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_domain', array(
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'https://bsky.social',
|
||||||
|
'sanitize_callback' => function($input) {
|
||||||
|
// Force the default if empty
|
||||||
|
if (empty($input)) {
|
||||||
|
return 'https://bsky.social';
|
||||||
|
}
|
||||||
|
// Ensure https:// is present
|
||||||
|
if (strpos($input, 'https://') !== 0) {
|
||||||
|
$input = 'https://' . $input;
|
||||||
|
}
|
||||||
|
// Remove any trailing slashes
|
||||||
|
return rtrim($input, '/');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_identifier', array(
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field'
|
||||||
|
));
|
||||||
|
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_password', array(
|
||||||
|
'type' => 'string',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field'
|
||||||
|
));
|
||||||
|
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_post_format', array(
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => 'title-excerpt-image-link',
|
||||||
|
'sanitize_callback' => 'sanitize_text_field'
|
||||||
|
));
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_include_title', array(
|
||||||
|
'type' => 'boolean',
|
||||||
|
'default' => true
|
||||||
|
));
|
||||||
|
register_setting('bluesky_connector_settings', 'bluesky_title_separator', array(
|
||||||
|
'type' => 'string',
|
||||||
|
'default' => "\n\n",
|
||||||
|
'sanitize_callback' => 'sanitize_text_field'
|
||||||
|
));
|
||||||
|
|
||||||
|
// Add settings sections and fields
|
||||||
|
add_settings_section(
|
||||||
|
'bluesky_connector_main',
|
||||||
|
__('Bluesky Connection Settings', 'bluesky-connector'),
|
||||||
|
array($this, 'render_settings_section'),
|
||||||
|
'bluesky_connector_settings'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_section(
|
||||||
|
'bluesky_format_section',
|
||||||
|
__('Post Format Settings', 'bluesky-connector'),
|
||||||
|
array($this, 'render_format_section'),
|
||||||
|
'bluesky_connector_settings'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_domain',
|
||||||
|
__('Bluesky Domain', 'bluesky-connector'),
|
||||||
|
array($this, 'render_domain_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_connector_main'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_identifier',
|
||||||
|
__('Bluesky Handle', 'bluesky-connector'),
|
||||||
|
array($this, 'render_identifier_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_connector_main'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_password',
|
||||||
|
__('App Password', 'bluesky-connector'),
|
||||||
|
array($this, 'render_password_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_connector_main'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_post_format',
|
||||||
|
__('Post Layout', 'bluesky-connector'),
|
||||||
|
array($this, 'render_post_format_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_format_section'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_include_title',
|
||||||
|
__('Include Post Title', 'bluesky-connector'),
|
||||||
|
array($this, 'render_include_title_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_format_section'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'bluesky_title_separator',
|
||||||
|
__('Title Separator', 'bluesky-connector'),
|
||||||
|
array($this, 'render_title_separator_field'),
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'bluesky_format_section'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add admin menu
|
||||||
|
*/
|
||||||
|
public function add_admin_menu()
|
||||||
|
{
|
||||||
|
add_options_page(
|
||||||
|
__('Bluesky Connector Settings', 'bluesky-connector'),
|
||||||
|
__('Bluesky Connector', 'bluesky-connector'),
|
||||||
|
'manage_options',
|
||||||
|
'bluesky-connector',
|
||||||
|
array($this, 'render_settings_page')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render settings page
|
||||||
|
*/
|
||||||
|
public function render_settings_page()
|
||||||
|
{
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add process queue button
|
||||||
|
if (isset($_POST['process_queue_now']) && check_admin_referer('bluesky_process_queue')) {
|
||||||
|
$this->process_post_queue();
|
||||||
|
add_settings_error(
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'queue_processed',
|
||||||
|
__('Queue processed.', 'bluesky-connector'),
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for form submission
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] === 'update_bluesky_settings') {
|
||||||
|
check_admin_referer('bluesky_connector_settings');
|
||||||
|
$this->handle_settings_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current settings
|
||||||
|
$settings = array(
|
||||||
|
'domain' => get_option('bluesky_domain', 'https://bsky.social'),
|
||||||
|
'identifier' => get_option('bluesky_identifier', ''),
|
||||||
|
'connection_status' => get_option('bluesky_connection_status', ''),
|
||||||
|
'last_error' => get_option('bluesky_last_error', '')
|
||||||
|
);
|
||||||
|
|
||||||
|
include BLUESKY_CONNECTOR_DIR . 'templates/settings-page.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render settings section description
|
||||||
|
*/
|
||||||
|
public function render_settings_section()
|
||||||
|
{
|
||||||
|
echo '<p>' . esc_html__('Configure your Bluesky connection settings below.', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render domain field
|
||||||
|
*/
|
||||||
|
public function render_domain_field()
|
||||||
|
{
|
||||||
|
$value = get_option('bluesky_domain', 'https://bsky.social');
|
||||||
|
echo '<input type="url" name="bluesky_domain" value="' . esc_attr($value) . '" class="regular-text">';
|
||||||
|
echo '<p class="description">' . esc_html__('The Bluesky API domain (default: https://bsky.social)', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render identifier field
|
||||||
|
*/
|
||||||
|
public function render_identifier_field()
|
||||||
|
{
|
||||||
|
$value = get_option('bluesky_identifier', '');
|
||||||
|
echo '<input type="text" name="bluesky_identifier" value="' . esc_attr($value) . '" class="regular-text">';
|
||||||
|
echo '<p class="description">' . esc_html__('Your Bluesky handle (e.g., username.bsky.social)', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render password field
|
||||||
|
*/
|
||||||
|
public function render_password_field()
|
||||||
|
{
|
||||||
|
echo '<input type="password" name="bluesky_password" class="regular-text">';
|
||||||
|
echo '<p class="description">' . esc_html__('Your Bluesky app password (will not be stored)', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle settings update
|
||||||
|
*/
|
||||||
|
private function handle_settings_update()
|
||||||
|
{
|
||||||
|
$identifier = sanitize_text_field($_POST['bluesky_identifier']);
|
||||||
|
$password = sanitize_text_field($_POST['bluesky_password']);
|
||||||
|
$domain = esc_url_raw($_POST['bluesky_domain']);
|
||||||
|
|
||||||
|
if (empty($identifier) || empty($password)) {
|
||||||
|
add_settings_error(
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'missing_credentials',
|
||||||
|
__('Both identifier and password are required.', 'bluesky-connector')
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to authenticate
|
||||||
|
$auth = new Bluesky_Auth($identifier, $password);
|
||||||
|
$result = $auth->get_access_token();
|
||||||
|
|
||||||
|
if (isset($result['error'])) {
|
||||||
|
update_option('bluesky_connection_status', 'error');
|
||||||
|
update_option('bluesky_last_error', $result['error']);
|
||||||
|
add_settings_error(
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'auth_failed',
|
||||||
|
sprintf(__('Authentication failed: %s', 'bluesky-connector'), $result['error'])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
update_option('bluesky_connection_status', 'connected');
|
||||||
|
update_option('bluesky_last_error', '');
|
||||||
|
add_settings_error(
|
||||||
|
'bluesky_connector_settings',
|
||||||
|
'settings_updated',
|
||||||
|
__('Settings saved and connected successfully.', 'bluesky-connector'),
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle post publish
|
||||||
|
*/
|
||||||
|
public function handle_post_publish($post_id, $post)
|
||||||
|
{
|
||||||
|
// Skip if not a public post
|
||||||
|
if ($post->post_status !== 'publish' || $post->post_type !== 'post') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already posted
|
||||||
|
if (get_post_meta($post_id, '_bluesky_posted', true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to queue
|
||||||
|
$queue = get_option('bluesky_post_queue', array());
|
||||||
|
$queue[] = $post_id;
|
||||||
|
update_option('bluesky_post_queue', array_unique($queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process post queue
|
||||||
|
*/
|
||||||
|
public function process_post_queue()
|
||||||
|
{
|
||||||
|
$queue = get_option('bluesky_post_queue', array());
|
||||||
|
if (empty($queue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$access_token = get_option('bluesky_access_jwt');
|
||||||
|
$did = get_option('bluesky_did');
|
||||||
|
|
||||||
|
if (!$access_token || !$did) {
|
||||||
|
error_log('Bluesky Connector: Missing access token or DID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($queue as $key => $post_id) {
|
||||||
|
$post = get_post($post_id);
|
||||||
|
if (!$post) {
|
||||||
|
unset($queue[$key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$formatter = new Post_Formatter($access_token, $did);
|
||||||
|
$response = $formatter->format_and_post($post);
|
||||||
|
|
||||||
|
if (!isset($response['error'])) {
|
||||||
|
unset($queue[$key]);
|
||||||
|
update_post_meta($post_id, '_bluesky_post_id', $response['uri']);
|
||||||
|
update_post_meta($post_id, '_bluesky_posted', current_time('mysql'));
|
||||||
|
update_post_meta($post_id, '_bluesky_status', 'success');
|
||||||
|
} else {
|
||||||
|
update_post_meta($post_id, '_bluesky_status', 'error');
|
||||||
|
update_post_meta($post_id, '_bluesky_error', $response['error']);
|
||||||
|
error_log('Bluesky Post Error: ' . print_r($response['error'], true));
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('Bluesky Connector Exception: ' . $e->getMessage());
|
||||||
|
update_post_meta($post_id, '_bluesky_status', 'error');
|
||||||
|
update_post_meta($post_id, '_bluesky_error', $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_option('bluesky_post_queue', array_values($queue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add post meta box
|
||||||
|
*/
|
||||||
|
public function add_post_meta_box()
|
||||||
|
{
|
||||||
|
add_meta_box(
|
||||||
|
'bluesky_post_status',
|
||||||
|
__('Bluesky Status', 'bluesky-connector'),
|
||||||
|
array($this, 'render_post_meta_box'),
|
||||||
|
'post',
|
||||||
|
'side',
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render post meta box
|
||||||
|
*/
|
||||||
|
public function render_post_meta_box($post)
|
||||||
|
{
|
||||||
|
$status = get_post_meta($post->ID, '_bluesky_status', true);
|
||||||
|
$posted_date = get_post_meta($post->ID, '_bluesky_posted', true);
|
||||||
|
$error = get_post_meta($post->ID, '_bluesky_error', true);
|
||||||
|
$post_id = get_post_meta($post->ID, '_bluesky_post_id', true);
|
||||||
|
|
||||||
|
include BLUESKY_CONNECTOR_DIR . 'templates/post-meta-box.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save post meta box
|
||||||
|
*/
|
||||||
|
public function save_post_meta_box($post_id)
|
||||||
|
{
|
||||||
|
if (!current_user_can('edit_post', $post_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any custom meta box saving logic here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle retry post AJAX request
|
||||||
|
*/
|
||||||
|
public function handle_retry_post()
|
||||||
|
{
|
||||||
|
check_ajax_referer('bluesky_admin', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('edit_posts')) {
|
||||||
|
wp_send_json_error(['message' => __('Permission denied.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_id = intval($_POST['post_id']);
|
||||||
|
$post = get_post($post_id);
|
||||||
|
|
||||||
|
if (!$post) {
|
||||||
|
wp_send_json_error(['message' => __('Post not found.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to queue
|
||||||
|
$queue = get_option('bluesky_post_queue', array());
|
||||||
|
$queue[] = $post_id;
|
||||||
|
update_option('bluesky_post_queue', array_unique($queue));
|
||||||
|
|
||||||
|
// Update post meta
|
||||||
|
update_post_meta($post_id, '_bluesky_status', 'queued');
|
||||||
|
delete_post_meta($post_id, '_bluesky_error');
|
||||||
|
|
||||||
|
wp_send_json_success(['message' => __('Post queued for retry.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle share post AJAX request
|
||||||
|
*/
|
||||||
|
public function handle_share_post()
|
||||||
|
{
|
||||||
|
check_ajax_referer('bluesky_admin', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('edit_posts')) {
|
||||||
|
wp_send_json_error(['message' => __('Permission denied.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_id = intval($_POST['post_id']);
|
||||||
|
$post = get_post($post_id);
|
||||||
|
|
||||||
|
if (!$post) {
|
||||||
|
wp_send_json_error(['message' => __('Post not found.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to queue
|
||||||
|
$queue = get_option('bluesky_post_queue', array());
|
||||||
|
$queue[] = $post_id;
|
||||||
|
update_option('bluesky_post_queue', array_unique($queue));
|
||||||
|
|
||||||
|
// Update post meta
|
||||||
|
update_post_meta($post_id, '_bluesky_status', 'queued');
|
||||||
|
|
||||||
|
wp_send_json_success(['message' => __('Post queued for sharing.', 'bluesky-connector')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue admin scripts and styles
|
||||||
|
*/
|
||||||
|
public function enqueue_admin_scripts($hook)
|
||||||
|
{
|
||||||
|
if ('post.php' !== $hook && 'post-new.php' !== $hook) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'bluesky-admin',
|
||||||
|
BLUESKY_CONNECTOR_URL . 'assets/js/admin.js',
|
||||||
|
array('jquery'),
|
||||||
|
BLUESKY_CONNECTOR_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_localize_script('bluesky-admin', 'blueskyAdmin', array(
|
||||||
|
'nonce' => wp_create_nonce('bluesky_admin'),
|
||||||
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
|
'strings' => array(
|
||||||
|
'error' => __('An error occurred. Please try again.', 'bluesky-connector'),
|
||||||
|
'success' => __('Operation completed successfully.', 'bluesky-connector')
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'bluesky-admin',
|
||||||
|
BLUESKY_CONNECTOR_URL . 'assets/css/admin.css',
|
||||||
|
array(),
|
||||||
|
BLUESKY_CONNECTOR_VERSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin activation
|
||||||
|
*/
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
// Create necessary options with default values
|
||||||
|
add_option('bluesky_domain', 'https://bsky.social');
|
||||||
|
add_option('bluesky_post_queue', array());
|
||||||
|
add_option('bluesky_connection_status', '');
|
||||||
|
|
||||||
|
// Schedule cron job
|
||||||
|
if (!wp_next_scheduled('process_bluesky_post_queue')) {
|
||||||
|
wp_schedule_event(time(), 'hourly', 'process_bluesky_post_queue');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create custom capabilities
|
||||||
|
$role = get_role('administrator');
|
||||||
|
if ($role) {
|
||||||
|
$role->add_cap('manage_bluesky_settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush rewrite rules
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin deactivation
|
||||||
|
*/
|
||||||
|
public function deactivate()
|
||||||
|
{
|
||||||
|
// Clear scheduled events
|
||||||
|
wp_clear_scheduled_hook('process_bluesky_post_queue');
|
||||||
|
|
||||||
|
// Remove custom capabilities
|
||||||
|
$role = get_role('administrator');
|
||||||
|
if ($role) {
|
||||||
|
$role->remove_cap('manage_bluesky_settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notices
|
||||||
|
*/
|
||||||
|
public function admin_notices()
|
||||||
|
{
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$screen = get_current_screen();
|
||||||
|
if ($screen->id !== 'settings_page_bluesky-connector') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_errors('bluesky_connector_settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add settings link to plugins page
|
||||||
|
*/
|
||||||
|
public function add_settings_link($links)
|
||||||
|
{
|
||||||
|
$settings_link = sprintf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
admin_url('options-general.php?page=bluesky-connector'),
|
||||||
|
__('Settings', 'bluesky-connector')
|
||||||
|
);
|
||||||
|
array_unshift($links, $settings_link);
|
||||||
|
return $links;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log plugin errors
|
||||||
|
*/
|
||||||
|
private function log_error($message, $data = array())
|
||||||
|
{
|
||||||
|
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||||
|
error_log(sprintf(
|
||||||
|
'[Bluesky Connector] %s | Data: %s',
|
||||||
|
$message,
|
||||||
|
print_r($data, true)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter for customizing post content before sending to Bluesky
|
||||||
|
*/
|
||||||
|
public function filter_post_content($content, $post)
|
||||||
|
{
|
||||||
|
return apply_filters('bluesky_post_content', $content, $post);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if post should be shared to Bluesky
|
||||||
|
*/
|
||||||
|
private function should_share_post($post)
|
||||||
|
{
|
||||||
|
// Skip if post is not published
|
||||||
|
if ($post->post_status !== 'publish') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if post type is not supported
|
||||||
|
$supported_post_types = apply_filters('bluesky_supported_post_types', array('post'));
|
||||||
|
if (!in_array($post->post_type, $supported_post_types)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already shared
|
||||||
|
if (get_post_meta($post->ID, '_bluesky_posted', true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow custom filtering
|
||||||
|
return apply_filters('bluesky_should_share_post', true, $post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_format_section() {
|
||||||
|
echo '<p>' . esc_html__('Customize how your posts appear on Bluesky.', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_post_format_field() {
|
||||||
|
$format = get_option('bluesky_post_format', 'title-excerpt-image-link');
|
||||||
|
$options = array(
|
||||||
|
'title-excerpt-image-link' => __('Title → Excerpt → Image → Link', 'bluesky-connector'),
|
||||||
|
'title-image-excerpt-link' => __('Title → Image → Excerpt → Link', 'bluesky-connector'),
|
||||||
|
'image-title-excerpt-link' => __('Image → Title → Excerpt → Link', 'bluesky-connector'),
|
||||||
|
'excerpt-image-link' => __('Excerpt → Image → Link', 'bluesky-connector'),
|
||||||
|
'title-excerpt-link' => __('Title → Excerpt → Link (No Image)', 'bluesky-connector'),
|
||||||
|
);
|
||||||
|
|
||||||
|
echo '<select name="bluesky_post_format" id="bluesky_post_format">';
|
||||||
|
foreach ($options as $value => $label) {
|
||||||
|
printf(
|
||||||
|
'<option value="%s" %s>%s</option>',
|
||||||
|
esc_attr($value),
|
||||||
|
selected($format, $value, false),
|
||||||
|
esc_html($label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</select>';
|
||||||
|
echo '<p class="description">' . esc_html__('Choose how you want your posts to be formatted on Bluesky.', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_include_title_field() {
|
||||||
|
$include_title = get_option('bluesky_include_title', true);
|
||||||
|
printf(
|
||||||
|
'<label><input type="checkbox" name="bluesky_include_title" value="1" %s> %s</label>',
|
||||||
|
checked($include_title, true, false),
|
||||||
|
__('Include post title at the beginning', 'bluesky-connector')
|
||||||
|
);
|
||||||
|
echo '<p class="description">' . esc_html__('Add the post title to the beginning of each Bluesky post.', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_title_separator_field() {
|
||||||
|
$separator = get_option('bluesky_title_separator', "\n\n");
|
||||||
|
$options = array(
|
||||||
|
"\n\n" => __('Double Line Break', 'bluesky-connector'),
|
||||||
|
"\n" => __('Single Line Break', 'bluesky-connector'),
|
||||||
|
" - " => __('Dash', 'bluesky-connector'),
|
||||||
|
": " => __('Colon', 'bluesky-connector'),
|
||||||
|
);
|
||||||
|
|
||||||
|
echo '<select name="bluesky_title_separator">';
|
||||||
|
foreach ($options as $value => $label) {
|
||||||
|
printf(
|
||||||
|
'<option value="%s" %s>%s</option>',
|
||||||
|
esc_attr($value),
|
||||||
|
selected($separator, $value, false),
|
||||||
|
esc_html($label)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</select>';
|
||||||
|
echo '<p class="description">' . esc_html__('Choose how to separate the title from the excerpt.', 'bluesky-connector') . '</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin
|
||||||
|
*/
|
||||||
|
function bluesky_connector_init()
|
||||||
|
{
|
||||||
|
return Bluesky_Connector::get_instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the plugin
|
||||||
|
add_action('plugins_loaded', 'bluesky_connector_init');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register uninstall hook
|
||||||
|
*/
|
||||||
|
register_uninstall_hook(__FILE__, 'bluesky_connector_uninstall');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up plugin data on uninstall
|
||||||
|
*/
|
||||||
|
function bluesky_connector_uninstall()
|
||||||
|
{
|
||||||
|
// Only run if explicitly uninstalling
|
||||||
|
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove options
|
||||||
|
delete_option('bluesky_domain');
|
||||||
|
delete_option('bluesky_identifier');
|
||||||
|
delete_option('bluesky_access_jwt');
|
||||||
|
delete_option('bluesky_refresh_jwt');
|
||||||
|
delete_option('bluesky_did');
|
||||||
|
delete_option('bluesky_post_queue');
|
||||||
|
delete_option('bluesky_connection_status');
|
||||||
|
delete_option('bluesky_last_error');
|
||||||
|
|
||||||
|
// Remove capabilities
|
||||||
|
$role = get_role('administrator');
|
||||||
|
if ($role) {
|
||||||
|
$role->remove_cap('manage_bluesky_settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear scheduled hooks
|
||||||
|
wp_clear_scheduled_hook('process_bluesky_post_queue');
|
||||||
|
|
||||||
|
// Remove post meta
|
||||||
|
global $wpdb;
|
||||||
|
$wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '_bluesky_%'");
|
||||||
|
}
|
70
includes/bluesky-api.php
Normal file
70
includes/bluesky-api.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
class Bluesky_API {
|
||||||
|
private $api_url = 'https://bsky.social/xrpc'; // Replace with the actual API endpoint
|
||||||
|
private $api_key;
|
||||||
|
private $did;
|
||||||
|
|
||||||
|
public function __construct($api_key, $did) {
|
||||||
|
$this->api_key = $api_key;
|
||||||
|
$this->did = $did;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create_post($post_data) {
|
||||||
|
$headers = array(
|
||||||
|
'Authorization' => 'Bearer ' . $this->api_key,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = wp_remote_post($this->api_url . '/com.atproto.repo.createRecord', array(
|
||||||
|
'headers' => $headers,
|
||||||
|
'body' => json_encode(array(
|
||||||
|
'repo' => $this->did,
|
||||||
|
'collection' => 'app.bsky.feed.post',
|
||||||
|
'record' => $post_data,
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
return array('error' => $response->get_error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode(wp_remote_retrieve_body($response), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolve_handle($handle) {
|
||||||
|
$response = wp_remote_get($this->api_url . '/com.atproto.identity.resolveHandle', array(
|
||||||
|
'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(
|
||||||
|
'Authorization' => 'Bearer ' . $this->api_key,
|
||||||
|
'Content-Type' => $mime_type,
|
||||||
|
);
|
||||||
|
|
||||||
|
$file_contents = file_get_contents($file_path);
|
||||||
|
if ($file_contents === false) {
|
||||||
|
return array('error' => 'Failed to read file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_post($this->api_url . '/com.atproto.repo.uploadBlob', array(
|
||||||
|
'headers' => $headers,
|
||||||
|
'body' => $file_contents,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
return array('error' => $response->get_error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode(wp_remote_retrieve_body($response), true);
|
||||||
|
}
|
||||||
|
}
|
152
includes/bluesky-auth.php
Normal file
152
includes/bluesky-auth.php
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
class Bluesky_Auth {
|
||||||
|
private $identifier;
|
||||||
|
private $password;
|
||||||
|
private $api_domain;
|
||||||
|
|
||||||
|
public function __construct($identifier, $password) {
|
||||||
|
$this->identifier = $identifier;
|
||||||
|
$this->password = $password;
|
||||||
|
|
||||||
|
// Get domain with fallback and force https://
|
||||||
|
$domain = get_option('bluesky_domain', 'https://bsky.social');
|
||||||
|
if (empty($domain)) {
|
||||||
|
$domain = 'https://bsky.social';
|
||||||
|
}
|
||||||
|
if (strpos($domain, 'https://') !== 0) {
|
||||||
|
$domain = 'https://' . $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure proper URL format
|
||||||
|
$this->api_domain = rtrim($domain, '/') . '/';
|
||||||
|
|
||||||
|
// Debug logs
|
||||||
|
error_log('Bluesky Auth - Using domain setting: ' . get_option('bluesky_domain'));
|
||||||
|
error_log('Bluesky Auth - Processed domain: ' . $this->api_domain);
|
||||||
|
error_log('Bluesky Auth - Identifier: ' . $this->identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_access_token() {
|
||||||
|
if ($this->should_refresh_token()) {
|
||||||
|
return $this->refresh_access_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token_url = $this->api_domain . 'xrpc/com.atproto.server.createSession';
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
error_log('Bluesky Auth - Attempting connection to: ' . $token_url);
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
);
|
||||||
|
|
||||||
|
$body = json_encode(array(
|
||||||
|
'identifier' => $this->identifier,
|
||||||
|
'password' => $this->password,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
error_log('Bluesky Auth - Request body: ' . $body);
|
||||||
|
|
||||||
|
$wp_version = get_bloginfo('version');
|
||||||
|
$user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url'));
|
||||||
|
|
||||||
|
$response = wp_remote_post($token_url, array(
|
||||||
|
'headers' => $headers,
|
||||||
|
'user-agent' => "$user_agent; Bluesky Connector",
|
||||||
|
'body' => $body,
|
||||||
|
'timeout' => 30, // Increase timeout
|
||||||
|
));
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
$error_message = $response->get_error_message();
|
||||||
|
error_log('Bluesky Auth Error: ' . $error_message);
|
||||||
|
return array('error' => $error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug response
|
||||||
|
$status_code = wp_remote_retrieve_response_code($response);
|
||||||
|
$response_body = wp_remote_retrieve_body($response);
|
||||||
|
error_log('Bluesky Auth - Response status: ' . $status_code);
|
||||||
|
error_log('Bluesky Auth - Response body: ' . $response_body);
|
||||||
|
|
||||||
|
$data = json_decode($response_body, true);
|
||||||
|
|
||||||
|
if (!empty($data['accessJwt']) && !empty($data['refreshJwt']) && !empty($data['did'])) {
|
||||||
|
update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt']));
|
||||||
|
update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt']));
|
||||||
|
update_option('bluesky_did', sanitize_text_field($data['did']));
|
||||||
|
update_option('bluesky_token_created', time());
|
||||||
|
delete_option('bluesky_password'); // Don't store password
|
||||||
|
return $data['accessJwt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// More detailed error reporting
|
||||||
|
$error_message = isset($data['error']) ? $data['error'] : 'Failed to get access token';
|
||||||
|
if (isset($data['message'])) {
|
||||||
|
$error_message .= ' - ' . $data['message'];
|
||||||
|
}
|
||||||
|
error_log('Bluesky Auth - Error: ' . $error_message);
|
||||||
|
return array('error' => $error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function should_refresh_token() {
|
||||||
|
$token_created = get_option('bluesky_token_created');
|
||||||
|
$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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function refresh_access_token() {
|
||||||
|
$refresh_token = get_option('bluesky_refresh_jwt');
|
||||||
|
if (empty($refresh_token)) {
|
||||||
|
return array('error' => 'No refresh token available');
|
||||||
|
}
|
||||||
|
|
||||||
|
$refresh_url = $this->api_domain . 'xrpc/com.atproto.server.refreshSession';
|
||||||
|
|
||||||
|
// Debug log
|
||||||
|
error_log('Bluesky Auth - Attempting token refresh at: ' . $refresh_url);
|
||||||
|
|
||||||
|
$wp_version = get_bloginfo('version');
|
||||||
|
$user_agent = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo('url'));
|
||||||
|
|
||||||
|
$response = wp_remote_post($refresh_url, array(
|
||||||
|
'headers' => array(
|
||||||
|
'Authorization' => 'Bearer ' . $refresh_token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
),
|
||||||
|
'user-agent' => "$user_agent; Bluesky Connector",
|
||||||
|
'timeout' => 30,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
$error_message = $response->get_error_message();
|
||||||
|
error_log('Bluesky Token Refresh Error: ' . $error_message);
|
||||||
|
return array('error' => $error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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'])) {
|
||||||
|
update_option('bluesky_access_jwt', sanitize_text_field($data['accessJwt']));
|
||||||
|
update_option('bluesky_refresh_jwt', sanitize_text_field($data['refreshJwt']));
|
||||||
|
update_option('bluesky_token_created', time());
|
||||||
|
return $data['accessJwt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$error_message = isset($data['error']) ? $data['error'] : 'Failed to refresh token';
|
||||||
|
if (isset($data['message'])) {
|
||||||
|
$error_message .= ' - ' . $data['message'];
|
||||||
|
}
|
||||||
|
error_log('Bluesky Auth Refresh - Error: ' . $error_message);
|
||||||
|
return array('error' => $error_message);
|
||||||
|
}
|
||||||
|
}
|
243
includes/post-formatter.php
Normal file
243
includes/post-formatter.php
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?php
|
||||||
|
class Post_Formatter {
|
||||||
|
private $api;
|
||||||
|
private $max_length = 300;
|
||||||
|
|
||||||
|
public function __construct($access_token, $did) {
|
||||||
|
$this->api = new Bluesky_API($access_token, $did);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function format_and_post($post) {
|
||||||
|
$content = $this->get_formatted_content($post);
|
||||||
|
|
||||||
|
$post_data = array(
|
||||||
|
'$type' => 'app.bsky.feed.post',
|
||||||
|
'text' => $content,
|
||||||
|
'createdAt' => gmdate('c', strtotime($post->post_date_gmt)),
|
||||||
|
'langs' => array('en')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add facets for the URL
|
||||||
|
$post_data['facets'] = $this->parse_facets($post);
|
||||||
|
|
||||||
|
// Handle image embed separately from text content
|
||||||
|
if (has_post_thumbnail($post->ID)) {
|
||||||
|
$image_data = $this->handle_featured_image($post->ID);
|
||||||
|
if (!empty($image_data) && !isset($image_data['error'])) {
|
||||||
|
$post_data['embed'] = $image_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->api->create_post($post_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_formatted_content($post) {
|
||||||
|
$format = get_option('bluesky_post_format', 'image-title-excerpt-link');
|
||||||
|
$include_title = get_option('bluesky_include_title', true);
|
||||||
|
|
||||||
|
// Get individual components
|
||||||
|
$title = $include_title ? $post->post_title : '';
|
||||||
|
$excerpt = $this->get_excerpt($post);
|
||||||
|
$url = wp_get_shortlink($post->ID);
|
||||||
|
|
||||||
|
// Start building content with explicit line breaks
|
||||||
|
$content = '';
|
||||||
|
|
||||||
|
// Add title with line break if it exists
|
||||||
|
if (!empty($title)) {
|
||||||
|
$content .= $title . "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add excerpt if it exists
|
||||||
|
if (!empty($excerpt)) {
|
||||||
|
$content .= $excerpt . "\n\n"; // Add double line break after excerpt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add URL on its own line
|
||||||
|
if (!empty($url)) {
|
||||||
|
$content .= $url; // URL starts on new line due to previous \n\n
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_excerpt($post) {
|
||||||
|
$text = wp_strip_all_tags($post->post_excerpt);
|
||||||
|
if (empty($text)) {
|
||||||
|
$text = wp_strip_all_tags($post->post_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = wp_get_shortlink($post->ID);
|
||||||
|
$include_title = get_option('bluesky_include_title', true);
|
||||||
|
|
||||||
|
// Calculate available length accounting for spacing and new lines
|
||||||
|
$available_length = $this->max_length;
|
||||||
|
$available_length -= strlen($url);
|
||||||
|
$available_length -= 2; // Account for \n\n after excerpt
|
||||||
|
|
||||||
|
if ($include_title) {
|
||||||
|
$available_length -= strlen($post->post_title);
|
||||||
|
$available_length -= 2; // Account for \n\n after title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strlen($text) > $available_length) {
|
||||||
|
$text = mb_substr($text, 0, $available_length - 3) . '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parse_facets($post) {
|
||||||
|
$facets = array();
|
||||||
|
$content = $this->get_formatted_content($post);
|
||||||
|
|
||||||
|
// Add link facet for the post URL
|
||||||
|
$url = wp_get_shortlink($post->ID);
|
||||||
|
$text_bytes = mb_convert_encoding($content, 'UTF-8');
|
||||||
|
$url_position = mb_strrpos($text_bytes, $url);
|
||||||
|
|
||||||
|
if ($url_position !== false) {
|
||||||
|
$facets[] = array(
|
||||||
|
'index' => array(
|
||||||
|
'byteStart' => $url_position,
|
||||||
|
'byteEnd' => $url_position + strlen($url),
|
||||||
|
),
|
||||||
|
'features' => array(
|
||||||
|
array(
|
||||||
|
'$type' => 'app.bsky.richtext.facet#link',
|
||||||
|
'uri' => $url,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $facets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handle_featured_image($post_id) {
|
||||||
|
$image_id = get_post_thumbnail_id($post_id);
|
||||||
|
$image_path = get_attached_file($image_id);
|
||||||
|
|
||||||
|
if (!$image_path) {
|
||||||
|
return array('error' => 'Image file not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime_type = get_post_mime_type($image_id);
|
||||||
|
|
||||||
|
// Get image data
|
||||||
|
$image_data = file_get_contents($image_path);
|
||||||
|
if ($image_data === false) {
|
||||||
|
return array('error' => 'Failed to read image file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size (1MB limit for Bluesky)
|
||||||
|
if (strlen($image_data) > 1000000) {
|
||||||
|
// If image is too large, attempt to resize it
|
||||||
|
$resized = $this->resize_image($image_path);
|
||||||
|
if ($resized) {
|
||||||
|
$image_data = file_get_contents($resized);
|
||||||
|
unlink($resized); // Clean up temporary file
|
||||||
|
} else {
|
||||||
|
return array('error' => 'Image file size exceeds 1MB limit and resize failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload image blob
|
||||||
|
$response = $this->api->upload_blob($image_path, $mime_type);
|
||||||
|
|
||||||
|
if (isset($response['error'])) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the alt text
|
||||||
|
$alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: '';
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'$type' => 'app.bsky.embed.images',
|
||||||
|
'images' => array(
|
||||||
|
array(
|
||||||
|
'alt' => $alt_text,
|
||||||
|
'image' => $response['blob'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resize_image($image_path) {
|
||||||
|
// Only proceed if GD is available
|
||||||
|
if (!function_exists('imagecreatefrompng')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime_type = mime_content_type($image_path);
|
||||||
|
list($width, $height) = getimagesize($image_path);
|
||||||
|
|
||||||
|
// Calculate new dimensions while maintaining aspect ratio
|
||||||
|
$max_dimension = 1000; // Reasonable size that should result in < 1MB file
|
||||||
|
if ($width > $height) {
|
||||||
|
$new_width = $max_dimension;
|
||||||
|
$new_height = floor($height * ($max_dimension / $width));
|
||||||
|
} else {
|
||||||
|
$new_height = $max_dimension;
|
||||||
|
$new_width = floor($width * ($max_dimension / $height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new image
|
||||||
|
$new_image = imagecreatetruecolor($new_width, $new_height);
|
||||||
|
|
||||||
|
// Handle different image types
|
||||||
|
switch ($mime_type) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
$source = imagecreatefromjpeg($image_path);
|
||||||
|
break;
|
||||||
|
case 'image/png':
|
||||||
|
$source = imagecreatefrompng($image_path);
|
||||||
|
// Preserve transparency
|
||||||
|
imagealphablending($new_image, false);
|
||||||
|
imagesavealpha($new_image, true);
|
||||||
|
break;
|
||||||
|
case 'image/gif':
|
||||||
|
$source = imagecreatefromgif($image_path);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$source) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
imagecopyresampled(
|
||||||
|
$new_image,
|
||||||
|
$source,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
$new_width,
|
||||||
|
$new_height,
|
||||||
|
$width,
|
||||||
|
$height
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create temporary file
|
||||||
|
$temp_file = tempnam(sys_get_temp_dir(), 'bluesky_img_');
|
||||||
|
|
||||||
|
// Save resized image
|
||||||
|
switch ($mime_type) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
imagejpeg($new_image, $temp_file, 85);
|
||||||
|
break;
|
||||||
|
case 'image/png':
|
||||||
|
imagepng($new_image, $temp_file, 8);
|
||||||
|
break;
|
||||||
|
case 'image/gif':
|
||||||
|
imagegif($new_image, $temp_file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
imagedestroy($source);
|
||||||
|
imagedestroy($new_image);
|
||||||
|
|
||||||
|
return $temp_file;
|
||||||
|
}
|
||||||
|
}
|
2
includes/settings.php
Normal file
2
includes/settings.php
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
// Additional settings-related functions can be added here if needed
|
58
templates/post-meta-box.php
Normal file
58
templates/post-meta-box.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<div class="bluesky-post-status">
|
||||||
|
<?php wp_nonce_field('bluesky_post_meta_box', 'bluesky_post_meta_box_nonce'); ?>
|
||||||
|
|
||||||
|
<?php if (!empty($status)) : ?>
|
||||||
|
<p>
|
||||||
|
<strong><?php _e('Status:', 'bluesky-connector'); ?></strong>
|
||||||
|
<?php
|
||||||
|
switch ($status) {
|
||||||
|
case 'success':
|
||||||
|
echo '<span class="bluesky-status-success">' . esc_html__('Posted', 'bluesky-connector') . '</span>';
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
echo '<span class="bluesky-status-error">' . esc_html__('Error', 'bluesky-connector') . '</span>';
|
||||||
|
break;
|
||||||
|
case 'queued':
|
||||||
|
echo '<span class="bluesky-status-pending">' . esc_html__('Queued', 'bluesky-connector') . '</span>';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo '<span class="bluesky-status-unknown">' . esc_html__('Unknown', 'bluesky-connector') . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if ($posted_date) : ?>
|
||||||
|
<p>
|
||||||
|
<strong><?php _e('Posted:', 'bluesky-connector'); ?></strong>
|
||||||
|
<?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($posted_date))); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($post_id) : ?>
|
||||||
|
<p>
|
||||||
|
<strong><?php _e('Bluesky Post ID:', 'bluesky-connector'); ?></strong>
|
||||||
|
<code><?php echo esc_html($post_id); ?></code>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error) : ?>
|
||||||
|
<p class="bluesky-error-message">
|
||||||
|
<strong><?php _e('Error:', 'bluesky-connector'); ?></strong>
|
||||||
|
<?php echo esc_html($error); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="bluesky-actions">
|
||||||
|
<?php if ($status === 'error' || empty($post_id)) : ?>
|
||||||
|
<button type="button" class="button bluesky-retry-post" data-post-id="<?php echo esc_attr($post->ID); ?>">
|
||||||
|
<?php _e('Retry Post', 'bluesky-connector'); ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<p><?php _e('This post has not been shared to Bluesky yet.', 'bluesky-connector'); ?></p>
|
||||||
|
<button type="button" class="button bluesky-share-post" data-post-id="<?php echo esc_attr($post->ID); ?>">
|
||||||
|
<?php _e('Share to Bluesky', 'bluesky-connector'); ?>
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
167
templates/settings-page.php
Normal file
167
templates/settings-page.php
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<div class="wrap">
|
||||||
|
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
||||||
|
|
||||||
|
<?php if (!empty($settings['connection_status'])) : ?>
|
||||||
|
<?php if ($settings['connection_status'] === 'connected') : ?>
|
||||||
|
<div class="notice notice-success">
|
||||||
|
<p><?php _e('Successfully connected to Bluesky!', 'bluesky-connector'); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="notice notice-error">
|
||||||
|
<p><?php printf(__('Connection error: %s', 'bluesky-connector'), esc_html($settings['last_error'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2><?php _e('Connection Settings', 'bluesky-connector'); ?></h2>
|
||||||
|
<form method="post" action="">
|
||||||
|
<?php wp_nonce_field('bluesky_connector_settings'); ?>
|
||||||
|
<input type="hidden" name="action" value="update_bluesky_settings">
|
||||||
|
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bluesky_domain"><?php _e('Bluesky Domain', 'bluesky-connector'); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input name="bluesky_domain"
|
||||||
|
type="url"
|
||||||
|
id="bluesky_domain"
|
||||||
|
value="https://bsky.social"
|
||||||
|
class="regular-text"
|
||||||
|
readonly>
|
||||||
|
<p class="description">
|
||||||
|
<?php _e('The Bluesky API domain (fixed to https://bsky.social)', 'bluesky-connector'); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bluesky_identifier"><?php _e('Bluesky Handle', 'bluesky-connector'); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input name="bluesky_identifier"
|
||||||
|
type="text"
|
||||||
|
id="bluesky_identifier"
|
||||||
|
value="<?php echo esc_attr($settings['identifier']); ?>"
|
||||||
|
class="regular-text"
|
||||||
|
placeholder="username.bsky.social"
|
||||||
|
required>
|
||||||
|
<p class="description">
|
||||||
|
<?php _e('Your full Bluesky handle (e.g., username.bsky.social)', 'bluesky-connector'); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bluesky_password"><?php _e('App Password', 'bluesky-connector'); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input name="bluesky_password"
|
||||||
|
type="password"
|
||||||
|
id="bluesky_password"
|
||||||
|
class="regular-text"
|
||||||
|
<?php echo empty($settings['identifier']) ? 'required' : ''; ?>>
|
||||||
|
<p class="description">
|
||||||
|
<?php _e('Your Bluesky app password (will not be stored)', 'bluesky-connector'); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php submit_button(__('Save Connection Settings', 'bluesky-connector')); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($settings['connection_status']) && $settings['connection_status'] === 'connected') : ?>
|
||||||
|
<div class="card" style="margin-top: 20px;">
|
||||||
|
<h2><?php _e('Post Format Settings', 'bluesky-connector'); ?></h2>
|
||||||
|
<form method="post" action="options.php">
|
||||||
|
<?php settings_fields('bluesky_connector_settings'); ?>
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bluesky_post_format"><?php _e('Post Layout', 'bluesky-connector'); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select name="bluesky_post_format" id="bluesky_post_format">
|
||||||
|
<option value="title-excerpt-link" <?php selected(get_option('bluesky_post_format'), 'title-excerpt-link'); ?>>
|
||||||
|
<?php _e('Title + Excerpt + Link (No Image)', 'bluesky-connector'); ?>
|
||||||
|
</option>
|
||||||
|
<option value="image-title-excerpt-link" <?php selected(get_option('bluesky_post_format'), 'image-title-excerpt-link'); ?>>
|
||||||
|
<?php _e('Image + Title + Excerpt + Link (Image will appear at top)', 'bluesky-connector'); ?>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="description">
|
||||||
|
<?php _e('Note: When including images, Bluesky will always display them at the top of the post regardless of format selection.', 'bluesky-connector'); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bluesky_include_title"><?php _e('Title Options', 'bluesky-connector'); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<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">
|
||||||
|
<?php _e('When enabled, the post title will be included at the beginning of the post text.', 'bluesky-connector'); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php submit_button(__('Save Format Settings', 'bluesky-connector')); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top: 20px;">
|
||||||
|
<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">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php _e('DID', 'bluesky-connector'); ?></th>
|
||||||
|
<td><code><?php echo esc_html(get_option('bluesky_did')); ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php _e('Last Token Refresh', 'bluesky-connector'); ?></th>
|
||||||
|
<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>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user