From edfd5886eb520fc152f2d5fa0c93e1b0474a661c Mon Sep 17 00:00:00 2001 From: anon Date: Sat, 26 Oct 2024 12:28:07 +0200 Subject: [PATCH] try to fix fucked up song/album titles, added dedicated album cover upload page, try to fetch covers from iTunes --- scrobble-handler.php | 659 +++++++++++++++++++++++++++++++++---------- 1 file changed, 510 insertions(+), 149 deletions(-) diff --git a/scrobble-handler.php b/scrobble-handler.php index e70164b..3516d19 100644 --- a/scrobble-handler.php +++ b/scrobble-handler.php @@ -21,6 +21,9 @@ class ScrobbleHandler { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_init', array($this, 'register_settings')); register_activation_hook(__FILE__, array($this, 'activate_plugin')); + + // Add action to handle form submission + add_action('admin_post_image_scrobble_upload', array($this, 'handle_image_upload')); } public function activate_plugin() { @@ -50,104 +53,120 @@ class ScrobbleHandler { } public function create_image($request) { - // Retrieve the raw image data from the request body - $image_data = $request->get_body(); + // Retrieve the raw image data from the request body + $image_data = $request->get_body(); - // Sanitize and retrieve parameters from the request - $author = sanitize_text_field($request->get_param('author')); - $album_title = sanitize_text_field($request->get_param('album_title')); - $song_title = sanitize_text_field($request->get_param('song_title')); // New parameter + // Sanitize and retrieve parameters from the request + $author = sanitize_text_field($request->get_param('author')); + $album_title = sanitize_text_field($request->get_param('album_title')); + $song_title = sanitize_text_field($request->get_param('song_title')); - // Validate required parameters - if (empty($author)) { - return new WP_Error( - 'missing_author', - 'Author is required.', - array('status' => 400) - ); - } + // Validate required parameters + if (empty($author)) { + return new WP_Error( + 'missing_author', + 'Author is required.', + array('status' => 400) + ); + } - // Ensure that either album_title or song_title is provided - if (empty($album_title) && empty($song_title)) { - return new WP_Error( - 'missing_titles', - 'Either album title or song title is required.', - array('status' => 400) - ); - } + // Ensure that either album_title or song_title is provided + if (empty($album_title) && empty($song_title)) { + return new WP_Error( + 'missing_titles', + 'Either album title or song title is required.', + array('status' => 400) + ); + } - // Determine which title to use: album_title takes precedence over song_title - $title = !empty($album_title) ? $album_title : $song_title; + // Determine which title to use: album_title takes precedence over song_title + $title = !empty($album_title) ? $album_title : $song_title; - // Generate a hash of the image data to check for duplicates - $image_hash = md5($image_data); + // Generate a hash of the image data to check for duplicates + $image_hash = md5($image_data); - // Check if an image with this hash already exists to prevent duplicates - $existing_attachment = $this->get_attachment_by_hash($image_hash); - if ($existing_attachment) { + // Check if an image with this hash already exists to prevent duplicates + $existing_attachment = $this->get_attachment_by_hash($image_hash); + if ($existing_attachment) { + return new WP_REST_Response( + array('url' => wp_get_attachment_url($existing_attachment->ID)), + 200 + ); + } + + // Save the image and create attachment + $result = $this->save_image_and_create_attachment($image_data, $author, $title, $image_hash, $album_title); + + if (is_wp_error($result)) { + return $result; + } + + // Return a successful response with the image URL return new WP_REST_Response( - array('url' => wp_get_attachment_url($existing_attachment->ID)), + array('url' => $result), 200 ); } - // Get the upload directory information - $upload_dir = wp_upload_dir(); + private function save_image_and_create_attachment($image_data, $author, $title, $image_hash, $album_title = '') { + // Get the upload directory information + $upload_dir = wp_upload_dir(); - // Create a sanitized filename using the author and the determined title - $filename = sanitize_file_name("{$author} - {$title}.jpg"); + // Create a sanitized filename using the author and the determined title + $filename = sanitize_file_name("{$author} - {$title}.jpg"); - // Ensure the filename is unique within the upload directory - $unique_filename = wp_unique_filename($upload_dir['path'], $filename); + // Ensure the filename is unique within the upload directory + $unique_filename = wp_unique_filename($upload_dir['path'], $filename); - // Construct the full file path - $file_path = trailingslashit($upload_dir['path']) . $unique_filename; + // Construct the full file path + $file_path = trailingslashit($upload_dir['path']) . $unique_filename; - // Attempt to save the image data to the specified file path - if (file_put_contents($file_path, $image_data) === false) { - return new WP_Error( - 'file_save_failed', - 'Failed to save the image file.', - array('status' => 500) + // Attempt to save the image data to the specified file path + if (file_put_contents($file_path, $image_data) === false) { + return new WP_Error( + 'file_save_failed', + 'Failed to save the image file.', + array('status' => 500) + ); + } + + // Prepare the attachment data + $attachment = array( + 'post_mime_type' => 'image/jpeg', + 'post_title' => "{$author} - {$title}", + 'post_content' => '', + 'post_status' => 'inherit' ); + + // Insert the attachment into the WordPress media library + $attach_id = wp_insert_attachment($attachment, $file_path); + + // Check for errors during attachment insertion + if (is_wp_error($attach_id)) { + @unlink($file_path); // Clean up the file if attachment creation failed + return $attach_id; // Return the WP_Error + } + + // Generate and update attachment metadata + require_once(ABSPATH . 'wp-admin/includes/image.php'); + $attach_data = wp_generate_attachment_metadata($attach_id, $file_path); + wp_update_attachment_metadata($attach_id, $attach_data); + + // Save the image hash in the attachment's metadata for future duplicate checks + update_post_meta($attach_id, 'image_hash', $image_hash); + + // Save the artist and album_title in attachment metadata for future lookups + if (!empty($album_title)) { + update_post_meta($attach_id, 'artist', strtolower($author)); + update_post_meta($attach_id, 'album_title', strtolower($album_title)); + } + + // Retrieve the URL of the newly created attachment + $image_url = wp_get_attachment_url($attach_id); + + return $image_url; } - // Prepare the attachment data - $attachment = array( - 'post_mime_type' => 'image/jpeg', - 'post_title' => "{$author} - {$title}", - 'post_content' => '', - 'post_status' => 'inherit' - ); - - // Insert the attachment into the WordPress media library - $attach_id = wp_insert_attachment($attachment, $file_path); - - // Check for errors during attachment insertion - if (is_wp_error($attach_id)) { - @unlink($file_path); // Clean up the file if attachment creation failed - return $attach_id; // Return the WP_Error - } - - // Generate and update attachment metadata - require_once(ABSPATH . 'wp-admin/includes/image.php'); - $attach_data = wp_generate_attachment_metadata($attach_id, $file_path); - wp_update_attachment_metadata($attach_id, $attach_data); - - // Save the image hash in the attachment's metadata for future duplicate checks - update_post_meta($attach_id, 'image_hash', $image_hash); - - // Retrieve the URL of the newly created attachment - $image_url = wp_get_attachment_url($attach_id); - - // Return a successful response with the image URL - return new WP_REST_Response( - array('url' => $image_url), - 200 - ); -} - - private function get_attachment_by_hash($hash) { $args = array( 'post_type' => 'attachment', @@ -171,86 +190,201 @@ class ScrobbleHandler { return null; } -public function scrobble($request) { - global $wpdb; - $data = $request->get_json_params(); + private function get_attachment_by_artist_album($artist, $album_title) { + global $wpdb; // Access the global $wpdb object + + // Primary method: Query attachments based on artist and album title + $args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => 1, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'artist', + 'value' => strtolower($artist), + 'compare' => '=' + ), + array( + 'key' => 'album_title', + 'value' => strtolower($album_title), + 'compare' => '=' + ) + ) + ); - // Updated required fields: 'album_name' is no longer required - $required_fields = ['song_name', 'author']; - - // Check for missing required fields - foreach ($required_fields as $field) { - if (!isset($data[$field])) { - return new WP_Error('missing_field', 'Missing required field: ' . $field, array('status' => 400)); + $query = new WP_Query($args); + + if ($query->have_posts()) { + // Attachment found via primary method + return wp_get_attachment_url($query->posts[0]->ID); } + + // Sanitize and prepare the variables + $album_name_lower = strtolower($album_title); + $author_lower = strtolower($artist); + $table_name = 'song_scrobbles'; + + // Prepare the SQL query using $wpdb->prepare to prevent SQL injection + $prepared_query = $wpdb->prepare( + " + SELECT cover_url + FROM {$table_name} + WHERE LOWER(album_name) = %s + AND LOWER(author) = %s + ORDER BY id DESC + LIMIT 1 + ", + $album_name_lower, + $author_lower + ); + + // Execute the query and retrieve the cover URL + $cover_url = $wpdb->get_var($prepared_query); + + if ($cover_url) { + // Optional: Validate the URL or process it as needed + return esc_url($cover_url); + } + + // Try fetching from Apple iTunes + $remote_album_url = $this->fetch_and_save_album_cover($artist, $album_title); + if ($remote_album_url) { + return esc_url($remote_album_url); + } + + // If both methods fail, return null + return null; } - - $table_name = 'song_scrobbles'; - $cover_url = isset($data['cover_url']) ? $data['cover_url'] : null; - $album_name = isset($data['album_name']) ? $data['album_name'] : null; - - // If 'album_name' is not provided, 'cover_url' must be present - if (empty($album_name)) { - if (empty($cover_url)) { - return new WP_Error( - 'missing_cover_url', - 'Cover URL must be provided if album name is not present', - array('status' => 400) - ); + + function normalize_names(string $author, string $songName, string $albumName): array + { + // Define the album name mapping + $albumNameMapping = [ + 'Nirvana' => [ + 'In Utero (20th Anniversary Remaster)' => 'In Utero', + 'Nevermind (Deluxe Edition)' => 'Nevermind', + 'Bleach: Deluxe Edition' => 'Bleach' + ] + ]; + + // Apply the album name mapping if applicable + if ($albumName && isset($albumNameMapping[$author])) { + foreach ($albumNameMapping[$author] as $variant => $standardized) { + // Use case-insensitive comparison + if (strcasecmp($albumName, $variant) === 0) { + $albumName = $standardized; + break; + } + } } - } else { - // If 'album_name' is provided but 'cover_url' is not, attempt to retrieve it from existing entries - if (empty($cover_url)) { - // Prepare case-insensitive search - $song_name_lower = strtolower($data['song_name']); - $album_name_lower = strtolower($album_name); - $author_lower = strtolower($data['author']); + + // Define a regex pattern to remove remaster annotations like "(2022 Remaster)" + $remasterPattern = '/\(\d+\s+Remaster\)/i'; + + // Remove remaster annotations from song and album names + $songName = preg_replace($remasterPattern, '', $songName); + $albumName = preg_replace($remasterPattern, '', $albumName); + + // Define a regex pattern to remove "(Deluxe Edition)" and "(Deluxe Version)" (case-insensitive) + $deluxePattern = '/\(deluxe\s+(edition|version)\)/i'; + + // Remove "(Deluxe Edition)" and "(Deluxe Version)" from album names + $albumName = preg_replace($deluxePattern, '', $albumName); + + // === Added Normalization for "COLLECTORS EDITION." === + + // Define a regex pattern to remove "COLLECTORS EDITION." at the end of album names + $collectorsPattern = '/\bCollectors\s+Edition\.?$/i'; + + // Remove "COLLECTORS EDITION." from album names + $albumName = preg_replace($collectorsPattern, '', $albumName); + + // ==================================================== + + // Trim any extra whitespace that might have resulted from the removal + $songName = trim($songName); + $albumName = trim($albumName); + + return [ + 'song_name' => $songName, + 'album_name' => $albumName, + ]; + } + + public function scrobble($request) { + global $wpdb; + $data = $request->get_json_params(); + + // Updated required fields: 'album_name' is no longer required + $required_fields = ['song_name', 'author']; - // Query to find the most recent matching entry - $query = $wpdb->prepare( - "SELECT cover_url FROM $table_name - WHERE LOWER(album_name) = %s AND LOWER(author) = %s - ORDER BY id DESC - LIMIT 1", - $album_name_lower, $author_lower - ); + // Check for missing required fields + foreach ($required_fields as $field) { + if (!isset($data[$field])) { + return new WP_Error('missing_field', 'Missing required field: ' . $field, array('status' => 400)); + } + } - $cover_url = $wpdb->get_var($query); + $table_name = 'song_scrobbles'; + $author = isset($data['author']) ? $data['author'] : null; + $cover_url = isset($data['cover_url']) ? $data['cover_url'] : null; - // If no matching entry is found, return an error - if (!$cover_url) { + $normalized = $this->normalize_names($data['author'], $data['song_name'], $data['album_name']); + $song_name = $normalized['song_name']; + $album_name = $normalized['album_name']; + + // If 'album_name' is not provided, 'cover_url' must be present + if (empty($album_name)) { + if (empty($cover_url)) { return new WP_Error( 'missing_cover_url', - 'Cover URL not provided and no existing entry found for the given album and author', + 'Cover URL must be provided if album name is not present', array('status' => 400) ); } + } else { + // If 'album_name' is provided but 'cover_url' is not, attempt to retrieve it from existing entries + if (empty($cover_url)) { + // Prepare case-insensitive search + $author_lower = strtolower($data['author']); + $album_name_lower = strtolower($album_name); + + // Try to find attachment + $cover_url = $this->get_attachment_by_artist_album($data['author'], $album_name); + if (!isset($cover_url)) { + // If no matching entry is found, return an error + return new WP_Error( + 'missing_cover_url', + $cover_url, + array('status' => 400) + ); + } + } } + + // Insert the scrobble data into the database + $result = $wpdb->insert( + $table_name, + array( + 'song_name' => $song_name, + 'album_name' => $album_name, // This can be null + 'cover_url' => $cover_url, + 'author' => $data['author'], + 'length_seconds' => isset($data['length_seconds']) ? intval($data['length_seconds']) : null + ), + array('%s', '%s', '%s', '%s', '%d') + ); + + // Handle potential database insertion errors + if ($result === false) { + return new WP_Error('db_error', 'Error saving scrobble', array('status' => 500)); + } + + // Return a successful response + return new WP_REST_Response(array('message' => 'Scrobble saved successfully'), 201); } - // Insert the scrobble data into the database - $result = $wpdb->insert( - $table_name, - array( - 'song_name' => $data['song_name'], - 'album_name' => $album_name, // This can be null - 'cover_url' => $cover_url, - 'author' => $data['author'], - 'length_seconds' => isset($data['length_seconds']) ? intval($data['length_seconds']) : null - ), - array('%s', '%s', '%s', '%s', '%d') - ); - - // Handle potential database insertion errors - if ($result === false) { - return new WP_Error('db_error', 'Error saving scrobble', array('status' => 500)); - } - - // Return a successful response - return new WP_REST_Response(array('message' => 'Scrobble saved successfully'), 201); -} - - public function add_admin_menu() { add_options_page( 'Image Scrobble Settings', @@ -259,6 +393,236 @@ public function scrobble($request) { 'image-scrobble-settings', array($this, 'settings_page') ); + + // Add submenu page for uploading attachments + add_options_page( + 'Upload Album Cover', + 'Upload Album Cover', + 'manage_options', + 'image-scrobble-upload', + array($this, 'upload_form_page') + ); + } + + public function upload_form_page() { + ?> +
+

Upload Album Cover

+

Album cover uploaded successfully.

'; + } elseif ($message == 'duplicate_image') { + echo '

An image with this content already exists.

'; + } elseif ($message == 'upload_error') { + echo '

Error during file upload.

'; + } elseif ($message == 'not_an_image') { + echo '

The uploaded file is not a valid image.

'; + } elseif ($message == 'missing_fields') { + echo '

Please fill in all required fields.

'; + } elseif ($message == 'upload_failed') { + echo '

Failed to upload image.

'; + } else { + echo '

An unknown error occurred.

'; + } + } + ?> +
+ + + + + + + + + + + + + + + +
Artist + +
Album Title + +
Album Cover Image + +
+ +
+ + 500) + ); + } + + // Decode the JSON response + $body = wp_remote_retrieve_body($response); + $data = json_decode($body, true); + + // Check if the API returned any results + if (empty($data['results'])) { + return new WP_Error( + 'no_results', + 'No album found matching the provided artist and album name.', + array('status' => 404) + ); + } + + // Initialize a variable to store the matched album + $matched_album = null; + + // Iterate through the results to find an exact match + foreach ($data['results'] as $album) { + $api_artist_name = strtolower($album['artistName']); + $api_album_name = strtolower($album['collectionName']); // or 'collectionCensoredName' + + // Normalize the input and API data for comparison + $input_artist_name = strtolower($artist_name); + $input_album_name = strtolower($album_name); + + // Remove any extra whitespace and special characters + $api_artist_name = preg_replace('/\s+/', ' ', trim($api_artist_name)); + $api_album_name = preg_replace('/\s+/', ' ', trim($api_album_name)); + $input_artist_name = preg_replace('/\s+/', ' ', trim($input_artist_name)); + $input_album_name = preg_replace('/\s+/', ' ', trim($input_album_name)); + + // Compare the artist and album names + if ($api_artist_name === $input_artist_name && $api_album_name === $input_album_name) { + $matched_album = $album; + break; + } + } + + // Check if a matched album was found + if (!$matched_album) { + return new WP_Error( + 'no_exact_match', + 'No exact match found for the provided artist and album name.', + array('status' => 404) + ); + } + + // Get the artwork URL and modify it + $artwork_url = $matched_album['artworkUrl100']; + $artwork_url_500 = str_replace('100x100bb', '1000x1000bb', $artwork_url); + + // Fetch the image data + $image_response = wp_remote_get($artwork_url_500); + + // Check for errors in the image request + if (is_wp_error($image_response)) { + return new WP_Error( + 'image_request_failed', + 'Failed to fetch the album cover image.', + array('status' => 500) + ); + } + + // Retrieve the image data + $image_data = wp_remote_retrieve_body($image_response); + + // Check if image data was retrieved + if (empty($image_data)) { + return new WP_Error( + 'empty_image_data', + 'No image data was returned from the iTunes API.', + array('status' => 500) + ); + } + + // Generate a hash of the image data to check for duplicates + $image_hash = md5($image_data); + + // Call the provided method to save the image and create an attachment + return $this->save_image_and_create_attachment( + $image_data, + $artist_name, + $album_name, + $image_hash, + $album_name // Passing the album title + ); + } + + public function handle_image_upload() { + // Check if current user has permission + if (!current_user_can('manage_options')) { + wp_die('Unauthorized user'); + } + + // Verify nonce + check_admin_referer('image_scrobble_upload', 'image_scrobble_upload_nonce'); + + // Get the posted data + $artist = sanitize_text_field($_POST['artist']); + $album_title = sanitize_text_field($_POST['album_title']); + + // Check if required fields are present + if (empty($artist) || empty($album_title) || empty($_FILES['album_cover'])) { + wp_redirect(add_query_arg('message', 'missing_fields', wp_get_referer())); + exit; + } + + // Handle the uploaded file + $file = $_FILES['album_cover']; + + // Check for upload errors + if ($file['error'] !== UPLOAD_ERR_OK) { + wp_redirect(add_query_arg('message', 'upload_error', wp_get_referer())); + exit; + } + + // Check if the file is an image + $check = getimagesize($file['tmp_name']); + if ($check === false) { + wp_redirect(add_query_arg('message', 'not_an_image', wp_get_referer())); + exit; + } + + // Read the image data + $image_data = file_get_contents($file['tmp_name']); + + // Generate a hash of the image data to check for duplicates + $image_hash = md5($image_data); + + // Check if an image with this hash already exists to prevent duplicates + $existing_attachment = $this->get_attachment_by_hash($image_hash); + if ($existing_attachment) { + wp_redirect(add_query_arg('message', 'duplicate_image', wp_get_referer())); + exit; + } + + // Save the image and create attachment (using same logic as create_image) + $image_url = $this->save_image_and_create_attachment($image_data, $artist, $album_title, $image_hash, $album_title); + + if (is_wp_error($image_url)) { + wp_redirect(add_query_arg('message', 'upload_failed', wp_get_referer())); + exit; + } + + // Redirect back with success message + wp_redirect(add_query_arg('message', 'upload_success', wp_get_referer())); + exit; } public function register_settings() { @@ -287,7 +651,4 @@ public function scrobble($request) {