mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 08:13:23 +00:00
ADD - share contacts and rich content in chats
This commit is contained in:
@@ -181,6 +181,10 @@ local function message_payload(row, profile_id)
|
||||
local deleted_everywhere = row.deleted_for_everyone == 1 or row.deleted_for_everyone == true
|
||||
local reactions = decode_array(row.reactions, "reactions")
|
||||
local waveform = row.media_waveform and decode_array(row.media_waveform, "media_waveform") or nil
|
||||
local share_payload = row.message_type == "share" and json.decode(row.media_payload or "") or nil
|
||||
if row.message_type == "share" and type(share_payload) ~= "table" then
|
||||
error(("[sky_phone] DarkChat message %s has an invalid share payload."):format(tostring(row.id)))
|
||||
end
|
||||
return {
|
||||
id = row.id,
|
||||
conversationId = row.conversation_id,
|
||||
@@ -189,12 +193,14 @@ local function message_payload(row, profile_id)
|
||||
messageType = deleted_everywhere and "system" or row.message_type,
|
||||
body = deleted_everywhere and "message_deleted" or row.body,
|
||||
mediaMime = deleted_everywhere and nil or row.media_mime,
|
||||
mediaPayload = not deleted_everywhere and row.message_type ~= "voice" and row.media_payload or nil,
|
||||
mediaPayload = not deleted_everywhere and row.message_type ~= "voice"
|
||||
and row.message_type ~= "share" and row.media_payload or nil,
|
||||
mediaDurationMs = deleted_everywhere and nil or tonumber(row.media_duration_ms),
|
||||
mediaWaveform = deleted_everywhere and nil or waveform,
|
||||
replyToId = row.reply_to_id,
|
||||
replyBody = row.reply_body,
|
||||
reactions = reactions,
|
||||
sharePayload = not deleted_everywhere and share_payload or nil,
|
||||
expiresAt = row.expires_at,
|
||||
createdAt = row.created_at,
|
||||
readAt = row.peer_last_read_at and row.peer_last_read_at >= row.created_at and row.peer_last_read_at or nil,
|
||||
@@ -538,6 +544,17 @@ Bridge.Callbacks.Register("sky_phone:darkchat:send", function(source, data)
|
||||
end
|
||||
media_payload = media_url
|
||||
media_mime = message_type == "image" and "image/jpeg" or "video/mp4"
|
||||
elseif message_type == "share" then
|
||||
local share
|
||||
local share_error
|
||||
share, share_error, media_payload = SkyPhoneEasyShare.SanitizeChatPayload(source, data.sharePayload)
|
||||
if not share then
|
||||
return { success = false, error = share_error or "invalid_payload" }
|
||||
end
|
||||
if body ~= "" and #body > Config.DarkChat.BodyMaxLength then
|
||||
return { success = false, error = "invalid_message" }
|
||||
end
|
||||
body = body ~= "" and body or share.title
|
||||
else
|
||||
return { success = false, error = "invalid_message" }
|
||||
end
|
||||
@@ -579,7 +596,8 @@ Bridge.Callbacks.Register("sky_phone:darkchat:send", function(source, data)
|
||||
conversationId = conversation_id,
|
||||
sender = profile.alias,
|
||||
messageType = message_type,
|
||||
preview = (message_type == "text" or message_type == "emoji") and body or message_type,
|
||||
preview = (message_type == "text" or message_type == "emoji" or message_type == "share")
|
||||
and body or message_type,
|
||||
})
|
||||
return { success = true, data = message }
|
||||
end)
|
||||
|
||||
@@ -521,7 +521,7 @@ local schema = {
|
||||
{ name = "recipient_sim_id", type = "CHAR(36) NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "sender_number", type = "VARCHAR(24) NOT NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "recipient_number", type = "VARCHAR(24) NOT NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "message_type", type = "ENUM('text', 'voice', 'image', 'gif', 'video') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "message_type", type = "ENUM('text', 'voice', 'image', 'gif', 'video', 'contact', 'share') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "body", type = "VARCHAR(2000) NOT NULL" },
|
||||
{ name = "media_payload", type = "MEDIUMTEXT NULL" },
|
||||
{ name = "media_mime", type = "VARCHAR(64) NULL", characterSet = "ascii", collation = "ascii_general_ci" },
|
||||
@@ -1022,7 +1022,7 @@ local schema = {
|
||||
{ name = "id", type = "CHAR(36) NOT NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "conversation_id", type = "CHAR(36) NOT NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "sender_profile_id", type = "BIGINT UNSIGNED NULL" },
|
||||
{ name = "message_type", type = "ENUM('text', 'emoji', 'gif', 'voice', 'image', 'video', 'system') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "message_type", type = "ENUM('text', 'emoji', 'gif', 'voice', 'image', 'video', 'share', 'system') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "body", type = "TEXT NOT NULL" },
|
||||
{ name = "media_payload", type = "LONGTEXT NULL" },
|
||||
{ name = "media_mime", type = "VARCHAR(80) NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
@@ -1680,8 +1680,9 @@ local schema = {
|
||||
{ name = "match_id", type = "CHAR(36) NOT NULL", characterSet = "ascii", collation = "ascii_bin" },
|
||||
{ name = "sender_account_id", type = "BIGINT UNSIGNED NOT NULL" },
|
||||
{ name = "body", type = "VARCHAR(1000) NOT NULL" },
|
||||
{ name = "message_type", type = "ENUM('text', 'image', 'gif', 'video') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "message_type", type = "ENUM('text', 'image', 'gif', 'video', 'share') NOT NULL DEFAULT 'text'" },
|
||||
{ name = "media_url", type = "VARCHAR(2048) NULL" },
|
||||
{ name = "share_payload", type = "LONGTEXT NULL" },
|
||||
{ name = "media_duration_ms", type = "INT UNSIGNED NULL" },
|
||||
{ name = "read_at", type = "DATETIME NULL" },
|
||||
{ name = "created_at", type = "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" },
|
||||
@@ -2173,15 +2174,15 @@ Bridge.Database.Query([[
|
||||
]], {})
|
||||
Bridge.Database.Query([[
|
||||
ALTER TABLE `sky_phone_flare_messages`
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'image', 'gif', 'video') NOT NULL DEFAULT 'text'
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'image', 'gif', 'video', 'share') NOT NULL DEFAULT 'text'
|
||||
]], {})
|
||||
Bridge.Database.Query([[
|
||||
ALTER TABLE `sky_phone_sms_messages`
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'voice', 'image', 'gif', 'video') NOT NULL DEFAULT 'text'
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'voice', 'image', 'gif', 'video', 'contact', 'share') NOT NULL DEFAULT 'text'
|
||||
]], {})
|
||||
Bridge.Database.Query([[
|
||||
ALTER TABLE `sky_phone_darkchat_messages`
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'emoji', 'gif', 'voice', 'image', 'video', 'system') NOT NULL DEFAULT 'text'
|
||||
MODIFY COLUMN `message_type` ENUM('text', 'emoji', 'gif', 'voice', 'image', 'video', 'share', 'system') NOT NULL DEFAULT 'text'
|
||||
]], {})
|
||||
Bridge.Database.Query([[
|
||||
ALTER TABLE `sky_phone_marketplace_images`
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
SkyPhoneEasyShare = {}
|
||||
|
||||
Bridge.Database.AfterMigration("sky_phone", function()
|
||||
local active_transfers = {}
|
||||
local valid_kinds = {
|
||||
@@ -259,6 +261,14 @@ local function sanitize_payload(source, device, data)
|
||||
return payload, nil, encoded
|
||||
end
|
||||
|
||||
function SkyPhoneEasyShare.SanitizeChatPayload(source, data)
|
||||
local device, error_response = current_device(source)
|
||||
if not device then
|
||||
return nil, error_response.error
|
||||
end
|
||||
return sanitize_payload(source, device, data)
|
||||
end
|
||||
|
||||
local function transfer_for_source(transfer, source)
|
||||
local incoming = transfer.recipient_source == source
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ Bridge.Database.AfterMigration("sky_phone", function()
|
||||
local genders = { woman = true, man = true, nonbinary = true }
|
||||
local interests = { woman = true, man = true, nonbinary = true, everyone = true }
|
||||
local looking_for = { longTerm = true, dates = true, friends = true }
|
||||
local message_types = { text = true, image = true, gif = true, video = true }
|
||||
local message_types = { text = true, image = true, gif = true, video = true, share = true }
|
||||
|
||||
local function allowed_gif_url(value)
|
||||
if type(value) ~= "string" or #value == 0 or #value > Config.Media.UrlMaxLength then
|
||||
@@ -386,6 +386,22 @@ local function validate_message(source, data)
|
||||
return { body = body, message_type = message_type }
|
||||
end
|
||||
|
||||
if message_type == "share" then
|
||||
local share, share_error, encoded = SkyPhoneEasyShare.SanitizeChatPayload(source, data.sharePayload)
|
||||
if not share then
|
||||
return nil, share_error or "invalid_payload"
|
||||
end
|
||||
local body = trim(data.body) or ""
|
||||
if #body > 1000 then
|
||||
return nil, "invalid_message"
|
||||
end
|
||||
return {
|
||||
body = body ~= "" and body or share.title,
|
||||
message_type = message_type,
|
||||
share_payload = encoded,
|
||||
}
|
||||
end
|
||||
|
||||
if type(data.mediaAssetId) ~= "string" then
|
||||
return nil, "invalid_attachment"
|
||||
end
|
||||
@@ -427,6 +443,10 @@ local function validate_message(source, data)
|
||||
end
|
||||
|
||||
local function message_payload(row, account_id)
|
||||
local share_payload = row.message_type == "share" and json.decode(row.share_payload or "") or nil
|
||||
if row.message_type == "share" and type(share_payload) ~= "table" then
|
||||
error(("[sky_phone] Flare message %s has an invalid share payload."):format(tostring(row.id)))
|
||||
end
|
||||
return {
|
||||
id = row.id,
|
||||
direction = tonumber(row.sender_account_id) == tonumber(account_id) and "sent" or "received",
|
||||
@@ -435,6 +455,7 @@ local function message_payload(row, account_id)
|
||||
messageType = row.message_type or "text",
|
||||
mediaUrl = row.media_url,
|
||||
mediaDurationMs = tonumber(row.media_duration_ms),
|
||||
sharePayload = share_payload,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -670,7 +691,7 @@ Bridge.Callbacks.Register("sky_phone:flare:thread", function(source, data)
|
||||
WHERE `match_id` = ? AND `sender_account_id` <> ? AND `read_at` IS NULL
|
||||
]], { match.id, account.id })
|
||||
local rows = Bridge.Database.Query([[
|
||||
SELECT `id`, `sender_account_id`, `body`, `message_type`, `media_url`,
|
||||
SELECT `id`, `sender_account_id`, `body`, `message_type`, `media_url`, `share_payload`,
|
||||
`media_duration_ms`, UNIX_TIMESTAMP(`created_at`) * 1000 AS `created_at_ms`
|
||||
FROM `sky_phone_flare_messages`
|
||||
WHERE `match_id` = ?
|
||||
@@ -705,11 +726,11 @@ Bridge.Callbacks.Register("sky_phone:flare:send", function(source, data)
|
||||
Bridge.Database.Query([[
|
||||
INSERT INTO `sky_phone_flare_messages`
|
||||
(`id`, `match_id`, `sender_account_id`, `body`, `message_type`, `media_url`,
|
||||
`media_duration_ms`)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`media_duration_ms`, `share_payload`)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
]], {
|
||||
id, match.id, account.id, message.body, message.message_type, message.media_url,
|
||||
message.media_duration_ms,
|
||||
message.media_duration_ms, message.share_payload,
|
||||
})
|
||||
local recipient_account_id = tonumber(match.account_a_id) == tonumber(account.id)
|
||||
and tonumber(match.account_b_id) or tonumber(match.account_a_id)
|
||||
@@ -728,6 +749,7 @@ Bridge.Callbacks.Register("sky_phone:flare:send", function(source, data)
|
||||
message_type = message.message_type,
|
||||
media_url = message.media_url,
|
||||
media_duration_ms = message.media_duration_ms,
|
||||
share_payload = message.share_payload,
|
||||
}, account.id),
|
||||
}
|
||||
end)
|
||||
|
||||
@@ -97,9 +97,59 @@ local function current_device(source)
|
||||
return device
|
||||
end
|
||||
|
||||
local function shared_contact(device, contact_id)
|
||||
if type(contact_id) ~= "string" or contact_id == "" or #contact_id > 36 then
|
||||
return nil, "invalid_contact"
|
||||
end
|
||||
local condition
|
||||
local params
|
||||
if device.account_id then
|
||||
condition = "contact.`account_id` = ?"
|
||||
params = { contact_id, tonumber(device.account_id) }
|
||||
else
|
||||
condition = "contact.`account_id` IS NULL AND contact.`device_imei` = ?"
|
||||
params = { contact_id, device.imei }
|
||||
end
|
||||
local rows = Bridge.Database.Query(([[
|
||||
SELECT contact.`name`, contact.`organization`, contact.`phone_number`, media.`url` AS `avatar_url`
|
||||
FROM `sky_phone_contacts` contact
|
||||
LEFT JOIN `sky_phone_media` media ON media.`id` = contact.`avatar_media_id`
|
||||
WHERE contact.`contact_id` = ? AND %s
|
||||
LIMIT 1
|
||||
]]):format(condition), params)
|
||||
local contact = rows[1]
|
||||
if not contact then
|
||||
return nil, "contact_not_found"
|
||||
end
|
||||
local name = trim(contact.name)
|
||||
local organization = trim(contact.organization)
|
||||
local number = SkyPhoneSimNumber.Normalize(
|
||||
contact.phone_number,
|
||||
Config.Sim.NumberLength,
|
||||
Config.Sim.NumberPrefix
|
||||
)
|
||||
if not name or name == "" or #name > Config.Calls.ContactNameMaxLength
|
||||
or (organization and #organization > Config.Calls.ContactNameMaxLength) or not number
|
||||
then
|
||||
error(("[sky_phone] Contact %s cannot be shared because its stored data is invalid."):format(contact_id))
|
||||
end
|
||||
local snapshot = {
|
||||
avatar_url = contact.avatar_url,
|
||||
name = name,
|
||||
organization = organization ~= "" and organization or nil,
|
||||
phone_number = number,
|
||||
}
|
||||
return {
|
||||
data = snapshot,
|
||||
payload = json.encode(snapshot),
|
||||
}
|
||||
end
|
||||
|
||||
local function format_message(row)
|
||||
row.media_duration_ms = tonumber(row.media_duration_ms)
|
||||
row.media_asset_id = nil
|
||||
row.contact = nil
|
||||
row.share = nil
|
||||
if row.message_type == "voice" then
|
||||
local waveform = row.media_waveform and json.decode(row.media_waveform) or nil
|
||||
if type(waveform) ~= "table" then
|
||||
@@ -107,6 +157,33 @@ local function format_message(row)
|
||||
end
|
||||
row.media_waveform = waveform
|
||||
row.media_payload = nil
|
||||
elseif row.message_type == "contact" then
|
||||
local contact = row.media_payload and json.decode(row.media_payload) or nil
|
||||
if type(contact) ~= "table" or type(contact.name) ~= "string"
|
||||
or type(contact.phone_number) ~= "string"
|
||||
or (contact.avatar_url ~= nil and type(contact.avatar_url) ~= "string")
|
||||
or (contact.organization ~= nil and type(contact.organization) ~= "string")
|
||||
then
|
||||
error(("[sky_phone] Message %s has an invalid contact payload."):format(tostring(row.id)))
|
||||
end
|
||||
row.contact = contact
|
||||
row.media_payload = nil
|
||||
row.media_duration_ms = nil
|
||||
row.media_mime = nil
|
||||
row.media_waveform = nil
|
||||
elseif row.message_type == "share" then
|
||||
local share = row.media_payload and json.decode(row.media_payload) or nil
|
||||
if type(share) ~= "table" or type(share.appId) ~= "string"
|
||||
or type(share.kind) ~= "string" or type(share.title) ~= "string"
|
||||
or type(share.copyText) ~= "string"
|
||||
then
|
||||
error(("[sky_phone] Message %s has an invalid share payload."):format(tostring(row.id)))
|
||||
end
|
||||
row.share = share
|
||||
row.media_payload = nil
|
||||
row.media_duration_ms = nil
|
||||
row.media_mime = nil
|
||||
row.media_waveform = nil
|
||||
elseif attachment_assets[row.message_type] then
|
||||
if not valid_stored_attachment(row.message_type, row.media_payload) then
|
||||
error(("[sky_phone] Message %s has an invalid attachment asset."):format(tostring(row.id)))
|
||||
@@ -414,6 +491,8 @@ Bridge.Callbacks.Register("sky_phone:messages:send", function(source, data)
|
||||
local body = trim(data.body) or ""
|
||||
local voice = nil
|
||||
local attachment = nil
|
||||
local contact = nil
|
||||
local share = nil
|
||||
if message_type == "text" then
|
||||
if body == "" or #body > Config.Messages.BodyMaxLength then
|
||||
return { success = false, error = "invalid_message" }
|
||||
@@ -424,6 +503,25 @@ Bridge.Callbacks.Register("sky_phone:messages:send", function(source, data)
|
||||
return { success = false, error = "invalid_voice" }
|
||||
end
|
||||
body = ""
|
||||
elseif message_type == "contact" then
|
||||
local contact_error
|
||||
contact, contact_error = shared_contact(device, data.contactId)
|
||||
if not contact then
|
||||
return { success = false, error = contact_error }
|
||||
end
|
||||
body = contact.data.name
|
||||
elseif message_type == "share" then
|
||||
local share_error
|
||||
local encoded
|
||||
share, share_error, encoded = SkyPhoneEasyShare.SanitizeChatPayload(source, data.sharePayload)
|
||||
if not share then
|
||||
return { success = false, error = share_error or "invalid_payload" }
|
||||
end
|
||||
if body ~= "" and #body > Config.Messages.BodyMaxLength then
|
||||
return { success = false, error = "invalid_message" }
|
||||
end
|
||||
share = { data = share, payload = encoded }
|
||||
body = body ~= "" and body or share.data.title
|
||||
elseif attachment_assets[message_type] then
|
||||
attachment = validate_attachment(source, device, message_type, data)
|
||||
if not attachment then
|
||||
@@ -459,7 +557,8 @@ Bridge.Callbacks.Register("sky_phone:messages:send", function(source, data)
|
||||
number,
|
||||
message_type,
|
||||
body,
|
||||
voice and voice.payload or attachment and attachment.payload or nil,
|
||||
voice and voice.payload or attachment and attachment.payload or contact and contact.payload
|
||||
or share and share.payload or nil,
|
||||
voice and voice.mime or attachment and attachment.mime or nil,
|
||||
voice and voice.duration or attachment and attachment.duration or nil,
|
||||
voice and voice.waveform or nil,
|
||||
|
||||
Reference in New Issue
Block a user