dominus-cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/index.js +4700 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.js +907 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +78 -0
- package/plugin/default.project.json +6 -0
- package/plugin/src/Executor.lua +116 -0
- package/plugin/src/Explorer.lua +251 -0
- package/plugin/src/Properties.lua +424 -0
- package/plugin/src/Protocol.lua +30 -0
- package/plugin/src/Reflection.lua +137 -0
- package/plugin/src/TestRunner.lua +141 -0
- package/plugin/src/UIBuilder.lua +247 -0
- package/plugin/src/Watcher.lua +85 -0
- package/plugin/src/WsClient.lua +81 -0
- package/plugin/src/init.server.lua +315 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Properties module: read/write instance properties
|
|
3
|
+
Uses ReflectionService to determine expected types for smart coercion.
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local Explorer = require(script.Parent.Explorer)
|
|
7
|
+
local ReflectionService = game:GetService("ReflectionService")
|
|
8
|
+
|
|
9
|
+
local Properties = {}
|
|
10
|
+
|
|
11
|
+
local IGNORED_PROPERTIES = {
|
|
12
|
+
Parent = true,
|
|
13
|
+
ClassName = true,
|
|
14
|
+
Archivable = true,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
-- Cache property type maps per class to avoid repeated reflection calls
|
|
18
|
+
local typeCache = {}
|
|
19
|
+
|
|
20
|
+
local function getPropertyTypeMap(className)
|
|
21
|
+
if typeCache[className] then return typeCache[className] end
|
|
22
|
+
|
|
23
|
+
local typeMap = {}
|
|
24
|
+
local ok, props = pcall(function()
|
|
25
|
+
return ReflectionService:GetPropertiesOfClass(className)
|
|
26
|
+
end)
|
|
27
|
+
if ok and props then
|
|
28
|
+
for _, prop in props do
|
|
29
|
+
if prop.ValueType then
|
|
30
|
+
typeMap[prop.Name] = tostring(prop.ValueType)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
typeCache[className] = typeMap
|
|
35
|
+
return typeMap
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
function Properties.get(path)
|
|
39
|
+
local instance = Explorer.resolveInstance(path)
|
|
40
|
+
if not instance then
|
|
41
|
+
return { success = false, error = "Instance not found: " .. path }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
local properties = {}
|
|
45
|
+
|
|
46
|
+
local ok, apiData = pcall(function()
|
|
47
|
+
local info = {}
|
|
48
|
+
local propData = ReflectionService:GetPropertiesOfClass(instance.ClassName)
|
|
49
|
+
for _, prop in propData do
|
|
50
|
+
if not IGNORED_PROPERTIES[prop.Name] then
|
|
51
|
+
local valueOk, value = pcall(function()
|
|
52
|
+
return instance[prop.Name]
|
|
53
|
+
end)
|
|
54
|
+
if valueOk then
|
|
55
|
+
table.insert(info, {
|
|
56
|
+
name = prop.Name,
|
|
57
|
+
value = tostring(value),
|
|
58
|
+
type = prop.ValueType and tostring(prop.ValueType) or typeof(value),
|
|
59
|
+
category = prop.Category or "Other",
|
|
60
|
+
})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
return info
|
|
65
|
+
end)
|
|
66
|
+
|
|
67
|
+
if ok then
|
|
68
|
+
properties = apiData
|
|
69
|
+
else
|
|
70
|
+
local commonProps = {"Name", "Position", "Size", "Color", "Material", "Transparency", "Anchored", "CanCollide"}
|
|
71
|
+
for _, propName in commonProps do
|
|
72
|
+
local valOk, val = pcall(function()
|
|
73
|
+
return instance[propName]
|
|
74
|
+
end)
|
|
75
|
+
if valOk and val ~= nil then
|
|
76
|
+
table.insert(properties, {
|
|
77
|
+
name = propName,
|
|
78
|
+
value = tostring(val),
|
|
79
|
+
type = typeof(val),
|
|
80
|
+
})
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return { properties = properties }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
--[[
|
|
89
|
+
Smart type coercion: converts JSON-friendly values into Roblox types.
|
|
90
|
+
Uses the expected property type from ReflectionService when available.
|
|
91
|
+
]]
|
|
92
|
+
local function coerceValue(instance, key, value)
|
|
93
|
+
local expectedType = nil
|
|
94
|
+
local typeMap = getPropertyTypeMap(instance.ClassName)
|
|
95
|
+
expectedType = typeMap[key]
|
|
96
|
+
|
|
97
|
+
-- If we don't know the expected type, try reading the current value
|
|
98
|
+
if not expectedType then
|
|
99
|
+
local curOk, curVal = pcall(function() return instance[key] end)
|
|
100
|
+
if curOk and curVal ~= nil then
|
|
101
|
+
expectedType = typeof(curVal)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- ═══════════════════════════════════════════
|
|
106
|
+
-- UDim2 (GUI Size, Position, etc.)
|
|
107
|
+
-- ═══════════════════════════════════════════
|
|
108
|
+
if expectedType == "UDim2" then
|
|
109
|
+
if type(value) == "table" then
|
|
110
|
+
-- {xScale, xOffset, yScale, yOffset} or named keys
|
|
111
|
+
if value.XScale or value.xScale or value.X then
|
|
112
|
+
return UDim2.new(
|
|
113
|
+
value.XScale or value.xScale or value.X and value.X.Scale or 0,
|
|
114
|
+
value.XOffset or value.xOffset or value.X and value.X.Offset or 0,
|
|
115
|
+
value.YScale or value.yScale or value.Y and value.Y.Scale or 0,
|
|
116
|
+
value.YOffset or value.yOffset or value.Y and value.Y.Offset or 0
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
-- {X = {Scale, Offset}, Y = {Scale, Offset}}
|
|
120
|
+
if value.X and type(value.X) == "table" then
|
|
121
|
+
return UDim2.new(
|
|
122
|
+
value.X.Scale or value.X[1] or 0,
|
|
123
|
+
value.X.Offset or value.X[2] or 0,
|
|
124
|
+
value.Y and (value.Y.Scale or value.Y[1]) or 0,
|
|
125
|
+
value.Y and (value.Y.Offset or value.Y[2]) or 0
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
-- Array: {xScale, xOffset, yScale, yOffset}
|
|
129
|
+
if value[1] ~= nil then
|
|
130
|
+
return UDim2.new(value[1] or 0, value[2] or 0, value[3] or 0, value[4] or 0)
|
|
131
|
+
end
|
|
132
|
+
-- fromScale shorthand: {scale_x, scale_y} when only 2 elements
|
|
133
|
+
return UDim2.new(0, 0, 0, 0)
|
|
134
|
+
end
|
|
135
|
+
if type(value) == "string" then
|
|
136
|
+
-- "0.5, 0, 0.5, 0" format
|
|
137
|
+
local a, b, c, d = value:match("([%d%.%-]+)%s*,%s*([%d%.%-]+)%s*,%s*([%d%.%-]+)%s*,%s*([%d%.%-]+)")
|
|
138
|
+
if a then return UDim2.new(tonumber(a), tonumber(b), tonumber(c), tonumber(d)) end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
-- ═══════════════════════════════════════════
|
|
143
|
+
-- UDim
|
|
144
|
+
-- ═══════════════════════════════════════════
|
|
145
|
+
if expectedType == "UDim" then
|
|
146
|
+
if type(value) == "table" then
|
|
147
|
+
return UDim.new(value.Scale or value[1] or 0, value.Offset or value[2] or 0)
|
|
148
|
+
end
|
|
149
|
+
if type(value) == "number" then
|
|
150
|
+
return UDim.new(value, 0)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
-- ═══════════════════════════════════════════
|
|
155
|
+
-- Vector3
|
|
156
|
+
-- ═══════════════════════════════════════════
|
|
157
|
+
if expectedType == "Vector3" then
|
|
158
|
+
if type(value) == "table" then
|
|
159
|
+
return Vector3.new(
|
|
160
|
+
value.X or value.x or value[1] or 0,
|
|
161
|
+
value.Y or value.y or value[2] or 0,
|
|
162
|
+
value.Z or value.z or value[3] or 0
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
-- ═══════════════════════════════════════════
|
|
168
|
+
-- Vector2
|
|
169
|
+
-- ═══════════════════════════════════════════
|
|
170
|
+
if expectedType == "Vector2" then
|
|
171
|
+
if type(value) == "table" then
|
|
172
|
+
return Vector2.new(
|
|
173
|
+
value.X or value.x or value[1] or 0,
|
|
174
|
+
value.Y or value.y or value[2] or 0
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
-- ═══════════════════════════════════════════
|
|
180
|
+
-- Color3
|
|
181
|
+
-- ═══════════════════════════════════════════
|
|
182
|
+
if expectedType == "Color3" then
|
|
183
|
+
if type(value) == "string" then
|
|
184
|
+
if value:sub(1, 1) == "#" then
|
|
185
|
+
return Color3.fromHex(value)
|
|
186
|
+
end
|
|
187
|
+
if value:match("^rgb") then
|
|
188
|
+
local r, g, b = value:match("(%d+)%D+(%d+)%D+(%d+)")
|
|
189
|
+
if r then return Color3.fromRGB(tonumber(r), tonumber(g), tonumber(b)) end
|
|
190
|
+
end
|
|
191
|
+
local ok, bc = pcall(BrickColor.new, value)
|
|
192
|
+
if ok then return bc.Color end
|
|
193
|
+
end
|
|
194
|
+
if type(value) == "table" then
|
|
195
|
+
-- {R, G, B} 0-1 or {r, g, b}
|
|
196
|
+
if value.R or value.r then
|
|
197
|
+
return Color3.new(value.R or value.r or 0, value.G or value.g or 0, value.B or value.b or 0)
|
|
198
|
+
end
|
|
199
|
+
-- {red, green, blue} 0-255
|
|
200
|
+
if value.red or value.Red then
|
|
201
|
+
return Color3.fromRGB(value.red or value.Red or 0, value.green or value.Green or 0, value.blue or value.Blue or 0)
|
|
202
|
+
end
|
|
203
|
+
-- Array [r, g, b]
|
|
204
|
+
if value[1] then
|
|
205
|
+
if value[1] > 1 or value[2] > 1 or value[3] > 1 then
|
|
206
|
+
return Color3.fromRGB(value[1], value[2], value[3])
|
|
207
|
+
end
|
|
208
|
+
return Color3.new(value[1], value[2], value[3])
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
-- ═══════════════════════════════════════════
|
|
214
|
+
-- BrickColor
|
|
215
|
+
-- ═══════════════════════════════════════════
|
|
216
|
+
if expectedType == "BrickColor" then
|
|
217
|
+
if type(value) == "string" then
|
|
218
|
+
return BrickColor.new(value)
|
|
219
|
+
end
|
|
220
|
+
if type(value) == "number" then
|
|
221
|
+
return BrickColor.new(value)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
-- ═══════════════════════════════════════════
|
|
226
|
+
-- CFrame
|
|
227
|
+
-- ═══════════════════════════════════════════
|
|
228
|
+
if expectedType == "CFrame" then
|
|
229
|
+
if type(value) == "table" then
|
|
230
|
+
if value.Position or value.position then
|
|
231
|
+
local pos = value.Position or value.position
|
|
232
|
+
local cf = CFrame.new(pos.x or pos.X or pos[1] or 0, pos.y or pos.Y or pos[2] or 0, pos.z or pos.Z or pos[3] or 0)
|
|
233
|
+
if value.Rotation or value.rotation then
|
|
234
|
+
local rot = value.Rotation or value.rotation
|
|
235
|
+
cf = cf * CFrame.Angles(
|
|
236
|
+
math.rad(rot.x or rot.X or rot[1] or 0),
|
|
237
|
+
math.rad(rot.y or rot.Y or rot[2] or 0),
|
|
238
|
+
math.rad(rot.z or rot.Z or rot[3] or 0)
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
return cf
|
|
242
|
+
end
|
|
243
|
+
-- Simple {x, y, z}
|
|
244
|
+
if value[1] then
|
|
245
|
+
return CFrame.new(value[1], value[2] or 0, value[3] or 0)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
-- ═══════════════════════════════════════════
|
|
251
|
+
-- Rect (for GUI ImageRectOffset, etc.)
|
|
252
|
+
-- ═══════════════════════════════════════════
|
|
253
|
+
if expectedType == "Rect" then
|
|
254
|
+
if type(value) == "table" then
|
|
255
|
+
return Rect.new(value[1] or value.Min and value.Min[1] or 0, value[2] or value.Min and value.Min[2] or 0,
|
|
256
|
+
value[3] or value.Max and value.Max[1] or 0, value[4] or value.Max and value.Max[2] or 0)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
-- ═══════════════════════════════════════════
|
|
261
|
+
-- NumberRange
|
|
262
|
+
-- ═══════════════════════════════════════════
|
|
263
|
+
if expectedType == "NumberRange" then
|
|
264
|
+
if type(value) == "table" then
|
|
265
|
+
return NumberRange.new(value.Min or value[1] or 0, value.Max or value[2] or 1)
|
|
266
|
+
end
|
|
267
|
+
if type(value) == "number" then
|
|
268
|
+
return NumberRange.new(value)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
-- ═══════════════════════════════════════════
|
|
273
|
+
-- NumberSequence (for transparency gradients, etc.)
|
|
274
|
+
-- ═══════════════════════════════════════════
|
|
275
|
+
if expectedType == "NumberSequence" then
|
|
276
|
+
if type(value) == "table" then
|
|
277
|
+
if value[1] and type(value[1]) == "table" then
|
|
278
|
+
local keypoints = {}
|
|
279
|
+
for _, kp in value do
|
|
280
|
+
table.insert(keypoints, NumberSequenceKeypoint.new(kp.Time or kp[1] or 0, kp.Value or kp[2] or 0, kp.Envelope or kp[3] or 0))
|
|
281
|
+
end
|
|
282
|
+
return NumberSequence.new(keypoints)
|
|
283
|
+
end
|
|
284
|
+
if #value == 2 and type(value[1]) == "number" then
|
|
285
|
+
return NumberSequence.new(value[1], value[2])
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
if type(value) == "number" then
|
|
289
|
+
return NumberSequence.new(value)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
-- ═══════════════════════════════════════════
|
|
294
|
+
-- ColorSequence
|
|
295
|
+
-- ═══════════════════════════════════════════
|
|
296
|
+
if expectedType == "ColorSequence" then
|
|
297
|
+
if type(value) == "table" then
|
|
298
|
+
if value[1] and type(value[1]) == "table" then
|
|
299
|
+
local keypoints = {}
|
|
300
|
+
for _, kp in value do
|
|
301
|
+
local color = Color3.new(1, 1, 1)
|
|
302
|
+
if kp.Color then
|
|
303
|
+
if type(kp.Color) == "string" and kp.Color:sub(1,1) == "#" then
|
|
304
|
+
color = Color3.fromHex(kp.Color)
|
|
305
|
+
elseif type(kp.Color) == "table" then
|
|
306
|
+
color = Color3.new(kp.Color[1] or 0, kp.Color[2] or 0, kp.Color[3] or 0)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
table.insert(keypoints, ColorSequenceKeypoint.new(kp.Time or kp[1] or 0, color))
|
|
310
|
+
end
|
|
311
|
+
return ColorSequence.new(keypoints)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
-- ═══════════════════════════════════════════
|
|
317
|
+
-- Font
|
|
318
|
+
-- ═══════════════════════════════════════════
|
|
319
|
+
if expectedType == "Font" then
|
|
320
|
+
if type(value) == "table" then
|
|
321
|
+
local family = value.Family or value.family or "rbxasset://fonts/families/SourceSansPro.json"
|
|
322
|
+
local weight = Enum.FontWeight.Regular
|
|
323
|
+
local style = Enum.FontStyle.Normal
|
|
324
|
+
if value.Weight or value.weight then
|
|
325
|
+
pcall(function() weight = Enum.FontWeight[value.Weight or value.weight] end)
|
|
326
|
+
end
|
|
327
|
+
if value.Style or value.style then
|
|
328
|
+
pcall(function() style = Enum.FontStyle[value.Style or value.style] end)
|
|
329
|
+
end
|
|
330
|
+
return Font.new(family, weight, style)
|
|
331
|
+
end
|
|
332
|
+
if type(value) == "string" then
|
|
333
|
+
return Font.new(value)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
-- ═══════════════════════════════════════════
|
|
338
|
+
-- EnumItem (any enum property)
|
|
339
|
+
-- ═══════════════════════════════════════════
|
|
340
|
+
if type(value) == "string" then
|
|
341
|
+
local currentVal = nil
|
|
342
|
+
pcall(function() currentVal = instance[key] end)
|
|
343
|
+
if typeof(currentVal) == "EnumItem" then
|
|
344
|
+
local enumType = tostring(currentVal.EnumType)
|
|
345
|
+
local enumOk, enumVal = pcall(function()
|
|
346
|
+
return Enum[enumType][value]
|
|
347
|
+
end)
|
|
348
|
+
if enumOk then return enumVal end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
-- ═══════════════════════════════════════════
|
|
353
|
+
-- Fallback: type-agnostic guesses (no expectedType known)
|
|
354
|
+
-- ═══════════════════════════════════════════
|
|
355
|
+
if type(value) == "table" and not expectedType then
|
|
356
|
+
-- UDim2 guess: has XScale/YScale keys
|
|
357
|
+
if value.XScale or value.xScale then
|
|
358
|
+
return UDim2.new(
|
|
359
|
+
value.XScale or value.xScale or 0, value.XOffset or value.xOffset or 0,
|
|
360
|
+
value.YScale or value.yScale or 0, value.YOffset or value.yOffset or 0
|
|
361
|
+
)
|
|
362
|
+
end
|
|
363
|
+
-- Vector3 guess: 3 number elements or X/Y/Z keys
|
|
364
|
+
if (value.X or value.x) and (value.Y or value.y) and (value.Z or value.z) then
|
|
365
|
+
return Vector3.new(value.X or value.x, value.Y or value.y, value.Z or value.z)
|
|
366
|
+
end
|
|
367
|
+
if value[1] and value[2] and value[3] and not value[4] then
|
|
368
|
+
return Vector3.new(value[1], value[2], value[3])
|
|
369
|
+
end
|
|
370
|
+
-- Color3 guess: R/G/B keys
|
|
371
|
+
if value.R or value.r then
|
|
372
|
+
return Color3.new(value.R or value.r or 0, value.G or value.g or 0, value.B or value.b or 0)
|
|
373
|
+
end
|
|
374
|
+
-- Vector2 guess: 2 number elements
|
|
375
|
+
if value[1] and value[2] and not value[3] then
|
|
376
|
+
return Vector2.new(value[1], value[2])
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
-- String-based color on color properties (fallback)
|
|
381
|
+
if type(value) == "string" and (key == "Color" or key == "Color3" or key:match("Color$")) then
|
|
382
|
+
if value:sub(1, 1) == "#" then
|
|
383
|
+
return Color3.fromHex(value)
|
|
384
|
+
end
|
|
385
|
+
local ok, bc = pcall(BrickColor.new, value)
|
|
386
|
+
if ok then return bc.Color end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
if type(value) == "string" and key == "BrickColor" then
|
|
390
|
+
return BrickColor.new(value)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
return value
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
function Properties.set(path, propertiesToSet)
|
|
397
|
+
local instance = Explorer.resolveInstance(path)
|
|
398
|
+
if not instance then
|
|
399
|
+
return { success = false, error = "Instance not found: " .. path }
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
local errors = {}
|
|
403
|
+
local set = {}
|
|
404
|
+
|
|
405
|
+
for key, value in propertiesToSet do
|
|
406
|
+
local coerced = coerceValue(instance, key, value)
|
|
407
|
+
local ok, err = pcall(function()
|
|
408
|
+
instance[key] = coerced
|
|
409
|
+
end)
|
|
410
|
+
if ok then
|
|
411
|
+
table.insert(set, key)
|
|
412
|
+
else
|
|
413
|
+
table.insert(errors, key .. ": " .. tostring(err))
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
if #errors > 0 then
|
|
418
|
+
return { success = false, error = table.concat(errors, "; "), set = set }
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
return { success = true, set = set }
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
return Properties
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Message protocol: JSON encoding/decoding with unique ID generation
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local HttpService = game:GetService("HttpService")
|
|
6
|
+
|
|
7
|
+
local Protocol = {}
|
|
8
|
+
|
|
9
|
+
function Protocol.encode(data)
|
|
10
|
+
return HttpService:JSONEncode(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
function Protocol.decode(json)
|
|
14
|
+
return HttpService:JSONDecode(json)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
function Protocol.generateId()
|
|
18
|
+
return HttpService:GenerateGUID(false)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
function Protocol.createMessage(msgType, payload)
|
|
22
|
+
return {
|
|
23
|
+
id = Protocol.generateId(),
|
|
24
|
+
type = msgType,
|
|
25
|
+
payload = payload,
|
|
26
|
+
ts = os.clock(),
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return Protocol
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
Reflection module: query Roblox class/property/method/event information
|
|
3
|
+
Uses the new ReflectionService API (2025+):
|
|
4
|
+
- GetClass(className, filter?)
|
|
5
|
+
- GetClasses(filter?)
|
|
6
|
+
- GetPropertiesOfClass(className, filter?)
|
|
7
|
+
- GetMethodsOfClass(className, filter?)
|
|
8
|
+
- GetEventsOfClass(className, filter?)
|
|
9
|
+
]]
|
|
10
|
+
|
|
11
|
+
local ReflectionService = game:GetService("ReflectionService")
|
|
12
|
+
local Reflection = {}
|
|
13
|
+
|
|
14
|
+
local function safeCall(fn, ...)
|
|
15
|
+
local ok, result = pcall(fn, ...)
|
|
16
|
+
if ok then return result end
|
|
17
|
+
return nil, result
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
function Reflection.getClassInfo(className)
|
|
21
|
+
local classData, classErr = safeCall(function()
|
|
22
|
+
return ReflectionService:GetClass(className)
|
|
23
|
+
end)
|
|
24
|
+
|
|
25
|
+
local properties = {}
|
|
26
|
+
local propData = safeCall(function()
|
|
27
|
+
return ReflectionService:GetPropertiesOfClass(className)
|
|
28
|
+
end)
|
|
29
|
+
if propData then
|
|
30
|
+
for _, prop in propData do
|
|
31
|
+
table.insert(properties, {
|
|
32
|
+
name = prop.Name,
|
|
33
|
+
type = prop.ValueType and tostring(prop.ValueType) or "unknown",
|
|
34
|
+
category = prop.Category or "Other",
|
|
35
|
+
serialized = prop.Serialized,
|
|
36
|
+
owner = prop.Owner,
|
|
37
|
+
})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local methods = {}
|
|
42
|
+
local methodData = safeCall(function()
|
|
43
|
+
return ReflectionService:GetMethodsOfClass(className)
|
|
44
|
+
end)
|
|
45
|
+
if methodData then
|
|
46
|
+
for _, method in methodData do
|
|
47
|
+
local params = {}
|
|
48
|
+
if method.Parameters then
|
|
49
|
+
for _, param in method.Parameters do
|
|
50
|
+
table.insert(params, {
|
|
51
|
+
name = param.Name,
|
|
52
|
+
type = param.Type and tostring(param.Type) or "unknown",
|
|
53
|
+
})
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
table.insert(methods, {
|
|
57
|
+
name = method.Name,
|
|
58
|
+
parameters = params,
|
|
59
|
+
owner = method.Owner,
|
|
60
|
+
})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local events = {}
|
|
65
|
+
local eventData = safeCall(function()
|
|
66
|
+
return ReflectionService:GetEventsOfClass(className)
|
|
67
|
+
end)
|
|
68
|
+
if eventData then
|
|
69
|
+
for _, event in eventData do
|
|
70
|
+
local params = {}
|
|
71
|
+
if event.Parameters then
|
|
72
|
+
for _, param in event.Parameters do
|
|
73
|
+
table.insert(params, {
|
|
74
|
+
name = param.Name,
|
|
75
|
+
type = param.Type and tostring(param.Type) or "unknown",
|
|
76
|
+
})
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
table.insert(events, {
|
|
80
|
+
name = event.Name,
|
|
81
|
+
parameters = params,
|
|
82
|
+
owner = event.Owner,
|
|
83
|
+
})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
local result = {
|
|
88
|
+
className = className,
|
|
89
|
+
properties = properties,
|
|
90
|
+
methods = methods,
|
|
91
|
+
events = events,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if classData then
|
|
95
|
+
result.superclass = classData.Superclass
|
|
96
|
+
result.creatable = classData.Creatable
|
|
97
|
+
result.serialized = classData.Serialized
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if classErr and #properties == 0 then
|
|
101
|
+
result.error = "Could not reflect class: " .. tostring(classErr)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
function Reflection.getPropertyTypes(className)
|
|
108
|
+
local propData = safeCall(function()
|
|
109
|
+
return ReflectionService:GetPropertiesOfClass(className)
|
|
110
|
+
end)
|
|
111
|
+
if not propData then return {} end
|
|
112
|
+
|
|
113
|
+
local typeMap = {}
|
|
114
|
+
for _, prop in propData do
|
|
115
|
+
typeMap[prop.Name] = prop.ValueType and tostring(prop.ValueType) or "unknown"
|
|
116
|
+
end
|
|
117
|
+
return typeMap
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
function Reflection.listClasses()
|
|
121
|
+
local classes = safeCall(function()
|
|
122
|
+
return ReflectionService:GetClasses()
|
|
123
|
+
end)
|
|
124
|
+
if not classes then return {} end
|
|
125
|
+
|
|
126
|
+
local result = {}
|
|
127
|
+
for _, cls in classes do
|
|
128
|
+
table.insert(result, {
|
|
129
|
+
name = cls.Name,
|
|
130
|
+
superclass = cls.Superclass,
|
|
131
|
+
creatable = cls.Creatable,
|
|
132
|
+
})
|
|
133
|
+
end
|
|
134
|
+
return result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
return Reflection
|