forked from Anaminus/rbxweb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
asset.go
340 lines (305 loc) · 10.1 KB
/
asset.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Deals with services related to ROBLOX assets.
package asset
import (
"bytes"
"encoding/json"
"errors"
"github.com/JanBerktold/rbxweb"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
// Asset types.
const (
TypeImage byte = 1
TypeTShirt byte = 2
TypeAudio byte = 3
TypeMesh byte = 4
TypeLua byte = 5
TypeHTML byte = 6
TypeText byte = 7
TypeHat byte = 8
TypePlace byte = 9
TypeModel byte = 10
TypeShirt byte = 11
TypePants byte = 12
TypeDecal byte = 13
TypeAvatar byte = 16
TypeHead byte = 17
TypeFace byte = 18
TypeGear byte = 19
TypeBadge byte = 21
TypeGroupEmblem byte = 22
TypeAnimation byte = 24
TypeArms byte = 25
TypeLegs byte = 26
TypeTorso byte = 27
TypeRightArm byte = 28
TypeLeftArm byte = 29
TypeLeftLeg byte = 30
TypeRightLeg byte = 31
TypePackage byte = 32
TypeYouTubeVideo byte = 33
TypeGamePass byte = 34
TypeApp byte = 35
TypeCode byte = 37
TypePlugin byte = 38
)
// GetLatestModel returns the asset id of the latest model for a given user.
// While this is useful for retrieving the asset id of a newly created model
// that was just uploaded, it is not necessarily reliable for this purpose.
func GetLatestModel(client *rbxweb.Client, userId int32) (assetId int64, err error) {
if userId == 0 {
return 0, errors.New("invalid user id")
}
query := url.Values{
"Category": {"Models"},
"SortType": {"RecentlyUpdated"},
"IncludeNotForSale": {"true"},
"ResultsPerPage": {"1"},
"CreatorID": {strconv.FormatInt(int64(userId), 10)},
}
resp, err := client.Get(client.GetURL(`api`, `/catalog/json`, query))
if err = client.AssertResp(resp, err); err != nil {
return 0, err
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
var asset []struct {
AssetId int64
}
if err = dec.Decode(&asset); err != nil {
return 0, errors.New("JSON decode failed: " + err.Error())
}
return asset[0].AssetId, nil
}
// GetIdFromVersion returns an asset id from an asset version id.
func GetIdFromVersion(client *rbxweb.Client, assetVersionId int64) (assetId int64, err error) {
query := url.Values{
"avid": {strconv.FormatInt(assetVersionId, 10)},
}
// This relies on how asset names are converted to url names. Currently,
// if an asset name is "_", its url becomes "unnamed".
req, _ := http.NewRequest("HEAD", client.GetURL(`www`, `/_-item`, query), nil)
resp, err := client.Do(req)
if err = client.AssertResp(resp, err); err != nil {
return 0, err
}
resp.Body.Close()
values, err := url.ParseQuery(resp.Header.Get("Location"))
if err = client.AssertResp(resp, err); err != nil {
return 0, err
}
return strconv.ParseInt(values.Get("id"), 10, 64)
}
// Upload generically uploads data from `reader` as an asset to the ROBLOX
// website. `info` can be used to specify information about the model. The
// following parameters are known:
//
// type - The type of asset.
// assetid - The id of the asset to update. 0 uploads a new asset.
// name - The name of the asset.
// description - The asset's description.
// genreTypeId - The asset's genre.
// isPublic - Whether the asset can be taken by other users.
// allowComments - Whether users can comment on the asset.
//
// The success of this function is highly dependent on these parameters. For
// example, most asset types may only be uploaded by authorized users.
// Parameters that specify information about the asset only apply for new
// assets. That is, updating an asset will only update the contents, but not
// the information about it.
//
// `assetVersionId` is the version id of the uploaded asset. This is unique
// for each upload. This can be used with GetIdFromVersion to get the asset
// id.
//
// This function requires the client to be logged in.
func Upload(client *rbxweb.Client, reader io.Reader, info url.Values) (assetVersionId int64, err error) {
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
req, _ := http.NewRequest("POST", client.GetURL(`www`, `/Data/Upload.ashx`, info), buf)
req.Header.Set("User-Agent", "roblox/rbxweb")
resp, err := client.Do(req)
if err = client.AssertResp(resp, err); err != nil {
return 0, err
}
defer resp.Body.Close()
r := new(bytes.Buffer)
r.ReadFrom(resp.Body)
assetVersionId, _ = strconv.ParseInt(r.String(), 10, 64)
return assetVersionId, err
}
// UploadModel uploads data from `reader` to Roblox as a Model asset. If
// updating an existing model, `modelId` should be the id of the model. If
// `modelId` is 0, then a new model will be uploaded. If uploading a new
// model, `info` can be used to specify information about the model.
//
// This function requires the client to be logged in.
func UploadModel(client *rbxweb.Client, reader io.Reader, modelId int64, info url.Values) (assetVersionId int64, err error) {
query := url.Values{
"assetid": {strconv.FormatInt(modelId, 10)},
"type": {"Model"},
// "name": {"Unnamed Model"},
// "description": {""},
// "genreTypeId": {"1"},
// "isPublic": {"False"},
// "allowComments": {"False"},
}
if info != nil {
for key, value := range info {
query[key] = value
}
}
return assetVersionId, err
}
// UploadModelFile is similar to UploadModel, but gets the data from a file
// name.
//
// This function requires the client to be logged in.
func UploadModelFile(client *rbxweb.Client, filename string, modelId int64, info url.Values) (assetVersionId int64, err error) {
var file *os.File
if file, err = os.Open(filename); err != nil {
return 0, err
}
defer file.Close()
return UploadModel(client, file, modelId, info)
}
// UpdatePlace uploads data from `reader` to Roblox as a Place asset.
// `placeId` must be the id of an existing place. This function cannot create
// a new place.
//
// This function requires the client to be logged in.
func UpdatePlace(client *rbxweb.Client, reader io.Reader, placeId int64) (err error) {
query := url.Values{
"assetid": {strconv.FormatInt(placeId, 10)},
"type": {"Place"},
}
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
req, _ := http.NewRequest("POST", client.GetURL(`www`, `/Data/Upload.ashx`, query), buf)
req.Header.Set("User-Agent", "Roblox")
resp, err := client.Do(req)
if err = client.AssertResp(resp, err); err != nil {
return err
}
defer resp.Body.Close()
return nil
}
// UpdatePlaceFile is similar to UpdatePlace, but gets the data from a file name.
//
// This function requires the client to be logged in.
func UpdatePlaceFile(client *rbxweb.Client, filename string, placeId int64) (err error) {
var file *os.File
if file, err = os.Open(filename); err != nil {
return
}
defer file.Close()
return UpdatePlace(client, file, placeId)
}
// Contains information about an asset.
type Info struct {
AssetId int64
ProductId int64
Name string
Description string
AssetTypeId int32
Creator struct {
Id int32
Name string
}
Created time.Time
Updated time.Time
PriceInRobux int64
PriceInTickets int64
Sales int32
IsNew bool
IsForSale bool
IsPublicDomain bool
IsLimited bool
IsLimitedUnique bool
Remaining int32
MinimumMembershipLevel int32
ContentRatingTypeId int32
}
// GetInfo returns information about an asset, given an asset id.
func GetInfo(client *rbxweb.Client, id int64) (info Info, err error) {
query := url.Values{
"assetId": {strconv.FormatInt(id, 10)},
}
resp, err := client.Get(client.GetURL(`api`, `/marketplace/productinfo`, query))
if err = client.AssertResp(resp, err); err != nil {
return Info{}, err
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
dec.Decode(&info)
return
}
func UserOwnsAsset(client *rbxweb.Client, assetId int64, userId int64) (bool, error) {
query := url.Values{
"userId": {strconv.FormatInt(userId, 10)},
"assetId": {strconv.FormatInt(assetId, 10)},
}
resp, err := client.Get(client.GetSecureURL(`api`, `/Ownership/HasAsset`, query))
if err = client.AssertResp(resp, err); err != nil {
return false, err
}
defer resp.Body.Close()
var result interface{}
dec := json.NewDecoder(resp.Body)
dec.Decode(&result)
if owns, ok := result.(bool); ok {
return owns, nil
} else {
errorData := result.(map[string]interface{})
return false, errors.New("Failed to check if user owns asset. Status code " + strconv.FormatInt(int64(errorData["code"].(float64)), 10) + ", message: " + errorData["message"].(string))
}
}
type AssetOptions struct {
Name string
Description string
EnableComments bool
Genre int64
PublicDomain bool
}
func ChangeAssetOptions(client *rbxweb.Client, assetId int64, options AssetOptions) error {
page := client.GetSecureURL(`www`, `/my/item.aspx`, url.Values{"id": {strconv.FormatInt(assetId, 10)}})
values := url.Values{
"ctl00$cphRoblox$NameTextBox": {options.Name},
"ctl00$cphRoblox$DescriptionTextBox": {options.Description},
"ctl00$cphRoblox$actualGenreSelection": {strconv.FormatInt(options.Genre, 10)},
"GenreButtons2": {strconv.FormatInt(options.Genre, 10)},
"comments": {""},
"rdoNotifications": {"on"},
"__EVENTTARGET": {"ctl00$cphRoblox$SubmitButtonBottom"},
}
if options.EnableComments {
values.Set("ctl00$cphRoblox$EnableCommentsCheckBox", "on")
}
if options.PublicDomain {
values.Set("ctl00$cphRoblox$PublicDomainCheckBox", "on")
}
return client.DoRawPost(page, values)
}
func DisownAsset(client *rbxweb.Client, assetId int64) error {
CSRF, err := client.GetCSRFToken()
if err != nil {
return err
}
data := url.Values{
"assetId": {strconv.FormatInt(assetId, 10)},
}
deleteRequest, _ := http.NewRequest("POST", client.GetSecureURL(`www`, `/asset/delete-from-inventory`, nil), bytes.NewBufferString(data.Encode()))
deleteRequest.Header.Set("X-CSRF-Token", CSRF)
deleteRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
response, err := client.Do(deleteRequest)
if err = client.AssertResp(response, err); err != nil {
return err
}
response.Body.Close()
return nil
}