diff --git a/DorkNet.Server/Controllers/API/Images/ImagesController.cs b/DorkNet.Server/Controllers/API/Images/ImagesController.cs
index 872f1e9..1873b88 100644
--- a/DorkNet.Server/Controllers/API/Images/ImagesController.cs
+++ b/DorkNet.Server/Controllers/API/Images/ImagesController.cs
@@ -29,10 +29,10 @@ namespace DorkNet.Server.Controllers.API.Images;
/// Returning [] (the previous catch-all) made the watch silently
/// drop the photo.
///
-/// Saved photos are stored as rows with
-/// IsPublic=false by default. A separate Share button (POST
-/// api/images/v4/share/{id}) flips IsPublic=true and surfaces them on
-/// feed.rec.net.
+/// Saved photos are stored as rows. The 2020
+/// client sends camera share state inside an imgMeta JSON form field
+/// (accessibility=1 for public ShareCamera uploads), while newer callers
+/// can still send top-level IsPublic.
///
[ApiController]
public class ImagesController(
@@ -82,12 +82,17 @@ public class ImagesController(
// Pull optional metadata from the form (the 2020 watch sometimes
// includes a caption / room context, sometimes posts bytes only).
var form = Request.HasFormContentType ? await Request.ReadFormAsync() : null;
+ var imageMeta = ParseImageMeta(form);
var caption = form?["Caption"].ToString() ?? form?["Description"].ToString() ?? string.Empty;
var taggedIds = form?["TaggedPlayerIds"].ToString() ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(taggedIds) && imageMeta?.PlayerIds.Count > 0)
+ taggedIds = string.Join(",", imageMeta.PlayerIds);
long roomId = 0;
if (form is not null && long.TryParse(form["RoomId"].ToString(), out var rid)) roomId = rid;
+ if (roomId == 0 && imageMeta?.RoomId is > 0) roomId = imageMeta.RoomId.Value;
if (roomId == 0) roomId = presence.GetRoom(pid)?.RoomId ?? 0;
- var isPublic = string.Equals(form?["IsPublic"].ToString(), "true", StringComparison.OrdinalIgnoreCase);
+ var explicitPublic = form is null ? null : TryReadBool(form["IsPublic"].ToString());
+ var isPublic = explicitPublic ?? (imageMeta?.Accessibility == 1 || imageMeta?.SavedImageType == 1);
var isProfileThumbnail = IsProfileThumbnail(form);
LogUploadForm("uploadsaved", pid, form, isProfileThumbnail);
@@ -280,13 +285,23 @@ public class ImagesController(
/// GET api/images/v1/slideshow — the watch's lobby
/// slideshow info. Response shape per SlideshowInfoDTO.Deserialize:
/// {ValidTill (DateTime, required), Images (list, required)}.
- /// Far-future ValidTill stops the watch from re-polling.
+ /// A short ValidTill lets the Rec Center board pick up newly
+ /// shared camera photos without needing a server restart.
[HttpGet("api/images/v1/slideshow")]
- public IActionResult SlideshowInfo() => Ok(new
+ public async Task SlideshowInfo()
{
- ValidTill = new DateTime(9999, 12, 31, 0, 0, 0, DateTimeKind.Utc),
- Images = Array.Empty