Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LibreMetaverse/Caps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public string CapabilityNameFromURI(Uri cap)
"ChatSessionRequest",
"CopyInventoryFromNotecard",
"CreateInventoryCategory",
"CreateTaskInventoryItem",
"DeclineFriendship",
"DeclineGroupInvite",
"DispatchRegionInfo",
Expand Down
10 changes: 10 additions & 0 deletions LibreMetaverse/Inventory/InventoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ public override string ToString()
/// <summary>Combined flags from <see cref="InventoryItemFlags"/></summary>
[Key("Flags")]
public uint Flags { get; set; }
/// <summary>VM runtime for scripts (e.g. "lsl2", "mono", "luau"), from the task inventory response's metadata; null if not a script or not provided</summary>
[Key("Runtime")]
public string? Runtime { get; set; }

/// <summary>Time and date the inventory item was created, stored as
/// UTC (Coordinated Universal Time)</summary>
Expand Down Expand Up @@ -269,6 +272,9 @@ public static InventoryItem FromOSD(OSD data)
"inv_type": "script",
"item_id": "806baac6-64cd-daae-efff-627208ed1d2b",
"metadata": {
"script": {
"runtime": "luau"
}
},
"name": "New Script",
"parent_id": "74a61033-366a-0b89-4d4b-649c5b0de2ad",
Expand Down Expand Up @@ -324,6 +330,10 @@ public static InventoryItem FromOSD(OSD data)
item.AssetType = assetType;
item.CreationDate = Utils.UnixTimeToDateTime(descItem["created_at"]);
item.Flags = descItem["flags"];
if (descItem["metadata"] is OSDMap meta && meta["script"] is OSDMap scriptMeta && scriptMeta.TryGetValue("runtime", out var runtime))
{
item.Runtime = runtime.AsString();
}

OSDMap perms = (OSDMap)descItem["permissions"];
item.OwnerID = perms["owner_id"];
Expand Down
74 changes: 70 additions & 4 deletions LibreMetaverse/Inventory/InventoryManager.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,13 +362,13 @@ private void SendCopyFromNotecardPacket(UUID objectID, UUID notecardID, UUID fol
}

public async Task<(bool uploadSuccess, string uploadStatus, bool compileSuccess, List<string>? compileMessages, UUID itemID, UUID assetID)> RequestUpdateScriptAgentInventoryAsync(
byte[] data, UUID itemID, bool mono, CancellationToken cancellationToken = default, IProgress<ProgressReport>? progress = null)
byte[] data, UUID itemID, ScriptTarget target = ScriptTarget.Mono, CancellationToken cancellationToken = default, IProgress<ProgressReport>? progress = null)
Comment on lines 364 to +365
{
var cap = GetCapabilityURI("UpdateScriptAgent");
if (cap == null)
throw new InvalidOperationException("UpdateScriptAgent capability is not currently available");

var request = new UpdateScriptAgentRequestMessage { ItemID = itemID, Target = mono ? "mono" : "lsl2" };
var request = new UpdateScriptAgentRequestMessage { ItemID = itemID, Target = ScriptTargetToString(target) };
try
{
var result = await PostCapAsync(cap, request.Serialize(), cancellationToken, progress).ConfigureAwait(false);
Expand All @@ -379,13 +379,13 @@ private void SendCopyFromNotecardPacket(UUID objectID, UUID notecardID, UUID fol
}

public async Task<(bool uploadSuccess, string uploadStatus, bool compileSuccess, List<string>? compileMessages, UUID itemID, UUID assetID)> RequestUpdateScriptTaskAsync(
byte[] data, UUID itemID, UUID taskID, bool mono, bool running, CancellationToken cancellationToken = default, IProgress<ProgressReport>? progress = null)
byte[] data, UUID itemID, UUID taskID, ScriptTarget target, bool running, CancellationToken cancellationToken = default, IProgress<ProgressReport>? progress = null)
{
var cap = GetCapabilityURI("UpdateScriptTask");
if (cap == null)
throw new InvalidOperationException("UpdateScriptTask capability is not currently available");

var msg = new UpdateScriptTaskUpdateMessage { ItemID = itemID, TaskID = taskID, ScriptRunning = running, Target = mono ? "mono" : "lsl2" };
var msg = new UpdateScriptTaskUpdateMessage { ItemID = itemID, TaskID = taskID, ScriptRunning = running, Target = ScriptTargetToString(target) };
try
{
var result = await PostCapAsync(cap, msg.Serialize(), cancellationToken, progress).ConfigureAwait(false);
Expand All @@ -395,6 +395,49 @@ private void SendCopyFromNotecardPacket(UUID objectID, UUID notecardID, UUID fol
catch (Exception ex) { return (false, ex.Message, false, null, UUID.Zero, UUID.Zero); }
}

public async Task<(bool success, string result)> RequestCreateTaskScriptAsync(
UUID objectID, string name, string description, ScriptTarget target, ScriptLanguage language,
CancellationToken cancellationToken = default)
{
var cap = GetCapabilityURI("CreateTaskInventoryItem");
if (cap == null)
throw new InvalidOperationException("CreateTaskInventoryItem capability is not currently available");

var msg = new CreateTaskInventoryItemMessage
{
ObjectID = objectID,
AssetType = AssetType.LSLText,
SubType = (int)language,
Name = name,
Description = description,
Vm = ScriptTargetToString(target),
Enabled = true,
Permissions = new Permissions(
(uint)int.MaxValue,
0,
0,
(uint)(PermissionMask.Transfer | PermissionMask.Move),
(uint)int.MaxValue)
};

try
{
var result = await PostCapAsync(cap, msg.Serialize(), cancellationToken).ConfigureAwait(false);
if (result is OSDMap map)
{
var success = map["success"].AsBoolean();
return (success, success ? map["name"].AsString() : map["message"].AsString());
}
return (false, string.Empty);
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
Logger.Error($"RequestCreateTaskScriptAsync failed for object {objectID}", ex, Client);
return (false, ex.Message);
}
}

/// <summary>
/// Async-first variant to request a single inventory item. Uses FetchInventory2 capability when available.
/// </summary>
Expand Down Expand Up @@ -1460,6 +1503,29 @@ public async Task<CopyItemsResult> RequestCopyItemsWithResultAsync(List<UUID> it
return new CopyItemsResult { Success = false, CopiedItems = null, Error = ex };
}
}

public enum ScriptLanguage
{
LSL = 0,
Lua = 1
}

public enum ScriptTarget
{
LSL2,
Mono,
Luau,
LSLLuau
}

private static string ScriptTargetToString(ScriptTarget target) => target switch
{
ScriptTarget.LSL2 => "lsl2",
ScriptTarget.Mono => "mono",
ScriptTarget.Luau => "luau",
ScriptTarget.LSLLuau => "lsl-luau",
_ => throw new ArgumentOutOfRangeException(nameof(target), target, "Unknown script target")
};
Comment thread
mercurylinden marked this conversation as resolved.
}
}

