From 4e7ceee4764a65fbe4bb2d33df94ad8400006262 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 28 Jul 2025 19:24:10 -0400 Subject: [PATCH] /media command added for multi media posts. --- ttyverse.pl | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/ttyverse.pl b/ttyverse.pl index f989220..0159395 100755 --- a/ttyverse.pl +++ b/ttyverse.pl @@ -2795,6 +2795,9 @@ Just type to talk! /visibility [level] Show current post visibility or set to: public, unlisted, private, direct +/media /path/to/file + Upload media (images, video, audio) with accessibility features + /quit Resumes your boring life. @@ -2912,6 +2915,12 @@ EOF &dmthump_no_skip if ($dmpause); # Also refresh DMs but don't skip timeline return 0; } + + # Media upload command + if (m#^/media\s+(.+)$#) { + my $file_path = $1; + return &handle_media_upload($file_path); + } if (m#^/a(gain)?(\s+\+\d+)?$#) { # the asynchronous form my $countmaybe = $2; $countmaybe =~ s/[^\d]//g if (length($countmaybe)); @@ -8061,6 +8070,194 @@ sub urlshorten { return ($urlshort = (($rc =~ m#^http://#) ? $rc : undef)); } +##### Media Upload Functions ##### + +sub handle_media_upload { + my $file_path = shift; + + # Validate file exists and is readable + unless (-f $file_path && -r $file_path) { + print $stdout "-- ERROR: File '$file_path' not found or not readable\n"; + return 0; + } + + # Check file size (Mastodon limit is typically 8MB for images, 40MB for video) + my $file_size = -s $file_path; + if ($file_size > 40 * 1024 * 1024) { # 40MB + print $stdout "-- ERROR: File too large (max 40MB)\n"; + return 0; + } + + # Detect MIME type + my $mime_type = &detect_mime_type($file_path); + unless ($mime_type) { + print $stdout "-- ERROR: Unable to determine file type\n"; + return 0; + } + + print $stdout "-- Detected file type: $mime_type\n" if ($verbose); + + # Handle alt-text for images (REQUIRED for accessibility) + my $alt_text = ""; + if ($mime_type =~ /^image\//) { + print $stdout "-- Images require alt-text for accessibility\n"; + $alt_text = &linein("Enter alt text for " . (split('/', $file_path))[-1] . ": "); + + # Empty alt-text cancels the upload (enforce accessibility) + unless (defined($alt_text) && length($alt_text) > 0) { + print $stdout "-- Upload cancelled: Alt-text is required for images\n"; + print $stdout "-- If you're going to use a client maintained by a blind guy, you can damn well describe your images!\n"; + return 0; + } + } + + # Get optional post message + my $post_message = &linein("Enter post message (optional): "); + $post_message = "" unless defined($post_message); + + # Upload media and create post + return &upload_media_and_post($file_path, $mime_type, $alt_text, $post_message); +} + +sub detect_mime_type { + my $file_path = shift; + + # Try using 'file' command first (most reliable) + my $file_output = `file -b --mime-type "$file_path" 2>/dev/null`; + chomp($file_output); + if ($file_output && $file_output =~ /^[\w-]+\/[\w-]+$/) { + return $file_output; + } + + # Fallback to file extension + my $extension = lc((split(/\./, $file_path))[-1]); + my %ext_to_mime = ( + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'gif' => 'image/gif', + 'webp' => 'image/webp', + 'mp4' => 'video/mp4', + 'webm' => 'video/webm', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'ogg' => 'audio/ogg', + 'wav' => 'audio/wav', + 'flac' => 'audio/flac' + ); + + return $ext_to_mime{$extension} || undef; +} + +sub upload_media_and_post { + my ($file_path, $mime_type, $alt_text, $post_message) = @_; + + print $stdout "-- Uploading media file...\n"; + + # Step 1: Upload media to get media ID + my $media_id = &upload_media_file($file_path, $mime_type, $alt_text); + unless ($media_id) { + print $stdout "-- ERROR: Media upload failed\n"; + return 0; + } + + print $stdout "-- Media uploaded successfully (ID: $media_id)\n"; + + # Step 2: Create post with media attachment + return &create_post_with_media($media_id, $post_message); +} + +sub upload_media_file { + my ($file_path, $mime_type, $alt_text) = @_; + + # Mastodon media upload endpoint + my $media_url = "${apibase}/media"; + + # Build curl command for multipart file upload + my $curl_cmd = "$baseagent"; + + # Add standard auth and options (from @wend) + foreach my $arg (@wend) { + $curl_cmd .= " '$arg'"; + } + + # Add OAuth Bearer token authentication + my $bearer_header = &signrequest($media_url, ''); + if ($bearer_header) { + $curl_cmd .= " $bearer_header"; + } + + # Add multipart form data + $curl_cmd .= " -X POST"; + $curl_cmd .= " -F 'file=\@$file_path;type=$mime_type'"; + + # Add alt-text if provided (for images) + if (length($alt_text)) { + my $escaped_alt = $alt_text; + $escaped_alt =~ s/'/'\\''/g; # Escape single quotes for shell + $curl_cmd .= " -F 'description=$escaped_alt'"; + } + + $curl_cmd .= " '$media_url'"; + + print $stdout "-- DEBUG: Upload command: $curl_cmd\n" if ($superverbose); + print $stdout "-- DEBUG: Uploading to $media_url\n" if ($verbose); + + # Execute the upload + my $response = `$curl_cmd 2>/dev/null`; + my $exit_code = $? >> 8; + + if ($exit_code != 0) { + print $stdout "-- ERROR: Upload failed (curl exit code: $exit_code)\n"; + return undef; + } + + print $stdout "-- DEBUG: Upload response: $response\n" if ($superverbose); + + # Parse JSON response to get media ID + my $media_data = &parsejson($response); + unless ($media_data && ref($media_data) eq 'HASH') { + print $stdout "-- ERROR: Invalid response from media upload\n"; + print $stdout "-- Response: $response\n" if ($verbose); + return undef; + } + + my $media_id = $media_data->{'id'}; + unless ($media_id) { + print $stdout "-- ERROR: No media ID in response\n"; + print $stdout "-- Response: $response\n" if ($verbose); + return undef; + } + + return $media_id; +} + +sub create_post_with_media { + my ($media_id, $post_message) = @_; + + # Build post data + my $post_data = "media_ids[]=$media_id"; + if (length($post_message)) { + $post_data .= "&status=" . &url_oauth_sub($post_message); + } + + # Add current visibility setting + $post_data .= "&visibility=$post_visibility" if defined($post_visibility); + + print $stdout "-- Creating post with media attachment...\n"; + + # Use existing postjson function for creating the post + my $result = &postjson($update, $post_data); + + if ($result) { + print $stdout "-- Post created successfully!\n"; + return 1; + } else { + print $stdout "-- ERROR: Failed to create post\n"; + return 0; + } +} + ##### optimizers -- these compile into an internal format ##### # utility routine for tquery support