mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 00:01:29 +00:00
ENH - overhaul Photos media experience
This commit is contained in:
@@ -210,7 +210,7 @@ Locales["en"] = {
|
||||
category = "Category",
|
||||
status = "Publication Status",
|
||||
cover = "Cover Image",
|
||||
chooseCover = "Choose from Gallery",
|
||||
chooseCover = "Choose from Photos",
|
||||
changeCover = "Change Cover",
|
||||
removeCover = "Remove Cover",
|
||||
coverAlt = "Selected article cover",
|
||||
@@ -269,7 +269,7 @@ Locales["en"] = {
|
||||
signInTitle = "Connect your Sky Cloud account", signInBody = "CrewLink uses your private Sky Cloud identity to keep groups and roles available across your phones.", openSettings = "Open Sky Cloud settings",
|
||||
authEyebrow = "Private crew network", authTitle = "Welcome to CrewLink",
|
||||
authBody = "Your iFruit email is linked automatically. Use your CrewLink username to continue.",
|
||||
login = "Log in", register = "Register", ifruitEmail = "iFruit address", gallery = "Gallery", camera = "Camera", backToLogin = "Back to CrewLink login",
|
||||
login = "Log in", register = "Register", ifruitEmail = "iFruit address", gallery = "Photos", camera = "Camera", backToLogin = "Back to CrewLink login",
|
||||
authErrors = {
|
||||
no_ifruit_account = "Sign in to your Sky Cloud account in Settings first.", invalid_username = "Use 3-20 letters, numbers, dots, or underscores.",
|
||||
profile_not_found = "No CrewLink profile exists for this iFruit email.", profile_exists = "This iFruit email already has a CrewLink profile.",
|
||||
@@ -314,7 +314,7 @@ Locales["en"] = {
|
||||
flare = {
|
||||
name = "Flare", signInTitle = "Sign in to Flare", signInBody = "Flare profiles and matches are linked to your private Sky Cloud account.",
|
||||
createProfile = "Create your profile", welcome = "Find your spark", welcomeBody = "Build a profile and meet people across Los Santos.",
|
||||
photo = "Profile photo", profilePhotos = "Profile photos", profilePhotosBody = "Choose up to six photos from Gallery. Your first photo is shown first.", addPhotos = "Add photos", choosePhotos = "Choose from Gallery", primaryPhoto = "Main", removePhoto = "Remove photo {number}",
|
||||
photo = "Profile photo", profilePhotos = "Profile photos", profilePhotosBody = "Choose up to six photos from Photos. Your first photo is shown first.", addPhotos = "Add photos", choosePhotos = "Choose from Photos", primaryPhoto = "Main", removePhoto = "Remove photo {number}",
|
||||
yourName = "Your name", namePlaceholder = "What should people call you?", age = "Age", bio = "About you", bioPlaceholder = "A short line that starts a conversation...",
|
||||
gender = "I am", showMe = "Show me", woman = "Woman", man = "Man", nonbinary = "Non-binary", women = "Women", men = "Men", nonbinaryPeople = "Non-binary people", everyone = "Everyone",
|
||||
minimumAge = "Minimum age", maximumAge = "Maximum age", start = "Start exploring", saveProfile = "Save profile", editProfileBody = "Keep your profile fresh and unmistakably you.",
|
||||
@@ -336,7 +336,7 @@ Locales["en"] = {
|
||||
messagePlaceholder = "Write a message", itsAMatch = "It's a spark!", matchBody = "You and {name} liked each other.", sayHello = "Say hello", keepSwiping = "Keep exploring", newMatchNotification = "You and {sender} found a spark!", newMessageNotification = "New message from {sender}",
|
||||
lookingFor = { longTerm = "Long-term connection", dates = "Good dates", friends = "New friends" },
|
||||
errors = {
|
||||
invalid_profile = "Check your name, age and profile text.", invalid_profile_photos = "Choose up to six photos from your own Gallery.", request_failed = "Flare could not save those changes. Try again.", invalid_target = "This profile is no longer available.", invalid_choice = "That swipe could not be saved.", invalid_discovery = "That Discovery setting is invalid.", discovery_disabled = "Enable Discovery before swiping.",
|
||||
invalid_profile = "Check your name, age and profile text.", invalid_profile_photos = "Choose up to six photos from your own Photos library.", request_failed = "Flare could not save those changes. Try again.", invalid_target = "This profile is no longer available.", invalid_choice = "That swipe could not be saved.", invalid_discovery = "That Discovery setting is invalid.", discovery_disabled = "Enable Discovery before swiping.",
|
||||
nothing_to_rewind = "There is no recent swipe to rewind.", cannot_rewind_match = "A swipe that created a match cannot be rewound.",
|
||||
match_not_found = "This match is no longer available.", invalid_message = "Write a message before sending.", invalid_attachment = "This attachment is unavailable.", gif_provider_unconfigured = "GIF search is not configured.", gif_provider_unauthorized = "The GIF provider key is invalid.", gif_provider_rate_limited = "GIF search is busy. Try again shortly.", gif_provider_failed = "GIFs are temporarily unavailable.", rate_limited = "Slow down for a moment and try again.", default = "Flare could not complete the request.",
|
||||
},
|
||||
@@ -365,7 +365,7 @@ Locales["en"] = {
|
||||
noResults = "No results", noResultsBody = "Try another username, caption, place, or hashtag.",
|
||||
newPost = "New Post", newStory = "New Story", post = "Post", story = "Story",
|
||||
choosePhotos = "Choose photos", choosePhoto = "Choose photo",
|
||||
choosePhotosHint = "Select up to five photos from Gallery", chooseStoryHint = "Select one photo from Gallery",
|
||||
choosePhotosHint = "Select up to five photos from Photos", chooseStoryHint = "Select one photo from Photos",
|
||||
selectedPhotos = "{count} selected", changePhotos = "Change selection", caption = "Caption",
|
||||
captionPlaceholder = "Write a caption...", storyTextPlaceholder = "Add a short story text...",
|
||||
location = "Location", locationPlaceholder = "Add a place", allowComments = "Allow comments",
|
||||
@@ -435,7 +435,7 @@ Locales["en"] = {
|
||||
createProfile = "Create Feather profile", profileDetailsHint = "Choose how people will recognize you.", displayName = "Display name", displayNamePlaceholder = "Your name", handle = "Username", handlePlaceholder = "your_username", handleHint = "3-30 letters, numbers or underscores",
|
||||
bio = "Bio", bioPlaceholder = "What should people know about you?", start = "Join Feather", chooseAvatar = "Choose profile photo", changeAvatar = "Change photo",
|
||||
newPost = "New feather", reply = "Reply", replyingTo = "Replying to @{handle}", quote = "Quote", post = "Post", posting = "Posting...",
|
||||
composerPlaceholder = "What's happening?", replyPlaceholder = "Post your reply", addPhotos = "Add photos", removePhoto = "Remove photo {number}", charactersLeft = "{count} left", composerAudience = "Everyone can join the conversation", mediaTitle = "Add to your feather", mediaBody = "Share up to four photos from your phone or take a new one.", chooseGallery = "Gallery", chooseGalleryBody = "Choose one or more saved photos", takePhoto = "Camera", takePhotoBody = "Take a new photo now", selectedPhotos = "Selected photos", photoLimit = "You can add up to four photos.",
|
||||
composerPlaceholder = "What's happening?", replyPlaceholder = "Post your reply", addPhotos = "Add photos", removePhoto = "Remove photo {number}", charactersLeft = "{count} left", composerAudience = "Everyone can join the conversation", mediaTitle = "Add to your feather", mediaBody = "Share up to four photos from your phone or take a new one.", chooseGallery = "Photos", chooseGalleryBody = "Choose one or more saved photos", takePhoto = "Camera", takePhotoBody = "Take a new photo now", selectedPhotos = "Selected photos", photoLimit = "You can add up to four photos.",
|
||||
emptyFeed = "Your timeline is quiet", emptyFeedBody = "Follow people or post the first feather.", emptyExplore = "No results found", emptyExploreBody = "Try another search or explore a topic.", noTrendingHashtags = "No hashtags yet", noTrendingHashtagsBody = "Hashtags from published posts will appear here.",
|
||||
happeningNow = "What's happening", trends = "Trends for you", trendingIn = "Trending in Los Santos", openTopic = "View conversation", postsCount = "{count} posts",
|
||||
exploreTabs = { explore = "Explore", trending = "Trends", news = "News" },
|
||||
@@ -455,7 +455,7 @@ Locales["en"] = {
|
||||
originalSound = "original sound", save = "Save", home = "Home", discover = "Discover", create = "Create", activity = "Activity", profile = "Profile", navigation = "FlipTok navigation",
|
||||
emptyFeed = "No videos yet", emptyFeedBody = "Follow creators or post the first FlipTok.", searchPlaceholder = "Search creators and videos", clearSearch = "Clear search",
|
||||
noActivity = "No activity yet", followers = "Followers", videos = "Videos", emptyBio = "No bio yet.", editProfile = "Edit profile",
|
||||
newVideo = "New FlipTok", createTitle = "Create your next FlipTok", createBody = "Choose a clip, set the cover and sound, then decide who can watch it.", chooseVideo = "Choose a video", chooseVideoHint = "Select one from Gallery", changeVideo = "Change",
|
||||
newVideo = "New FlipTok", createTitle = "Create your next FlipTok", createBody = "Choose a clip, set the cover and sound, then decide who can watch it.", chooseVideo = "Choose a video", chooseVideoHint = "Select one from Photos", changeVideo = "Change",
|
||||
caption = "Caption", captionPlaceholder = "Write a caption...", location = "Add location", whoCanWatch = "Who can watch", public = "Everyone",
|
||||
followersOnly = "Followers", private = "Only me", allowComments = "Allow comments", saveDraft = "Drafts", publishing = "Posting...", post = "Post",
|
||||
draftSaved = "Draft saved.", published = "Your FlipTok is live.", linkCopied = "Video link copied.", reported = "Report submitted.", blocked = "Creator blocked.",
|
||||
@@ -468,7 +468,7 @@ Locales["en"] = {
|
||||
customSoundFormats = "YouTube, YouTube Music, Shorts, MP3, M4A, AAC, OGG, OPUS, WAV, or WEBM", useCustomSound = "Use sound", loadingSound = "Loading sound", invalidCustomSoundLink = "Use a YouTube link or a direct public HTTPS audio-file link.", customSoundLoadFailed = "This sound could not be loaded.",
|
||||
trimAndCover = "Trim & cover", trimStart = "Start", trimEnd = "End", coverFrame = "Cover", originalVolume = "Original sound", musicVolume = "Music",
|
||||
moderation = "Moderation", reports = "Open reports", noReports = "No open reports", removeVideo = "Remove video", dismissReport = "Dismiss",
|
||||
done = "Done", displayName = "Name", username = "Username", bio = "Bio", accountType = "Account type", profilePhoto = "Profile photo", changePhoto = "Change photo", chooseFromGallery = "Gallery", takePhoto = "Camera", removePhoto = "Remove photo", noConnections = "No profiles to show",
|
||||
done = "Done", displayName = "Name", username = "Username", bio = "Bio", accountType = "Account type", profilePhoto = "Profile photo", changePhoto = "Change photo", chooseFromGallery = "Photos", takePhoto = "Camera", removePhoto = "Remove photo", noConnections = "No profiles to show",
|
||||
authTitle = "Your FlipTok account", login = "Sign In", register = "Register", createAccount = "Create Account", logout = "Sign Out",
|
||||
loginBody = "Sign in to continue with your videos, follows, and saved posts.", registerBody = "Create a private FlipTok login for this profile.",
|
||||
password = "Password", confirmPassword = "Confirm password", passwordsMismatch = "The passwords do not match.",
|
||||
@@ -850,7 +850,7 @@ Locales["en"] = {
|
||||
noRecents = "No Recent Calls", noContacts = "No Contacts", searchContacts = "Search Contacts", favorites = "Favorites",
|
||||
addContact = "New Contact", newContact = "New", editContact = "Edit Contact", contactName = "Name", firstName = "First Name", lastName = "Last Name", companyOrGroup = "Company or Group", phoneNumber = "Phone Number",
|
||||
officialContact = "Official company contact",
|
||||
choosePhoto = "Choose Contact Photo", chooseGallery = "Gallery", takePhoto = "Camera", removePhoto = "Remove Photo",
|
||||
choosePhoto = "Choose Contact Photo", chooseGallery = "Photos", takePhoto = "Camera", removePhoto = "Remove Photo",
|
||||
message = "Message", video = "Video", mail = "Mail", defaultLabel = "Default", privateLabel = "Private",
|
||||
contactCard = "Contact Card", removeContact = "Remove Contact", returnToCall = "Return to Call", mobile = "Mobile", messagesProfile = "Messages", notes = "Notes",
|
||||
sendMessage = "Send Message", shareContact = "Share Contact", addFavorite = "Add to Favorites", removeFavorite = "Remove from Favorites",
|
||||
@@ -1097,7 +1097,7 @@ Locales["en"] = {
|
||||
driverModeBody = "Accept ride requests and track your earnings", completedRides = "Completed",
|
||||
cancelledRides = "Cancelled", acceptance = "Acceptance", rating = "Rating", notAvailable = "N/A",
|
||||
profile = "Your profile", editProfile = "Edit Profile", editProfileBody = "Change your public name and profile photo",
|
||||
profilePhoto = "Profile photo", gallery = "Gallery", camera = "Camera", profileDetails = "Profile details",
|
||||
profilePhoto = "Profile photo", gallery = "Photos", camera = "Camera", profileDetails = "Profile details",
|
||||
profileName = "Name", profileNamePlaceholder = "Your SkyRide name", removeProfilePhoto = "Remove profile photo",
|
||||
saveProfile = "Save Profile", profileSaved = "Your SkyRide profile was updated.",
|
||||
memberSince = "SkyRide member since {date}", rideComplete = "Ride Complete", rateRide = "Rate your ride",
|
||||
@@ -1257,8 +1257,8 @@ Locales["en"] = {
|
||||
name = "Camera", flash = "Flash", flip = "Flip camera", landscape = "Switch to landscape",
|
||||
portrait = "Switch to portrait", photo = "Photo", video = "Video", microphoneOn = "Microphone on", microphoneOff = "Microphone muted",
|
||||
focusHelp = "Space for movement", returnHelp = "Space to return", uploading = "{count} uploading",
|
||||
saving = "Saving video...", openGallery = "Open Gallery", takePhoto = "Take photo",
|
||||
startRecording = "Start recording", stopRecording = "Stop recording", saved = "Saved to Gallery.",
|
||||
saving = "Saving video...", openGallery = "Open Photos", takePhoto = "Take photo",
|
||||
startRecording = "Start recording", stopRecording = "Stop recording", saved = "Saved to Photos.",
|
||||
zoom = "Set camera zoom to {zoom}",
|
||||
errors = {
|
||||
cancelled = "Capture cancelled.", capture_failed = "Unable to capture the game view.",
|
||||
@@ -1389,7 +1389,7 @@ Locales["en"] = {
|
||||
createProfile = "Create your CityMarkt profile", editProfile = "Edit profile",
|
||||
profileIntro = "Your iFruit email stays linked to this profile.", profileEmail = "iFruit email",
|
||||
displayName = "Display name", profileBio = "About you", saveProfile = "Save profile",
|
||||
cancel = "Cancel", profileSaved = "Your profile was saved.", chooseGallery = "Gallery",
|
||||
cancel = "Cancel", profileSaved = "Your profile was saved.", chooseGallery = "Photos",
|
||||
takePhoto = "Camera", removeProfilePhoto = "Remove photo",
|
||||
phone = "Phone", contactSeller = "Contact seller", messagePlaceholder = "Hi, is this still available?",
|
||||
signInToMessage = "Sign in to your Sky Cloud account to send a message.",
|
||||
@@ -1450,11 +1450,11 @@ Locales["en"] = {
|
||||
createProfile = "Create your profile", editProfile = "Edit profile", profileSetupBody = "Your iFruit email is linked automatically.",
|
||||
profileEmail = "iFruit email", profileHandle = "Username", profileHandlePlaceholder = "your.name",
|
||||
profileHandleHint = "Use 3-24 lowercase letters, numbers, dots or underscores.", profileBio = "Bio",
|
||||
profileBioPlaceholder = "Tell the city a little about yourself...", profilePhoto = "Profile photo", profilePhotoHint = "Choose a photo from your Gallery or take a new one.", removeProfilePhoto = "Remove photo", profileSave = "Save profile", profileCancel = "Cancel", profileSaved = "Your profile was saved.",
|
||||
profileBioPlaceholder = "Tell the city a little about yourself...", profilePhoto = "Profile photo", profilePhotoHint = "Choose a photo from Photos or take a new one.", removeProfilePhoto = "Remove photo", profileSave = "Save profile", profileCancel = "Cancel", profileSaved = "Your profile was saved.",
|
||||
location = "Location", sharedFrom = "Shared from CityMarkt", openCityMarkt = "Open CityMarkt listing",
|
||||
newPost = "New local post", shareWithCity = "Share with the city", publish = "Publish", published = "Your post is live.", deleted = "Post deleted.",
|
||||
title = "Title", body = "Your story", category = "Category", titlePlaceholder = "What should people know?", bodyPlaceholder = "Add details, a recommendation or directions...",
|
||||
photos = "Photos", optional = "optional", camera = "Camera", gallery = "Gallery", photoLimit = "You can add up to six photos.",
|
||||
photos = "Photos", optional = "optional", camera = "Camera", gallery = "Photos", photoLimit = "You can add up to six photos.",
|
||||
cityMarktShare = "Share to Local Pages", cityMarktShared = "Shared to Local Pages.", cityMarktShareHint = "One CityMarkt share per day",
|
||||
cityMarktAlreadyShared = "Already published", cityMarktOpenSharedHint = "Open the Local Pages post",
|
||||
cityMarktAppMissing = "Local Pages is not installed", cityMarktInstallHint = "Install Local Pages to share this listing",
|
||||
@@ -1592,7 +1592,7 @@ Locales["en"] = {
|
||||
destinations = "Share destinations", newMessage = "New Message", sentToChat = "Sent to chat.",
|
||||
shareProfile = "Share Profile", chooseConversation = "Choose a conversation",
|
||||
kinds = {
|
||||
contact = "Contact", document = "Document", link = "Link", location = "Location",
|
||||
contact = "Contact", document = "Document", link = "Link", location = "Location", media = "Media",
|
||||
note = "Note", photo = "Photo", playlist = "Playlist", post = "Post",
|
||||
profile = "Profile", text = "Message", track = "Track", video = "Video",
|
||||
},
|
||||
@@ -1616,9 +1616,31 @@ Locales["en"] = {
|
||||
},
|
||||
},
|
||||
photos = {
|
||||
name = "Gallery", count = "{count} items", loading = "Loading Gallery...",
|
||||
name = "Photos", library = "Library",
|
||||
counts = {
|
||||
all = "{count} Objects", allOne = "{count} Object",
|
||||
favorite = "{count} Favorite", favorites = "{count} Favorites",
|
||||
photo = "{count} Photo", photos = "{count} Photos",
|
||||
video = "{count} Video", videos = "{count} Videos",
|
||||
},
|
||||
sorting = {
|
||||
action = "Sort Photos", title = "Sort Order",
|
||||
show = "Show", allItems = "All Items", favorites = "Favorites",
|
||||
newestFirst = "Newest First", oldestFirst = "Oldest First",
|
||||
},
|
||||
selection = {
|
||||
action = "Select", selected = "{count} Selected",
|
||||
share = "Share Selected", shareTitle = "{count} Media Items",
|
||||
shareCopy = "{count} photos and videos shared from Photos.",
|
||||
delete = "Delete Selected", deleteTitle = "Delete Selected Media?",
|
||||
deleteBody = "The selected photos and videos will be permanently deleted.",
|
||||
deleted = "{count} media items deleted.", limit = "You can select up to 50 media items.",
|
||||
},
|
||||
loading = "Loading Photos...",
|
||||
emptyTitle = "No Photos or Videos", emptyBody = "Captures from Camera will appear here.",
|
||||
photo = "Photo", video = "Video", photoAlt = "Gallery photo", videoAlt = "Gallery video",
|
||||
photo = "Photo", video = "Video", photoAlt = "Photo library image", videoAlt = "Photo library video",
|
||||
today = "Today", yesterday = "Yesterday",
|
||||
addFavorite = "Add to Favorites", removeFavorite = "Remove from Favorites",
|
||||
delete = "Delete media", deleteTitle = "Delete Media?",
|
||||
deleteBody = "This photo or video will be permanently deleted.", deleted = "Media deleted.",
|
||||
zoomIn = "Zoom in", zoomOut = "Zoom out", resetZoom = "Reset zoom",
|
||||
@@ -1636,7 +1658,7 @@ Locales["en"] = {
|
||||
cancelled = "The media action was cancelled.", capture_failed = "Unable to capture the game view.",
|
||||
invalid_import_request = "The import request is invalid.", invalid_import_media = "This media item cannot be imported.",
|
||||
invalid_media_type = "The media type is invalid.", invalid_upload = "The upload could not be verified.",
|
||||
invalid_upload_token = "The upload session is no longer valid.", missing_config = "Gallery uploads are not configured.",
|
||||
invalid_upload_token = "The upload session is no longer valid.", missing_config = "Photos uploads are not configured.",
|
||||
import_media_not_allowed = "This media item is not allowed.", import_media_too_large = "This media item is too large.",
|
||||
import_media_unavailable = "This media item is no longer available.", import_provider_failed = "The website could not load its media.",
|
||||
import_provider_unauthorized = "The website credentials are invalid.", import_source_not_found = "This website is not registered.",
|
||||
@@ -1645,7 +1667,7 @@ Locales["en"] = {
|
||||
import_url_unavailable = "The linked media could not be reached.", import_size_unavailable = "The website did not provide the media size.",
|
||||
not_found = "The media item no longer exists.", owner_changed = "The active phone account changed.",
|
||||
operation_in_progress = "Another media operation is already in progress.",
|
||||
rate_limited = "Too many media actions. Try again shortly.", request_failed = "The Gallery request failed.",
|
||||
rate_limited = "Too many media actions. Try again shortly.", request_failed = "The Photos request failed.",
|
||||
request_timeout = "The media service timed out.", unsupported = "This media format is not supported.",
|
||||
upload_failed = "The media upload failed.", upload_timeout = "The media upload timed out.",
|
||||
},
|
||||
|
||||
@@ -453,6 +453,15 @@ RegisterNUICallback("gallery:delete", function(data, cb)
|
||||
cb({ success = true })
|
||||
end)
|
||||
|
||||
RegisterNUICallback("gallery:delete-many", function(data, cb)
|
||||
if type(data) ~= "table" then
|
||||
cb({ success = false, error = "invalid_request" })
|
||||
return
|
||||
end
|
||||
TriggerServerEvent("sky_phone:media:delete-many", data)
|
||||
cb({ success = true })
|
||||
end)
|
||||
|
||||
RegisterNUICallback("memos:requestUpload", function(data, cb)
|
||||
if type(data) ~= "table" then
|
||||
cb({ success = false, error = "invalid_request" })
|
||||
@@ -501,6 +510,10 @@ RegisterNetEvent("sky_phone:media:delete-result", function(data)
|
||||
SendNUIMessage({ type = "media:deleteResult", data = data })
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:media:delete-many-result", function(data)
|
||||
SendNUIMessage({ type = "media:deleteManyResult", data = data })
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:memos:upload-ready", function(data)
|
||||
SendNUIMessage({ type = "memos:uploadReady", data = data })
|
||||
end)
|
||||
|
||||
@@ -274,6 +274,8 @@ local server_callbacks = {
|
||||
"flare:thread",
|
||||
"flare:send",
|
||||
"gallery:list",
|
||||
"gallery:counts",
|
||||
"gallery:favorite",
|
||||
"media:config",
|
||||
"media:import:sources",
|
||||
"media:import:list",
|
||||
|
||||
@@ -386,6 +386,7 @@ local schema = {
|
||||
collation = "ascii_bin",
|
||||
},
|
||||
{ name = "verified_at", type = "DATETIME NULL" },
|
||||
{ name = "favorite", type = "TINYINT(1) NOT NULL DEFAULT 0" },
|
||||
{ name = "created_at", type = "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" },
|
||||
},
|
||||
primaryKey = "id",
|
||||
|
||||
@@ -7,6 +7,7 @@ local valid_kinds = {
|
||||
document = true,
|
||||
link = true,
|
||||
location = true,
|
||||
media = true,
|
||||
note = true,
|
||||
photo = true,
|
||||
playlist = true,
|
||||
@@ -671,6 +672,45 @@ local function sanitize_payload(source, device, data)
|
||||
payload.title = note.title ~= "" and note.title or title
|
||||
payload.copyText = plain_note_body(note.body)
|
||||
payload.meta = { body = note.body, title = note.title }
|
||||
elseif data.kind == "media" and app_id == "photos" and type(data.meta) == "table" and type(data.meta.mediaIds) == "table" then
|
||||
local media_ids = {}
|
||||
local seen_ids = {}
|
||||
for _, value in ipairs(data.meta.mediaIds) do
|
||||
local media_id = tonumber(value)
|
||||
if media_id and media_id > 0 and media_id == math.floor(media_id) and not seen_ids[media_id] then
|
||||
seen_ids[media_id] = true
|
||||
media_ids[#media_ids + 1] = media_id
|
||||
end
|
||||
end
|
||||
if #media_ids < 1 or #media_ids > 50 then
|
||||
return nil, "invalid_payload"
|
||||
end
|
||||
local placeholders = {}
|
||||
local media_params = {}
|
||||
for _, media_id in ipairs(media_ids) do
|
||||
placeholders[#placeholders + 1] = "?"
|
||||
media_params[#media_params + 1] = media_id
|
||||
end
|
||||
append_params(media_params, owner_params)
|
||||
local media_rows = Bridge.Database.Query(([[
|
||||
SELECT `id`, `url`, `media_type` AS `mediaType`
|
||||
FROM `sky_phone_media`
|
||||
WHERE `id` IN (%s) AND %s AND `media_type` IN ('photo', 'video')
|
||||
ORDER BY `created_at` ASC, `id` ASC
|
||||
]]):format(table.concat(placeholders, ", "), condition), media_params)
|
||||
if #media_rows ~= #media_ids then
|
||||
return nil, "not_owned"
|
||||
end
|
||||
local items = {}
|
||||
for _, row in ipairs(media_rows) do
|
||||
items[#items + 1] = {
|
||||
id = tonumber(row.id),
|
||||
mediaType = row.mediaType,
|
||||
url = row.url,
|
||||
}
|
||||
end
|
||||
payload.imageUrl = items[1].url
|
||||
payload.meta = { items = items }
|
||||
elseif data.kind == "photo" or data.kind == "video" then
|
||||
local url, media_error = SkyPhoneMedia.ResolveOwnedMedia(source, payload.id, data.kind)
|
||||
if not url then
|
||||
|
||||
@@ -305,6 +305,15 @@ local function delete_result(source, correlation_id, success, error_code, media_
|
||||
})
|
||||
end
|
||||
|
||||
local function delete_many_result(source, correlation_id, success, error_code, deleted_ids)
|
||||
TriggerClientEvent("sky_phone:media:delete-many-result", source, {
|
||||
correlationId = correlation_id,
|
||||
success = success,
|
||||
error = error_code,
|
||||
deletedIds = deleted_ids,
|
||||
})
|
||||
end
|
||||
|
||||
local function parse_metadata(value)
|
||||
if type(value) == "table" then
|
||||
return value
|
||||
@@ -412,10 +421,13 @@ Bridge.Callbacks.Register("sky_phone:gallery:list", function(source, data)
|
||||
condition = condition .. " AND `media_type` = ?"
|
||||
params[#params + 1] = media_type
|
||||
end
|
||||
if data.favoriteOnly == true then
|
||||
condition = condition .. " AND `favorite` = 1"
|
||||
end
|
||||
params[#params + 1] = limit
|
||||
params[#params + 1] = offset
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT `id`, `url`, `media_type` AS `mediaType`,
|
||||
SELECT `id`, `url`, `media_type` AS `mediaType`, `favorite`,
|
||||
UNIX_TIMESTAMP(`created_at`) * 1000 AS `createdAt`
|
||||
FROM `sky_phone_media`
|
||||
WHERE %s
|
||||
@@ -425,10 +437,85 @@ Bridge.Callbacks.Register("sky_phone:gallery:list", function(source, data)
|
||||
for _, row in ipairs(rows) do
|
||||
row.id = tonumber(row.id)
|
||||
row.createdAt = tonumber(row.createdAt) or 0
|
||||
row.favorite = tonumber(row.favorite) == 1
|
||||
end
|
||||
return { success = true, data = rows }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:gallery:favorite", function(source, data)
|
||||
if not SkyPhone.AllowOperation(source, "media_favorite", 60, 60)
|
||||
or type(data) ~= "table"
|
||||
or type(data.favorite) ~= "boolean"
|
||||
then
|
||||
return { success = false, error = "invalid_request" }
|
||||
end
|
||||
local media_id = tonumber(data.id)
|
||||
if not media_id or media_id < 1 or media_id ~= math.floor(media_id) then
|
||||
return { success = false, error = "invalid_request" }
|
||||
end
|
||||
local owner, error_response = session_owner(source)
|
||||
if not owner then
|
||||
return error_response
|
||||
end
|
||||
local condition, owner_params = owner_condition(owner)
|
||||
local params = { media_id }
|
||||
for _, value in ipairs(owner_params) do
|
||||
params[#params + 1] = value
|
||||
end
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT `id`
|
||||
FROM `sky_phone_media`
|
||||
WHERE `id` = ? AND %s AND `media_type` IN ('photo', 'video')
|
||||
LIMIT 1
|
||||
]]):format(condition), params)
|
||||
if not rows[1] then
|
||||
return { success = false, error = "media_not_found" }
|
||||
end
|
||||
local update_params = { data.favorite and 1 or 0, media_id }
|
||||
for _, value in ipairs(owner_params) do
|
||||
update_params[#update_params + 1] = value
|
||||
end
|
||||
Bridge.Database.Query(
|
||||
("UPDATE `sky_phone_media` SET `favorite` = ? WHERE `id` = ? AND %s"):format(condition),
|
||||
update_params
|
||||
)
|
||||
return {
|
||||
success = true,
|
||||
data = { id = media_id, favorite = data.favorite },
|
||||
}
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:gallery:counts", function(source)
|
||||
local owner, error_response = session_owner(source)
|
||||
if not owner then
|
||||
return error_response
|
||||
end
|
||||
local condition, params = owner_condition(owner)
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT
|
||||
COUNT(*) AS `all_count`,
|
||||
COUNT(CASE WHEN `favorite` = 1 THEN 1 END) AS `favorite_count`,
|
||||
COUNT(CASE WHEN `media_type` = 'photo' AND `favorite` = 1 THEN 1 END) AS `favorite_photo_count`,
|
||||
COUNT(CASE WHEN `media_type` = 'video' AND `favorite` = 1 THEN 1 END) AS `favorite_video_count`,
|
||||
COUNT(CASE WHEN `media_type` = 'photo' THEN 1 END) AS `photo_count`,
|
||||
COUNT(CASE WHEN `media_type` = 'video' THEN 1 END) AS `video_count`
|
||||
FROM `sky_phone_media`
|
||||
WHERE %s AND `media_type` IN ('photo', 'video')
|
||||
]]):format(condition), params)
|
||||
local counts = rows[1] or {}
|
||||
return {
|
||||
success = true,
|
||||
data = {
|
||||
all = tonumber(counts.all_count) or 0,
|
||||
favoritePhotos = tonumber(counts.favorite_photo_count) or 0,
|
||||
favorites = tonumber(counts.favorite_count) or 0,
|
||||
favoriteVideos = tonumber(counts.favorite_video_count) or 0,
|
||||
photos = tonumber(counts.photo_count) or 0,
|
||||
videos = tonumber(counts.video_count) or 0,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
local function current_messaging_device(source)
|
||||
local session, error_response = SkyPhone.RequireSession(source)
|
||||
if not session then
|
||||
@@ -676,6 +763,7 @@ RegisterNetEvent("sky_phone:media:complete-upload", function(data)
|
||||
id = media_id,
|
||||
url = verified.url,
|
||||
mediaType = state.media_type,
|
||||
favorite = false,
|
||||
createdAt = os.time() * 1000,
|
||||
})
|
||||
end)
|
||||
@@ -708,6 +796,43 @@ RegisterNetEvent("sky_phone:media:fail-upload", function(data)
|
||||
upload_result(src, state.correlation_id, false, error_code)
|
||||
end)
|
||||
|
||||
local function delete_owned_media(src, owner, media_id)
|
||||
local condition, params = owner_condition(owner)
|
||||
local query_params = { media_id }
|
||||
for _, value in ipairs(params) do
|
||||
query_params[#query_params + 1] = value
|
||||
end
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT `id`, `remote_id`, `origin` FROM `sky_phone_media`
|
||||
WHERE `id` = ? AND %s AND `media_type` IN ('photo', 'video') LIMIT 1
|
||||
]]):format(condition), query_params)
|
||||
local row = rows[1]
|
||||
if not row then
|
||||
return false, "not_found"
|
||||
end
|
||||
local delete_key = row.origin == "phone_upload" and row.remote_id or ("import:%s"):format(media_id)
|
||||
if pending_deletes[delete_key] then
|
||||
return false, "operation_in_progress"
|
||||
end
|
||||
pending_deletes[delete_key] = src
|
||||
if row.origin == "phone_upload" then
|
||||
local references = Bridge.Database.Query(
|
||||
"SELECT COUNT(*) AS `count` FROM `sky_phone_media` WHERE `remote_id` = ?",
|
||||
{ row.remote_id }
|
||||
)
|
||||
if (tonumber(references[1] and references[1].count) or 0) <= 1 then
|
||||
local deleted, delete_error = delete_remote_file(row.remote_id)
|
||||
if not deleted then
|
||||
pending_deletes[delete_key] = nil
|
||||
return false, delete_error
|
||||
end
|
||||
end
|
||||
end
|
||||
Bridge.Database.Query(("DELETE FROM `sky_phone_media` WHERE `id` = ? AND %s"):format(condition), query_params)
|
||||
pending_deletes[delete_key] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
RegisterNetEvent("sky_phone:media:delete", function(data)
|
||||
local src = source
|
||||
data = type(data) == "table" and data or {}
|
||||
@@ -726,43 +851,54 @@ RegisterNetEvent("sky_phone:media:delete", function(data)
|
||||
delete_result(src, correlation_id, false, error_response.error, media_id)
|
||||
return
|
||||
end
|
||||
local condition, params = owner_condition(owner)
|
||||
local query_params = { media_id }
|
||||
for _, value in ipairs(params) do
|
||||
query_params[#query_params + 1] = value
|
||||
end
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT `id`, `remote_id`, `origin` FROM `sky_phone_media`
|
||||
WHERE `id` = ? AND %s AND `media_type` IN ('photo', 'video') LIMIT 1
|
||||
]]):format(condition), query_params)
|
||||
local row = rows[1]
|
||||
if not row then
|
||||
delete_result(src, correlation_id, false, "not_found", media_id)
|
||||
local deleted, delete_error = delete_owned_media(src, owner, media_id)
|
||||
if not deleted then
|
||||
delete_result(src, correlation_id, false, delete_error, media_id)
|
||||
return
|
||||
end
|
||||
local delete_key = row.origin == "phone_upload" and row.remote_id or ("import:%s"):format(media_id)
|
||||
if pending_deletes[delete_key] then
|
||||
delete_result(src, correlation_id, false, "operation_in_progress", media_id)
|
||||
delete_result(src, correlation_id, true, nil, media_id)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("sky_phone:media:delete-many", function(data)
|
||||
local src = source
|
||||
data = type(data) == "table" and data or {}
|
||||
local correlation_id = data.correlationId
|
||||
if type(correlation_id) ~= "string" or #correlation_id > 80 or type(data.ids) ~= "table" then
|
||||
delete_many_result(src, correlation_id, false, "invalid_request", {})
|
||||
return
|
||||
end
|
||||
pending_deletes[delete_key] = src
|
||||
if row.origin == "phone_upload" then
|
||||
local references = Bridge.Database.Query(
|
||||
"SELECT COUNT(*) AS `count` FROM `sky_phone_media` WHERE `remote_id` = ?",
|
||||
{ row.remote_id }
|
||||
)
|
||||
if (tonumber(references[1] and references[1].count) or 0) <= 1 then
|
||||
local deleted, delete_error = delete_remote_file(row.remote_id)
|
||||
if not deleted then
|
||||
pending_deletes[delete_key] = nil
|
||||
delete_result(src, correlation_id, false, delete_error, media_id)
|
||||
return
|
||||
end
|
||||
local media_ids = {}
|
||||
local seen_ids = {}
|
||||
for _, value in ipairs(data.ids) do
|
||||
local media_id = tonumber(value)
|
||||
if media_id and media_id > 0 and media_id == math.floor(media_id) and not seen_ids[media_id] then
|
||||
seen_ids[media_id] = true
|
||||
media_ids[#media_ids + 1] = media_id
|
||||
end
|
||||
end
|
||||
Bridge.Database.Query(("DELETE FROM `sky_phone_media` WHERE `id` = ? AND %s"):format(condition), query_params)
|
||||
pending_deletes[delete_key] = nil
|
||||
delete_result(src, correlation_id, true, nil, media_id)
|
||||
if #media_ids < 1 or #media_ids > 50 then
|
||||
delete_many_result(src, correlation_id, false, "invalid_request", {})
|
||||
return
|
||||
end
|
||||
if not SkyPhone.AllowOperation(src, "media_delete_many", 10, 60) then
|
||||
delete_many_result(src, correlation_id, false, "rate_limited", {})
|
||||
return
|
||||
end
|
||||
local owner, error_response = session_owner(src)
|
||||
if not owner then
|
||||
delete_many_result(src, correlation_id, false, error_response.error, {})
|
||||
return
|
||||
end
|
||||
local deleted_ids = {}
|
||||
for _, media_id in ipairs(media_ids) do
|
||||
local deleted, delete_error = delete_owned_media(src, owner, media_id)
|
||||
if not deleted then
|
||||
delete_many_result(src, correlation_id, false, delete_error, deleted_ids)
|
||||
return
|
||||
end
|
||||
deleted_ids[#deleted_ids + 1] = media_id
|
||||
end
|
||||
delete_many_result(src, correlation_id, true, nil, deleted_ids)
|
||||
end)
|
||||
|
||||
function SkyPhoneMedia.GetDeviceRemoteIds(imei)
|
||||
|
||||
@@ -351,6 +351,7 @@ local function store_import(owner, media)
|
||||
|
||||
return {
|
||||
createdAt = os.time() * 1000,
|
||||
favorite = false,
|
||||
id = media_id,
|
||||
mediaType = media.mediaType,
|
||||
url = media.url,
|
||||
|
||||
Reference in New Issue
Block a user