5 changes: 5 additions & 0 deletions LibreMetaverse/Inventory/InventoryManager.Handlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ private async Task<OSD> PostCapAsync(Uri uri, OSD payload, CancellationToken can
var result = await Client.HttpCapsClient.PostAsync(uri, OSDFormat.Xml, payload, cancellationToken, progress).ConfigureAwait(false);
var responseData = result.data ?? throw new InvalidOperationException("Empty response from capability POST");

if (result.response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new System.Net.Http.HttpRequestException($"{result.response.StatusCode}({(int)result.response.StatusCode}): {System.Text.Encoding.UTF8.GetString(responseData)}");
}
Comment on lines +47 to +50

try { return OSDParser.Deserialize(responseData); }
catch (Exception ex) { throw new InvalidOperationException($"Failed to parse capability response: {ex.Message}", ex); }
}
Expand Down
59 changes: 59 additions & 0 deletions LibreMetaverse/Messages/LindenMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2736,6 +2736,65 @@ public void Deserialize(OSDMap map)
}
}

/// <summary>Request POSTed to the CreateTaskInventoryItem capability to create a new item in a task's inventory.</summary>
public class CreateTaskInventoryItemMessage : IMessage
{
/// <summary>UUID of the in-world object whose inventory will receive the new item</summary>
public UUID ObjectID;
public AssetType AssetType;
/// <summary>Script sub-type: 0 = LSL, 1 = Lua (SST_LUA); ignored for non-script types</summary>
public int SubType;
public string Name;
public string Description;
public Permissions Permissions;
/// <summary>VM runtime for scripts: "lsl2", "mono", or "luau" (required for Lua); null = omit (use for notecards)</summary>
public string? Vm;
/// <summary> Whether the script starts enabled (default: true)</summary>
public bool? Enabled;
/// <summary>Optional template asset; UUID.Zero = server default</summary>
public UUID TemplateID;
/// <summary>Optional initial text content for notecards; null/empty = omitted</summary>
public string? Text;

public OSDMap Serialize()
{
var paramsMap = new OSDMap();
if (Vm != null)
{
paramsMap["vm"] = OSD.FromString(Vm);
}
if (Enabled.HasValue)
{
paramsMap["enabled"] = OSD.FromBoolean(Enabled.Value);
}
if (TemplateID != UUID.Zero)
paramsMap["template_id"] = OSD.FromUUID(TemplateID);
if (!string.IsNullOrEmpty(Text))
paramsMap["text"] = OSD.FromString(Text);

return new OSDMap
{
["object_id"] = OSD.FromUUID(ObjectID),
["asset_type"] = OSD.FromInteger((int)AssetType),
["sub_type"] = OSD.FromInteger(SubType),
["name"] = OSD.FromString(Name),
["description"] = OSD.FromString(Description),
["permissions"] = new OSDMap
{
["base"] = OSD.FromInteger((int)Permissions.BaseMask),
["owner"] = OSD.FromInteger((int)Permissions.OwnerMask),
["everyone"] = OSD.FromInteger((int)Permissions.EveryoneMask),
["group"] = OSD.FromInteger((int)Permissions.GroupMask),
["next_owner"] = OSD.FromInteger((int)Permissions.NextOwnerMask)
},
["params"] = paramsMap
};
}

// Request-only message; Deserialize is not used
public void Deserialize(OSDMap map) => throw new NotSupportedException("CreateTaskInventoryItemMessage is request-only");
}


/// <summary>
/// Metadata POSTed to the SendPostcard capability. This is the first step of a two-phase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public override async Task<string> ExecuteAsync(string[] args, UUID fromAgentId)

using var uploadCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var (uploadSuccess, _, compileSuccess, _, itemId, assetId) = await Client.Inventory
.RequestUpdateScriptAgentInventoryAsync(Encoding.UTF8.GetBytes(body), createdItem.UUID, true, uploadCts.Token).ConfigureAwait(false);
.RequestUpdateScriptAgentInventoryAsync(Encoding.UTF8.GetBytes(body), createdItem.UUID, InventoryManager.ScriptTarget.Mono, uploadCts.Token).ConfigureAwait(false);

var log = $"Filename: {file}";
log += uploadSuccess ? $" Script successfully uploaded, ItemID {itemId} AssetID {assetId}" : $" Script failed to upload, ItemID {itemId}";
Expand Down