Updated to handle the separate post requests for artwork, and other now playing data, hope it works lol
This commit is contained in:
@ -2,84 +2,155 @@
|
||||
/*
|
||||
Plugin Name: Apple Music Now Playing API
|
||||
Description: Accepts JSON now-playing updates via REST, lets user set a secure API key on the plugin page, a shortcode widget, that can be added to Footer
|
||||
Version: 1.3.4
|
||||
Version: 1.4.0
|
||||
Author: Sophia Atkinson
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
defined("ABSPATH") || exit();
|
||||
|
||||
// === Register REST API endpoint ===
|
||||
add_action('rest_api_init', function () {
|
||||
register_rest_route('nowplaying/v1', '/update', [
|
||||
'methods' => 'POST',
|
||||
'callback' => 'nowplaying_api_handle_post',
|
||||
'permission_callback' => '__return_true',
|
||||
// === Register REST API endpoints ===
|
||||
add_action("rest_api_init", function () {
|
||||
register_rest_route("nowplaying/v1", "/update", [
|
||||
"methods" => "POST",
|
||||
"callback" => "nowplaying_api_handle_post",
|
||||
"permission_callback" => "__return_true",
|
||||
]);
|
||||
register_rest_route("nowplaying/v1", "/update/artwork", [
|
||||
"methods" => "POST",
|
||||
"callback" => "nowplaying_api_handle_art_post",
|
||||
"permission_callback" => "__return_true",
|
||||
]);
|
||||
});
|
||||
|
||||
function nowplaying_api_handle_post(WP_REST_Request $request) {
|
||||
$stored_key = get_option('nowplaying_api_key');
|
||||
$auth_header = $request->get_header('authorization');
|
||||
function nowplaying_api_handle_post(WP_REST_Request $request)
|
||||
{
|
||||
$stored_key = get_option("nowplaying_api_key");
|
||||
$auth_header = $request->get_header("authorization");
|
||||
|
||||
if (!$auth_header || $auth_header !== "Bearer $stored_key") {
|
||||
return new WP_REST_Response('Forbidden: Invalid API key.', 403);
|
||||
$expected_prefix = "bearer ";
|
||||
$auth_header_normalized = strtolower(trim($auth_header ?? ""));
|
||||
if (
|
||||
!$auth_header ||
|
||||
stripos($auth_header_normalized, $expected_prefix) !== 0 ||
|
||||
substr(trim($auth_header), strlen($expected_prefix)) !== $stored_key
|
||||
) {
|
||||
return new WP_REST_Response("Forbidden: Invalid API key.", 403);
|
||||
}
|
||||
|
||||
$data = json_decode($request->get_body(), true);
|
||||
|
||||
if ($data === null) {
|
||||
return new WP_REST_Response('Invalid JSON.', 400);
|
||||
return new WP_REST_Response("Invalid JSON.", 400);
|
||||
}
|
||||
|
||||
$file_path = plugin_dir_path(__FILE__) . 'nowplaying.json';
|
||||
$file_path = plugin_dir_path(__FILE__) . "nowplaying.json";
|
||||
$written = file_put_contents(
|
||||
$file_path,
|
||||
json_encode($data, JSON_PRETTY_PRINT)
|
||||
);
|
||||
try {
|
||||
$written = file_put_contents(
|
||||
$file_path,
|
||||
json_encode($data, JSON_PRETTY_PRINT)
|
||||
);
|
||||
if ($written === false) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return new WP_REST_Response("Failed to write to nowplaying.json.", 500);
|
||||
}
|
||||
|
||||
return new WP_REST_Response("Now Playing updated.", 200);
|
||||
}
|
||||
|
||||
function nowplaying_api_handle_art_post(WP_REST_Request $request)
|
||||
{
|
||||
$stored_key = get_option("nowplaying_api_key");
|
||||
$auth_header = $request->get_header("authorization");
|
||||
|
||||
if (!$auth_header || $auth_header !== "Bearer $stored_key") {
|
||||
return new WP_REST_Response("Forbidden: Invalid API key.", 403);
|
||||
}
|
||||
|
||||
$data = json_decode($request->get_body(), true);
|
||||
|
||||
if ($data === null) {
|
||||
return new WP_REST_Response("Invalid JSON.", 400);
|
||||
}
|
||||
|
||||
if (!isset($data["image"]) || !is_string($data["image"])) {
|
||||
return new WP_REST_Response(
|
||||
'Missing or invalid "image" property.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$file_path = plugin_dir_path(__FILE__) . "nowplaying-art.json";
|
||||
|
||||
try {
|
||||
$written = file_put_contents($file_path, json_encode($data, JSON_PRETTY_PRINT));
|
||||
if (!$written) throw new Exception();
|
||||
$written = file_put_contents(
|
||||
$file_path,
|
||||
json_encode($data, JSON_PRETTY_PRINT)
|
||||
);
|
||||
if ($written === false) {
|
||||
throw new Exception();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return new WP_REST_Response('Failed to write to nowplaying.json.', 500);
|
||||
return new WP_REST_Response(
|
||||
"Failed to write to nowplaying-art.json.",
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response('Now Playing updated.', 200);
|
||||
return new WP_REST_Response("Artwork updated.", 200);
|
||||
}
|
||||
|
||||
// === Admin Settings Page ===
|
||||
add_action('admin_menu', function () {
|
||||
add_action("admin_menu", function () {
|
||||
add_options_page(
|
||||
'Now Playing API Settings',
|
||||
'Now Playing API',
|
||||
'manage_options',
|
||||
'nowplaying-api',
|
||||
'nowplaying_api_settings_page'
|
||||
"Now Playing API Settings",
|
||||
"Now Playing API",
|
||||
"manage_options",
|
||||
"nowplaying-api",
|
||||
"nowplaying_api_settings_page"
|
||||
);
|
||||
});
|
||||
|
||||
function nowplaying_api_settings_page() {
|
||||
if (!current_user_can('manage_options')) return;
|
||||
function nowplaying_api_settings_page()
|
||||
{
|
||||
if (!current_user_can("manage_options")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_POST['nowplaying_api_key']) && check_admin_referer('nowplaying_save_key', 'nowplaying_nonce')) {
|
||||
$new_key = sanitize_text_field($_POST['nowplaying_api_key']);
|
||||
if (
|
||||
isset($_POST["nowplaying_api_key"]) &&
|
||||
check_admin_referer("nowplaying_save_key", "nowplaying_nonce")
|
||||
) {
|
||||
$new_key = sanitize_text_field($_POST["nowplaying_api_key"]);
|
||||
if (strlen($new_key) === 64) {
|
||||
update_option('nowplaying_api_key', $new_key);
|
||||
update_option("nowplaying_api_key", $new_key);
|
||||
echo '<div class="updated"><p>API key updated.</p></div>';
|
||||
} else {
|
||||
echo '<div class="error"><p>API key must be exactly 64 characters.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['generate_key']) && check_admin_referer('nowplaying_save_key', 'nowplaying_nonce')) {
|
||||
if (
|
||||
isset($_POST["generate_key"]) &&
|
||||
check_admin_referer("nowplaying_save_key", "nowplaying_nonce")
|
||||
) {
|
||||
$generated_key = bin2hex(random_bytes(32));
|
||||
update_option('nowplaying_api_key', $generated_key);
|
||||
update_option("nowplaying_api_key", $generated_key);
|
||||
echo '<div class="updated"><p>New API key generated.</p></div>';
|
||||
}
|
||||
|
||||
$current_key = get_option('nowplaying_api_key');
|
||||
$current_key = get_option("nowplaying_api_key");
|
||||
if (!$current_key) {
|
||||
$current_key = bin2hex(random_bytes(32));
|
||||
update_option('nowplaying_api_key', $current_key);
|
||||
update_option("nowplaying_api_key", $current_key);
|
||||
}
|
||||
|
||||
$url = esc_url(site_url('/wp-json/nowplaying/v1/update'));
|
||||
$url = esc_url(site_url("/wp-json/nowplaying/v1/update"));
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>Apple Music Now Playing API Settings</h1>
|
||||
@ -87,33 +158,40 @@ function nowplaying_api_settings_page() {
|
||||
<code><?php echo $url; ?></code>
|
||||
|
||||
<form method="post">
|
||||
<?php wp_nonce_field('nowplaying_save_key', 'nowplaying_nonce'); ?>
|
||||
<?php wp_nonce_field("nowplaying_save_key", "nowplaying_nonce"); ?>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th><label for="nowplaying_api_key">Current API Key</label></th>
|
||||
<td>
|
||||
<input type="text" name="nowplaying_api_key" value="<?php echo esc_attr($current_key); ?>" class="regular-text" maxlength="64" />
|
||||
<input type="text" name="nowplaying_api_key" value="<?php echo esc_attr(
|
||||
$current_key
|
||||
); ?>" class="regular-text" maxlength="64" />
|
||||
<p class="description">Keep this key secret. <br> Must be 64 characters.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php submit_button('Save API Key'); ?>
|
||||
<?php submit_button("Save API Key"); ?>
|
||||
</form>
|
||||
|
||||
<form method="post" style="margin-top: 20px;">
|
||||
<?php wp_nonce_field('nowplaying_save_key', 'nowplaying_nonce'); ?>
|
||||
<?php submit_button('Generate New API Key', 'secondary', 'generate_key'); ?>
|
||||
<?php wp_nonce_field("nowplaying_save_key", "nowplaying_nonce"); ?>
|
||||
<?php submit_button(
|
||||
"Generate New API Key",
|
||||
"secondary",
|
||||
"generate_key"
|
||||
); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// === Shortcode [now-playing-widget] ===
|
||||
add_shortcode('now-playing-widget', function () {
|
||||
$plugin_url = plugin_dir_url(__FILE__) . 'nowplaying.json';
|
||||
add_shortcode("now-playing-widget", function () {
|
||||
$plugin_url = plugin_dir_url(__FILE__) . "nowplaying.json";
|
||||
$art_url = plugin_dir_url(__FILE__) . "nowplaying-art.json";
|
||||
ob_start();
|
||||
?>
|
||||
<?php $css_url = plugin_dir_url(__FILE__) . 'assets/css/amnp-style.css'; ?>
|
||||
<?php $css_url = plugin_dir_url(__FILE__) . "assets/css/amnp-style.css"; ?>
|
||||
<link rel="stylesheet" type="text/css" href="<?php echo esc_url($css_url); ?>">
|
||||
<div class="amnp hasjs" id="widget">
|
||||
<img title="" id="cover" src="data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22150%22%20height%3D%22150%22%20viewBox%3D%220%200%20150%20150%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%231E1F22%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M31.66%2073.75h1.61q.59%200%201.03-.15t.73-.42q.29-.28.44-.68.14-.39.14-.88%200-.46-.14-.84-.15-.38-.43-.64-.29-.26-.73-.4t-1.04-.14h-1.61zm-2.15-5.82h3.76q1.16%200%202.01.27.84.28%201.4.76.55.49.82%201.17.26.68.26%201.49%200%20.84-.28%201.54t-.84%201.2q-.56.51-1.4.79t-1.97.28h-1.61v4.07h-2.15zm9.61-.32h1.98V79.5h-1.98zm8.29%209.77V76q-.85.04-1.44.14-.58.11-.93.28-.36.17-.51.39-.15.23-.15.49%200%20.52.31.74.31.23.8.23.61%200%201.05-.22.45-.22.87-.67m-4.17-4.31-.36-.63q1.42-1.3%203.41-1.3.72%200%201.29.24.57.23.96.65t.6%201.01q.2.58.2%201.28v5.18h-.9q-.28%200-.43-.08-.15-.09-.24-.34l-.17-.6q-.32.28-.61.5-.3.21-.62.35-.32.15-.68.22-.37.08-.81.08-.52%200-.96-.14t-.76-.42-.49-.7q-.18-.41-.18-.97%200-.31.11-.62.1-.3.34-.58.23-.28.61-.53.37-.25.92-.43.55-.19%201.28-.3.73-.12%201.66-.14v-.48q0-.83-.35-1.22-.35-.4-1.02-.4-.48%200-.79.11-.32.11-.56.25t-.43.26q-.2.11-.44.11-.21%200-.35-.11-.15-.11-.23-.25m13.83-1.78h1.58l-4.47%2010.47q-.09.2-.23.31t-.43.11h-1.47l1.54-3.3-3.32-7.59H52q.24%200%20.37.11.13.12.19.26l1.75%204.26q.09.21.15.43.06.21.11.44.07-.23.15-.45.07-.22.16-.43l1.64-4.25q.07-.16.22-.26.14-.11.33-.11m2.63%200h1.98v8.21H59.7zm2.26-2.39q0%20.26-.11.48-.1.22-.27.39-.18.17-.41.27t-.49.1-.49-.1-.39-.27q-.17-.17-.27-.39t-.1-.48.1-.5q.1-.23.27-.4.16-.16.39-.26t.49-.1.49.1.41.26q.17.17.27.4.11.24.11.5m3.45%202.75.14.65q.25-.25.52-.46.28-.21.59-.36.3-.15.66-.23.35-.09.76-.09.68%200%201.2.23t.86.64q.35.41.53.99.18.57.18%201.26v5.22h-1.97v-5.22q0-.76-.35-1.17-.34-.41-1.05-.41-.51%200-.96.23-.44.23-.84.63v5.94H63.7v-8.21h1.21q.38%200%20.5.36m10.31%203.55q.37%200%20.64-.1.28-.1.46-.28t.27-.42q.09-.25.09-.55%200-.61-.36-.96-.37-.36-1.1-.36-.72%200-1.09.36-.36.35-.36.96%200%20.29.09.54t.27.43.46.28q.27.1.63.1m2.24%204.67q0-.24-.15-.39-.14-.16-.39-.24t-.58-.12-.7-.06l-.77-.04q-.4-.02-.77-.06-.33.18-.54.43-.2.25-.2.57%200%20.22.11.41t.34.32q.24.14.61.21.38.08.92.08.56%200%20.96-.08.4-.09.66-.24.26-.14.38-.35.12-.2.12-.44m-.4-8.3h2.36v.74q0%20.35-.42.43l-.74.14q.17.42.17.92%200%20.61-.24%201.1-.25.5-.68.84t-1.02.53-1.27.19q-.24%200-.46-.02-.22-.03-.44-.07-.38.23-.38.52%200%20.25.22.37.23.11.61.16.37.05.85.06t.99.05q.5.04.98.14t.86.32q.37.21.6.59.23.37.23.95%200%20.55-.27%201.06t-.77.91q-.51.4-1.25.64-.74.25-1.69.25-.92%200-1.61-.18t-1.15-.48q-.45-.3-.68-.69-.22-.4-.22-.82%200-.58.35-.96.35-.39.95-.62-.32-.17-.52-.45-.19-.28-.19-.74%200-.18.07-.38.07-.19.2-.39.13-.19.33-.36t.47-.31q-.62-.33-.98-.89-.35-.56-.35-1.32%200-.6.24-1.1.25-.49.68-.84.44-.34%201.03-.53.6-.18%201.3-.18.53%200%201%20.11.46.1.84.31m15.67-3.64h1.89V79.5h-1.1q-.26%200-.43-.08-.17-.09-.33-.29l-6.04-7.71q.05.53.05.98v7.1h-1.9V67.93h1.13q.14%200%20.24.01.1.02.17.05.08.04.15.11.07.06.16.18l6.06%207.74-.04-.55q-.01-.27-.01-.51zm7.79%203.23q.91%200%201.66.3t1.28.84.82%201.33q.29.78.29%201.75%200%20.98-.29%201.76t-.82%201.34q-.53.55-1.28.84-.75.3-1.66.3-.92%200-1.67-.3-.75-.29-1.29-.84-.53-.56-.82-1.34t-.29-1.76q0-.97.29-1.75.29-.79.82-1.33.54-.54%201.29-.84t1.67-.3m0%206.94q1.02%200%201.52-.69.49-.69.49-2.01%200-1.33-.49-2.03-.5-.69-1.52-.69-1.04%200-1.54.7t-.5%202.02.5%202.01%201.54.69m10.66-6.81h1.97v8.21h-1.21q-.39%200-.49-.36l-.14-.66q-.5.52-1.11.83-.61.32-1.43.32-.67%200-1.19-.23t-.87-.64q-.35-.42-.53-.99t-.18-1.26v-5.22h1.98v5.22q0%20.75.34%201.16.35.41%201.05.41.51%200%20.96-.22.45-.23.85-.63zm6.66%208.34q-1.07%200-1.64-.61-.58-.6-.58-1.66v-4.59h-.84q-.16%200-.27-.1-.11-.11-.11-.31v-.79l1.32-.21.42-2.24q.04-.16.15-.25t.29-.09h1.02v2.58h2.19v1.41h-2.19v4.45q0%20.38.19.6t.51.22q.19%200%20.31-.05.13-.04.22-.09l.16-.09q.07-.05.15-.05t.14.05q.06.04.12.13l.59.96q-.43.36-.99.54-.56.19-1.16.19%22%2F%3E%3C%2Fsvg%3E" alt="Album cover" />
|
||||
@ -124,17 +202,19 @@ add_shortcode('now-playing-widget', function () {
|
||||
<div id="time-info"><span id="current-time">0:00</span><span id="total-time">0:00</span></div>
|
||||
</div>
|
||||
<?php
|
||||
$json_path = plugin_dir_path(__FILE__) . 'nowplaying.json';
|
||||
$nowplaying_data = @json_decode(file_get_contents($json_path), true) ?? [];
|
||||
$np_details = esc_html($nowplaying_data['details'] ?? 'Not playing');
|
||||
$np_artist = esc_html($nowplaying_data['state'] ?? '');
|
||||
$np_status = isset($nowplaying_data['paused']) && $nowplaying_data['paused'] ? 'Paused' : 'Playing';
|
||||
$np_cover = esc_attr($nowplaying_data['artworkBase64'] ?? '');
|
||||
$np_album = esc_attr($nowplaying_data['album'] ?? '');
|
||||
$json_path = plugin_dir_path(__FILE__) . "nowplaying.json";
|
||||
$art_path = plugin_dir_path(__FILE__) . "nowplaying-art.json";
|
||||
$nowplaying_data = @json_decode(file_get_contents($json_path), true) ?? [];
|
||||
$art_data = @json_decode(file_get_contents($art_path), true) ?? [];
|
||||
$np_details = esc_html($nowplaying_data["details"] ?? "Not playing");
|
||||
$np_artist = esc_html($nowplaying_data["artist"] ?? "");
|
||||
$np_status = isset($nowplaying_data["paused"]) && $nowplaying_data["paused"] ? "Paused" : "Playing";
|
||||
$np_cover = esc_attr($art_data["image"] ?? ($nowplaying_data["artworkBase64"] ?? ""));
|
||||
$np_album = esc_attr($nowplaying_data["album"] ?? "");
|
||||
?>
|
||||
<noscript>
|
||||
<div class="amnp" id="widget">
|
||||
<img src="<?php echo $np_cover ?: '...' ?>" alt="Album cover" title="<?php echo $np_album; ?>" />
|
||||
<img src="<?php echo $np_cover ?: "data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22150%22%20height%3D%22150%22%20viewBox%3D%220%200%20150%20150%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%231E1F22%22%2F%3E%3Cpath%20fill%3D%22%23FFF%22%20d%3D%22M31.66%2073.75h1.61q.59%200%201.03-.15t.73-.42q.29-.28.44-.68.14-.39.14-.88%200-.46-.14-.84-.15-.38-.43-.64-.29-.26-.73-.4t-1.04-.14h-1.61zm-2.15-5.82h3.76q1.16%200%202.01.27.84.28%201.4.76.55.49.82%201.17.26.68.26%201.49%200%20.84-.28%201.54t-.84%201.2q-.56.51-1.4.79t-1.97.28h-1.61v4.07h-2.15zm9.61-.32h1.98V79.5h-1.98zm8.29%209.77V76q-.85.04-1.44.14-.58.11-.93.28-.36.17-.51.39-.15.23-.15.49%200%20.52.31.74.31.23.8.23.61%200%201.05-.22.45-.22.87-.67m-4.17-4.31-.36-.63q1.42-1.3%203.41-1.3.72%200%201.29.24.57.23.96.65t.6%201.01q.2.58.2%201.28v5.18h-.9q-.28%200-.43-.08-.15-.09-.24-.34l-.17-.6q-.32.28-.61.5-.3.21-.62.35-.32.15-.68.22-.37.08-.81.08-.52%200-.96-.14t-.76-.42-.49-.7q-.18-.41-.18-.97%200-.31.11-.62.1-.3.34-.58.23-.28.61-.53.37-.25.92-.43.55-.19%201.28-.3.73-.12%201.66-.14v-.48q0-.83-.35-1.22-.35-.4-1.02-.4-.48%200-.79.11-.32.11-.56.25t-.43.26q-.2.11-.44.11-.21%200-.35-.11-.15-.11-.23-.25m13.83-1.78h1.58l-4.47%2010.47q-.09.2-.23.31t-.43.11h-1.47l1.54-3.3-3.32-7.59H52q.24%200%20.37.11.13.12.19.26l1.75%204.26q.09.21.15.43.06.21.11.44.07-.23.15-.45.07-.22.16-.43l1.64-4.25q.07-.16.22-.26.14-.11.33-.11m2.63%200h1.98v8.21H59.7zm2.26-2.39q0%20.26-.11.48-.1.22-.27.39-.18.17-.41.27t-.49.1-.49-.1-.39-.27q-.17-.17-.27-.39t-.1-.48.1-.5q.1-.23.27-.4.16-.16.39-.26t.49-.1.49.1.41.26q.17.17.27.4.11.24.11.5m3.45%202.75.14.65q.25-.25.52-.46.28-.21.59-.36.3-.15.66-.23.35-.09.76-.09.68%200%201.2.23t.86.64q.35.41.53.99.18.57.18%201.26v5.22h-1.97v-5.22q0-.76-.35-1.17-.34-.41-1.05-.41-.51%200-.96.23-.44.23-.84.63v5.94H63.7v-8.21h1.21q.38%200%20.5.36m10.31%203.55q.37%200%20.64-.1.28-.1.46-.28t.27-.42q.09-.25.09-.55%200-.61-.36-.96-.37-.36-1.1-.36-.72%200-1.09.36-.36.35-.36.96%200%20.29.09.54t.27.43.46.28q.27.1.63.1m2.24%204.67q0-.24-.15-.39-.14-.16-.39-.24t-.58-.12-.7-.06l-.77-.04q-.4-.02-.77-.06-.33.18-.54.43-.2.25-.2.57%200%20.22.11.41t.34.32q.24.14.61.21.38.08.92.08.56%200%20.96-.08.4-.09.66-.24.26-.14.38-.35.12-.2.12-.44m-.4-8.3h2.36v.74q0%20.35-.42.43l-.74.14q.17.42.17.92%200%20.61-.24%201.1-.25.5-.68.84t-1.02.53-1.27.19q-.24%200-.46-.02-.22-.03-.44-.07-.38.23-.38.52%200%20.25.22.37.23.11.61.16.37.05.85.06t.99.05q.5.04.98.14t.86.32q.37.21.6.59.23.37.23.95%200%20.55-.27%201.06t-.77.91q-.51.4-1.25.64-.74.25-1.69.25-.92%200-1.61-.18t-1.15-.48q-.45-.3-.68-.69-.22-.4-.22-.82%200-.58.35-.96.35-.39.95-.62-.32-.17-.52-.45-.19-.28-.19-.74%200-.18.07-.38.07-.19.2-.39.13-.19.33-.36t.47-.31q-.62-.33-.98-.89-.35-.56-.35-1.32%200-.6.24-1.1.25-.49.68-.84.44-.34%201.03-.53.6-.18%201.3-.18.53%200%201%20.11.46.1.84.31m15.67-3.64h1.89V79.5h-1.1q-.26%200-.43-.08-.17-.09-.33-.29l-6.04-7.71q.05.53.05.98v7.1h-1.9V67.93h1.13q.14%200%20.24.01.1.02.17.05.08.04.15.11.07.06.16.18l6.06%207.74-.04-.55q-.01-.27-.01-.51zm7.79%203.23q.91%200%201.66.3t1.28.84.82%201.33q.29.78.29%201.75%200%20.98-.29%201.76t-.82%201.34q-.53.55-1.28.84-.75.3-1.66.3-.92%200-1.67-.3-.75-.29-1.29-.84-.53-.56-.82-1.34t-.29-1.76q0-.97.29-1.75.29-.79.82-1.33.54-.54%201.29-.84t1.67-.3m0%206.94q1.02%200%201.52-.69.49-.69.49-2.01%200-1.33-.49-2.03-.5-.69-1.52-.69-1.04%200-1.54.7t-.5%202.02.5%202.01%201.54.69m10.66-6.81h1.97v8.21h-1.21q-.39%200-.49-.36l-.14-.66q-.5.52-1.11.83-.61.32-1.43.32-.67%200-1.19-.23t-.87-.64q-.35-.42-.53-.99t-.18-1.26v-5.22h1.98v5.22q0%20.75.34%201.16.35.41%201.05.41.51%200%20.96-.22.45-.23.85-.63zm6.66%208.34q-1.07%200-1.64-.61-.58-.6-.58-1.66v-4.59h-.84q-.16%200-.27-.1-.11-.11-.11-.31v-.79l1.32-.21.42-2.24q.04-.16.15-.25t.29-.09h1.02v2.58h2.19v1.41h-2.19v4.45q0%20.38.19.6t.51.22q.19%200%20.31-.05.13-.04.22-.09l.16-.09q.07-.05.15-.05t.14.05q.06.04.12.13l.59.96q-.43.36-.99.54-.56.19-1.16.19%22%2F%3E%3C%2Fsvg%3E"; ?>" alt="Album cover" title="<?php echo $np_album; ?>" />
|
||||
<div title="<?php echo $np_details; ?>"><?php echo $np_details; ?></div>
|
||||
<div title="<?php echo $np_artist; ?>"><?php echo $np_artist; ?></div>
|
||||
<div><?php echo $np_status; ?></div>
|
||||
@ -142,84 +222,136 @@ add_shortcode('now-playing-widget', function () {
|
||||
</noscript>
|
||||
|
||||
<script>
|
||||
let lastData={}, intervalId=null;
|
||||
function formatTime(s){if(!s||isNaN(s))return"0:00";let m=Math.floor(s/60),sec=Math.floor(s%60);return m+":"+(sec<10?"0":"")+sec}
|
||||
function deepEqual(a,b){return JSON.stringify(a)===JSON.stringify(b)}
|
||||
async function fetchNowPlaying(){
|
||||
if(document.hidden)return;
|
||||
try{
|
||||
const res=await fetch('<?php echo esc_url($plugin_url); ?>?'+Date.now(),{cache:'no-store'});
|
||||
if(!res.ok)throw new Error('Network error');
|
||||
const data=await res.json();
|
||||
if(deepEqual(data,lastData))return;
|
||||
lastData=data;
|
||||
const {details="Not playing", state="", paused, artworkBase64='', currentTime=0, duration=0, album=""} = data;
|
||||
document.getElementById('title').textContent = details;
|
||||
document.getElementById('title').title = details;
|
||||
document.getElementById('artist').textContent = state;
|
||||
document.getElementById('artist').title = state;
|
||||
if (artworkBase64) document.getElementById('cover').src = artworkBase64;
|
||||
document.getElementById('cover').alt = details + " cover";
|
||||
document.getElementById('cover').title = album;
|
||||
document.getElementById('status').textContent = paused ? "Paused" : "Playing";
|
||||
document.getElementById('progress-bar').style.width = (duration > 0 ? (currentTime / duration) * 100 : 0) + '%';
|
||||
document.getElementById('current-time').textContent = formatTime(currentTime);
|
||||
document.getElementById('total-time').textContent = formatTime(duration);
|
||||
}catch(err){console.error("Fetch failed:",err)}
|
||||
let lastNowPlaying = null;
|
||||
let lastSongKey = null;
|
||||
let lastArtImage = null;
|
||||
let intervalId = null;
|
||||
function formatTime(s){if(!s||isNaN(s)){return "0:00"}let m=Math.floor(s/60),sec=Math.floor(s%60);return m+":"+(sec<10?"0":"")+sec}
|
||||
async function fetchJson(url) {
|
||||
try {
|
||||
const res = await fetch(url + '?' + Date.now(), { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
console.error('Fetch error:', e);
|
||||
return null;
|
||||
}
|
||||
function startFetching(){if(intervalId===null){fetchNowPlaying();intervalId=setInterval(fetchNowPlaying,1000)}}
|
||||
function stopFetching(){if(intervalId!==null){clearInterval(intervalId);intervalId=null}}
|
||||
const widget=document.getElementById('widget');
|
||||
let widgetVisible=false;
|
||||
const observer=new IntersectionObserver((entries)=>{entries.forEach(entry=>{widgetVisible=entry.isIntersecting;updateFetchingState()})},{threshold:0.1});
|
||||
observer.observe(widget);
|
||||
document.addEventListener('visibilitychange',updateFetchingState);
|
||||
function updateFetchingState(){if(!document.hidden&&widgetVisible){startFetching()}else{stopFetching()}}
|
||||
updateFetchingState();
|
||||
</script>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
});
|
||||
}
|
||||
async function updateWidget() {
|
||||
if (document.hidden) return;
|
||||
const nowPlaying = await fetchJson('<?php echo esc_url($plugin_url); ?>');
|
||||
if (!nowPlaying) return;
|
||||
const currentSongKey = (nowPlaying.details || '') + '|' + (nowPlaying.album || '');
|
||||
if (JSON.stringify(nowPlaying) !== JSON.stringify(lastNowPlaying)) {
|
||||
lastNowPlaying = nowPlaying;
|
||||
|
||||
// === Register Customizer Widget Support with Title Option ===
|
||||
class Now_Playing_Widget extends WP_Widget {
|
||||
public function __construct() {
|
||||
document.getElementById('title').textContent = nowPlaying.details || 'Not playing';
|
||||
document.getElementById('title').title = nowPlaying.details || '';
|
||||
document.getElementById('artist').textContent = nowPlaying.state || '';
|
||||
document.getElementById('artist').title = nowPlaying.state || '';
|
||||
document.getElementById('status').textContent = nowPlaying.paused ? 'Paused' : 'Playing';
|
||||
const currentTime = typeof nowPlaying.currentTime === 'number' ? nowPlaying.currentTime : 0;
|
||||
const duration = typeof nowPlaying.duration === 'number' ? nowPlaying.duration : 0;
|
||||
document.getElementById('progress-bar').style.width =
|
||||
(duration > 0 ? (currentTime / duration) * 100 : 0) + '%';
|
||||
document.getElementById('current-time').textContent = formatTime(nowPlaying.currentTime);
|
||||
document.getElementById('total-time').textContent = formatTime(nowPlaying.duration);
|
||||
}
|
||||
if (currentSongKey !== lastSongKey) {
|
||||
lastSongKey = currentSongKey;
|
||||
const artData = await fetchJson('<?php echo esc_url($art_url); ?>');
|
||||
let image = null;
|
||||
if (artData?.image) {
|
||||
image = artData.image;
|
||||
} else if (nowPlaying.artworkBase64) {
|
||||
image = nowPlaying.artworkBase64;
|
||||
}
|
||||
if (image && image !== lastArtImage) {
|
||||
lastArtImage = image;
|
||||
const cover = document.getElementById('cover');
|
||||
cover.src = image;
|
||||
cover.alt = nowPlaying.details || 'Album cover';
|
||||
cover.title = nowPlaying.album || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
function startLoop() {
|
||||
if (intervalId === null) {
|
||||
updateWidget();
|
||||
intervalId = setInterval(updateWidget, 1000);
|
||||
}
|
||||
}
|
||||
function stopLoop(){if (intervalId !== null) {clearInterval(intervalId);intervalId = null;}}
|
||||
const widget = document.getElementById('widget');
|
||||
let isVisible = false;
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {isVisible = entry.isIntersecting;updateFetchState();});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
function updateFetchState() {
|
||||
if (!document.hidden && isVisible) {
|
||||
startLoop();
|
||||
} else {
|
||||
stopLoop();
|
||||
}
|
||||
}
|
||||
observer.observe(widget);
|
||||
document.addEventListener('visibilitychange', updateFetchState);
|
||||
updateFetchState();
|
||||
</script>
|
||||
<?php return ob_get_clean();
|
||||
}); // === Register Customizer Widget Support with Title Option ===
|
||||
class Now_Playing_Widget extends WP_Widget
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
'now_playing_widget',
|
||||
__('Now Playing Widget', 'nowplaying'),
|
||||
['description' => __('Displays the current Apple Music song from your API.', 'nowplaying')]
|
||||
"now_playing_widget",
|
||||
__("Now Playing Widget", "nowplaying"),
|
||||
[
|
||||
"description" => __(
|
||||
"Displays the current Apple Music song from your API.",
|
||||
"nowplaying"
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance) {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '');
|
||||
echo $args['before_widget'];
|
||||
public function widget($args, $instance)
|
||||
{
|
||||
$title = apply_filters("widget_title", $instance["title"] ?? "");
|
||||
echo $args["before_widget"];
|
||||
if (!empty($title)) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
echo $args["before_title"] .
|
||||
esc_html($title) .
|
||||
$args["after_title"];
|
||||
}
|
||||
echo do_shortcode('[now-playing-widget]');
|
||||
echo $args['after_widget'];
|
||||
echo do_shortcode("[now-playing-widget]");
|
||||
echo $args["after_widget"];
|
||||
}
|
||||
|
||||
public function form($instance) {
|
||||
$title = esc_attr($instance['title'] ?? '');
|
||||
?>
|
||||
public function form($instance)
|
||||
{
|
||||
$title = esc_attr($instance["title"] ?? ""); ?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('Title:'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
|
||||
<label for="<?php echo esc_attr(
|
||||
$this->get_field_id("title")
|
||||
); ?>"><?php _e("Title:"); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr(
|
||||
$this->get_field_id("title")
|
||||
); ?>"
|
||||
name="<?php echo esc_attr(
|
||||
$this->get_field_name("title")
|
||||
); ?>" type="text"
|
||||
value="<?php echo $title; ?>" />
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance) {
|
||||
public function update($new_instance, $old_instance)
|
||||
{
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance["title"] = sanitize_text_field($new_instance["title"] ?? "");
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
add_action('widgets_init', function () {
|
||||
register_widget('Now_Playing_Widget');
|
||||
add_action("widgets_init", function () {
|
||||
register_widget("Now_Playing_Widget");
|
||||
});
|
||||
|
Reference in New Issue
Block a user