Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a762bc96f7 |
22
.drone.yml
22
.drone.yml
@@ -7,17 +7,6 @@ platform:
|
||||
arch: amd64
|
||||
|
||||
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
|
||||
image: plugins/docker
|
||||
settings:
|
||||
@@ -30,15 +19,6 @@ steps:
|
||||
password:
|
||||
from_secret: GIT_PASS
|
||||
dockerfile: Containerfile
|
||||
depends_on:
|
||||
- build
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
---
|
||||
kind: signature
|
||||
hmac: 52507904dfaada892c05a61422dc5e147c1438419ed841d0f1e3e3ec2b193540
|
||||
|
||||
...
|
||||
- master
|
||||
1676
Cargo.lock
generated
1676
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@@ -1,7 +1,7 @@
|
||||
workspace = { members = ["rbx_asset", "rox_compiler"] }
|
||||
[package]
|
||||
name = "asset-tool"
|
||||
version = "0.5.1"
|
||||
version = "0.4.7"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -10,20 +10,18 @@ edition = "2021"
|
||||
anyhow = "1.0.75"
|
||||
clap = { version = "4.4.2", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
git2 = { version = "0.20.0", optional = true }
|
||||
rbx_asset = { path = "rbx_asset", features = ["gzip", "rustls-tls"], default-features = false }
|
||||
rbx_binary = "2.0.0"
|
||||
rbx_dom_weak = "4.0.0"
|
||||
rbx_reflection_database = "2.0.1"
|
||||
rbx_xml = "2.0.0"
|
||||
git2 = "0.18.1"
|
||||
lazy-regex = "3.1.0"
|
||||
pollster = "0.3.0"
|
||||
rbx_asset = { path = "rbx_asset" }
|
||||
rbx_binary = "0.7.4"
|
||||
rbx_dom_weak = "2.7.0"
|
||||
rbx_reflection_database = "0.2.10"
|
||||
rbx_xml = "0.13.3"
|
||||
rox_compiler = { path = "rox_compiler" }
|
||||
serde_json = "1.0.111"
|
||||
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread", "fs"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
git = ["dep:git2"]
|
||||
|
||||
[profile.release]
|
||||
#lto = true
|
||||
strip = true
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
FROM alpine:3.22 AS runtime
|
||||
COPY /target/x86_64-unknown-linux-musl/release/asset-tool /usr/local/bin/
|
||||
ENTRYPOINT ["/usr/local/bin/asset-tool"]
|
||||
# Using the `rust-musl-builder` as base image, instead of
|
||||
# the official Rust toolchain
|
||||
FROM docker.io/clux/muslrust:stable AS chef
|
||||
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]
|
||||
name = "rbx_asset"
|
||||
version = "0.5.0"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
publish = ["strafesnet"]
|
||||
repository = "https://git.itzana.me/StrafesNET/asset-tool"
|
||||
@@ -10,22 +10,10 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["gzip", "default-tls"]
|
||||
gzip = ["dep:flate2"]
|
||||
|
||||
default-tls = ["reqwest/default-tls"]
|
||||
rustls-tls = ["reqwest/rustls-tls"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.10.1"
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
flate2 = { version = "1.0.29", optional = true }
|
||||
reqwest = { version = "0.12.4", features = [
|
||||
"json", "multipart",
|
||||
# default features
|
||||
"charset", "http2", "system-proxy"
|
||||
], default-features = false }
|
||||
flate2 = "1.0.29"
|
||||
reqwest = { version = "0.12.4", features = ["json","multipart"] }
|
||||
serde = { version = "1.0.199", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
url = "2.5.0"
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use reqwest::Body;
|
||||
|
||||
pub trait ContentType:Into<Body>{
|
||||
fn content_type(&self)->&'static str;
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Json<T>(pub(crate)T);
|
||||
impl<T:Into<Body>> From<Json<T>> for Body{
|
||||
fn from(Json(value):Json<T>)->Self{
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
impl<T:Into<Body>> ContentType for Json<T>{
|
||||
fn content_type(&self)->&'static str{
|
||||
"application/json"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Text<T>(pub(crate)T);
|
||||
impl<T:Into<Body>> From<Text<T>> for Body{
|
||||
fn from(Text(value):Text<T>)->Self{
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
impl<T:Into<Body>> ContentType for Text<T>{
|
||||
fn content_type(&self)->&'static str{
|
||||
"text/plain"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Binary<T>(pub(crate)T);
|
||||
impl<T:Into<Body>> From<Binary<T>> for Body{
|
||||
fn from(Binary(value):Binary<T>)->Self{
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
impl<T:Into<Body>> ContentType for Binary<T>{
|
||||
fn content_type(&self)->&'static str{
|
||||
"application/octet-stream"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::body::{Binary,ContentType,Json};
|
||||
use crate::util::{serialize_u64,deserialize_u64,response_ok};
|
||||
use crate::types::{ResponseError,MaybeGzippedBytes};
|
||||
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub enum AssetType{
|
||||
Audio,
|
||||
Decal,
|
||||
Model,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct CreateAssetRequest{
|
||||
pub assetType:AssetType,
|
||||
pub creationContext:CreationContext,
|
||||
@@ -32,7 +29,7 @@ pub struct AssetOperation{
|
||||
operation:RobloxOperation,
|
||||
}
|
||||
impl AssetOperation{
|
||||
pub async fn try_get_asset(&self,context:&Context)->Result<AssetResponse,AssetOperationError>{
|
||||
pub async fn try_get_asset(&self,context:&CloudContext)->Result<AssetResponse,AssetOperationError>{
|
||||
serde_json::from_value(
|
||||
self.operation
|
||||
.try_get_reponse(context).await
|
||||
@@ -43,7 +40,6 @@ impl AssetOperation{
|
||||
#[derive(Debug)]
|
||||
pub enum CreateError{
|
||||
Parse(url::ParseError),
|
||||
Response(ResponseError),
|
||||
Serialize(serde_json::Error),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
@@ -55,7 +51,7 @@ impl std::fmt::Display for CreateError{
|
||||
impl std::error::Error for CreateError{}
|
||||
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UpdateAssetRequest{
|
||||
pub assetId:u64,
|
||||
pub displayName:Option<String>,
|
||||
@@ -64,48 +60,48 @@ pub struct UpdateAssetRequest{
|
||||
|
||||
//woo nested roblox stuff
|
||||
#[derive(Clone,Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub enum Creator{
|
||||
userId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
||||
groupId(#[serde(deserialize_with="deserialize_u64",serialize_with="serialize_u64")]u64),
|
||||
userId(String),//u64 string
|
||||
groupId(String),//u64 string
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct CreationContext{
|
||||
pub creator:Creator,
|
||||
pub expectedPrice:Option<u64>,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub enum ModerationState{
|
||||
Reviewing,
|
||||
Rejected,
|
||||
Approved,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct ModerationResult{
|
||||
pub moderationState:ModerationState,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct Preview{
|
||||
pub asset:String,
|
||||
pub altText:String,
|
||||
}
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UpdatePlaceRequest{
|
||||
pub universeId:u64,
|
||||
pub placeId:u64,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UpdatePlaceResponse{
|
||||
pub versionNumber:u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum UpdateError{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
SerializeError(serde_json::Error),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
@@ -116,10 +112,10 @@ impl std::fmt::Display for UpdateError{
|
||||
}
|
||||
impl std::error::Error for UpdateError{}
|
||||
|
||||
struct GetAssetOperationRequest<'a>{
|
||||
operation_id:&'a str,
|
||||
struct GetAssetOperationRequest{
|
||||
operation_id:String,
|
||||
}
|
||||
pub struct GetAssetLatestRequest{
|
||||
pub struct GetAssetInfoRequest{
|
||||
pub asset_id:u64,
|
||||
}
|
||||
/*
|
||||
@@ -144,35 +140,35 @@ pub struct GetAssetLatestRequest{
|
||||
}
|
||||
*/
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct AssetResponse{
|
||||
//u64 wrapped in quotes wohoo!!
|
||||
#[serde(deserialize_with="deserialize_u64")]
|
||||
#[serde(serialize_with="serialize_u64")]
|
||||
pub assetId:u64,
|
||||
pub assetId:String,//u64 wrapped in quotes wohoo!!
|
||||
pub assetType:AssetType,
|
||||
pub creationContext:CreationContext,
|
||||
pub description:Option<String>,
|
||||
pub description:String,
|
||||
pub displayName:String,
|
||||
pub path:String,
|
||||
pub revisionCreateTime:chrono::DateTime<chrono::Utc>,
|
||||
#[serde(deserialize_with="deserialize_u64")]
|
||||
#[serde(serialize_with="serialize_u64")]
|
||||
pub revisionId:u64,
|
||||
pub revisionId:String,//u64
|
||||
pub moderationResult:ModerationResult,
|
||||
pub icon:Option<String>,
|
||||
#[serde(default)]
|
||||
pub previews:Vec<Preview>,
|
||||
pub previews:Option<Vec<Preview>>,
|
||||
}
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct GetAssetVersionRequest{
|
||||
pub asset_id:u64,
|
||||
pub version:u64,
|
||||
}
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct GetAssetRequest{
|
||||
pub asset_id:u64,
|
||||
pub version:Option<u64>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum GetError{
|
||||
Parse(url::ParseError),
|
||||
Response(ResponseError),
|
||||
ParseError(url::ParseError),
|
||||
Reqwest(reqwest::Error),
|
||||
IO(std::io::Error)
|
||||
}
|
||||
impl std::fmt::Display for GetError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
@@ -181,42 +177,12 @@ impl std::fmt::Display for GetError{
|
||||
}
|
||||
impl std::error::Error for GetError{}
|
||||
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
pub struct AssetLocation(
|
||||
// the location is private so users cannot mutate it
|
||||
String
|
||||
);
|
||||
impl AssetLocation{
|
||||
pub fn location(&self)->&str{
|
||||
let Self(location)=self;
|
||||
location
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct AssetMetadata{
|
||||
pub metadataType:u32,
|
||||
pub value:String,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct AssetLocationInfo{
|
||||
pub location:Option<AssetLocation>,
|
||||
pub requestId:String,
|
||||
pub isArchived:bool,
|
||||
pub assetTypeId:u32,
|
||||
#[serde(default)]
|
||||
pub assetMetadatas:Vec<AssetMetadata>,
|
||||
pub isRecordable:bool,
|
||||
}
|
||||
|
||||
pub struct AssetVersionsRequest{
|
||||
pub asset_id:u64,
|
||||
pub cursor:Option<String>,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct AssetVersion{
|
||||
pub Id:u64,
|
||||
pub assetId:u64,
|
||||
@@ -228,7 +194,7 @@ pub struct AssetVersion{
|
||||
pub isPublished:bool,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct AssetVersionsResponse{
|
||||
pub previousPageCursor:Option<String>,
|
||||
pub nextPageCursor:Option<String>,
|
||||
@@ -237,7 +203,6 @@ pub struct AssetVersionsResponse{
|
||||
#[derive(Debug)]
|
||||
pub enum AssetVersionsError{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
impl std::fmt::Display for AssetVersionsError{
|
||||
@@ -252,12 +217,13 @@ pub struct InventoryPageRequest{
|
||||
pub cursor:Option<String>,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct InventoryItem{
|
||||
pub id:u64,
|
||||
pub name:String,
|
||||
}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct InventoryPageResponse{
|
||||
pub totalResults:u64,//up to 50
|
||||
pub filteredKeyword:Option<String>,//""
|
||||
@@ -272,7 +238,6 @@ pub struct InventoryPageResponse{
|
||||
#[derive(Debug)]
|
||||
pub enum InventoryPageError{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
impl std::fmt::Display for InventoryPageError{
|
||||
@@ -295,7 +260,7 @@ impl std::fmt::Display for OperationError{
|
||||
}
|
||||
impl std::error::Error for OperationError{}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
struct RobloxOperation{
|
||||
pub path:Option<String>,
|
||||
pub metadata:Option<String>,
|
||||
@@ -314,120 +279,33 @@ impl RobloxOperation{
|
||||
None=>self.path.as_deref()?.get(11..),
|
||||
}
|
||||
}
|
||||
pub async fn try_get_reponse(&self,context:&Context)->Result<serde_json::Value,OperationError>{
|
||||
pub async fn try_get_reponse(&self,context:&CloudContext)->Result<serde_json::Value,OperationError>{
|
||||
context.get_asset_operation(GetAssetOperationRequest{
|
||||
operation_id:self.operation_id()
|
||||
.ok_or(OperationError::NoOperationId)?,
|
||||
.ok_or(OperationError::NoOperationId)?
|
||||
.to_owned(),
|
||||
}).await.map_err(OperationError::Get)?
|
||||
.response.ok_or(OperationError::NotDone)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LuauSessionError{
|
||||
Get(GetError),
|
||||
Unspecified,
|
||||
NotDone,
|
||||
NoOutput,
|
||||
NoError,
|
||||
//idk how to do this better
|
||||
enum ReaderType<R:std::io::Read>{
|
||||
GZip(flate2::read::GzDecoder<std::io::BufReader<R>>),
|
||||
Raw(std::io::BufReader<R>),
|
||||
}
|
||||
impl std::fmt::Display for LuauSessionError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f,"{self:?}")
|
||||
fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{
|
||||
let mut buf=std::io::BufReader::new(input);
|
||||
let peek=std::io::BufRead::fill_buf(&mut buf)?;
|
||||
match &peek[0..2]{
|
||||
b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))),
|
||||
_=>Ok(ReaderType::Raw(buf)),
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{
|
||||
let mut contents=Vec::new();
|
||||
readable.read_to_end(&mut contents)?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -442,12 +320,12 @@ impl ApiKey{
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context{
|
||||
api_key:String,
|
||||
client:reqwest::Client,
|
||||
pub struct CloudContext{
|
||||
pub api_key:String,
|
||||
pub client:reqwest::Client,
|
||||
}
|
||||
|
||||
impl Context{
|
||||
impl CloudContext{
|
||||
pub fn new(api_key:ApiKey)->Self{
|
||||
Self{
|
||||
api_key:api_key.get(),
|
||||
@@ -459,10 +337,9 @@ impl Context{
|
||||
.header("x-api-key",self.api_key.as_str())
|
||||
.send().await
|
||||
}
|
||||
async fn post(&self,url:url::Url,body:impl ContentType)->Result<reqwest::Response,reqwest::Error>{
|
||||
async fn post(&self,url:url::Url,body:impl Into<reqwest::Body>+Clone)->Result<reqwest::Response,reqwest::Error>{
|
||||
self.client.post(url)
|
||||
.header("x-api-key",self.api_key.as_str())
|
||||
.header("Content-Type",body.content_type())
|
||||
.body(body)
|
||||
.send().await
|
||||
}
|
||||
@@ -491,9 +368,9 @@ impl Context{
|
||||
.text("request",request_config)
|
||||
.part("fileContent",part);
|
||||
|
||||
let operation=response_ok(
|
||||
self.post_form(url,form).await.map_err(CreateError::Reqwest)?
|
||||
).await.map_err(CreateError::Response)?
|
||||
let operation=self.post_form(url,form).await
|
||||
.map_err(CreateError::Reqwest)?
|
||||
.error_for_status().map_err(CreateError::Reqwest)?
|
||||
.json::<RobloxOperation>().await.map_err(CreateError::Reqwest)?;
|
||||
|
||||
Ok(AssetOperation{
|
||||
@@ -510,97 +387,62 @@ impl Context{
|
||||
.text("request",request_config)
|
||||
.part("fileContent",reqwest::multipart::Part::bytes(body));
|
||||
|
||||
let operation=response_ok(
|
||||
self.patch_form(url,form).await.map_err(UpdateError::Reqwest)?
|
||||
).await.map_err(UpdateError::Response)?
|
||||
let operation=self.patch_form(url,form).await
|
||||
.map_err(UpdateError::Reqwest)?
|
||||
//roblox api documentation is very poor, just give the status code and drop the json
|
||||
.error_for_status().map_err(UpdateError::Reqwest)?
|
||||
.json::<RobloxOperation>().await.map_err(UpdateError::Reqwest)?;
|
||||
|
||||
Ok(AssetOperation{
|
||||
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 url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::Parse)?;
|
||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
||||
|
||||
response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
.error_for_status().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:GetAssetInfoRequest)->Result<AssetResponse,GetError>{
|
||||
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::Parse)?;
|
||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
||||
|
||||
response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
.error_for_status().map_err(GetError::Reqwest)?
|
||||
.json::<AssetResponse>().await.map_err(GetError::Reqwest)
|
||||
}
|
||||
pub async fn get_asset_version_info(&self,config:GetAssetVersionRequest)->Result<AssetResponse,GetError>{
|
||||
pub async fn get_asset_version(&self,config:GetAssetVersionRequest)->Result<Vec<u8>,GetError>{
|
||||
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::Parse)?;
|
||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(GetError::ParseError)?;
|
||||
|
||||
response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
.json::<AssetResponse>().await.map_err(GetError::Reqwest)
|
||||
}
|
||||
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 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().await.map_err(GetError::Reqwest)
|
||||
}
|
||||
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 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().await.map_err(GetError::Reqwest)
|
||||
}
|
||||
pub async fn get_asset(&self,config:&AssetLocation)->Result<MaybeGzippedBytes,GetError>{
|
||||
let url=reqwest::Url::parse(config.location()).map_err(GetError::Parse)?;
|
||||
|
||||
let bytes=response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
let body=self.get(url).await.map_err(GetError::Reqwest)?
|
||||
.error_for_status().map_err(GetError::Reqwest)?
|
||||
.bytes().await.map_err(GetError::Reqwest)?;
|
||||
|
||||
Ok(MaybeGzippedBytes::new(bytes))
|
||||
match maybe_gzip_decode(&mut std::io::Cursor::new(body)){
|
||||
Ok(ReaderType::GZip(readable))=>read_readable(readable),
|
||||
Ok(ReaderType::Raw(readable))=>read_readable(readable),
|
||||
Err(e)=>Err(e),
|
||||
}.map_err(GetError::IO)
|
||||
}
|
||||
pub async fn get_asset(&self,config:GetAssetRequest)->Result<Vec<u8>,GetError>{
|
||||
let version=match config.version{
|
||||
Some(version)=>version,
|
||||
None=>self.get_asset_info(GetAssetInfoRequest{asset_id:config.asset_id}).await?.revisionId.parse().unwrap(),
|
||||
};
|
||||
self.get_asset_version(GetAssetVersionRequest{
|
||||
asset_id:config.asset_id,
|
||||
version,
|
||||
}).await
|
||||
}
|
||||
pub async fn get_asset_versions(&self,config:AssetVersionsRequest)->Result<AssetVersionsResponse,AssetVersionsError>{
|
||||
let raw_url=format!("https://apis.roblox.com/assets/v1/assets/{}/versions",config.asset_id);
|
||||
let url=reqwest::Url::parse(raw_url.as_str()).map_err(AssetVersionsError::ParseError)?;
|
||||
|
||||
response_ok(
|
||||
self.get(url).await.map_err(AssetVersionsError::Reqwest)?
|
||||
).await.map_err(AssetVersionsError::Response)?
|
||||
self.get(url).await.map_err(AssetVersionsError::Reqwest)?
|
||||
.error_for_status().map_err(AssetVersionsError::Reqwest)?
|
||||
.json::<AssetVersionsResponse>().await.map_err(AssetVersionsError::Reqwest)
|
||||
}
|
||||
pub async fn inventory_page(&self,config:InventoryPageRequest)->Result<InventoryPageResponse,InventoryPageError>{
|
||||
@@ -613,9 +455,8 @@ impl Context{
|
||||
}
|
||||
}
|
||||
|
||||
response_ok(
|
||||
self.get(url).await.map_err(InventoryPageError::Reqwest)?
|
||||
).await.map_err(InventoryPageError::Response)?
|
||||
self.get(url).await.map_err(InventoryPageError::Reqwest)?
|
||||
.error_for_status().map_err(InventoryPageError::Reqwest)?
|
||||
.json::<InventoryPageResponse>().await.map_err(InventoryPageError::Reqwest)
|
||||
}
|
||||
pub async fn update_place(&self,config:UpdatePlaceRequest,body:impl Into<reqwest::Body>+Clone)->Result<UpdatePlaceResponse,UpdateError>{
|
||||
@@ -627,9 +468,8 @@ impl Context{
|
||||
query.append_pair("versionType","Published");
|
||||
}
|
||||
|
||||
response_ok(
|
||||
self.post(url,Binary(body)).await.map_err(UpdateError::Reqwest)?
|
||||
).await.map_err(UpdateError::Response)?
|
||||
self.post(url,body).await.map_err(UpdateError::Reqwest)?
|
||||
.error_for_status().map_err(UpdateError::Reqwest)?
|
||||
.json::<UpdatePlaceResponse>().await.map_err(UpdateError::Reqwest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
use crate::body::{ContentType,Json};
|
||||
use crate::util::response_ok;
|
||||
use crate::types::{ResponseError,MaybeGzippedBytes};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PostError{
|
||||
Reqwest(reqwest::Error),
|
||||
@@ -15,7 +11,7 @@ impl std::fmt::Display for PostError{
|
||||
impl std::error::Error for PostError{}
|
||||
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct CreateRequest{
|
||||
pub name:String,
|
||||
pub description:String,
|
||||
@@ -27,14 +23,7 @@ pub struct CreateRequest{
|
||||
pub enum CreateError{
|
||||
ParseError(url::ParseError),
|
||||
PostError(PostError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
ParseInt{
|
||||
response:String,
|
||||
err:std::num::ParseIntError,
|
||||
},
|
||||
VersionHeaderMissing,
|
||||
ToStr(reqwest::header::ToStrError),
|
||||
}
|
||||
impl std::fmt::Display for CreateError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -43,7 +32,7 @@ impl std::fmt::Display for CreateError{
|
||||
}
|
||||
impl std::error::Error for CreateError{}
|
||||
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UploadRequest{
|
||||
pub assetid:u64,
|
||||
pub name:Option<String>,
|
||||
@@ -57,14 +46,7 @@ pub enum UploadError{
|
||||
ParseError(url::ParseError),
|
||||
PostError(PostError),
|
||||
Reqwest(reqwest::Error),
|
||||
Response(ResponseError),
|
||||
AssetIdIsZero,
|
||||
ParseInt{
|
||||
response:String,
|
||||
err:std::num::ParseIntError,
|
||||
},
|
||||
VersionHeaderMissing,
|
||||
ToStr(reqwest::header::ToStrError),
|
||||
}
|
||||
impl std::fmt::Display for UploadError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -73,15 +55,13 @@ impl std::fmt::Display for UploadError{
|
||||
}
|
||||
impl std::error::Error for UploadError{}
|
||||
#[derive(Debug,serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UploadResponse{
|
||||
pub AssetId:u64,
|
||||
pub AssetVersion:u64,
|
||||
pub AssetVersionId:u64,
|
||||
}
|
||||
|
||||
pub struct GetAssetDetailsRequest{
|
||||
pub asset_id:u64,
|
||||
}
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct GetAssetRequest{
|
||||
pub asset_id:u64,
|
||||
pub version:Option<u64>,
|
||||
@@ -89,8 +69,8 @@ pub struct GetAssetRequest{
|
||||
#[derive(Debug)]
|
||||
pub enum GetError{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
IO(std::io::Error)
|
||||
}
|
||||
impl std::fmt::Display for GetError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -99,127 +79,24 @@ impl std::fmt::Display for GetError{
|
||||
}
|
||||
impl std::error::Error for GetError{}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GetAssetV2Error{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
VersionHeaderMissing,
|
||||
ToStr(reqwest::header::ToStrError),
|
||||
ParseInt(std::num::ParseIntError),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
impl std::fmt::Display for GetAssetV2Error{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for GetAssetV2Error{}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct GetAssetV2AssetMetadata{
|
||||
pub metadataType:u32,
|
||||
pub value:String,
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct GetAssetV2Location{
|
||||
pub assetFormat:String,// "source"
|
||||
location:String,// this value is private so users cannot mutate it
|
||||
#[serde(default)]
|
||||
pub assetMetadatas:Vec<GetAssetV2AssetMetadata>,
|
||||
}
|
||||
impl GetAssetV2Location{
|
||||
pub fn location(&self)->&str{
|
||||
&self.location
|
||||
}
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct GetAssetV2Info{
|
||||
pub locations:Vec<GetAssetV2Location>,
|
||||
pub requestId:String,
|
||||
pub isArchived:bool,
|
||||
pub assetTypeId:u32,
|
||||
pub isRecordable:Option<bool>,
|
||||
pub IsHashDynamic:Option<bool>,
|
||||
pub IsCopyrightProtected:Option<bool>,
|
||||
}
|
||||
|
||||
pub struct GetAssetV2{
|
||||
pub version:u64,
|
||||
pub info:GetAssetV2Info,
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Eq,PartialEq,Hash)]
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
pub enum CreatorType{
|
||||
User,
|
||||
Group,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct Creator{
|
||||
pub Id:u64,
|
||||
pub Name:String,
|
||||
pub CreatorType:CreatorType,
|
||||
pub CreatorTargetId:u64,
|
||||
pub HasVerifiedBadge:bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
pub struct AssetDetails{
|
||||
pub TargetId:u64,
|
||||
pub ProductType:Option<String>,
|
||||
pub AssetId:u64,
|
||||
pub ProductId:u64,
|
||||
pub Name:String,
|
||||
pub Description:String,
|
||||
pub AssetTypeId:u32,
|
||||
pub Creator:Creator,
|
||||
pub IconImageAssetId:u64,
|
||||
pub Created:chrono::DateTime<chrono::Utc>,
|
||||
pub Updated:chrono::DateTime<chrono::Utc>,
|
||||
pub PriceInRobux:Option<u32>,
|
||||
pub PriceInTickets:Option<u32>,
|
||||
pub Sales:u32,
|
||||
pub IsNew:bool,
|
||||
pub IsForSale:bool,
|
||||
pub IsPublicDomain:bool,
|
||||
pub IsLimited:bool,
|
||||
pub IsLimitedUnique:bool,
|
||||
pub Remaining:Option<u32>,
|
||||
pub MinimumMembershipLevel:u32,
|
||||
pub ContentRatingTypeId:u32,
|
||||
pub SaleAvailabilityLocations:Option<String>,
|
||||
pub SaleLocation:Option<String>,
|
||||
pub CollectibleItemId:Option<u64>,
|
||||
pub CollectibleProductId:Option<u64>,
|
||||
pub CollectiblesItemDetails:Option<String>,
|
||||
}
|
||||
|
||||
pub struct AssetVersionsPageRequest{
|
||||
pub asset_id:u64,
|
||||
pub cursor:Option<String>,
|
||||
}
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct AssetVersion{
|
||||
pub Id:u64,
|
||||
pub assetId:u64,
|
||||
pub assetVersionNumber:u64,
|
||||
pub creatorType:CreatorType,
|
||||
pub creatorType:String,
|
||||
pub creatorTargetId:u64,
|
||||
pub creatingUniverseId:Option<u64>,
|
||||
pub created:chrono::DateTime<chrono::Utc>,
|
||||
pub isPublished:bool,
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct AssetVersionsPageResponse{
|
||||
pub previousPageCursor:Option<String>,
|
||||
pub nextPageCursor:Option<String>,
|
||||
@@ -228,7 +105,6 @@ pub struct AssetVersionsPageResponse{
|
||||
#[derive(Debug)]
|
||||
pub enum PageError{
|
||||
ParseError(url::ParseError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
}
|
||||
impl std::fmt::Display for PageError{
|
||||
@@ -255,12 +131,13 @@ pub struct CreationsPageRequest{
|
||||
pub cursor:Option<String>,
|
||||
}
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct CreationsItem{
|
||||
pub id:u64,
|
||||
pub name:String,
|
||||
}
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct CreationsPageResponse{
|
||||
pub totalResults:u64,//up to 50
|
||||
pub filteredKeyword:Option<String>,//""
|
||||
@@ -279,102 +156,68 @@ pub struct UserInventoryPageRequest{
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UserInventoryItemOwner{
|
||||
pub userId:u64,
|
||||
pub username:String,
|
||||
pub buildersClubMembershipType:String,
|
||||
userId:u64,
|
||||
username:String,
|
||||
buildersClubMembershipType:u64,
|
||||
}
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UserInventoryItem{
|
||||
pub userAssetId:u64,
|
||||
pub assetId:u64,
|
||||
pub assetName:String,
|
||||
pub collectibleItemId:Option<String>,
|
||||
pub collectibleItemInstanceId:Option<String>,
|
||||
pub owner:UserInventoryItemOwner,
|
||||
pub created:chrono::DateTime<chrono::Utc>,
|
||||
pub updated:chrono::DateTime<chrono::Utc>,
|
||||
userAssetId:u64,
|
||||
assetId:u64,
|
||||
assetName:String,
|
||||
collectibleItemId:Option<String>,
|
||||
collectibleItemInstanceId:Option<String>,
|
||||
owner:UserInventoryItemOwner,
|
||||
created:chrono::DateTime<chrono::Utc>,
|
||||
updated:chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
#[allow(nonstandard_style,dead_code)]
|
||||
pub struct UserInventoryPageResponse{
|
||||
pub previousPageCursor:Option<String>,
|
||||
pub nextPageCursor:Option<String>,
|
||||
pub data:Vec<UserInventoryItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SetAssetsPermissionsError{
|
||||
Parse(url::ParseError),
|
||||
JSONEncode(serde_json::Error),
|
||||
Patch(PostError),
|
||||
Response(ResponseError),
|
||||
Reqwest(reqwest::Error),
|
||||
//idk how to do this better
|
||||
enum ReaderType<R:std::io::Read>{
|
||||
GZip(flate2::read::GzDecoder<std::io::BufReader<R>>),
|
||||
Raw(std::io::BufReader<R>),
|
||||
}
|
||||
impl std::fmt::Display for SetAssetsPermissionsError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f,"{self:?}")
|
||||
fn maybe_gzip_decode<R:std::io::Read>(input:R)->std::io::Result<ReaderType<R>>{
|
||||
let mut buf=std::io::BufReader::new(input);
|
||||
let peek=std::io::BufRead::fill_buf(&mut buf)?;
|
||||
match &peek[0..2]{
|
||||
b"\x1f\x8b"=>Ok(ReaderType::GZip(flate2::read::GzDecoder::new(buf))),
|
||||
_=>Ok(ReaderType::Raw(buf)),
|
||||
}
|
||||
}
|
||||
impl std::error::Error for SetAssetsPermissionsError{}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
struct AssetPermissions{
|
||||
assetId:u64,
|
||||
grantToDependencies:bool,//true
|
||||
}
|
||||
#[derive(serde::Serialize)]
|
||||
#[expect(nonstandard_style)]
|
||||
struct SetAssetsPermissions<'a>{
|
||||
subjectType:&'a str,// "Universe"
|
||||
subjectId:&'a str,// "4422715291"
|
||||
action:&'a str,// "Use",
|
||||
enableDeepAccessCheck:bool,//true,
|
||||
requests:&'a [AssetPermissions],
|
||||
}
|
||||
pub struct SetAssetsPermissionsRequest<'a>{
|
||||
pub universe_id:u64,
|
||||
pub asset_ids:&'a [u64],
|
||||
}
|
||||
impl SetAssetsPermissionsRequest<'_>{
|
||||
fn serialize(&self)->Result<String,serde_json::Error>{
|
||||
let ref requests:Vec<_>=self.asset_ids.iter().map(|&asset_id|AssetPermissions{
|
||||
assetId:asset_id,
|
||||
grantToDependencies:true,
|
||||
}).collect();
|
||||
let ref subject_id=self.universe_id.to_string();
|
||||
let ref permissions=SetAssetsPermissions{
|
||||
subjectType:"Universe",
|
||||
subjectId:subject_id,
|
||||
action:"Use",
|
||||
enableDeepAccessCheck:true,
|
||||
requests,
|
||||
};
|
||||
serde_json::to_string(permissions)
|
||||
}
|
||||
fn read_readable(mut readable:impl std::io::Read)->std::io::Result<Vec<u8>>{
|
||||
let mut contents=Vec::new();
|
||||
readable.read_to_end(&mut contents)?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cookie(String);
|
||||
impl Cookie{
|
||||
/// cookie is prepended with ".ROBLOSECURITY=" by this function
|
||||
pub fn new(cookie:String)->Self{
|
||||
Self(format!(".ROBLOSECURITY={cookie}"))
|
||||
Self(cookie)
|
||||
}
|
||||
pub fn get(self)->String{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Context{
|
||||
cookie:String,
|
||||
client:reqwest::Client,
|
||||
pub struct CookieContext{
|
||||
pub cookie:String,
|
||||
pub client:reqwest::Client,
|
||||
}
|
||||
|
||||
impl Context{
|
||||
impl CookieContext{
|
||||
pub fn new(cookie:Cookie)->Self{
|
||||
Self{
|
||||
cookie:cookie.get(),
|
||||
@@ -407,29 +250,6 @@ impl Context{
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
async fn patch(&self,url:url::Url,body:impl ContentType+Clone)->Result<reqwest::Response,PostError>{
|
||||
let mut resp=self.client.patch(url.clone())
|
||||
.header("Cookie",self.cookie.as_str())
|
||||
.header("Content-Type",body.content_type())
|
||||
.body(body.clone())
|
||||
.send().await.map_err(PostError::Reqwest)?;
|
||||
|
||||
//This is called a CSRF challenge apparently
|
||||
if resp.status()==reqwest::StatusCode::FORBIDDEN{
|
||||
if let Some(csrf_token)=resp.headers().get("X-CSRF-Token"){
|
||||
resp=self.client.patch(url)
|
||||
.header("X-CSRF-Token",csrf_token)
|
||||
.header("Cookie",self.cookie.as_str())
|
||||
.header("Content-Type",body.content_type())
|
||||
.body(body)
|
||||
.send().await.map_err(PostError::Reqwest)?;
|
||||
}else{
|
||||
Err(PostError::CSRF)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
pub async fn create(&self,config:CreateRequest,body:impl Into<reqwest::Body>+Clone)->Result<UploadResponse,CreateError>{
|
||||
let mut url=reqwest::Url::parse("https://data.roblox.com/Data/Upload.ashx?json=1&type=Model&genreTypeId=1").map_err(CreateError::ParseError)?;
|
||||
//url borrow scope
|
||||
@@ -445,31 +265,10 @@ impl Context{
|
||||
query.append_pair("groupId",group_id.to_string().as_str());
|
||||
}
|
||||
}
|
||||
let response=response_ok(
|
||||
self.post(url,body).await.map_err(CreateError::PostError)?
|
||||
).await.map_err(CreateError::Response)?;
|
||||
|
||||
let version_str=response
|
||||
.headers()
|
||||
.get("roblox-assetversionnumber")
|
||||
.ok_or(CreateError::VersionHeaderMissing)?
|
||||
.to_str()
|
||||
.map_err(CreateError::ToStr)?;
|
||||
let version=version_str.parse()
|
||||
.map_err(|err|CreateError::ParseInt{err,response:version_str.to_owned()})?;
|
||||
|
||||
let response=response.text().await.map_err(CreateError::Reqwest)?;
|
||||
|
||||
match response.parse(){
|
||||
Ok(asset_id)=>Ok(UploadResponse{
|
||||
AssetId:asset_id,
|
||||
AssetVersion:version,
|
||||
}),
|
||||
Err(err)=>Err(CreateError::ParseInt{
|
||||
response,
|
||||
err,
|
||||
})
|
||||
}
|
||||
self.post(url,body).await.map_err(CreateError::PostError)?
|
||||
.error_for_status().map_err(CreateError::Reqwest)?
|
||||
.json::<UploadResponse>().await.map_err(CreateError::Reqwest)
|
||||
}
|
||||
pub async fn upload(&self,config:UploadRequest,body:impl Into<reqwest::Body>+Clone)->Result<UploadResponse,UploadError>{
|
||||
let mut url=reqwest::Url::parse("https://data.roblox.com/Data/Upload.ashx?json=1&type=Model&genreTypeId=1").map_err(UploadError::ParseError)?;
|
||||
@@ -497,33 +296,12 @@ impl Context{
|
||||
query.append_pair("groupId",group_id.to_string().as_str());
|
||||
}
|
||||
}
|
||||
let response=response_ok(
|
||||
self.post(url,body).await.map_err(UploadError::PostError)?
|
||||
).await.map_err(UploadError::Response)?;
|
||||
|
||||
let version_str=response
|
||||
.headers()
|
||||
.get("roblox-assetversionnumber")
|
||||
.ok_or(UploadError::VersionHeaderMissing)?
|
||||
.to_str()
|
||||
.map_err(UploadError::ToStr)?;
|
||||
let version=version_str.parse()
|
||||
.map_err(|err|UploadError::ParseInt{err,response:version_str.to_owned()})?;
|
||||
|
||||
let response=response.text().await.map_err(UploadError::Reqwest)?;
|
||||
|
||||
match response.parse(){
|
||||
Ok(asset_id)=>Ok(UploadResponse{
|
||||
AssetId:asset_id,
|
||||
AssetVersion:version,
|
||||
}),
|
||||
Err(err)=>Err(UploadError::ParseInt{
|
||||
response,
|
||||
err,
|
||||
})
|
||||
}
|
||||
self.post(url,body).await.map_err(UploadError::PostError)?
|
||||
.error_for_status().map_err(UploadError::Reqwest)?
|
||||
.json::<UploadResponse>().await.map_err(UploadError::Reqwest)
|
||||
}
|
||||
pub async fn get_asset(&self,config:GetAssetRequest)->Result<MaybeGzippedBytes,GetError>{
|
||||
pub async fn get_asset(&self,config:GetAssetRequest)->Result<Vec<u8>,GetError>{
|
||||
let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v1/asset/").map_err(GetError::ParseError)?;
|
||||
//url borrow scope
|
||||
{
|
||||
@@ -533,62 +311,17 @@ impl Context{
|
||||
query.append_pair("version",version.to_string().as_str());
|
||||
}
|
||||
}
|
||||
|
||||
let bytes=response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
let body=self.get(url).await.map_err(GetError::Reqwest)?
|
||||
.error_for_status().map_err(GetError::Reqwest)?
|
||||
.bytes().await.map_err(GetError::Reqwest)?;
|
||||
|
||||
Ok(MaybeGzippedBytes::new(bytes))
|
||||
match maybe_gzip_decode(&mut std::io::Cursor::new(body)){
|
||||
Ok(ReaderType::GZip(readable))=>read_readable(readable),
|
||||
Ok(ReaderType::Raw(readable))=>read_readable(readable),
|
||||
Err(e)=>Err(e),
|
||||
}.map_err(GetError::IO)
|
||||
}
|
||||
pub async fn get_asset_v2(&self,config:GetAssetRequest)->Result<GetAssetV2,GetAssetV2Error>{
|
||||
let mut url=reqwest::Url::parse("https://assetdelivery.roblox.com/v2/asset").map_err(GetAssetV2Error::ParseError)?;
|
||||
//url borrow scope
|
||||
{
|
||||
let mut query=url.query_pairs_mut();//borrow here
|
||||
query.append_pair("ID",config.asset_id.to_string().as_str());
|
||||
if let Some(version)=config.version{
|
||||
query.append_pair("version",version.to_string().as_str());
|
||||
}
|
||||
}
|
||||
let response=response_ok(
|
||||
self.get(url).await.map_err(GetAssetV2Error::Reqwest)?
|
||||
).await.map_err(GetAssetV2Error::Response)?;
|
||||
|
||||
let version=response
|
||||
.headers()
|
||||
.get("roblox-assetversionnumber")
|
||||
.ok_or(GetAssetV2Error::VersionHeaderMissing)?
|
||||
.to_str()
|
||||
.map_err(GetAssetV2Error::ToStr)?
|
||||
.parse()
|
||||
.map_err(GetAssetV2Error::ParseInt)?;
|
||||
|
||||
let info=response.json().await.map_err(GetAssetV2Error::Reqwest)?;
|
||||
|
||||
Ok(GetAssetV2{
|
||||
version,
|
||||
info,
|
||||
})
|
||||
}
|
||||
pub async fn get_asset_v2_download(&self,config:&GetAssetV2Location)->Result<MaybeGzippedBytes,GetError>{
|
||||
let url=reqwest::Url::parse(config.location.as_str()).map_err(GetError::ParseError)?;
|
||||
|
||||
let bytes=response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
.bytes().await.map_err(GetError::Reqwest)?;
|
||||
|
||||
Ok(MaybeGzippedBytes::new(bytes))
|
||||
}
|
||||
pub async fn get_asset_details(&self,config:GetAssetDetailsRequest)->Result<AssetDetails,GetError>{
|
||||
let url=reqwest::Url::parse(format!("https://economy.roblox.com/v2/assets/{}/details",config.asset_id).as_str()).map_err(GetError::ParseError)?;
|
||||
response_ok(
|
||||
self.get(url).await.map_err(GetError::Reqwest)?
|
||||
).await.map_err(GetError::Response)?
|
||||
.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)?;
|
||||
//url borrow scope
|
||||
{
|
||||
@@ -600,9 +333,9 @@ impl Context{
|
||||
query.append_pair("cursor",cursor);
|
||||
}
|
||||
}
|
||||
response_ok(
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
).await.map_err(PageError::Response)?
|
||||
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
.error_for_status().map_err(PageError::Reqwest)?
|
||||
.json::<AssetVersionsPageResponse>().await.map_err(PageError::Reqwest)
|
||||
}
|
||||
pub async fn get_creations_page(&self,config:&CreationsPageRequest)->Result<CreationsPageResponse,PageError>{
|
||||
@@ -615,9 +348,9 @@ impl Context{
|
||||
query.append_pair("cursor",cursor);
|
||||
}
|
||||
}
|
||||
response_ok(
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
).await.map_err(PageError::Response)?
|
||||
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
.error_for_status().map_err(PageError::Reqwest)?
|
||||
.json::<CreationsPageResponse>().await.map_err(PageError::Reqwest)
|
||||
}
|
||||
pub async fn get_user_inventory_page(&self,config:&UserInventoryPageRequest)->Result<UserInventoryPageResponse,PageError>{
|
||||
@@ -629,21 +362,9 @@ impl Context{
|
||||
query.append_pair("cursor",cursor);
|
||||
}
|
||||
}
|
||||
response_ok(
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
).await.map_err(PageError::Response)?
|
||||
|
||||
self.get(url).await.map_err(PageError::Reqwest)?
|
||||
.error_for_status().map_err(PageError::Reqwest)?
|
||||
.json::<UserInventoryPageResponse>().await.map_err(PageError::Reqwest)
|
||||
}
|
||||
/// Used to enable an asset to be loaded onto a group game.
|
||||
pub async fn set_assets_permissions(&self,config:SetAssetsPermissionsRequest<'_>)->Result<(),SetAssetsPermissionsError>{
|
||||
let url=reqwest::Url::parse("https://apis.roblox.com/asset-permissions-api/v1/assets/permissions").map_err(SetAssetsPermissionsError::Parse)?;
|
||||
|
||||
let body=config.serialize().map_err(SetAssetsPermissionsError::JSONEncode)?;
|
||||
|
||||
response_ok(
|
||||
self.patch(url,Json(body)).await.map_err(SetAssetsPermissionsError::Patch)?
|
||||
).await.map_err(SetAssetsPermissionsError::Response)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
pub mod cloud;
|
||||
pub mod cookie;
|
||||
pub mod types;
|
||||
mod body;
|
||||
mod util;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
#[derive(Debug)]
|
||||
pub struct UrlAndBody{
|
||||
pub url:url::Url,
|
||||
pub body:String,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ResponseError{
|
||||
Reqwest(reqwest::Error),
|
||||
Details{
|
||||
status_code:reqwest::StatusCode,
|
||||
url_and_body:Box<UrlAndBody>,
|
||||
},
|
||||
}
|
||||
impl std::fmt::Display for ResponseError{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ResponseError{}
|
||||
|
||||
#[cfg(feature="gzip")]
|
||||
use flate2::read::GzDecoder;
|
||||
|
||||
/// Some bytes that might be gzipped. Use the read_with or to_vec methods to transparently decode gzip.
|
||||
pub struct MaybeGzippedBytes{
|
||||
bytes:bytes::Bytes,
|
||||
}
|
||||
impl MaybeGzippedBytes{
|
||||
pub(crate) fn new(bytes:bytes::Bytes)->Self{
|
||||
Self{bytes}
|
||||
}
|
||||
pub fn into_inner(self)->bytes::Bytes{
|
||||
self.bytes
|
||||
}
|
||||
/// get a reference to the bytes, ignoring gzip decoding
|
||||
pub fn as_raw_ref(&self)->&[u8]{
|
||||
self.bytes.as_ref()
|
||||
}
|
||||
/// Transparently decode gzip data, if present (intermediate allocation)
|
||||
#[cfg(feature="gzip")]
|
||||
pub fn to_vec(&self)->std::io::Result<Vec<u8>>{
|
||||
use std::io::Read;
|
||||
match self.bytes.get(0..2){
|
||||
Some(b"\x1f\x8b")=>{
|
||||
let mut buf=Vec::new();
|
||||
GzDecoder::new(self.bytes.as_ref()).read_to_end(&mut buf)?;
|
||||
Ok(buf)
|
||||
},
|
||||
_=>Ok(self.bytes.to_vec())
|
||||
}
|
||||
}
|
||||
/// Read the bytes with the provided decoders.
|
||||
/// The idea is to make a function that is generic over std::io::Read
|
||||
/// and pass the same function to both closures.
|
||||
/// This two closure hack must be done because of the different concrete types.
|
||||
#[cfg(feature="gzip")]
|
||||
pub fn read_with<'a,ReadGzip,ReadRaw,T>(&'a self,read_gzip:ReadGzip,read_raw:ReadRaw)->T
|
||||
where
|
||||
ReadGzip:Fn(GzDecoder<&'a [u8]>)->T,
|
||||
ReadRaw:Fn(&'a [u8])->T,
|
||||
{
|
||||
match self.bytes.get(0..2){
|
||||
Some(b"\x1f\x8b")=>read_gzip(GzDecoder::new(self.bytes.as_ref())),
|
||||
_=>read_raw(self.bytes.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use crate::types::{ResponseError,UrlAndBody};
|
||||
|
||||
// lazy function to draw out meaningful info from http response on failure
|
||||
pub(crate) async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,ResponseError>{
|
||||
let status_code=response.status();
|
||||
if status_code.is_success(){
|
||||
Ok(response)
|
||||
}else{
|
||||
let url=response.url().to_owned();
|
||||
let bytes=response.bytes().await.map_err(ResponseError::Reqwest)?;
|
||||
let body=String::from_utf8_lossy(&bytes).to_string();
|
||||
Err(ResponseError::Details{
|
||||
status_code,
|
||||
url_and_body:Box::new(UrlAndBody{url,body})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
use serde::de::{Error,Unexpected};
|
||||
use serde::{Deserializer,Serializer};
|
||||
|
||||
struct U64StringVisitor;
|
||||
impl serde::de::Visitor<'_> for U64StringVisitor{
|
||||
type Value=u64;
|
||||
fn expecting(&self,formatter:&mut std::fmt::Formatter)->std::fmt::Result{
|
||||
write!(formatter,"string value with int")
|
||||
}
|
||||
fn visit_str<E:Error>(self,v:&str)->Result<Self::Value,E>{
|
||||
v.parse().map_err(|_|E::invalid_value(Unexpected::Str(v),&"u64"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_u64<'de,D:Deserializer<'de>>(deserializer:D)->Result<u64,D::Error>{
|
||||
deserializer.deserialize_any(U64StringVisitor)
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_u64<S:Serializer>(v:&u64,serializer:S)->Result<S::Ok,S::Error>{
|
||||
serializer.serialize_str(v.to_string().as_str())
|
||||
}
|
||||
@@ -10,8 +10,8 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.30"
|
||||
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
|
||||
lazy-regex = "3.1.0"
|
||||
rayon = "1.8.0"
|
||||
rbx_dom_weak = "4.0.0"
|
||||
rbx_xml = "2.0.0"
|
||||
rbx_dom_weak = "2.7.0"
|
||||
rbx_xml = "0.13.3"
|
||||
tokio = { version = "1.35.1", features = ["fs"] }
|
||||
|
||||
@@ -28,16 +28,6 @@ impl std::fmt::Display for PropertiesOverride{
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
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,"_")
|
||||
lazy_regex::regex!(r"[^A-Za-z0-9.-]").replace_all(s,"_")
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::path::{Path,PathBuf};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::lazy_regex;
|
||||
use crate::common::{sanitize,Style,PropertiesOverride};
|
||||
|
||||
//holy smokes what am I doing lmao
|
||||
@@ -17,6 +16,7 @@ use crate::common::{sanitize,Style,PropertiesOverride};
|
||||
//I could use a function!
|
||||
//eventually:
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]//idk why this thinks it's dead code, the errors are printed out in various places
|
||||
pub enum QueryResolveError{
|
||||
NotFound,//0 results
|
||||
Ambiguous,//>1 results
|
||||
@@ -203,7 +203,7 @@ impl ScriptWithOverrides{
|
||||
let mut count=0;
|
||||
for line in source.lines(){
|
||||
//only string type properties are supported atm
|
||||
if let Some(captures)=lazy_regex!(r#"^\-\-\s*Properties\.([A-Za-z]\w*)\s*\=\s*"(\w+)"$"#)
|
||||
if let Some(captures)=lazy_regex::regex!(r#"^\-\-\s*Properties\.([A-Za-z]\w*)\s*\=\s*"(\w+)"$"#)
|
||||
.captures(line){
|
||||
count+=line.len();
|
||||
match &captures[1]{
|
||||
@@ -340,7 +340,7 @@ impl CompileNode{
|
||||
//reject goobers
|
||||
let is_goober=matches!(style,Some(Style::Rojo));
|
||||
let (ext_len,file_discernment)={
|
||||
if let Some(captures)=lazy_regex!(r"^.*(\.module\.lua|\.client\.lua|\.server\.lua)$")
|
||||
if let Some(captures)=lazy_regex::regex!(r"^.*(\.module\.lua|\.client\.lua|\.server\.lua)$")
|
||||
.captures(file_name.as_str()){
|
||||
let ext=&captures[1];
|
||||
(ext.len(),match ext{
|
||||
@@ -354,7 +354,7 @@ impl CompileNode{
|
||||
".server.lua"=>FileDiscernment::Script(ScriptHint::Script),
|
||||
_=>panic!("Regex failed"),
|
||||
})
|
||||
}else if let Some(captures)=lazy_regex!(r"^.*(\.rbxmx|\.lua)$")
|
||||
}else if let Some(captures)=lazy_regex::regex!(r"^.*(\.rbxmx|\.lua)$")
|
||||
.captures(file_name.as_str()){
|
||||
let ext=&captures[1];
|
||||
(ext.len(),match ext{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use rbx_dom_weak::ustr;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use crate::common::{sanitize,Style,PropertiesOverride};
|
||||
|
||||
@@ -91,7 +90,7 @@ fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_na
|
||||
|
||||
if let Some(item)=dom.get_by_ref(node.referent){
|
||||
//TODO: delete disabled scripts
|
||||
if let Some(rbx_dom_weak::types::Variant::String(source))=item.properties.get(&ustr("Source")){
|
||||
if let Some(rbx_dom_weak::types::Variant::String(source))=item.properties.get("Source"){
|
||||
if properties.is_some(){
|
||||
//rox style
|
||||
let source=properties.to_string()+source.as_str();
|
||||
|
||||
705
src/main.rs
705
src/main.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user