Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 173ad65c5c | |||
|
e10cfcfcb5
|
|||
| 9080c20227 | |||
|
3ec2b659f3
|
|||
| f2db341277 | |||
|
5a95ccc633
|
|||
|
68a0c7113e
|
|||
|
2088256e29
|
|||
|
d50e86f809
|
|||
|
b37b08d564
|
|||
| 316200ead4 | |||
|
d66f786aca
|
|||
| 4d36c3b9b8 | |||
|
e05c1ddabf
|
|||
| d8a65d9d91 | |||
|
a2b4980bf3
|
|||
| 395eee9a19 | |||
|
ad3c446b43
|
|||
| cec7307acc | |||
|
6cc9ded572
|
|||
| 7800a70b80 | |||
|
2faddb741f
|
|||
|
f59a2e0b6a
|
|||
|
e5e75ef3cb
|
|||
|
fdf0c14309
|
|||
|
8885eb744b
|
|||
|
c0ded31a6a
|
|||
|
d9bf50e1c4
|
|||
|
55d5f97f0b
|
|||
|
7665ccc5d1
|
|||
|
24f50de2ae
|
|||
|
afd8a74e34
|
|||
|
ddf294c6f9
|
|||
|
a9a8c01cb1
|
|||
|
287969b7d5
|
|||
|
b80868e722
|
|||
|
e1503ba898
|
|||
|
97086e351f
|
|||
|
89d96db03c
|
|||
|
6b9ae59e7f
|
|||
| dd4344f514 | |||
|
8a400faae2
|
|||
|
6dff5f5145
|
|||
|
f3f048e293
|
|||
|
10c9ddd696
|
|||
|
d73567050c
|
|||
|
c1e53e42bc
|
|||
|
b3defd31fc
|
|||
| 10a50948fc | |||
| 2987a6b321 | |||
| ad8e1865f3 | |||
| 27deef3dd6 | |||
| 89a478eaac | |||
| d6adc1da45 | |||
| e89edf287f | |||
| 4ffeaa5784 | |||
| 607f964928 | |||
| 8dc7c96f2d | |||
| 68d751f81f | |||
| c2052be036 | |||
| 2e9485dea6 |
22
.drone.yml
22
.drone.yml
@@ -7,6 +7,17 @@ platform:
|
|||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: clux/muslrust:1.89.0-stable
|
||||||
|
commands:
|
||||||
|
- cargo build --release --target x86_64-unknown-linux-musl
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
- name: image
|
- name: image
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
@@ -19,6 +30,15 @@ steps:
|
|||||||
password:
|
password:
|
||||||
from_secret: GIT_PASS
|
from_secret: GIT_PASS
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
|
depends_on:
|
||||||
|
- build
|
||||||
when:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
---
|
||||||
|
kind: signature
|
||||||
|
hmac: 52507904dfaada892c05a61422dc5e147c1438419ed841d0f1e3e3ec2b193540
|
||||||
|
|
||||||
|
...
|
||||||
|
|||||||
2349
Cargo.lock
generated
2349
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -1,7 +1,7 @@
|
|||||||
workspace = { members = ["rbx_asset", "rox_compiler"] }
|
workspace = { members = ["rbx_asset", "rox_compiler"] }
|
||||||
[package]
|
[package]
|
||||||
name = "asset-tool"
|
name = "asset-tool"
|
||||||
version = "0.4.12"
|
version = "0.5.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@@ -10,17 +10,20 @@ edition = "2021"
|
|||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
clap = { version = "4.4.2", features = ["derive"] }
|
clap = { version = "4.4.2", features = ["derive"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
gix = "0.72.1"
|
git2 = { version = "0.20.0", optional = true }
|
||||||
lazy-regex = "3.1.0"
|
rbx_asset = { path = "rbx_asset", features = ["gzip", "rustls-tls"], default-features = false }
|
||||||
rbx_asset = { path = "rbx_asset" }
|
rbx_binary = "2.0.0"
|
||||||
rbx_binary = "1.0.0"
|
rbx_dom_weak = "4.0.0"
|
||||||
rbx_dom_weak = "3.0.0"
|
rbx_reflection_database = "2.0.1"
|
||||||
rbx_reflection_database = "1.0.3"
|
rbx_xml = "2.0.0"
|
||||||
rbx_xml = "1.0.0"
|
|
||||||
rox_compiler = { path = "rox_compiler" }
|
rox_compiler = { path = "rox_compiler" }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread", "fs"] }
|
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread", "fs"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
git = ["dep:git2"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
#lto = true
|
#lto = true
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
@@ -1,23 +1,3 @@
|
|||||||
# Using the `rust-musl-builder` as base image, instead of
|
FROM alpine:3.22 AS runtime
|
||||||
# the official Rust toolchain
|
COPY /target/x86_64-unknown-linux-musl/release/asset-tool /usr/local/bin/
|
||||||
FROM docker.io/clux/muslrust:stable AS chef
|
ENTRYPOINT ["/usr/local/bin/asset-tool"]
|
||||||
USER root
|
|
||||||
RUN cargo install cargo-chef
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
FROM chef AS planner
|
|
||||||
COPY . .
|
|
||||||
RUN cargo chef prepare --recipe-path recipe.json
|
|
||||||
|
|
||||||
FROM chef AS builder
|
|
||||||
COPY --from=planner /app/recipe.json recipe.json
|
|
||||||
# Notice that we are specifying the --target flag!
|
|
||||||
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
|
|
||||||
COPY . .
|
|
||||||
RUN cargo build --release --target x86_64-unknown-linux-musl --bin asset-tool
|
|
||||||
|
|
||||||
FROM docker.io/alpine:latest AS runtime
|
|
||||||
RUN addgroup -S myuser && adduser -S myuser -G myuser
|
|
||||||
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/asset-tool /usr/local/bin/
|
|
||||||
USER myuser
|
|
||||||
ENTRYPOINT ["/usr/local/bin/asset-tool"]
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rbx_asset"
|
name = "rbx_asset"
|
||||||
version = "0.4.8"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = ["strafesnet"]
|
publish = ["strafesnet"]
|
||||||
repository = "https://git.itzana.me/StrafesNET/asset-tool"
|
repository = "https://git.itzana.me/StrafesNET/asset-tool"
|
||||||
@@ -11,14 +11,21 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["gzip"]
|
default = ["gzip", "default-tls"]
|
||||||
gzip = ["dep:flate2"]
|
gzip = ["dep:flate2"]
|
||||||
|
|
||||||
|
default-tls = ["reqwest/default-tls"]
|
||||||
|
rustls-tls = ["reqwest/rustls-tls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
flate2 = { version = "1.0.29", optional = true }
|
flate2 = { version = "1.0.29", optional = true }
|
||||||
reqwest = { version = "0.12.4", features = ["json","multipart"] }
|
reqwest = { version = "0.12.4", features = [
|
||||||
|
"json", "multipart",
|
||||||
|
# default features
|
||||||
|
"charset", "http2", "system-proxy"
|
||||||
|
], default-features = false }
|
||||||
serde = { version = "1.0.199", features = ["derive"] }
|
serde = { version = "1.0.199", features = ["derive"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
use crate::body::{Binary,ContentType,Json};
|
||||||
use crate::util::{serialize_u64,deserialize_u64,response_ok};
|
use crate::util::{serialize_u64,deserialize_u64,response_ok};
|
||||||
use crate::types::{ResponseError,MaybeGzippedBytes};
|
use crate::types::{ResponseError,MaybeGzippedBytes};
|
||||||
|
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub enum AssetType{
|
pub enum AssetType{
|
||||||
Audio,
|
Audio,
|
||||||
Decal,
|
Decal,
|
||||||
Model,
|
Model,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct CreateAssetRequest{
|
pub struct CreateAssetRequest{
|
||||||
pub assetType:AssetType,
|
pub assetType:AssetType,
|
||||||
pub creationContext:CreationContext,
|
pub creationContext:CreationContext,
|
||||||
@@ -55,7 +55,7 @@ impl std::fmt::Display for CreateError{
|
|||||||
impl std::error::Error for CreateError{}
|
impl std::error::Error for CreateError{}
|
||||||
|
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UpdateAssetRequest{
|
pub struct UpdateAssetRequest{
|
||||||
pub assetId:u64,
|
pub assetId:u64,
|
||||||
pub displayName:Option<String>,
|
pub displayName:Option<String>,
|
||||||
@@ -64,42 +64,41 @@ pub struct UpdateAssetRequest{
|
|||||||
|
|
||||||
//woo nested roblox stuff
|
//woo nested roblox stuff
|
||||||
#[derive(Clone,Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Clone,Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub enum Creator{
|
pub enum Creator{
|
||||||
userId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
userId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
||||||
groupId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
groupId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct CreationContext{
|
pub struct CreationContext{
|
||||||
pub creator:Creator,
|
pub creator:Creator,
|
||||||
pub expectedPrice:Option<u64>,
|
pub expectedPrice:Option<u64>,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub enum ModerationState{
|
pub enum ModerationState{
|
||||||
Reviewing,
|
Reviewing,
|
||||||
Rejected,
|
Rejected,
|
||||||
Approved,
|
Approved,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct ModerationResult{
|
pub struct ModerationResult{
|
||||||
pub moderationState:ModerationState,
|
pub moderationState:ModerationState,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct Preview{
|
pub struct Preview{
|
||||||
pub asset:String,
|
pub asset:String,
|
||||||
pub altText:String,
|
pub altText:String,
|
||||||
}
|
}
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UpdatePlaceRequest{
|
pub struct UpdatePlaceRequest{
|
||||||
pub universeId:u64,
|
pub universeId:u64,
|
||||||
pub placeId:u64,
|
pub placeId:u64,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UpdatePlaceResponse{
|
pub struct UpdatePlaceResponse{
|
||||||
pub versionNumber:u64,
|
pub versionNumber:u64,
|
||||||
}
|
}
|
||||||
@@ -117,8 +116,8 @@ impl std::fmt::Display for UpdateError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for UpdateError{}
|
impl std::error::Error for UpdateError{}
|
||||||
|
|
||||||
struct GetAssetOperationRequest{
|
struct GetAssetOperationRequest<'a>{
|
||||||
operation_id:String,
|
operation_id:&'a str,
|
||||||
}
|
}
|
||||||
pub struct GetAssetLatestRequest{
|
pub struct GetAssetLatestRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
@@ -145,7 +144,7 @@ pub struct GetAssetLatestRequest{
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetResponse{
|
pub struct AssetResponse{
|
||||||
//u64 wrapped in quotes wohoo!!
|
//u64 wrapped in quotes wohoo!!
|
||||||
#[serde(deserialize_with="deserialize_u64")]
|
#[serde(deserialize_with="deserialize_u64")]
|
||||||
@@ -165,14 +164,13 @@ pub struct AssetResponse{
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub previews:Vec<Preview>,
|
pub previews:Vec<Preview>,
|
||||||
}
|
}
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct GetAssetVersionRequest{
|
pub struct GetAssetVersionRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
pub version:u64,
|
pub version:u64,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum GetError{
|
pub enum GetError{
|
||||||
ParseError(url::ParseError),
|
Parse(url::ParseError),
|
||||||
Response(ResponseError),
|
Response(ResponseError),
|
||||||
Reqwest(reqwest::Error),
|
Reqwest(reqwest::Error),
|
||||||
}
|
}
|
||||||
@@ -196,13 +194,13 @@ impl AssetLocation{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,serde::Deserialize)]
|
#[derive(Debug,serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetMetadata{
|
pub struct AssetMetadata{
|
||||||
pub metadataType:u32,
|
pub metadataType:u32,
|
||||||
pub value:String,
|
pub value:String,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize)]
|
#[derive(Debug,serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetLocationInfo{
|
pub struct AssetLocationInfo{
|
||||||
pub location:Option<AssetLocation>,
|
pub location:Option<AssetLocation>,
|
||||||
pub requestId:String,
|
pub requestId:String,
|
||||||
@@ -218,7 +216,7 @@ pub struct AssetVersionsRequest{
|
|||||||
pub cursor:Option<String>,
|
pub cursor:Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetVersion{
|
pub struct AssetVersion{
|
||||||
pub Id:u64,
|
pub Id:u64,
|
||||||
pub assetId:u64,
|
pub assetId:u64,
|
||||||
@@ -230,7 +228,7 @@ pub struct AssetVersion{
|
|||||||
pub isPublished:bool,
|
pub isPublished:bool,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize)]
|
#[derive(Debug,serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetVersionsResponse{
|
pub struct AssetVersionsResponse{
|
||||||
pub previousPageCursor:Option<String>,
|
pub previousPageCursor:Option<String>,
|
||||||
pub nextPageCursor:Option<String>,
|
pub nextPageCursor:Option<String>,
|
||||||
@@ -254,13 +252,12 @@ pub struct InventoryPageRequest{
|
|||||||
pub cursor:Option<String>,
|
pub cursor:Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct InventoryItem{
|
pub struct InventoryItem{
|
||||||
pub id:u64,
|
pub id:u64,
|
||||||
pub name:String,
|
pub name:String,
|
||||||
}
|
}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct InventoryPageResponse{
|
pub struct InventoryPageResponse{
|
||||||
pub totalResults:u64,//up to 50
|
pub totalResults:u64,//up to 50
|
||||||
pub filteredKeyword:Option<String>,//""
|
pub filteredKeyword:Option<String>,//""
|
||||||
@@ -298,7 +295,7 @@ impl std::fmt::Display for OperationError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for OperationError{}
|
impl std::error::Error for OperationError{}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
struct RobloxOperation{
|
struct RobloxOperation{
|
||||||
pub path:Option<String>,
|
pub path:Option<String>,
|
||||||
pub metadata:Option<String>,
|
pub metadata:Option<String>,
|
||||||
@@ -320,13 +317,119 @@ impl RobloxOperation{
|
|||||||
pub async fn try_get_reponse(&self,context:&Context)->Result<serde_json::Value,OperationError>{
|
pub async fn try_get_reponse(&self,context:&Context)->Result<serde_json::Value,OperationError>{
|
||||||
context.get_asset_operation(GetAssetOperationRequest{
|
context.get_asset_operation(GetAssetOperationRequest{
|
||||||
operation_id:self.operation_id()
|
operation_id:self.operation_id()
|
||||||
.ok_or(OperationError::NoOperationId)?
|
.ok_or(OperationError::NoOperationId)?,
|
||||||
.to_owned(),
|
|
||||||
}).await.map_err(OperationError::Get)?
|
}).await.map_err(OperationError::Get)?
|
||||||
.response.ok_or(OperationError::NotDone)
|
.response.ok_or(OperationError::NotDone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LuauSessionError{
|
||||||
|
Get(GetError),
|
||||||
|
Unspecified,
|
||||||
|
NotDone,
|
||||||
|
NoOutput,
|
||||||
|
NoError,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for LuauSessionError{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for LuauSessionError{}
|
||||||
|
#[derive(Debug,serde::Serialize)]
|
||||||
|
#[expect(nonstandard_style)]
|
||||||
|
pub struct LuauSessionCreate<'a>{
|
||||||
|
pub script:&'a str,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub user:Option<&'a str>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub timeout:Option<&'a str>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub binaryInput:Option<&'a str>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub enableBinaryOutput:Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub binaryOutputUri:Option<&'a str>,
|
||||||
|
}
|
||||||
|
#[derive(Debug,serde::Deserialize)]
|
||||||
|
#[expect(nonstandard_style)]
|
||||||
|
pub enum LuauSessionState{
|
||||||
|
STATE_UNSPECIFIED,
|
||||||
|
PROCESSING,
|
||||||
|
COMPLETE,
|
||||||
|
FAILED,
|
||||||
|
}
|
||||||
|
#[derive(Debug,serde::Deserialize)]
|
||||||
|
pub struct LuauError{
|
||||||
|
pub code:String,
|
||||||
|
pub message:String,
|
||||||
|
}
|
||||||
|
#[derive(Debug,serde::Deserialize)]
|
||||||
|
pub struct LuauResults{
|
||||||
|
pub results:Vec<serde_json::Value>,
|
||||||
|
}
|
||||||
|
#[derive(Debug,serde::Deserialize)]
|
||||||
|
#[expect(nonstandard_style)]
|
||||||
|
pub struct LuauSessionResponse{
|
||||||
|
path:String,
|
||||||
|
#[serde(deserialize_with="deserialize_u64")]
|
||||||
|
pub user:u64,
|
||||||
|
pub state:LuauSessionState,
|
||||||
|
pub script:String,
|
||||||
|
pub error:Option<LuauError>,
|
||||||
|
pub output:Option<LuauResults>,
|
||||||
|
pub binaryInput:String,
|
||||||
|
pub enableBinaryOutput:bool,
|
||||||
|
pub binaryOutputUri:String,
|
||||||
|
}
|
||||||
|
impl LuauSessionResponse{
|
||||||
|
pub fn path(&self)->&str{
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
pub async fn try_get_result(&self,context:&Context)->Result<Result<LuauResults,LuauError>,LuauSessionError>{
|
||||||
|
let response=context.get_luau_session(self).await.map_err(LuauSessionError::Get)?;
|
||||||
|
match response.state{
|
||||||
|
LuauSessionState::STATE_UNSPECIFIED=>Err(LuauSessionError::Unspecified),
|
||||||
|
LuauSessionState::PROCESSING=>Err(LuauSessionError::NotDone),
|
||||||
|
LuauSessionState::COMPLETE=>Ok(Ok(response.output.ok_or(LuauSessionError::NoOutput)?)),
|
||||||
|
LuauSessionState::FAILED=>Ok(Err(response.error.ok_or(LuauSessionError::NoError)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait AsSessionPath{
|
||||||
|
fn into_session_path(&self)->impl AsRef<str>;
|
||||||
|
}
|
||||||
|
impl AsSessionPath for LuauSessionResponse{
|
||||||
|
fn into_session_path(&self)->impl AsRef<str>{
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct LuauSessionLatestRequest{
|
||||||
|
pub universe_id:u64,
|
||||||
|
pub place_id:u64,
|
||||||
|
}
|
||||||
|
impl AsSessionPath for LuauSessionLatestRequest{
|
||||||
|
fn into_session_path(&self)->impl AsRef<str>{
|
||||||
|
let universe_id=self.universe_id;
|
||||||
|
let place_id=self.place_id;
|
||||||
|
format!("universes/{universe_id}/places/{place_id}/luau-execution-session-tasks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct LuauSessionVersionRequest{
|
||||||
|
pub universe_id:u64,
|
||||||
|
pub place_id:u64,
|
||||||
|
pub version_id:u64,
|
||||||
|
}
|
||||||
|
impl AsSessionPath for LuauSessionVersionRequest{
|
||||||
|
fn into_session_path(&self)->impl AsRef<str>{
|
||||||
|
let universe_id=self.universe_id;
|
||||||
|
let place_id=self.place_id;
|
||||||
|
let version_id=self.version_id;
|
||||||
|
format!("universes/{universe_id}/places/{place_id}/versions/{version_id}/luau-execution-session-tasks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApiKey(String);
|
pub struct ApiKey(String);
|
||||||
impl ApiKey{
|
impl ApiKey{
|
||||||
@@ -356,9 +459,10 @@ impl Context{
|
|||||||
.header("x-api-key",self.api_key.as_str())
|
.header("x-api-key",self.api_key.as_str())
|
||||||
.send().await
|
.send().await
|
||||||
}
|
}
|
||||||
async fn post(&self,url:url::Url,body:impl Into<reqwest::Body>+Clone)->Result<reqwest::Response,reqwest::Error>{
|
async fn post(&self,url:url::Url,body:impl ContentType)->Result<reqwest::Response,reqwest::Error>{
|
||||||
self.client.post(url)
|
self.client.post(url)
|
||||||
.header("x-api-key",self.api_key.as_str())
|
.header("x-api-key",self.api_key.as_str())
|
||||||
|
.header("Content-Type",body.content_type())
|
||||||
.body(body)
|
.body(body)
|
||||||
.send().await
|
.send().await
|
||||||
}
|
}
|
||||||
@@ -415,18 +519,38 @@ impl Context{
|
|||||||
operation,
|
operation,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async fn get_asset_operation(&self,config:GetAssetOperationRequest)->Result<RobloxOperation,GetError>{
|
async fn get_asset_operation(&self,config:GetAssetOperationRequest<'_>)->Result<RobloxOperation,GetError>{
|
||||||
let raw_url=format!("https://apis.roblox.com/assets/v1/operations/{}",config.operation_id);
|
let raw_url=format!("https://apis.roblox.com/assets/v1/operations/{}",config.operation_id);
|
||||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
).await.map_err(GetError::Response)?
|
).await.map_err(GetError::Response)?
|
||||||
.json::<RobloxOperation>().await.map_err(GetError::Reqwest)
|
.json::<RobloxOperation>().await.map_err(GetError::Reqwest)
|
||||||
}
|
}
|
||||||
|
pub async fn create_luau_session(&self,config:&impl AsSessionPath,session:LuauSessionCreate<'_>)->Result<LuauSessionResponse,CreateError>{
|
||||||
|
let raw_url=format!("https://apis.roblox.com/cloud/v2/{}",config.into_session_path().as_ref());
|
||||||
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(CreateError::Parse)?;
|
||||||
|
|
||||||
|
let body=serde_json::to_string(&session).map_err(CreateError::Serialize)?;
|
||||||
|
|
||||||
|
response_ok(
|
||||||
|
self.post(url,Json(body)).await.map_err(CreateError::Reqwest)?
|
||||||
|
).await.map_err(CreateError::Response)?
|
||||||
|
.json::<LuauSessionResponse>().await.map_err(CreateError::Reqwest)
|
||||||
|
}
|
||||||
|
pub async fn get_luau_session(&self,config:&impl AsSessionPath)->Result<LuauSessionResponse,GetError>{
|
||||||
|
let raw_url=format!("https://apis.roblox.com/cloud/v2/{}",config.into_session_path().as_ref());
|
||||||
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
|
response_ok(
|
||||||
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
|
).await.map_err(GetError::Response)?
|
||||||
|
.json::<LuauSessionResponse>().await.map_err(GetError::Reqwest)
|
||||||
|
}
|
||||||
pub async fn get_asset_info(&self,config:GetAssetLatestRequest)->Result<AssetResponse,GetError>{
|
pub async fn get_asset_info(&self,config:GetAssetLatestRequest)->Result<AssetResponse,GetError>{
|
||||||
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}",config.asset_id);
|
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}",config.asset_id);
|
||||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
@@ -435,7 +559,7 @@ impl Context{
|
|||||||
}
|
}
|
||||||
pub async fn get_asset_version_info(&self,config:GetAssetVersionRequest)->Result<AssetResponse,GetError>{
|
pub async fn get_asset_version_info(&self,config:GetAssetVersionRequest)->Result<AssetResponse,GetError>{
|
||||||
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions/{}",config.asset_id,config.version);
|
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions/{}",config.asset_id,config.version);
|
||||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
@@ -444,7 +568,7 @@ impl Context{
|
|||||||
}
|
}
|
||||||
pub async fn get_asset_location(&self,config:GetAssetLatestRequest)->Result<AssetLocationInfo,GetError>{
|
pub async fn get_asset_location(&self,config:GetAssetLatestRequest)->Result<AssetLocationInfo,GetError>{
|
||||||
let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}",config.asset_id);
|
let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}",config.asset_id);
|
||||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
@@ -453,7 +577,7 @@ impl Context{
|
|||||||
}
|
}
|
||||||
pub async fn get_asset_version_location(&self,config:GetAssetVersionRequest)->Result<AssetLocationInfo,GetError>{
|
pub async fn get_asset_version_location(&self,config:GetAssetVersionRequest)->Result<AssetLocationInfo,GetError>{
|
||||||
let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}/version/{}",config.asset_id,config.version);
|
let raw_url=format!("https://apis.roblox.com/asset-delivery-api/v1/assetId/{}/version/{}",config.asset_id,config.version);
|
||||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
@@ -461,7 +585,7 @@ impl Context{
|
|||||||
.json().await.map_err(GetError::Reqwest)
|
.json().await.map_err(GetError::Reqwest)
|
||||||
}
|
}
|
||||||
pub async fn get_asset(&self,config:&AssetLocation)->Result<MaybeGzippedBytes,GetError>{
|
pub async fn get_asset(&self,config:&AssetLocation)->Result<MaybeGzippedBytes,GetError>{
|
||||||
let url=reqwest::Url::parse(config.location()).map_err(GetError::ParseError)?;
|
let url=reqwest::Url::parse(config.location()).map_err(GetError::Parse)?;
|
||||||
|
|
||||||
let bytes=response_ok(
|
let bytes=response_ok(
|
||||||
self.get(url).await.map_err(GetError::Reqwest)?
|
self.get(url).await.map_err(GetError::Reqwest)?
|
||||||
@@ -504,7 +628,7 @@ impl Context{
|
|||||||
}
|
}
|
||||||
|
|
||||||
response_ok(
|
response_ok(
|
||||||
self.post(url,body).await.map_err(UpdateError::Reqwest)?
|
self.post(url,Binary(body)).await.map_err(UpdateError::Reqwest)?
|
||||||
).await.map_err(UpdateError::Response)?
|
).await.map_err(UpdateError::Response)?
|
||||||
.json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest)
|
.json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ impl std::fmt::Display for PostError{
|
|||||||
impl std::error::Error for PostError{}
|
impl std::error::Error for PostError{}
|
||||||
|
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct CreateRequest{
|
pub struct CreateRequest{
|
||||||
pub name:String,
|
pub name:String,
|
||||||
pub description:String,
|
pub description:String,
|
||||||
@@ -43,7 +43,7 @@ impl std::fmt::Display for CreateError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for CreateError{}
|
impl std::error::Error for CreateError{}
|
||||||
|
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UploadRequest{
|
pub struct UploadRequest{
|
||||||
pub assetid:u64,
|
pub assetid:u64,
|
||||||
pub name:Option<String>,
|
pub name:Option<String>,
|
||||||
@@ -73,17 +73,15 @@ impl std::fmt::Display for UploadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for UploadError{}
|
impl std::error::Error for UploadError{}
|
||||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UploadResponse{
|
pub struct UploadResponse{
|
||||||
pub AssetId:u64,
|
pub AssetId:u64,
|
||||||
pub AssetVersion:u64,
|
pub AssetVersion:u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct GetAssetDetailsRequest{
|
pub struct GetAssetDetailsRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
}
|
}
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct GetAssetRequest{
|
pub struct GetAssetRequest{
|
||||||
pub asset_id:u64,
|
pub asset_id:u64,
|
||||||
pub version:Option<u64>,
|
pub version:Option<u64>,
|
||||||
@@ -118,13 +116,13 @@ impl std::fmt::Display for GetAssetV2Error{
|
|||||||
impl std::error::Error for GetAssetV2Error{}
|
impl std::error::Error for GetAssetV2Error{}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct GetAssetV2AssetMetadata{
|
pub struct GetAssetV2AssetMetadata{
|
||||||
pub metadataType:u32,
|
pub metadataType:u32,
|
||||||
pub value:String,
|
pub value:String,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct GetAssetV2Location{
|
pub struct GetAssetV2Location{
|
||||||
pub assetFormat:String,// "source"
|
pub assetFormat:String,// "source"
|
||||||
location:String,// this value is private so users cannot mutate it
|
location:String,// this value is private so users cannot mutate it
|
||||||
@@ -137,14 +135,15 @@ impl GetAssetV2Location{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct GetAssetV2Info{
|
pub struct GetAssetV2Info{
|
||||||
pub locations:Vec<GetAssetV2Location>,
|
pub locations:Vec<GetAssetV2Location>,
|
||||||
pub requestId:String,
|
pub requestId:String,
|
||||||
pub IsHashDynamic:bool,
|
|
||||||
pub IsCopyrightProtected:bool,
|
|
||||||
pub isArchived:bool,
|
pub isArchived:bool,
|
||||||
pub assetTypeId:u32,
|
pub assetTypeId:u32,
|
||||||
|
pub isRecordable:Option<bool>,
|
||||||
|
pub IsHashDynamic:Option<bool>,
|
||||||
|
pub IsCopyrightProtected:Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GetAssetV2{
|
pub struct GetAssetV2{
|
||||||
@@ -161,7 +160,7 @@ pub enum CreatorType{
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct Creator{
|
pub struct Creator{
|
||||||
pub Id:u64,
|
pub Id:u64,
|
||||||
pub Name:String,
|
pub Name:String,
|
||||||
@@ -172,7 +171,7 @@ pub struct Creator{
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetDetails{
|
pub struct AssetDetails{
|
||||||
pub TargetId:u64,
|
pub TargetId:u64,
|
||||||
pub ProductType:Option<String>,
|
pub ProductType:Option<String>,
|
||||||
@@ -208,7 +207,7 @@ pub struct AssetVersionsPageRequest{
|
|||||||
pub cursor:Option<String>,
|
pub cursor:Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetVersion{
|
pub struct AssetVersion{
|
||||||
pub Id:u64,
|
pub Id:u64,
|
||||||
pub assetId:u64,
|
pub assetId:u64,
|
||||||
@@ -220,7 +219,7 @@ pub struct AssetVersion{
|
|||||||
pub isPublished:bool,
|
pub isPublished:bool,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct AssetVersionsPageResponse{
|
pub struct AssetVersionsPageResponse{
|
||||||
pub previousPageCursor:Option<String>,
|
pub previousPageCursor:Option<String>,
|
||||||
pub nextPageCursor:Option<String>,
|
pub nextPageCursor:Option<String>,
|
||||||
@@ -256,13 +255,12 @@ pub struct CreationsPageRequest{
|
|||||||
pub cursor:Option<String>,
|
pub cursor:Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
|
||||||
pub struct CreationsItem{
|
pub struct CreationsItem{
|
||||||
pub id:u64,
|
pub id:u64,
|
||||||
pub name:String,
|
pub name:String,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct CreationsPageResponse{
|
pub struct CreationsPageResponse{
|
||||||
pub totalResults:u64,//up to 50
|
pub totalResults:u64,//up to 50
|
||||||
pub filteredKeyword:Option<String>,//""
|
pub filteredKeyword:Option<String>,//""
|
||||||
@@ -281,14 +279,14 @@ pub struct UserInventoryPageRequest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UserInventoryItemOwner{
|
pub struct UserInventoryItemOwner{
|
||||||
pub userId:u64,
|
pub userId:u64,
|
||||||
pub username:String,
|
pub username:String,
|
||||||
pub buildersClubMembershipType:u64,
|
pub buildersClubMembershipType:String,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UserInventoryItem{
|
pub struct UserInventoryItem{
|
||||||
pub userAssetId:u64,
|
pub userAssetId:u64,
|
||||||
pub assetId:u64,
|
pub assetId:u64,
|
||||||
@@ -300,7 +298,7 @@ pub struct UserInventoryItem{
|
|||||||
pub updated:chrono::DateTime<chrono::Utc>,
|
pub updated:chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize,serde::Serialize)]
|
#[derive(serde::Deserialize,serde::Serialize)]
|
||||||
#[allow(nonstandard_style,dead_code)]
|
#[expect(nonstandard_style)]
|
||||||
pub struct UserInventoryPageResponse{
|
pub struct UserInventoryPageResponse{
|
||||||
pub previousPageCursor:Option<String>,
|
pub previousPageCursor:Option<String>,
|
||||||
pub nextPageCursor:Option<String>,
|
pub nextPageCursor:Option<String>,
|
||||||
@@ -323,13 +321,13 @@ impl std::fmt::Display for SetAssetsPermissionsError{
|
|||||||
impl std::error::Error for SetAssetsPermissionsError{}
|
impl std::error::Error for SetAssetsPermissionsError{}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
#[allow(nonstandard_style)]
|
#[expect(nonstandard_style)]
|
||||||
struct AssetPermissions{
|
struct AssetPermissions{
|
||||||
assetId:u64,
|
assetId:u64,
|
||||||
grantToDependencies:bool,//true
|
grantToDependencies:bool,//true
|
||||||
}
|
}
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
#[allow(nonstandard_style)]
|
#[expect(nonstandard_style)]
|
||||||
struct SetAssetsPermissions<'a>{
|
struct SetAssetsPermissions<'a>{
|
||||||
subjectType:&'a str,// "Universe"
|
subjectType:&'a str,// "Universe"
|
||||||
subjectId:&'a str,// "4422715291"
|
subjectId:&'a str,// "4422715291"
|
||||||
@@ -590,7 +588,7 @@ impl Context{
|
|||||||
).await.map_err(GetError::Response)?
|
).await.map_err(GetError::Response)?
|
||||||
.json().await.map_err(GetError::Reqwest)
|
.json().await.map_err(GetError::Reqwest)
|
||||||
}
|
}
|
||||||
pub async fn get_asset_versions_page(&self,config:AssetVersionsPageRequest)->Result<AssetVersionsPageResponse,PageError>{
|
pub async fn get_asset_versions_page(&self,config:&AssetVersionsPageRequest)->Result<AssetVersionsPageResponse,PageError>{
|
||||||
let mut url=reqwest::Url::parse(format!("https://develop.roblox.com/v1/assets/{}/saved-versions",config.asset_id).as_str()).map_err(PageError::ParseError)?;
|
let mut url=reqwest::Url::parse(format!("https://develop.roblox.com/v1/assets/{}/saved-versions",config.asset_id).as_str()).map_err(PageError::ParseError)?;
|
||||||
//url borrow scope
|
//url borrow scope
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UrlAndBody{
|
pub struct UrlAndBody{
|
||||||
pub url:url::Url,
|
pub url:url::Url,
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lazy-regex = "3.1.0"
|
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
|
||||||
rayon = "1.8.0"
|
rayon = "1.8.0"
|
||||||
rbx_dom_weak = "3.0.0"
|
rbx_dom_weak = "4.0.0"
|
||||||
rbx_xml = "1.0.0"
|
rbx_xml = "2.0.0"
|
||||||
tokio = { version = "1.35.1", features = ["fs"] }
|
tokio = { version = "1.35.1", features = ["fs"] }
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ impl std::fmt::Display for PropertiesOverride{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn sanitize(s:&str)->std::borrow::Cow<'_,str>{
|
#[macro_export]
|
||||||
lazy_regex::regex!(r"[^A-Za-z0-9.-]").replace_all(s,"_")
|
macro_rules! lazy_regex{
|
||||||
|
($r:literal)=>{{
|
||||||
|
use regex::Regex;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
static RE:LazyLock<Regex>=LazyLock::new(||Regex::new($r).unwrap());
|
||||||
|
&RE
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sanitize(s:&str)->std::borrow::Cow<'_,str>{
|
||||||
|
lazy_regex!(r"[^A-Za-z0-9.-]").replace_all(s,"_")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::path::{Path,PathBuf};
|
|||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
use crate::lazy_regex;
|
||||||
use crate::common::{sanitize,Style,PropertiesOverride};
|
use crate::common::{sanitize,Style,PropertiesOverride};
|
||||||
|
|
||||||
//holy smokes what am I doing lmao
|
//holy smokes what am I doing lmao
|
||||||
@@ -16,7 +17,6 @@ use crate::common::{sanitize,Style,PropertiesOverride};
|
|||||||
//I could use a function!
|
//I could use a function!
|
||||||
//eventually:
|
//eventually:
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]//idk why this thinks it's dead code, the errors are printed out in various places
|
|
||||||
pub enum QueryResolveError{
|
pub enum QueryResolveError{
|
||||||
NotFound,//0 results
|
NotFound,//0 results
|
||||||
Ambiguous,//>1 results
|
Ambiguous,//>1 results
|
||||||
@@ -203,7 +203,7 @@ impl ScriptWithOverrides{
|
|||||||
let mut count=0;
|
let mut count=0;
|
||||||
for line in source.lines(){
|
for line in source.lines(){
|
||||||
//only string type properties are supported atm
|
//only string type properties are supported atm
|
||||||
if let Some(captures)=lazy_regex::regex!(r#"^\-\-\s*Properties\.([A-Za-z]\w*)\s*\=\s*"(\w+)"$"#)
|
if let Some(captures)=lazy_regex!(r#"^\-\-\s*Properties\.([A-Za-z]\w*)\s*\=\s*"(\w+)"$"#)
|
||||||
.captures(line){
|
.captures(line){
|
||||||
count+=line.len();
|
count+=line.len();
|
||||||
match &captures[1]{
|
match &captures[1]{
|
||||||
@@ -340,7 +340,7 @@ impl CompileNode{
|
|||||||
//reject goobers
|
//reject goobers
|
||||||
let is_goober=matches!(style,Some(Style::Rojo));
|
let is_goober=matches!(style,Some(Style::Rojo));
|
||||||
let (ext_len,file_discernment)={
|
let (ext_len,file_discernment)={
|
||||||
if let Some(captures)=lazy_regex::regex!(r"^.*(\.module\.lua|\.client\.lua|\.server\.lua)$")
|
if let Some(captures)=lazy_regex!(r"^.*(\.module\.lua|\.client\.lua|\.server\.lua)$")
|
||||||
.captures(file_name.as_str()){
|
.captures(file_name.as_str()){
|
||||||
let ext=&captures[1];
|
let ext=&captures[1];
|
||||||
(ext.len(),match ext{
|
(ext.len(),match ext{
|
||||||
@@ -354,7 +354,7 @@ impl CompileNode{
|
|||||||
".server.lua"=>FileDiscernment::Script(ScriptHint::Script),
|
".server.lua"=>FileDiscernment::Script(ScriptHint::Script),
|
||||||
_=>panic!("Regex failed"),
|
_=>panic!("Regex failed"),
|
||||||
})
|
})
|
||||||
}else if let Some(captures)=lazy_regex::regex!(r"^.*(\.rbxmx|\.lua)$")
|
}else if let Some(captures)=lazy_regex!(r"^.*(\.rbxmx|\.lua)$")
|
||||||
.captures(file_name.as_str()){
|
.captures(file_name.as_str()){
|
||||||
let ext=&captures[1];
|
let ext=&captures[1];
|
||||||
(ext.len(),match ext{
|
(ext.len(),match ext{
|
||||||
|
|||||||
431
src/main.rs
431
src/main.rs
@@ -1,14 +1,17 @@
|
|||||||
use std::{io::Read,path::PathBuf};
|
use std::io::Read;
|
||||||
|
use std::path::{Path,PathBuf};
|
||||||
use clap::{Args,Parser,Subcommand};
|
use clap::{Args,Parser,Subcommand};
|
||||||
use anyhow::{anyhow,Result as AResult};
|
use anyhow::{anyhow,Result as AResult};
|
||||||
use futures::StreamExt;
|
use futures::{StreamExt,TryStreamExt};
|
||||||
use rbx_asset::cloud::{ApiKey,Context as CloudContext};
|
use rbx_asset::cloud::{ApiKey,Context as CloudContext};
|
||||||
use rbx_asset::cookie::{Cookie,Context as CookieContext,AssetVersion,CreationsItem};
|
use rbx_asset::cookie::{Cookie,Context as CookieContext,AssetVersion,CreationsItem};
|
||||||
|
|
||||||
type AssetID=u64;
|
type AssetID=u64;
|
||||||
type AssetIDFileMap=Vec<(AssetID,PathBuf)>;
|
type AssetIDFileMap=Vec<(AssetID,PathBuf)>;
|
||||||
|
#[cfg(feature="git")]
|
||||||
const CONCURRENT_DECODE:usize=8;
|
const CONCURRENT_DECODE:usize=8;
|
||||||
const CONCURRENT_REQUESTS:usize=32;
|
const CONCURRENT_REQUESTS:usize=32;
|
||||||
|
const CONCURRENT_FS:usize=64;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author,version,about,long_about=None)]
|
#[command(author,version,about,long_about=None)]
|
||||||
@@ -29,6 +32,7 @@ enum Commands{
|
|||||||
DownloadVersionV2(DownloadVersionSubcommand),
|
DownloadVersionV2(DownloadVersionSubcommand),
|
||||||
DownloadDecompile(DownloadDecompileSubcommand),
|
DownloadDecompile(DownloadDecompileSubcommand),
|
||||||
DownloadCreationsJson(DownloadCreationsJsonSubcommand),
|
DownloadCreationsJson(DownloadCreationsJsonSubcommand),
|
||||||
|
DownloadCreationsHistory(DownloadCreationsHistorySubcommand),
|
||||||
DownloadUserInventoryJson(DownloadUserInventoryJsonSubcommand),
|
DownloadUserInventoryJson(DownloadUserInventoryJsonSubcommand),
|
||||||
CreateAsset(CreateAssetSubcommand),
|
CreateAsset(CreateAssetSubcommand),
|
||||||
CreateAssetMedia(CreateAssetMediaSubcommand),
|
CreateAssetMedia(CreateAssetMediaSubcommand),
|
||||||
@@ -40,8 +44,11 @@ enum Commands{
|
|||||||
CompileUploadAsset(CompileUploadAssetSubcommand),
|
CompileUploadAsset(CompileUploadAssetSubcommand),
|
||||||
CompileUploadPlace(CompileUploadPlaceSubcommand),
|
CompileUploadPlace(CompileUploadPlaceSubcommand),
|
||||||
Decompile(DecompileSubcommand),
|
Decompile(DecompileSubcommand),
|
||||||
|
#[cfg(feature="git")]
|
||||||
DecompileHistoryIntoGit(DecompileHistoryIntoGitSubcommand),
|
DecompileHistoryIntoGit(DecompileHistoryIntoGitSubcommand),
|
||||||
|
#[cfg(feature="git")]
|
||||||
DownloadAndDecompileHistoryIntoGit(DownloadAndDecompileHistoryIntoGitSubcommand),
|
DownloadAndDecompileHistoryIntoGit(DownloadAndDecompileHistoryIntoGitSubcommand),
|
||||||
|
RunLuau(RunLuauSubcommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download a range of assets from the asset version history. Download summary is saved to `output_folder/versions.json`, and can be optionally used to download only new versions the next time.
|
/// Download a range of assets from the asset version history. Download summary is saved to `output_folder/versions.json`, and can be optionally used to download only new versions the next time.
|
||||||
@@ -147,6 +154,8 @@ struct DownloadCreationsJsonSubcommand{
|
|||||||
group_id:Option<u64>,
|
group_id:Option<u64>,
|
||||||
#[arg(long,group="owner",required=true)]
|
#[arg(long,group="owner",required=true)]
|
||||||
user_id:Option<u64>,
|
user_id:Option<u64>,
|
||||||
|
#[arg(long)]
|
||||||
|
continue_from_cursor:Option<bool>,
|
||||||
}
|
}
|
||||||
/// Download the list of asset ids (not the assets themselves) in a user's inventory. The output is written to `output_folder/versions.json`
|
/// Download the list of asset ids (not the assets themselves) in a user's inventory. The output is written to `output_folder/versions.json`
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
@@ -427,6 +436,24 @@ struct DownloadAndDecompileHistoryIntoGitSubcommand{
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
write_scripts:Option<bool>,
|
write_scripts:Option<bool>,
|
||||||
}
|
}
|
||||||
|
/// Run a Luau script.
|
||||||
|
#[derive(Args)]
|
||||||
|
struct RunLuauSubcommand{
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_literal:Option<String>,
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_envvar:Option<String>,
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_file:Option<PathBuf>,
|
||||||
|
#[arg(long,group="script",required=true)]
|
||||||
|
script_literal:Option<String>,
|
||||||
|
#[arg(long,group="script",required=true)]
|
||||||
|
script_file:Option<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
universe_id:u64,
|
||||||
|
#[arg(long)]
|
||||||
|
place_id:u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,clap::ValueEnum)]
|
#[derive(Clone,Copy,Debug,clap::ValueEnum)]
|
||||||
enum Style{
|
enum Style{
|
||||||
@@ -581,7 +608,9 @@ async fn main()->AResult<()>{
|
|||||||
subcommand.group_id,
|
subcommand.group_id,
|
||||||
)?,
|
)?,
|
||||||
subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()),
|
subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()),
|
||||||
|
subcommand.continue_from_cursor.unwrap_or(false),
|
||||||
).await,
|
).await,
|
||||||
|
Commands::DownloadCreationsHistory(subcommand)=>subcommand.run().await,
|
||||||
Commands::DownloadUserInventoryJson(subcommand)=>download_user_inventory_json(
|
Commands::DownloadUserInventoryJson(subcommand)=>download_user_inventory_json(
|
||||||
cookie_from_args(
|
cookie_from_args(
|
||||||
subcommand.cookie_literal,
|
subcommand.cookie_literal,
|
||||||
@@ -713,6 +742,7 @@ async fn main()->AResult<()>{
|
|||||||
write_models:subcommand.write_models.unwrap_or(false),
|
write_models:subcommand.write_models.unwrap_or(false),
|
||||||
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
||||||
}).await,
|
}).await,
|
||||||
|
#[cfg(feature="git")]
|
||||||
Commands::DecompileHistoryIntoGit(subcommand)=>decompile_history_into_git(DecompileHistoryConfig{
|
Commands::DecompileHistoryIntoGit(subcommand)=>decompile_history_into_git(DecompileHistoryConfig{
|
||||||
git_committer_name:subcommand.git_committer_name,
|
git_committer_name:subcommand.git_committer_name,
|
||||||
git_committer_email:subcommand.git_committer_email,
|
git_committer_email:subcommand.git_committer_email,
|
||||||
@@ -723,6 +753,7 @@ async fn main()->AResult<()>{
|
|||||||
write_models:subcommand.write_models.unwrap_or(false),
|
write_models:subcommand.write_models.unwrap_or(false),
|
||||||
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
||||||
}).await,
|
}).await,
|
||||||
|
#[cfg(feature="git")]
|
||||||
Commands::DownloadAndDecompileHistoryIntoGit(subcommand)=>download_and_decompile_history_into_git(DownloadAndDecompileHistoryConfig{
|
Commands::DownloadAndDecompileHistoryIntoGit(subcommand)=>download_and_decompile_history_into_git(DownloadAndDecompileHistoryConfig{
|
||||||
git_committer_name:subcommand.git_committer_name,
|
git_committer_name:subcommand.git_committer_name,
|
||||||
git_committer_email:subcommand.git_committer_email,
|
git_committer_email:subcommand.git_committer_email,
|
||||||
@@ -738,6 +769,21 @@ async fn main()->AResult<()>{
|
|||||||
write_models:subcommand.write_models.unwrap_or(false),
|
write_models:subcommand.write_models.unwrap_or(false),
|
||||||
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
write_scripts:subcommand.write_scripts.unwrap_or(true),
|
||||||
}).await,
|
}).await,
|
||||||
|
Commands::RunLuau(subcommand)=>run_luau(RunLuauConfig{
|
||||||
|
api_key:api_key_from_args(
|
||||||
|
subcommand.api_key_literal,
|
||||||
|
subcommand.api_key_envvar,
|
||||||
|
subcommand.api_key_file,
|
||||||
|
).await?,
|
||||||
|
script:match subcommand.script_literal{
|
||||||
|
Some(script)=>script,
|
||||||
|
None=>std::fs::read_to_string(subcommand.script_file.unwrap())?,
|
||||||
|
},
|
||||||
|
request:rbx_asset::cloud::LuauSessionLatestRequest{
|
||||||
|
place_id:subcommand.place_id,
|
||||||
|
universe_id:subcommand.universe_id,
|
||||||
|
},
|
||||||
|
}).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,7 +897,7 @@ struct CreateAssetMediasConfig{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
enum CreateAssetMediasError{
|
enum CreateAssetMediasError{
|
||||||
NoFileStem(PathBuf),
|
NoFileStem(PathBuf),
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
@@ -866,7 +912,7 @@ impl std::fmt::Display for CreateAssetMediasError{
|
|||||||
impl std::error::Error for CreateAssetMediasError{}
|
impl std::error::Error for CreateAssetMediasError{}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
enum PollOperationError{
|
enum PollOperationError{
|
||||||
CreateAssetMedias(CreateAssetMediasError),
|
CreateAssetMedias(CreateAssetMediasError),
|
||||||
AssetOperation(rbx_asset::cloud::AssetOperationError),
|
AssetOperation(rbx_asset::cloud::AssetOperationError),
|
||||||
@@ -879,7 +925,7 @@ impl std::fmt::Display for PollOperationError{
|
|||||||
impl std::error::Error for PollOperationError{}
|
impl std::error::Error for PollOperationError{}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
enum DownloadDecalError{
|
enum DownloadDecalError{
|
||||||
PollOperation(PollOperationError),
|
PollOperation(PollOperationError),
|
||||||
ParseInt(std::num::ParseIntError),
|
ParseInt(std::num::ParseIntError),
|
||||||
@@ -1120,30 +1166,100 @@ async fn download_location(api_key:ApiKey,asset_id:AssetID,version:Option<u64>)-
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_creations_pages(context:&CookieContext,owner:rbx_asset::cookie::Owner)->AResult<Vec<CreationsItem>>{
|
async fn get_creations_pages(
|
||||||
let mut config=rbx_asset::cookie::CreationsPageRequest{
|
context:&CookieContext,
|
||||||
owner,
|
asset_list:&mut Vec<rbx_asset::cookie::CreationsItem>,
|
||||||
cursor:None,
|
config:&mut rbx_asset::cookie::CreationsPageRequest,
|
||||||
};
|
)->AResult<()>{
|
||||||
let mut asset_list=Vec::new();
|
|
||||||
loop{
|
loop{
|
||||||
let mut page=context.get_creations_page(&config).await?;
|
let mut page=context.get_creations_page(&config).await?;
|
||||||
asset_list.append(&mut page.data);
|
asset_list.append(&mut page.data);
|
||||||
if page.nextPageCursor.is_none(){
|
config.cursor=page.nextPageCursor;
|
||||||
|
if config.cursor.is_none(){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
config.cursor=page.nextPageCursor;
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn download_creations_pages_from_checkpoint(context:&CookieContext,owner:rbx_asset::cookie::Owner,output_folder:&Path,continue_from_cursor:bool)->AResult<Vec<CreationsItem>>{
|
||||||
|
let mut versions_path=output_folder.to_owned();
|
||||||
|
versions_path.set_file_name("versions.json");
|
||||||
|
let mut cursor_path=output_folder.to_owned();
|
||||||
|
cursor_path.set_file_name("cursor");
|
||||||
|
|
||||||
|
let (mut asset_list,mut config)=if continue_from_cursor{
|
||||||
|
// load state from files
|
||||||
|
let (versions,cursor)=tokio::join!(
|
||||||
|
tokio::fs::read(versions_path.as_path()),
|
||||||
|
tokio::fs::read_to_string(cursor_path.as_path()),
|
||||||
|
);
|
||||||
|
// allow versions to not exist
|
||||||
|
let (versions,cursor)=match (versions,cursor){
|
||||||
|
// continue downloading
|
||||||
|
(Ok(versions),Ok(cursor))=>(serde_json::from_slice(&versions)?,Some(cursor)),
|
||||||
|
// already downloaded
|
||||||
|
(Ok(versions),Err(e)) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>return Ok(serde_json::from_slice(&versions)?),
|
||||||
|
// not downloaded
|
||||||
|
(Err(e),result) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>{
|
||||||
|
match result{
|
||||||
|
Ok(_)=>{},
|
||||||
|
Err(e) if matches!(e.kind(),std::io::ErrorKind::NotFound)=>{},
|
||||||
|
Err(e)=>Err(e)?,
|
||||||
|
}
|
||||||
|
(Vec::new(),None)
|
||||||
|
},
|
||||||
|
// other errors
|
||||||
|
(Ok(_),Err(e))=>Err(e)?,
|
||||||
|
(Err(e),_)=>Err(e)?,
|
||||||
|
};
|
||||||
|
(
|
||||||
|
versions,
|
||||||
|
rbx_asset::cookie::CreationsPageRequest{
|
||||||
|
owner,
|
||||||
|
cursor,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
// create new state
|
||||||
|
(
|
||||||
|
Vec::new(),
|
||||||
|
rbx_asset::cookie::CreationsPageRequest{
|
||||||
|
owner,
|
||||||
|
cursor:None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
get_creations_pages(&context,&mut asset_list,&mut config).await?;
|
||||||
|
|
||||||
|
let cursor_fut=async{
|
||||||
|
if let Some(cursor)=config.cursor{
|
||||||
|
println!("writing cursor state...");
|
||||||
|
// there was a problem, write out cursor
|
||||||
|
tokio::fs::write(cursor_path,cursor).await?;
|
||||||
|
}else{
|
||||||
|
// no cursor
|
||||||
|
if let Err(e)=tokio::fs::remove_file(cursor_path).await{
|
||||||
|
match e.kind(){
|
||||||
|
std::io::ErrorKind::NotFound=>println!("Cannot delete cursor: file not found"),
|
||||||
|
_=>Err(e)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
let versions_fut=tokio::fs::write(versions_path,serde_json::to_string(&asset_list)?);
|
||||||
|
|
||||||
|
tokio::try_join!(versions_fut,cursor_fut)?;
|
||||||
|
|
||||||
Ok(asset_list)
|
Ok(asset_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_creations_json(cookie:Cookie,owner:rbx_asset::cookie::Owner,output_folder:PathBuf)->AResult<()>{
|
async fn download_creations_json(cookie:Cookie,owner:rbx_asset::cookie::Owner,output_folder:PathBuf,continue_from_cursor:bool)->AResult<()>{
|
||||||
let context=CookieContext::new(cookie);
|
let context=CookieContext::new(cookie);
|
||||||
let item_list=get_creations_pages(&context,owner).await?;
|
|
||||||
|
|
||||||
let mut path=output_folder.clone();
|
download_creations_pages_from_checkpoint(&context,owner,output_folder.as_path(),continue_from_cursor).await?;
|
||||||
path.set_file_name("versions.json");
|
|
||||||
tokio::fs::write(path,serde_json::to_string(&item_list)?).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1168,7 +1284,7 @@ async fn download_user_inventory_json(cookie:Cookie,user_id:u64,output_folder:Pa
|
|||||||
let mut versions_path=output_folder.clone();
|
let mut versions_path=output_folder.clone();
|
||||||
versions_path.set_file_name("versions.json");
|
versions_path.set_file_name("versions.json");
|
||||||
let mut cursor_path=output_folder.clone();
|
let mut cursor_path=output_folder.clone();
|
||||||
cursor_path.set_file_name("cursor.json");
|
cursor_path.set_file_name("cursor");
|
||||||
|
|
||||||
let context=CookieContext::new(cookie);
|
let context=CookieContext::new(cookie);
|
||||||
|
|
||||||
@@ -1216,18 +1332,163 @@ async fn download_user_inventory_json(cookie:Cookie,user_id:u64,output_folder:Pa
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Download all versions of all assets created by a group or user. The output is written to a folder structure in the output directory.
|
||||||
|
#[derive(Args)]
|
||||||
|
struct DownloadCreationsHistorySubcommand{
|
||||||
|
#[arg(long,group="cookie",required=true)]
|
||||||
|
cookie_literal:Option<String>,
|
||||||
|
#[arg(long,group="cookie",required=true)]
|
||||||
|
cookie_envvar:Option<String>,
|
||||||
|
#[arg(long,group="cookie",required=true)]
|
||||||
|
cookie_file:Option<PathBuf>,
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_literal:Option<String>,
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_envvar:Option<String>,
|
||||||
|
#[arg(long,group="api_key",required=true)]
|
||||||
|
api_key_file:Option<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
output_folder:Option<PathBuf>,
|
||||||
|
#[arg(long,group="owner",required=true)]
|
||||||
|
group_id:Option<u64>,
|
||||||
|
#[arg(long,group="owner",required=true)]
|
||||||
|
user_id:Option<u64>,
|
||||||
|
#[arg(long)]
|
||||||
|
r#continue:Option<bool>,
|
||||||
|
}
|
||||||
|
impl DownloadCreationsHistorySubcommand{
|
||||||
|
async fn run(self)->AResult<()>{
|
||||||
|
download_creations_history(
|
||||||
|
cookie_from_args(
|
||||||
|
self.cookie_literal,
|
||||||
|
self.cookie_envvar,
|
||||||
|
self.cookie_file,
|
||||||
|
).await?,
|
||||||
|
api_key_from_args(
|
||||||
|
self.api_key_literal,
|
||||||
|
self.api_key_envvar,
|
||||||
|
self.api_key_file,
|
||||||
|
).await?,
|
||||||
|
owner_from_args(
|
||||||
|
self.user_id,
|
||||||
|
self.group_id,
|
||||||
|
)?,
|
||||||
|
self.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()),
|
||||||
|
self.r#continue.unwrap_or(false),
|
||||||
|
).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn download_creations_history(cookie:Cookie,api_key:ApiKey,owner:rbx_asset::cookie::Owner,output_folder:PathBuf,r#continue:bool)->AResult<()>{
|
||||||
|
|
||||||
|
let cookie_context=CookieContext::new(cookie);
|
||||||
|
let cloud_context=CloudContext::new(api_key);
|
||||||
|
|
||||||
|
// get list of all assets in inventory
|
||||||
|
let asset_list=download_creations_pages_from_checkpoint(&cookie_context,owner,output_folder.as_path(),r#continue).await?;
|
||||||
|
|
||||||
|
// create folder directories
|
||||||
|
let asset_folders:Vec<PathBuf> ={
|
||||||
|
futures::stream::iter(asset_list.iter().map(|asset|async{
|
||||||
|
// create asset folder
|
||||||
|
let mut asset_folder=output_folder.clone();
|
||||||
|
asset_folder.push(asset.id.to_string());
|
||||||
|
tokio::fs::create_dir_all(asset_folder.as_path()).await?;
|
||||||
|
Ok::<_,anyhow::Error>(asset_folder)
|
||||||
|
}))
|
||||||
|
.buffered(CONCURRENT_FS)
|
||||||
|
.try_collect().await?
|
||||||
|
};
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error<'a>{
|
||||||
|
NoLocations(Job<'a>),
|
||||||
|
GetVersionLocationError(rbx_asset::cloud::GetError),
|
||||||
|
GetError(rbx_asset::cloud::GetError),
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Copy,Debug)]
|
||||||
|
struct Job<'a>{
|
||||||
|
path:&'a PathBuf,
|
||||||
|
asset_id:u64,
|
||||||
|
asset_version:u64,
|
||||||
|
}
|
||||||
|
let mut job_list=Vec::new();
|
||||||
|
|
||||||
|
// create flattened futures stream to parallel download all asset versions
|
||||||
|
for (path,asset) in asset_folders.iter().zip(asset_list){
|
||||||
|
|
||||||
|
// save versions file
|
||||||
|
let mut versions_path=path.to_owned();
|
||||||
|
versions_path.push("versions.json");
|
||||||
|
|
||||||
|
let version_history=if r#continue{
|
||||||
|
let file=tokio::fs::read(versions_path.as_path()).await?;
|
||||||
|
serde_json::from_slice(&file)?
|
||||||
|
}else{
|
||||||
|
println!("Downloading history for {} - {}",asset.id,asset.name);
|
||||||
|
let version_history=get_version_history(&cookie_context,asset.id).await?;
|
||||||
|
println!("Found {} versions",version_history.len());
|
||||||
|
tokio::fs::write(versions_path,serde_json::to_string(&version_history)?).await?;
|
||||||
|
version_history
|
||||||
|
};
|
||||||
|
|
||||||
|
job_list.extend(version_history.into_iter().map(|asset_version|
|
||||||
|
Job{
|
||||||
|
path,
|
||||||
|
asset_id:asset.id,
|
||||||
|
asset_version:asset_version.assetVersionNumber,
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Completed jobs list. Number of jobs: {}",job_list.len());
|
||||||
|
|
||||||
|
futures::stream::iter(job_list).map(async|job|{
|
||||||
|
let mut dest=job.path.to_owned();
|
||||||
|
dest.push(format!("{}_v{}.rbxl",job.asset_id,job.asset_version));
|
||||||
|
//if the file already exists, don't try downloading it again
|
||||||
|
if tokio::fs::try_exists(dest.as_path()).await.map_err(Error::Io)?{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let location=cloud_context.get_asset_version_location(rbx_asset::cloud::GetAssetVersionRequest{
|
||||||
|
asset_id:job.asset_id,
|
||||||
|
version:job.asset_version,
|
||||||
|
}).await.map_err(Error::GetVersionLocationError)?;
|
||||||
|
let location=location.location.ok_or(Error::NoLocations(job))?;
|
||||||
|
let downloaded=cloud_context.get_asset(&location).await.map_err(Error::GetError)?;
|
||||||
|
tokio::fs::write(dest,downloaded.to_vec().map_err(Error::Io)?).await.map_err(Error::Io)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.buffer_unordered(CONCURRENT_REQUESTS)
|
||||||
|
.for_each(async|result|{
|
||||||
|
match result{
|
||||||
|
Ok(())=>{},
|
||||||
|
Err(Error::NoLocations(job))=>println!("Job failed due to no locations: asset_id={} version={}",job.asset_id,job.asset_version),
|
||||||
|
Err(e)=>println!("Error: {e:?}"),
|
||||||
|
}
|
||||||
|
}).await;
|
||||||
|
|
||||||
|
println!("All jobs complete.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_version_history(context:&CookieContext,asset_id:AssetID)->AResult<Vec<AssetVersion>>{
|
async fn get_version_history(context:&CookieContext,asset_id:AssetID)->AResult<Vec<AssetVersion>>{
|
||||||
let mut cursor:Option<String>=None;
|
let mut page_request=rbx_asset::cookie::AssetVersionsPageRequest{
|
||||||
|
asset_id,
|
||||||
|
cursor:None,
|
||||||
|
};
|
||||||
let mut asset_list=Vec::new();
|
let mut asset_list=Vec::new();
|
||||||
loop{
|
loop{
|
||||||
let mut page=context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{asset_id,cursor}).await?;
|
let mut page=context.get_asset_versions_page(&page_request).await?;
|
||||||
asset_list.append(&mut page.data);
|
asset_list.append(&mut page.data);
|
||||||
if page.nextPageCursor.is_none(){
|
if page.nextPageCursor.is_none(){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cursor=page.nextPageCursor;
|
page_request.cursor=page.nextPageCursor;
|
||||||
}
|
}
|
||||||
asset_list.sort_by(|a,b|a.assetVersionNumber.cmp(&b.assetVersionNumber));
|
asset_list.sort_by_key(|a|a.assetVersionNumber);
|
||||||
Ok(asset_list)
|
Ok(asset_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1283,9 +1544,12 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{
|
|||||||
let mut join_set=tokio::task::JoinSet::new();
|
let mut join_set=tokio::task::JoinSet::new();
|
||||||
|
|
||||||
//poll paged list of all asset versions
|
//poll paged list of all asset versions
|
||||||
let mut cursor:Option<String>=None;
|
let mut page_request=rbx_asset::cookie::AssetVersionsPageRequest{
|
||||||
|
asset_id:config.asset_id,
|
||||||
|
cursor:None,
|
||||||
|
};
|
||||||
loop{
|
loop{
|
||||||
let mut page=context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{asset_id:config.asset_id,cursor}).await?;
|
let mut page=context.get_asset_versions_page(&page_request).await?;
|
||||||
let context=&context;
|
let context=&context;
|
||||||
let output_folder=config.output_folder.clone();
|
let output_folder=config.output_folder.clone();
|
||||||
let data=&page.data;
|
let data=&page.data;
|
||||||
@@ -1344,10 +1608,10 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{
|
|||||||
}else{
|
}else{
|
||||||
asset_list.append(&mut page.data);
|
asset_list.append(&mut page.data);
|
||||||
}
|
}
|
||||||
cursor=page.nextPageCursor;
|
page_request.cursor=page.nextPageCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
asset_list.sort_by(|a,b|a.assetVersionNumber.cmp(&b.assetVersionNumber));
|
asset_list.sort_by_key(|a|a.assetVersionNumber);
|
||||||
|
|
||||||
let mut path=config.output_folder.clone();
|
let mut path=config.output_folder.clone();
|
||||||
path.set_file_name("versions.json");
|
path.set_file_name("versions.json");
|
||||||
@@ -1361,7 +1625,7 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
enum LoadDomError{
|
enum LoadDomError{
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
RbxBinary(rbx_binary::DecodeError),
|
RbxBinary(rbx_binary::DecodeError),
|
||||||
@@ -1453,6 +1717,7 @@ async fn download_decompile(config:DownloadDecompileConfig)->AResult<()>{
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="git")]
|
||||||
struct WriteCommitConfig{
|
struct WriteCommitConfig{
|
||||||
git_committer_name:String,
|
git_committer_name:String,
|
||||||
git_committer_email:String,
|
git_committer_email:String,
|
||||||
@@ -1463,7 +1728,8 @@ struct WriteCommitConfig{
|
|||||||
write_scripts:bool,
|
write_scripts:bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_commit(config:WriteCommitConfig,b:Result<AResult<(AssetVersion,rox_compiler::DecompiledContext)>,tokio::task::JoinError>,repo:&gix::Repository)->AResult<()>{
|
#[cfg(feature="git")]
|
||||||
|
async fn write_commit(config:WriteCommitConfig,b:Result<AResult<(AssetVersion,rox_compiler::DecompiledContext)>,tokio::task::JoinError>,repo:&git2::Repository)->AResult<()>{
|
||||||
let (asset_version,context)=b??;
|
let (asset_version,context)=b??;
|
||||||
println!("writing files for version {}",asset_version.assetVersionNumber);
|
println!("writing files for version {}",asset_version.assetVersionNumber);
|
||||||
|
|
||||||
@@ -1494,17 +1760,12 @@ async fn write_commit(config:WriteCommitConfig,b:Result<AResult<(AssetVersion,ro
|
|||||||
write_scripts:config.write_scripts,
|
write_scripts:config.write_scripts,
|
||||||
}).await?;
|
}).await?;
|
||||||
|
|
||||||
let time=gix::date::Time::new(asset_version.created.timestamp(),0);
|
let date=asset_version.created;
|
||||||
let sig=gix::actor::Signature{
|
//let sig=repo.signature()?; //this pulls default name and email
|
||||||
name:config.git_committer_name.into(),
|
let sig=git2::Signature::new(config.git_committer_name.as_str(),config.git_committer_email.as_str(),&git2::Time::new(date.timestamp(),0)).unwrap();
|
||||||
email:config.git_committer_email.into(),
|
|
||||||
time,
|
|
||||||
};
|
|
||||||
let tree_id={
|
let tree_id={
|
||||||
let mut tree_index = repo.index()?;
|
let mut tree_index = repo.index()?;
|
||||||
tree.index()?.
|
match tree_index.add_all(std::iter::once("*"),git2::IndexAddOption::DEFAULT,None){
|
||||||
tree.peel_to_entry(path);
|
|
||||||
match tree_index.add_all(std::iter::once("*"),gix::IndexAddOption::DEFAULT,None){
|
|
||||||
Ok(_)=>(),
|
Ok(_)=>(),
|
||||||
Err(e)=>println!("tree_index.add_all error: {}",e),
|
Err(e)=>println!("tree_index.add_all error: {}",e),
|
||||||
}
|
}
|
||||||
@@ -1515,44 +1776,41 @@ async fn write_commit(config:WriteCommitConfig,b:Result<AResult<(AssetVersion,ro
|
|||||||
tree_index.write()?;
|
tree_index.write()?;
|
||||||
tree_index.write_tree()?
|
tree_index.write_tree()?
|
||||||
};
|
};
|
||||||
let tree=repo.tree(tree_id)?;
|
let tree=repo.find_tree(tree_id)?;
|
||||||
|
|
||||||
let mut parents=Vec::new();
|
let mut parents=Vec::new();
|
||||||
|
|
||||||
match repo.head_commit(){
|
match repo.head(){
|
||||||
Ok(commit)=>{
|
Ok(reference)=>{
|
||||||
|
let commit=reference.peel_to_commit()?;
|
||||||
|
|
||||||
//test tree against commit tree to see if there is any changes
|
//test tree against commit tree to see if there is any changes
|
||||||
let commit_tree=commit.tree()?;
|
let commit_tree=commit.tree()?;
|
||||||
let diff=repo.diff_tree_to_tree(Some(&commit_tree),Some(&tree),None)?;
|
let diff=repo.diff_tree_to_tree(Some(&commit_tree),Some(&tree),None)?;
|
||||||
if diff.is_empty(){
|
if diff.get_delta(0).is_none(){
|
||||||
println!("no changes");
|
println!("no changes");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
parents.push(commit.id());
|
parents.push(commit);
|
||||||
},
|
},
|
||||||
//If the repo head is not found, great, continue and make the first commit
|
Err(e)=>println!("repo head error {:?}",e),
|
||||||
Err(gix::reference::head_commit::Error::Head(gix::reference::find::existing::Error::NotFound{..}))=>(),
|
|
||||||
//for other errors
|
|
||||||
Err(e)=>Err(e)?,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut timebuf=gix::date::parse::TimeBuf::default();
|
repo.commit(
|
||||||
let sig_ref=sig.to_ref(&mut timebuf);
|
Some("HEAD"),//update_ref
|
||||||
|
&sig,//author
|
||||||
repo.commit_as(
|
&sig,//commiter
|
||||||
sig_ref,//commiter
|
&format!("v{}", asset_version.assetVersionNumber),//message
|
||||||
sig_ref,//author
|
|
||||||
"HEAD",
|
|
||||||
&format!("v{}",asset_version.assetVersionNumber),//message
|
|
||||||
&tree,//tree (basically files)
|
&tree,//tree (basically files)
|
||||||
parents,//parents
|
parents.iter().collect::<Vec<&git2::Commit<'_>>>().as_slice(),//parents
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
//commit
|
//commit
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="git")]
|
||||||
struct DecompileHistoryConfig{
|
struct DecompileHistoryConfig{
|
||||||
git_committer_name:String,
|
git_committer_name:String,
|
||||||
git_committer_email:String,
|
git_committer_email:String,
|
||||||
@@ -1564,20 +1822,14 @@ struct DecompileHistoryConfig{
|
|||||||
write_scripts:bool,
|
write_scripts:bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="git")]
|
||||||
async fn decompile_history_into_git(config:DecompileHistoryConfig)->AResult<()>{
|
async fn decompile_history_into_git(config:DecompileHistoryConfig)->AResult<()>{
|
||||||
//use prexisting versions list
|
//use prexisting versions list
|
||||||
let mut versions_path=config.input_folder.clone();
|
let mut versions_path=config.input_folder.clone();
|
||||||
versions_path.push("versions.json");
|
versions_path.push("versions.json");
|
||||||
let asset_list:Vec<AssetVersion>=serde_json::from_reader(std::fs::File::open(versions_path)?)?;
|
let asset_list:Vec<AssetVersion>=serde_json::from_reader(std::fs::File::open(versions_path)?)?;
|
||||||
|
|
||||||
let repo=gix::discover(gix::create::into(
|
let repo=git2::Repository::init(config.output_folder.as_path())?;
|
||||||
config.output_folder.as_path(),
|
|
||||||
gix::create::Kind::Bare,
|
|
||||||
gix::create::Options{
|
|
||||||
destination_must_be_empty:true,
|
|
||||||
fs_capabilities:None
|
|
||||||
}
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
//decompile all versions
|
//decompile all versions
|
||||||
futures::stream::iter(asset_list.into_iter()
|
futures::stream::iter(asset_list.into_iter()
|
||||||
@@ -1609,6 +1861,7 @@ async fn decompile_history_into_git(config:DecompileHistoryConfig)->AResult<()>{
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="git")]
|
||||||
struct DownloadAndDecompileHistoryConfig{
|
struct DownloadAndDecompileHistoryConfig{
|
||||||
cookie:Cookie,
|
cookie:Cookie,
|
||||||
asset_id:AssetID,
|
asset_id:AssetID,
|
||||||
@@ -1621,20 +1874,14 @@ struct DownloadAndDecompileHistoryConfig{
|
|||||||
write_scripts:bool,
|
write_scripts:bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="git")]
|
||||||
async fn download_and_decompile_history_into_git(config:DownloadAndDecompileHistoryConfig)->AResult<()>{
|
async fn download_and_decompile_history_into_git(config:DownloadAndDecompileHistoryConfig)->AResult<()>{
|
||||||
let context=CookieContext::new(config.cookie);
|
let context=CookieContext::new(config.cookie);
|
||||||
|
|
||||||
//poll paged list of all asset versions
|
//poll paged list of all asset versions
|
||||||
let asset_list=get_version_history(&context,config.asset_id).await?;
|
let asset_list=get_version_history(&context,config.asset_id).await?;
|
||||||
|
|
||||||
let repo=gix::discover(gix::create::into(
|
let repo=git2::Repository::init(config.output_folder.clone())?;
|
||||||
config.output_folder.as_path(),
|
|
||||||
gix::create::Kind::Bare,
|
|
||||||
gix::create::Options{
|
|
||||||
destination_must_be_empty:true,
|
|
||||||
fs_capabilities:None
|
|
||||||
}
|
|
||||||
)?)?;
|
|
||||||
|
|
||||||
//download all versions
|
//download all versions
|
||||||
let asset_id=config.asset_id;
|
let asset_id=config.asset_id;
|
||||||
@@ -1767,3 +2014,43 @@ async fn compile_upload_place(config:CompileUploadPlaceConfig)->AResult<()>{
|
|||||||
println!("UploadResponse={:?}",resp);
|
println!("UploadResponse={:?}",resp);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_luau_result_exp_backoff(
|
||||||
|
context:&CloudContext,
|
||||||
|
luau_session:&rbx_asset::cloud::LuauSessionResponse
|
||||||
|
)->Result<Result<rbx_asset::cloud::LuauResults,rbx_asset::cloud::LuauError>,rbx_asset::cloud::LuauSessionError>{
|
||||||
|
const BACKOFF_MUL:f32=1.395_612_5;//exp(1/3)
|
||||||
|
let mut backoff=1000f32;
|
||||||
|
loop{
|
||||||
|
match luau_session.try_get_result(context).await{
|
||||||
|
//try again when the operation is not done
|
||||||
|
Err(rbx_asset::cloud::LuauSessionError::NotDone)=>(),
|
||||||
|
//return all other results
|
||||||
|
other_result=>return other_result,
|
||||||
|
}
|
||||||
|
println!("Operation not complete; waiting {:.0}ms...",backoff);
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(backoff as u64)).await;
|
||||||
|
backoff*=BACKOFF_MUL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct RunLuauConfig{
|
||||||
|
api_key:ApiKey,
|
||||||
|
script:String,
|
||||||
|
request:rbx_asset::cloud::LuauSessionLatestRequest,
|
||||||
|
}
|
||||||
|
async fn run_luau(config:RunLuauConfig)->AResult<()>{
|
||||||
|
let context=CloudContext::new(config.api_key);
|
||||||
|
let session=rbx_asset::cloud::LuauSessionCreate{
|
||||||
|
script:&config.script,
|
||||||
|
user:None,
|
||||||
|
timeout:None,
|
||||||
|
binaryInput:None,
|
||||||
|
enableBinaryOutput:None,
|
||||||
|
binaryOutputUri:None,
|
||||||
|
};
|
||||||
|
let response=context.create_luau_session(&config.request,session).await?;
|
||||||
|
dbg!(&response);
|
||||||
|
let result=get_luau_result_exp_backoff(&context,&response).await?;
|
||||||
|
dbg!(&result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user