@adiholick/nvstudio-mcp 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ --!strict
2
+ -- ============================================================================
3
+ -- Skill Module: batch_create_instances
4
+ -- Deskripsi: Pembangun Hierarki Instance Massal & Deklaratif (Tree / Batch List)
5
+ -- ============================================================================
6
+
7
+ local HttpService = game:GetService("HttpService")
8
+
9
+ return function(targetInstance: Instance?, data: any, ctx: any, targetPath: string?)
10
+ local success, config = pcall(function()
11
+ if type(data) == "table" then return data end
12
+ return HttpService:JSONDecode(tostring(data))
13
+ end)
14
+
15
+ if not success or type(config) ~= "table" then
16
+ return { status = "error", error = "Format data JSON tidak valid untuk batch_create_instances." }
17
+ end
18
+
19
+ local setPropsFunc = require(script.Parent:WaitForChild("set_properties"))
20
+ local defaultParent = targetInstance or game.Workspace
21
+
22
+ if config.target and ctx and ctx.getInstanceFromPath then
23
+ local resolved = ctx.getInstanceFromPath(config.target)
24
+ if resolved then defaultParent = resolved end
25
+ end
26
+
27
+ local createdList = {}
28
+ local errors = {}
29
+ local createdCount = 0
30
+
31
+ local function applyProps(inst: Instance, props: any)
32
+ if not props or type(props) ~= "table" or not next(props) then return end
33
+ local propJson = HttpService:JSONEncode(props)
34
+ local res = setPropsFunc(inst, propJson, ctx, inst:GetFullName())
35
+ if res and res.status == "error" then
36
+ table.insert(errors, string.format("[%s] Error properti: %s", inst.Name, tostring(res.result or res.error)))
37
+ end
38
+ end
39
+
40
+ -- 1. MODE HIERARCHICAL TREE (Pohon Bersarang)
41
+ local function buildTreeNode(parentInst: Instance, node: any): Instance?
42
+ local className = node.className or node.ClassName
43
+ if not className or type(className) ~= "string" then
44
+ table.insert(errors, "Node pohon tidak memiliki ClassName yang valid.")
45
+ return nil
46
+ end
47
+
48
+ local ok, inst = pcall(function()
49
+ local newObj = Instance.new(className)
50
+ if node.name or node.Name then
51
+ newObj.Name = tostring(node.name or node.Name)
52
+ end
53
+ newObj.Parent = parentInst
54
+ return newObj
55
+ end)
56
+
57
+ if not ok or not inst then
58
+ table.insert(errors, string.format("Gagal membuat instance '%s': %s", className, tostring(inst)))
59
+ return nil
60
+ end
61
+
62
+ createdCount = createdCount + 1
63
+ table.insert(createdList, inst:GetFullName())
64
+
65
+ -- Terapkan properti
66
+ applyProps(inst, node.properties or node.Properties)
67
+
68
+ -- Rekursif untuk seluruh child
69
+ local children = node.children or node.Children
70
+ if children and type(children) == "table" then
71
+ for _, childNode in ipairs(children) do
72
+ buildTreeNode(inst, childNode)
73
+ end
74
+ end
75
+
76
+ return inst
77
+ end
78
+
79
+ -- Eksekusi Mode Tree jika disediakan
80
+ if config.tree and type(config.tree) == "table" then
81
+ buildTreeNode(defaultParent, config.tree)
82
+ end
83
+
84
+ -- 2. MODE FLAT ARRAY (Daftar Instance Terpisah)
85
+ local instancesArray = config.instances or config.items
86
+ if instancesArray and type(instancesArray) == "table" then
87
+ for idx, item in ipairs(instancesArray) do
88
+ local parentInst = defaultParent
89
+ if item.parent and ctx and ctx.getInstanceFromPath then
90
+ local resParent = ctx.getInstanceFromPath(item.parent)
91
+ if resParent then parentInst = resParent end
92
+ end
93
+
94
+ local className = item.className or item.ClassName
95
+ if className then
96
+ local ok, inst = pcall(function()
97
+ local newObj = Instance.new(className)
98
+ if item.name or item.Name then
99
+ newObj.Name = tostring(item.name or item.Name)
100
+ end
101
+ newObj.Parent = parentInst
102
+ return newObj
103
+ end)
104
+
105
+ if ok and inst then
106
+ createdCount = createdCount + 1
107
+ table.insert(createdList, inst:GetFullName())
108
+ applyProps(inst, item.properties or item.Properties)
109
+ else
110
+ table.insert(errors, string.format("Item #%d (%s) gagal dibuat: %s", idx, tostring(className), tostring(inst)))
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ local status = (createdCount > 0 or #errors == 0) and "success" or "error"
117
+ return {
118
+ status = status,
119
+ result = {
120
+ totalCreated = createdCount,
121
+ created = createdList,
122
+ hasErrors = (#errors > 0),
123
+ errors = errors,
124
+ message = string.format("Berhasil membuat %d instance secara batch.", createdCount)
125
+ }
126
+ }
127
+ end
128
+
@@ -0,0 +1,69 @@
1
+ --!strict
2
+ -- ============================================================================
3
+ -- Skill Module: batch_set_properties
4
+ -- Deskripsi: Modifikasi Properti Massal untuk Beberapa Instance Sekaligus
5
+ -- ============================================================================
6
+
7
+ local HttpService = game:GetService("HttpService")
8
+
9
+ return function(targetInstance: Instance?, data: any, ctx: any, targetPath: string?)
10
+ local success, config = pcall(function()
11
+ if type(data) == "table" then return data end
12
+ return HttpService:JSONDecode(tostring(data))
13
+ end)
14
+
15
+ if not success or type(config) ~= "table" then
16
+ return { status = "error", error = "Format data JSON tidak valid untuk batch_set_properties." }
17
+ end
18
+
19
+ local setPropsFunc = require(script.Parent:WaitForChild("set_properties"))
20
+ local updates = config.updates or config
21
+ if type(updates) ~= "table" then
22
+ return { status = "error", error = "Parameter 'updates' harus berupa array pembaruan properti." }
23
+ end
24
+
25
+ local updatedCount = 0
26
+ local successfulTargets = {}
27
+ local errors = {}
28
+
29
+ for idx, updateItem in ipairs(updates) do
30
+ local inst = targetInstance
31
+ local instPath = updateItem.target or updateItem.path
32
+ if instPath and ctx and ctx.getInstanceFromPath then
33
+ local resolved = ctx.getInstanceFromPath(instPath)
34
+ if resolved then
35
+ inst = resolved
36
+ else
37
+ table.insert(errors, string.format("Item #%d: Target path '%s' tidak ditemukan.", idx, tostring(instPath)))
38
+ continue
39
+ end
40
+ end
41
+
42
+ local props = updateItem.properties or updateItem.props
43
+ if inst and props and type(props) == "table" then
44
+ local propJson = HttpService:JSONEncode(props)
45
+ local res = setPropsFunc(inst, propJson, ctx, inst:GetFullName())
46
+ if res and res.status == "error" then
47
+ table.insert(errors, string.format("[%s]: %s", inst.Name, tostring(res.result or res.error)))
48
+ else
49
+ updatedCount = updatedCount + 1
50
+ table.insert(successfulTargets, inst:GetFullName())
51
+ end
52
+ else
53
+ table.insert(errors, string.format("Item #%d: Instance atau properties tidak valid.", idx))
54
+ end
55
+ end
56
+
57
+ local status = (updatedCount > 0 or #errors == 0) and "success" or "error"
58
+ return {
59
+ status = status,
60
+ result = {
61
+ totalUpdated = updatedCount,
62
+ updatedInstances = successfulTargets,
63
+ hasErrors = (#errors > 0),
64
+ errors = errors,
65
+ message = string.format("Berhasil memperbarui properti untuk %d instance.", updatedCount)
66
+ }
67
+ }
68
+ end
69
+
@@ -40,9 +40,32 @@ return function(targetInstance, data, ctx, targetPath)
40
40
  tryProp(prop)
41
41
  end
42
42
 
43
- -- Attributes & Tags
43
+ -- Attributes & Tags (sanitasi tipe non-JSON agar tidak throw error di JSONEncode)
44
44
  pcall(function()
45
- props.Attributes = targetInstance:GetAttributes()
45
+ local rawAttrs = targetInstance:GetAttributes()
46
+ local safeAttrs = {}
47
+ for k, val in pairs(rawAttrs) do
48
+ if typeof(val) == "Vector3" then
49
+ safeAttrs[k] = {math.round(val.X*100)/100, math.round(val.Y*100)/100, math.round(val.Z*100)/100}
50
+ elseif typeof(val) == "Color3" then
51
+ safeAttrs[k] = {math.round(val.R * 255), math.round(val.G * 255), math.round(val.B * 255)}
52
+ elseif typeof(val) == "CFrame" then
53
+ safeAttrs[k] = tostring(val)
54
+ elseif typeof(val) == "EnumItem" then
55
+ safeAttrs[k] = val.Name
56
+ elseif typeof(val) == "UDim2" then
57
+ safeAttrs[k] = {val.X.Scale, val.X.Offset, val.Y.Scale, val.Y.Offset}
58
+ elseif typeof(val) == "Instance" then
59
+ safeAttrs[k] = val:GetFullName()
60
+ elseif typeof(val) == "DateTime" then
61
+ safeAttrs[k] = val:ToIsoDate()
62
+ elseif typeof(val) == "string" or typeof(val) == "number" or typeof(val) == "boolean" then
63
+ safeAttrs[k] = val
64
+ else
65
+ safeAttrs[k] = tostring(val)
66
+ end
67
+ end
68
+ props.Attributes = safeAttrs
46
69
  end)
47
70
  pcall(function()
48
71
  props.Tags = CollectionService:GetTags(targetInstance)
@@ -1,23 +1,96 @@
1
- return function(targetInstance, data, ctx, targetPath)
2
- local InsertService = game:GetService("InsertService")
3
- local rawId = tostring(data or ""):gsub("rbxassetid://", ""):gsub("%s+", "")
1
+ --!strict
2
+ -- ============================================================================
3
+ -- Skill Module: insert_asset (Upgraded with Asset Security Scanner & Script Stripper)
4
+ -- ============================================================================
5
+
6
+ local HttpService = game:GetService("HttpService")
7
+ local InsertService = game:GetService("InsertService")
8
+
9
+ local SecurityScanner = require(script.Parent:WaitForChild("security_scanner"))
10
+
11
+ return function(targetInstance: Instance?, data: any, ctx: any, targetPath: string?)
12
+ local assetIdStr = ""
13
+ local stripScripts = true -- Default TRUE demi keamanan mutlak
14
+ local scanSecurity = true
15
+
16
+ if typeof(data) == "string" then
17
+ local success, parsed = pcall(function()
18
+ return HttpService:JSONDecode(data)
19
+ end)
20
+ if success and typeof(parsed) == "table" then
21
+ assetIdStr = tostring(parsed.asset_id or parsed.assetId or "")
22
+ if parsed.strip_scripts ~= nil then stripScripts = parsed.strip_scripts end
23
+ if parsed.scan_security ~= nil then scanSecurity = parsed.scan_security end
24
+ else
25
+ assetIdStr = data
26
+ end
27
+ elseif typeof(data) == "table" then
28
+ assetIdStr = tostring(data.asset_id or data.assetId or "")
29
+ if data.strip_scripts ~= nil then stripScripts = data.strip_scripts end
30
+ if data.scan_security ~= nil then scanSecurity = data.scan_security end
31
+ end
32
+
33
+ local rawId = string.gsub(assetIdStr, "rbxassetid://", "")
34
+ rawId = string.gsub(rawId, "%s+", "")
4
35
  local assetId = tonumber(rawId)
5
- if not assetId then
36
+
37
+ if not assetId or assetId <= 0 then
6
38
  return { status = "error", error = "Asset ID harus berupa angka yang valid." }
7
39
  end
8
-
40
+
9
41
  local success, model = pcall(function()
10
42
  return InsertService:LoadAsset(assetId)
11
43
  end)
12
-
13
- if success and model then
14
- if targetInstance == game or targetInstance.ClassName == "DataModel" then
15
- model.Parent = game.Workspace
44
+
45
+ if not success or not model then
46
+ return {
47
+ status = "error",
48
+ error = "Gagal memuat aset. Pastikan Asset ID benar dan model berstatus publik di Creator Store."
49
+ }
50
+ end
51
+
52
+ local securityNotes = {}
53
+ local removedScripts = {}
54
+
55
+ -- 1. Scan keamanan jika diaktifkan
56
+ if scanSecurity then
57
+ local scanResult = SecurityScanner.scanInstance(model, true)
58
+ if scanResult.riskLevel == "DANGEROUS" then
59
+ table.insert(securityNotes, string.format("⚠️ PERINGATAN: Aset ini memiliki %d ancaman BERBAHAYA (Backdoor/Obfuscation/Webhook)!", scanResult.highThreats))
60
+ end
61
+ end
62
+
63
+ -- 2. Sanitasi: Hapus skrip jika strip_scripts = true
64
+ if stripScripts then
65
+ removedScripts = SecurityScanner.stripScripts(model)
66
+ if #removedScripts > 0 then
67
+ table.insert(securityNotes, string.format("🛡️ Keamanan: %d skrip bawaan telah dihapus secara otomatis demi keamanan game Anda.", #removedScripts))
16
68
  else
17
- model.Parent = targetInstance
69
+ table.insert(securityNotes, "🛡️ Keamanan: Aset bersih, tidak ditemukan skrip berbahaya.")
18
70
  end
19
- return { status = "success", result = "Aset berhasil dimasukkan ke " .. model.Parent.Name .. "." }
20
71
  else
21
- return { status = "error", error = "Gagal memuat aset. Pastikan Asset ID benar dan akun/plugin Anda memiliki izin (Ownership/Public)." }
72
+ table.insert(securityNotes, "ℹ️ Catatan: strip_scripts = false, skrip bawaan model dipertahankan.")
73
+ end
74
+
75
+ -- 3. Masukkan model ke target
76
+ local parentTarget = targetInstance
77
+ if not parentTarget or parentTarget == game or parentTarget.ClassName == "DataModel" then
78
+ parentTarget = game.Workspace
22
79
  end
80
+
81
+ model.Parent = parentTarget
82
+
83
+ return {
84
+ status = "success",
85
+ result = {
86
+ message = string.format("Aset '%s' (ID: %d) berhasil dimasukkan ke %s.", model.Name, assetId, parentTarget.Name),
87
+ assetName = model.Name,
88
+ assetId = assetId,
89
+ parent = parentTarget:GetFullName(),
90
+ stripScriptsApplied = stripScripts,
91
+ removedScriptsCount = #removedScripts,
92
+ removedScripts = removedScripts,
93
+ securityNotes = table.concat(securityNotes, "\n"),
94
+ }
95
+ }
23
96
  end