ENH - make phone identity and SIM cards configurable

This commit is contained in:
Dominik
2026-08-11 12:25:51 +02:00
parent 72e5f91e02
commit ebac6e5509
13 changed files with 484 additions and 67 deletions
+160 -29
View File
@@ -2,6 +2,8 @@ Bridge.Database.AfterMigration("sky_phone", function()
SkyPhoneSim = {}
local unique_phones = Config.Phone.Unique ~= false
local sim_cards_enabled = Config.Sim.Enabled ~= false
local pending_insertions = {}
local operation_locks = {}
local sim_types = {
@@ -24,14 +26,14 @@ local function uuid()
return rows[1].id
end
local function reserve_sim(sim_type)
local function reserve_sim(sim_type, is_virtual)
local sim_id
local number = SkyPhoneSimNumber.Reserve(uuid, function(candidate)
sim_id = uuid()
local result = Bridge.Database.Query([[
INSERT IGNORE INTO `sky_phone_sims` (`id`, `phone_number`, `sim_type`)
VALUES (?, ?, ?)
]], { sim_id, candidate, sim_type })
INSERT IGNORE INTO `sky_phone_sims` (`id`, `phone_number`, `sim_type`, `is_virtual`)
VALUES (?, ?, ?, ?)
]], { sim_id, candidate, sim_type, is_virtual and 1 or 0 })
return affected_rows(result) == 1
end, Config.Sim.NumberLength, Config.Sim.NumberPrefix)
if not number then
@@ -62,6 +64,91 @@ local function sim_metadata(sim)
return metadata
end
local function set_phone_sim_metadata(source, phone_slot, sim)
if not unique_phones then
return true
end
if not phone_slot then
return false
end
local metadata = phone_slot.metadata or {}
metadata.sim_id = sim and sim.id or nil
metadata.phone_number = sim and sim.phone_number or nil
metadata.formatted_number = sim and SkyPhoneSimNumber.Format(
sim.phone_number,
Config.Sim.NumberGroups,
Config.Sim.NumberLength,
Config.Sim.NumberPrefix
) or nil
return Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, metadata)
end
local function prepare_device(source, phone_slot, imei)
local device = SkyPhone.LoadDevice(imei)
if not device then
return false, "request_failed"
end
local sim = device.sim_id and load_sim(device.sim_id) or nil
if sim_cards_enabled then
if sim and tonumber(sim.is_virtual) == 1 then
local result = Bridge.Database.Query([[
UPDATE `sky_phone_devices`
SET `sim_id` = NULL
WHERE `imei` = ? AND `sim_id` = ?
]], { imei, sim.id })
if affected_rows(result) ~= 1 then
return false, "request_failed"
end
sim = nil
end
if not set_phone_sim_metadata(source, phone_slot, sim) then
return false, "metadata_unsupported"
end
return true
end
if sim then
if not set_phone_sim_metadata(source, phone_slot, sim) then
return false, "metadata_unsupported"
end
return true
end
local automatic_sim = reserve_sim("anonymous", true)
local result = Bridge.Database.Query([[
UPDATE `sky_phone_devices`
SET `sim_id` = ?
WHERE `imei` = ? AND `sim_id` IS NULL
]], { automatic_sim.id, imei })
if affected_rows(result) ~= 1 then
Bridge.Database.Query("DELETE FROM `sky_phone_sims` WHERE `id` = ?", { automatic_sim.id })
device = SkyPhone.LoadDevice(imei)
sim = device and device.sim_id and load_sim(device.sim_id) or nil
if not sim then
return false, "request_failed"
end
if not set_phone_sim_metadata(source, phone_slot, sim) then
return false, "metadata_unsupported"
end
return true
end
if not set_phone_sim_metadata(source, phone_slot, automatic_sim) then
Bridge.Database.Query(
"UPDATE `sky_phone_devices` SET `sim_id` = NULL WHERE `imei` = ? AND `sim_id` = ?",
{ imei, automatic_sim.id }
)
Bridge.Database.Query("DELETE FROM `sky_phone_sims` WHERE `id` = ?", { automatic_sim.id })
return false, "metadata_unsupported"
end
return true
end
SkyPhoneSim.PrepareDevice = prepare_device
local function resolve_used_sim(source, used_item, item_name)
local slot_id = used_item and (used_item.slot or used_item.id)
local slot = slot_id and Bridge.Inventory.GetSlot(source, slot_id) or nil
@@ -82,7 +169,12 @@ local function ensure_sim(source, slot, sim_type)
end
local metadata = slot.metadata or {}
local sim = metadata.sim_id and load_sim(metadata.sim_id) or nil
if metadata.sim_id and (not sim or sim.sim_type ~= sim_type or sim.phone_number ~= metadata.phone_number) then
if metadata.sim_id and (
not sim
or tonumber(sim.is_virtual) == 1
or sim.sim_type ~= sim_type
or sim.phone_number ~= metadata.phone_number
) then
return nil, "invalid_sim"
end
if not sim then
@@ -97,30 +189,46 @@ end
local function list_phone_choices(source)
local choices = {}
local seen = {}
local choice_error
for _, slot in ipairs(Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)) do
local imei = SkyPhone.EnsureDevice(source, slot)
if imei then
local device = SkyPhone.LoadDevice(imei)
choices[#choices + 1] = {
imei = imei,
name = device.device_name,
occupied = device.sim_id ~= nil,
number = device.phone_number,
}
local imei, error_code = SkyPhone.EnsureDevice(source, slot)
if imei and not seen[imei] then
local prepared, prepare_error = prepare_device(source, slot, imei)
if prepared then
local device = SkyPhone.LoadDevice(imei)
choices[#choices + 1] = {
imei = imei,
name = device.device_name,
occupied = device.sim_id ~= nil,
number = device.phone_number,
}
seen[imei] = true
else
Bridge.Debug(
"error",
"[sky_phone] Could not prepare SIM target %s for source %s: %s.",
imei,
tostring(source),
tostring(prepare_error)
)
choice_error = choice_error or prepare_error
end
elseif not imei then
choice_error = choice_error or error_code
end
end
return choices
return choices, choice_error
end
local function rollback_phone_metadata(source, phone_slot, old_sim)
local metadata = phone_slot.metadata or {}
metadata.sim_id = old_sim and old_sim.id or nil
metadata.phone_number = old_sim and old_sim.phone_number or nil
metadata.formatted_number = old_sim and SkyPhoneSimNumber.Format(old_sim.phone_number, Config.Sim.NumberGroups, Config.Sim.NumberLength, Config.Sim.NumberPrefix) or nil
Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, metadata)
set_phone_sim_metadata(source, phone_slot, old_sim)
end
local function insert_sim(source, phone_imei, confirmed)
if not sim_cards_enabled then
return { success = false, error = "disabled" }
end
if operation_locks[source] then
return { success = false, error = "operation_in_progress" }
end
@@ -139,6 +247,10 @@ local function insert_sim(source, phone_imei, confirmed)
return { success = false, error = "phone_not_owned" }
end
local phone_slot = phone_matches[1]
local prepared, prepare_error = prepare_device(source, phone_slot, phone_imei)
if not prepared then
return { success = false, error = prepare_error }
end
local device = SkyPhone.LoadDevice(phone_imei)
local old_sim = device.sim_id and load_sim(device.sim_id) or nil
if old_sim and not confirmed then
@@ -146,11 +258,7 @@ local function insert_sim(source, phone_imei, confirmed)
end
operation_locks[source] = true
local phone_metadata = phone_slot.metadata or {}
phone_metadata.sim_id = pending.sim.id
phone_metadata.phone_number = pending.sim.phone_number
phone_metadata.formatted_number = SkyPhoneSimNumber.Format(pending.sim.phone_number, Config.Sim.NumberGroups, Config.Sim.NumberLength, Config.Sim.NumberPrefix)
if not Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, phone_metadata) then
if not set_phone_sim_metadata(source, phone_slot, pending.sim) then
operation_locks[source] = nil
return { success = false, error = "metadata_unsupported" }
end
@@ -211,6 +319,9 @@ local function insert_sim(source, phone_imei, confirmed)
end
local function use_sim(source, used_item)
if not sim_cards_enabled then
return false
end
local item_name = used_item and used_item.name
local sim_type = item_name and sim_types[item_name]
if not sim_type then
@@ -234,10 +345,10 @@ local function use_sim(source, used_item)
TriggerClientEvent("sky_phone:device:error", source, error_code)
return false
end
local choices = list_phone_choices(source)
local choices, choice_error = list_phone_choices(source)
if #choices == 0 then
operation_locks[source] = nil
TriggerClientEvent("sky_phone:device:error", source, "phone_required")
TriggerClientEvent("sky_phone:device:error", source, choice_error or "phone_required")
return false
end
pending_insertions[source] = {
@@ -257,10 +368,15 @@ local function use_sim(source, used_item)
return true
end
-- Register the guarded callbacks in both modes so a resource-only restart can
-- replace registrations left behind in framework-owned usable-item tables.
Bridge.Inventory.RegisterUsableItem(Config.Sim.RegisteredItem, use_sim)
Bridge.Inventory.RegisterUsableItem(Config.Sim.AnonymousItem, use_sim)
Bridge.Callbacks.Register("sky_phone:sim:insert", function(source, data)
if not sim_cards_enabled then
return { success = false, error = "disabled" }
end
if type(data) ~= "table" or not SkyPhoneImei.IsValid(data.imei) then
return { success = false, error = "invalid_request" }
end
@@ -268,6 +384,9 @@ Bridge.Callbacks.Register("sky_phone:sim:insert", function(source, data)
end)
Bridge.Callbacks.Register("sky_phone:sim:eject", function(source)
if not sim_cards_enabled then
return { success = false, error = "disabled" }
end
if operation_locks[source] then
return { success = false, error = "operation_in_progress" }
end
@@ -277,7 +396,7 @@ Bridge.Callbacks.Register("sky_phone:sim:eject", function(source)
end
local device = SkyPhone.LoadDevice(session.imei)
local sim = device and device.sim_id and load_sim(device.sim_id) or nil
if not sim then
if not sim or tonumber(sim.is_virtual) == 1 then
return { success = false, error = "no_sim" }
end
local phone_slot = Bridge.Inventory.GetSlot(source, session.slot)
@@ -288,7 +407,10 @@ Bridge.Callbacks.Register("sky_phone:sim:eject", function(source)
end
operation_locks[source] = true
rollback_phone_metadata(source, phone_slot, nil)
if not set_phone_sim_metadata(source, phone_slot, nil) then
operation_locks[source] = nil
return { success = false, error = "metadata_unsupported" }
end
if not Bridge.Inventory.AddItem(source, item_name, 1, nil, metadata) then
rollback_phone_metadata(source, phone_slot, sim)
operation_locks[source] = nil
@@ -313,4 +435,13 @@ AddEventHandler("playerDropped", function()
pending_insertions[source] = nil
operation_locks[source] = nil
end)
if sim_cards_enabled then
Bridge.Database.Query([[
UPDATE `sky_phone_devices` d
INNER JOIN `sky_phone_sims` s ON s.`id` = d.`sim_id`
SET d.`sim_id` = NULL
WHERE s.`is_virtual` = 1
]], {})
end
end)