commit d00edefc95bd6acf4919ba4c179305dc98a63972 Author: anon Date: Fri Oct 11 17:38:13 2024 +0000 initial commit diff --git a/album-collage-generator.php b/album-collage-generator.php new file mode 100644 index 0000000..212f29e --- /dev/null +++ b/album-collage-generator.php @@ -0,0 +1,529 @@ +db = $wpdb; + + add_action('rest_api_init', array($this, 'register_endpoint')); + + $this->background_color = $this->hex_to_rgb('#141617'); + $this->text_color = $this->hex_to_rgb('#ececec'); + $this->western_font = plugin_dir_path(__FILE__) . 'assets/co1251n.ttf'; + $this->japanese_font = plugin_dir_path(__FILE__) . 'assets/BIZUDPMincho-Regular.ttf'; + $this->blacklisted_artists = array('Drake', 'Mac Miller'); // placeholder values + } + + public function register_endpoint() { + register_rest_route('top-albums/v1', '/last-month', array( + 'methods' => 'GET', + 'callback' => array($this, 'get_or_generate_image'), + 'permission_callback' => '__return_true', + 'args' => array( + 'month' => array( + 'required' => false, + 'validate_callback' => array($this, 'is_valid_month'), + ), + 'year' => array( + 'required' => false, + 'validate_callback' => array($this, 'is_valid_year'), + ), + 'overwrite' => array( + 'required' => false, + 'default' => false, + 'validate_callback' => array($this, 'is_valid_overwrite'), + ), + ), + )); + } + + public function is_valid_month($param, $request, $key) { + $month = intval($param); + return $month >= 1 && $month <= 12; + } + + public function is_valid_year($param, $request, $key) { + $year = intval($param); + $current_year = intval(date('Y')); + return $year >= 2000 && $year <= $current_year; + } + + public function is_valid_overwrite($param, $request, $key) { + // Accept boolean or string representations + if (is_bool($param)) { + return true; + } + if (is_string($param)) { + $param = strtolower($param); + return in_array($param, array('1', 'true', 'yes'), true); + } + if (is_numeric($param)) { + return in_array($param, array(1, 0), true); + } + return false; + } + + private function interpret_boolean($value) { + if (is_bool($value)) { + return $value; + } + if (is_string($value)) { + $value = strtolower($value); + return in_array($value, array('1', 'true', 'yes'), true); + } + if (is_numeric($value)) { + return (bool)$value; + } + return false; + } + + private function hex_to_rgb($hex) { + $hex = str_replace('#', '', $hex); + if(strlen($hex) == 3) { + $r = hexdec(substr($hex,0,1).substr($hex,0,1)); + $g = hexdec(substr($hex,1,1).substr($hex,1,1)); + $b = hexdec(substr($hex,2,1).substr($hex,2,1)); + } else { + $r = hexdec(substr($hex,0,2)); + $g = hexdec(substr($hex,2,2)); + $b = hexdec(substr($hex,4,2)); + } + return array($r, $g, $b); + } + + private function get_last_month_data() { + $last_month = date('Y-m', strtotime('last day of previous month')); + return explode('-', $last_month); + } + + private function get_top_albums($year, $month) { + // Prepare placeholders for blacklisted artists + $placeholders = array_fill(0, count($this->blacklisted_artists), '%s'); + $placeholders_str = implode(', ', $placeholders); + + // Merge all parameters for $wpdb->prepare + $query_params = array($year, $month); + $query_params = array_merge($query_params, $this->blacklisted_artists); + + $query = $this->db->prepare( + "WITH ranked_covers AS ( + SELECT + album_name, + author, + cover_url, + ROW_NUMBER() OVER (PARTITION BY album_name, author ORDER BY time DESC) as rn + FROM song_scrobbles + WHERE cover_url != '' AND cover_url IS NOT NULL + ), + album_stats AS ( + SELECT + album_name, + author, + COUNT(*) as play_count, + COUNT(DISTINCT song_name) as unique_song_count + FROM song_scrobbles + WHERE YEAR(time) = %d AND MONTH(time) = %d + AND album_name != '' AND album_name IS NOT NULL + -- Exclude blacklisted artists + AND author NOT IN ($placeholders_str) + -- Exclude albums with '¥' symbol in album_name or author + AND album_name NOT LIKE '%%¥%%' + AND author NOT LIKE '%%¥%%' + -- Exclude albums with Cyrillic characters in album_name or author + AND album_name NOT REGEXP '[А-яЁё]' + AND author NOT REGEXP '[А-яЁё]' + GROUP BY album_name, author + HAVING LENGTH(CONCAT(album_name, author)) <= 60 AND unique_song_count >= 1 + ) + SELECT + a.album_name, + a.author, + r.cover_url, + a.play_count, + a.unique_song_count, + CASE + WHEN a.unique_song_count = 1 THEN 1.0 + WHEN a.unique_song_count = 5 THEN 1.25 + WHEN a.unique_song_count = 12 THEN 1.6 + ELSE 1.0 + (a.unique_song_count * 0.05) + END as multiplier, + (a.play_count * + CASE + WHEN a.unique_song_count = 1 THEN 1.0 + WHEN a.unique_song_count = 5 THEN 1.25 + WHEN a.unique_song_count = 12 THEN 1.6 + ELSE 1.0 + (a.unique_song_count * 0.05) + END + ) as rank_score + FROM album_stats a + LEFT JOIN ranked_covers r + ON a.album_name = r.album_name + AND a.author = r.author + AND r.rn = 1 + ORDER BY rank_score DESC + LIMIT 25", + ...$query_params + ); + + return $this->db->get_results($query); + } + + public function get_or_generate_image($request) { + $month = $request->get_param('month'); + $year = $request->get_param('year'); + $overwrite = $request->get_param('overwrite'); // Retrieve 'overwrite' parameter + + // Convert 'overwrite' to boolean + $overwrite = $this->interpret_boolean($overwrite); + + if ($month && $year) { + $month = intval($month); + $year = intval($year); + + // Validate that the date is not in the future + $provided_date = strtotime("{$year}-{$month}-01"); + $now = time(); + + if ($provided_date > $now) { + return new WP_Error('invalid_date', 'The provided month and year cannot be in the future.', array('status' => 400)); + } + + // Ensure the date is valid + if (!checkdate($month, 1, $year)) { + return new WP_Error('invalid_date', 'The provided month and year are invalid.', array('status' => 400)); + } + + } else { + list($year, $month) = $this->get_last_month_data(); + } + + $cache_key = "top_albums_{$year}_{$month}"; + + // Check if we have a cached image + $cached_image_id = get_option($cache_key); + + if ($overwrite && $cached_image_id) { // If overwrite is true and cached image exists + // Delete the old attachment + $deleted = wp_delete_attachment($cached_image_id, true); + if ($deleted) { + // Remove the cached option + delete_option($cache_key); + $cached_image_id = false; // Reset to indicate no cached image + } else { + return new WP_Error('deletion_failed', 'Failed to delete the existing image.', array('status' => 500)); + } + } + + if ($cached_image_id) { + $attachment_path = get_attached_file($cached_image_id); + if ($attachment_path && file_exists($attachment_path)) { + // Serve the cached image directly as a response + $image_data = file_get_contents($attachment_path); + $mime_type = mime_content_type($attachment_path); + + // Send appropriate headers + header('Content-Type: ' . $mime_type); + header('Content-Length: ' . strlen($image_data)); + header('Content-Disposition: inline; filename="' . basename($attachment_path) . '"'); + + // Prevent WordPress from sending additional content + remove_filter('rest_pre_serve_request', 'rest_send_cors_headers'); + + echo $image_data; + exit; + } + } + + // If no cached image, generate a new one + $image_data = $this->generate_image($year, $month); + + // Save the generated image as an attachment + $upload_dir = wp_upload_dir(); + $filename = "top_albums_{$year}_{$month}.jpg"; + $file_path = $upload_dir['path'] . '/' . $filename; + file_put_contents($file_path, $image_data); + + $attachment = array( + 'post_mime_type' => 'image/jpeg', + 'post_title' => "Top Albums for {$year}-{$month}", + 'post_content' => '', + 'post_status' => 'inherit' + ); + + $attach_id = wp_insert_attachment($attachment, $file_path); + 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 attachment ID for future use + update_option($cache_key, $attach_id); + + // Serve the newly generated image directly as a response + $mime_type = mime_content_type($file_path); + + // Send appropriate headers + header('Content-Type: ' . $mime_type); + header('Content-Length: ' . strlen($image_data)); + header('Content-Disposition: inline; filename="' . basename($file_path) . '"'); + + // Prevent WordPress from sending additional content + remove_filter('rest_pre_serve_request', 'rest_send_cors_headers'); + + echo $image_data; + exit; + } + + private function generate_image($year, $month) { + $albums = $this->get_top_albums($year, $month); + + $image_width = 2600; + $image_height = 1680; + $album_size = 300; + $gap = 8; + $left_margin = 100; + $top_margin = 100; + + $image = imagecreatetruecolor($image_width, $image_height); + $background_color = imagecolorallocate($image, $this->background_color[0], $this->background_color[1], $this->background_color[2]); + imagefill($image, 0, 0, $background_color); + + $text_color = imagecolorallocate($image, $this->text_color[0], $this->text_color[1], $this->text_color[2]); + + // Add album covers (left side) + for ($i = 0; $i < min(count($albums), 25); $i++) { + $row = floor($i / 5); + $col = $i % 5; + + $x = $left_margin + $col * ($album_size + $gap); + $y = $top_margin + $row * ($album_size + $gap); + + $album_cover = @imagecreatefromjpeg($albums[$i]->cover_url); + if ($album_cover !== false) { + $this->apply_retro_effects($album_cover); + imagecopyresampled($image, $album_cover, $x, $y, 0, 0, $album_size, $album_size, imagesx($album_cover), imagesy($album_cover)); + imagedestroy($album_cover); + } else { + $placeholder_color = imagecolorallocate($image, 100, 100, 100); + imagefilledrectangle($image, $x, $y, $x + $album_size, $y + $album_size, $placeholder_color); + } + } + + // Add album titles (right side) + $text_x = $left_margin + 5 * ($album_size + $gap) + 20; + $text_y = $top_margin + 18; + $line_height = 35; + $font_size = 20; + $group_spacing = 50; + + for ($i = 0; $i < min(count($albums), 25); $i++) { + $album_text = $albums[$i]->author . ' - ' . $albums[$i]->album_name; + $font = $this->is_japanese($album_text) ? $this->japanese_font : $this->western_font; + + $this->add_retro_text($image, $font_size, $text_x, $text_y, $text_color, $font, $album_text); + + if (($i + 1) % 5 == 0 && $i < 24) { + $text_y += $line_height + $group_spacing; + } else { + $text_y += $line_height; + } + } + + $this->apply_overall_retro_effects($image); + + ob_start(); + imagejpeg($image); + $image_data = ob_get_clean(); + imagedestroy($image); + + return $image_data; + } + + private function apply_retro_effects(&$image) { + // Add noise + $this->add_noise($image, 20); + + // Reduce color depth + $this->reduce_color_depth($image); + + // Add slight blur + // imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR); + } + + private function add_noise(&$image, $amount) { + $width = imagesx($image); + $height = imagesy($image); + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + if (rand(0, 100) < $amount) { + $rgb = imagecolorat($image, $x, $y); + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + $noise = rand(-20, 20); + $r = max(0, min(255, $r + $noise)); + $g = max(0, min(255, $g + $noise)); + $b = max(0, min(255, $b + $noise)); + + $color = imagecolorallocate($image, $r, $g, $b); + imagesetpixel($image, $x, $y, $color); + } + } + } + } + + private function reduce_color_depth(&$image) { + imagetruecolortopalette($image, false, 64); + } + + private function add_retro_text(&$image, $font_size, $x, $y, $color, $font, $text) { + // Add a slight shadow + $shadow_color = imagecolorallocatealpha($image, 0, 0, 0, 75); + imagettftext($image, $font_size, 0, $x + 1, $y + 1, $shadow_color, $font, $text); + + // Add main text with a slight glow + $glow_color = imagecolorallocatealpha($image, 255, 255, 255, 75); + imagettftext($image, $font_size, 0, $x - 1, $y - 1, $glow_color, $font, $text); + imagettftext($image, $font_size, 0, $x, $y, $color, $font, $text); + } + + private function apply_overall_retro_effects(&$image) { + // Add scanlines + $this->add_scanlines($image); + + // Add vignette effect + $this->add_vignette($image); + + // Add slight color aberration + $this->add_color_aberration($image); + + // Add subtle grain + $this->add_grain($image); + } + + private function add_scanlines(&$image) { + $height = imagesy($image); + $scanline_color = imagecolorallocatealpha($image, 0, 0, 0, 70); + + for ($y = 0; $y < $height; $y += 2) { + imageline($image, 0, $y, imagesx($image), $y, $scanline_color); + } + } + + private function add_vignette(&$image) { + $width = imagesx($image); + $height = imagesy($image); + $center_x = $width / 2; + $center_y = $height / 2; + $max_distance = sqrt($center_x * $center_x + $center_y * $center_y); + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $distance = sqrt(pow($x - $center_x, 2) + pow($y - $center_y, 2)); + $vignette = 1 - ($distance / $max_distance); + $vignette = max(0, min(1, $vignette)); + + // Make vignette more subtle + $vignette = 1 - (1 - $vignette) * 0.25; // Adjust 0.25 to control intensity + + $rgb = imagecolorat($image, $x, $y); + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + $r = (int)($r * $vignette); + $g = (int)($g * $vignette); + $b = (int)($b * $vignette); + + $color = imagecolorallocate($image, $r, $g, $b); + imagesetpixel($image, $x, $y, $color); + } + } + } + + private function add_color_aberration(&$image) { + $width = imagesx($image); + $height = imagesy($image); + $aberration = 1; // Reduced from 2 to 1 for subtler effect + + $new_image = imagecreatetruecolor($width, $height); + imagesavealpha($new_image, true); + imagealphablending($new_image, false); + $transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127); + imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); + imagealphablending($new_image, true); + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $rgb = imagecolorat($image, $x, $y); + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + // Shift red channel slightly to the left + $red_x = max(0, $x - $aberration); + $red_color = imagecolorallocate($new_image, $r, 0, 0); + imagesetpixel($new_image, $red_x, $y, $red_color); + + // Keep green channel in place + $green_color = imagecolorallocate($new_image, 0, $g, 0); + imagesetpixel($new_image, $x, $y, $green_color); + + // Shift blue channel slightly to the right + $blue_x = min($width - 1, $x + $aberration); + $blue_color = imagecolorallocate($new_image, 0, 0, $b); + imagesetpixel($new_image, $blue_x, $y, $blue_color); + } + } + + // Merge the aberrated image back onto the original with reduced opacity + imagecopymerge($image, $new_image, 0, 0, 0, 0, $width, $height, 3); // Reduced from 50 to 30 + imagedestroy($new_image); + } + + private function add_grain(&$image) { + $width = imagesx($image); + $height = imagesy($image); + $grain_amount = 8; // Adjust this value to control grain intensity (1-10 recommended) + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + if (rand(0, 100) < $grain_amount) { + $rgb = imagecolorat($image, $x, $y); + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + $noise = rand(-10, 10); + $r = max(0, min(255, $r + $noise)); + $g = max(0, min(255, $g + $noise)); + $b = max(0, min(255, $b + $noise)); + + $color = imagecolorallocate($image, $r, $g, $b); + imagesetpixel($image, $x, $y, $color); + } + } + } + } + + private function is_japanese($text) { + return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $text); + } + +} + +new AlbumCollageGenerator(); diff --git a/assets/BIZUDPMincho-Regular.ttf b/assets/BIZUDPMincho-Regular.ttf new file mode 100644 index 0000000..9548587 Binary files /dev/null and b/assets/BIZUDPMincho-Regular.ttf differ diff --git a/assets/co1251n.ttf b/assets/co1251n.ttf new file mode 100644 index 0000000..3f806ed Binary files /dev/null and b/assets/co1251n.ttf differ diff --git a/assets/font.ttf b/assets/font.ttf new file mode 100644 index 0000000..f878b42 Binary files /dev/null and b/assets/font.ttf differ diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000..f2dad10 --- /dev/null +++ b/plugin.php @@ -0,0 +1,20 @@ + image scrobble` in the wordpress admin. + +**example:** + +```bash +curl -X POST 'https://yourwebsite.com/wp-json/image-scrobble/v1/scrobble' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Bearer YOUR_AUTH_KEY' \ +-d '{ + "song_name": "Song Title", + "author": "Artist Name", + "album_name": "Album Title", + "cover_url": "https://example.com/cover.jpg", + "length_seconds": 240 +}' +``` + +### generating album collage + +you can generate a collage of your top albums for any month. + +**endpoint:** `GET /wp-json/top-albums/v1/last-month` + +**parameters:** + +- `month` (optional) +- `year` (optional) +- `overwrite` (optional, default `false`) + +if you don't provide `month` and `year`, it defaults to last month. + +**example:** + +```bash +curl -X GET 'https://yourwebsite.com/wp-json/top-albums/v1/last-month?month=9&year=2023' +``` + +this returns the collage image. if you want to force regenerate it (maybe you added more scrobbles), set `overwrite=true`. + +### notes + +- the collage ignores some blacklisted artists. you can change that in the code if you care (`$this->blacklisted_artists`). +- the plugin might break in unexpected ways. use at your own risk. diff --git a/scrobble-handler.php b/scrobble-handler.php new file mode 100644 index 0000000..e70164b --- /dev/null +++ b/scrobble-handler.php @@ -0,0 +1,293 @@ +auth_key = get_option($this->option_name); + + add_action('rest_api_init', array($this, 'register_routes')); + 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')); + } + + public function activate_plugin() { + // Set default AUTH_KEY if not exists + if (!get_option($this->option_name)) { + update_option($this->option_name, wp_generate_password(32, false)); + } + } + + public function register_routes() { + register_rest_route('image-scrobble/v1', '/create', array( + 'methods' => 'POST', + 'callback' => array($this, 'create_image'), + 'permission_callback' => array($this, 'check_auth') + )); + + register_rest_route('image-scrobble/v1', '/scrobble', array( + 'methods' => 'POST', + 'callback' => array($this, 'scrobble'), + 'permission_callback' => array($this, 'check_auth') + )); + } + + public function check_auth($request) { + $auth_header = $request->get_header('Authorization'); + return $auth_header && $auth_header === "Bearer {$this->auth_key}"; + } + + public function create_image($request) { + // 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 + + // 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) + ); + } + + // 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); + + // 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 + ); + } + + // 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"); + + // 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; + + // 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); + + // 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', + 'post_status' => 'inherit', + 'posts_per_page' => 1, + 'meta_query' => array( + array( + 'key' => 'image_hash', + 'value' => $hash, + 'compare' => '=' + ) + ) + ); + + $query = new WP_Query($args); + + if ($query->have_posts()) { + return $query->posts[0]; + } + + return null; + } + +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']; + + // 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)); + } + } + + $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) + ); + } + } 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']); + + // 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 + ); + + $cover_url = $wpdb->get_var($query); + + // If no matching entry is found, return an error + if (!$cover_url) { + return new WP_Error( + 'missing_cover_url', + 'Cover URL not provided and no existing entry found for the given album and author', + array('status' => 400) + ); + } + } + } + + // 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', + 'Image Scrobble', + 'manage_options', + 'image-scrobble-settings', + array($this, 'settings_page') + ); + } + + public function register_settings() { + register_setting('image_scrobble_settings', $this->option_name); + } + + public function settings_page() { + ?> +
+

Image Scrobble Settings

+
+ + + + + + +
AUTH_KEY + +
+ +
+
+