Skip to content

Commit

Permalink
Merge pull request #67 from fekie/dev-main
Browse files Browse the repository at this point in the history
Dev main
  • Loading branch information
fekie authored May 14, 2024
2 parents 84197c6 + 283c903 commit 90d7558
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 61 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ keywords = ["roblox", "api", "roblox-api"]
license = "MIT"
name = "roboat"
readme = "README.md"
repository = "https://github.com/fekie/roboat"
version = "0.34.2"
repository = "https://github.com/Chloe-Woahie/roboat"
version = "0.33.3"

[dependencies]
reqwest = { version = "0.11.14", default-features = false, features = [
"rustls-tls",
"json",
"multipart",
"blocking",
] }
thiserror = "1.0.40"
serde = { version = "1.0.136", features = ["derive"] }
Expand Down
101 changes: 51 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,56 +23,57 @@ All public methods in this crate are documented and have at least one correspond
Documentation can be found [here](https://docs.rs/roboat/).

# Coverage

- Auth API - [`auth.roblox.com/*`]
- Force Refresh X-CSRF-TOKEN - [`Client::force_refresh_xcsrf_token`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.force_refresh_xcsrf_token)
- BEDEV2 API - [`apis.roblox.com/*`]
- Fetch Non-Tradable Limited Details - [`Client::non_tradable_limited_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.non_tradable_limited_details)
- Fetch Collectible Product ID - [`Client::collectible_product_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_product_id)
- Fetch Collectible Product ID Bulk - [`Client::collectible_product_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_product_id_bulk)
- Fetch Collectible Creator ID - [`Client::collectible_creator_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_creator_id)
- Purchase Non-Tradable Limited - [`Client::purchase_non_tradable_limited`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.purchase_non_tradable_limited)
- Catalog API - [`catalog.roblox.com/*`]
- Fetch Item Details - [`Client::item_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.item_details)
- Fetch Product ID - [`Client::product_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id)
- Fetch Product ID Bulk - [`Client::product_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id_bulk)
- Fetch Collectible Item ID - [`Client::collectible_item_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id)
- Fetch Collectible Item ID Bulk - [`Client::collectible_item_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id_bulk)
- Avatar Catalog Search - [`Client::avatar_catalog_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.avatar_catalog_search)
- Chat API - [`chat.roblox.com/*`]
- Fetch Unread Conversation Count - [`Client::unread_conversation_count`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.unread_conversation_count)
- Economy API - [`economy.roblox.com/*`]
- Fetch Robux Balance - [`Client::robux`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.robux)
- Fetch Resellers - [`Client::resellers`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.resellers)
- Fetch User Sales - [`Client::user_sales`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_sales)
- Put Limited On Sale - [`Client::put_limited_on_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.put_limited_on_sale)
- Take Limited Off Sale - [`Client::take_limited_off_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.take_limited_off_sale)
- Purchase Tradable Limited - [`Client::purchase_tradable_limited`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.purchase_tradable_limited)
- Group API - [`groups.roblox.com/*`]
- Fetch Group Roles - [`Client::group_roles`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.group_roles)
- Fetch Group Role Members - [`Client::group_role_members`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.group_role_members)
- Set Group Member Role - [`Client::set_group_member_role`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.set_group_member_role)
- Presence API - [`presence.roblox.com/*`]
- Register Presence - [`Client::register_presence`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.register_presence)
- Private Messages API - [`privatemessages.roblox.com/*`]
- Fetch Messages - [`Client::messages`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.messages)
- Thumbnails API - [`thumbnails.roblox.com/*`]
- Fetch Thumbnail Url Bulk - [`Client::thumbnail_url_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.thumbnail_url_bulk)
- Fetch Thumbnail Url - [`Client::thumbnail_url`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.thumbnail_url)
- Trades API - [`trades.roblox.com/*`]
- Accept Trade - [`Client::accept_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.accept_trade)
- Decline Trade - [`Client::decline_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.decline_trade)
- Send Trade - [`Client::send_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.send_trade)
- Fetch Trades List - [`Client::trades`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trades)
- Fetch Trade Details - [`Client::trade_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trade_details)
- Fetch Trade Count - [`Client::trade_count`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trade_count)
- Users API - [`users.roblox.com/*`]
- Fetch User ID - [`Client::user_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_id)
- Fetch Username - [`Client::username`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.username)
- Fetch Display Name - [`Client::display_name`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.display_name)
- User Search - [`Client::user_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_search)
- Username User Search - [`Client::username_user_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.username_user_search)
- Fetch User Details - [`Client::user_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_details)
* Auth API - [`auth.roblox.com/*`]
- Force Refresh X-CSRF-TOKEN - [`Client::force_refresh_xcsrf_token`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.force_refresh_xcsrf_token)
* BEDEV2 API - [`apis.roblox.com/*`]
- Fetch Non-Tradable Limited Details - [`Client::non_tradable_limited_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.non_tradable_limited_details)
- Fetch Collectible Product ID - [`Client::collectible_product_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_product_id)
- Fetch Collectible Product ID Bulk - [`Client::collectible_product_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_product_id_bulk)
- Fetch Collectible Creator ID - [`Client::collectible_creator_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_creator_id)
- Purchase Non-Tradable Limited - [`Client::purchase_non_tradable_limited`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.purchase_non_tradable_limited)
* Catalog API - [`catalog.roblox.com/*`]
- Fetch Item Details - [`Client::item_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.item_details)
- Fetch Product ID - [`Client::product_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id)
- Fetch Product ID Bulk - [`Client::product_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.product_id_bulk)
- Fetch Collectible Item ID - [`Client::collectible_item_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id)
- Fetch Collectible Item ID Bulk - [`Client::collectible_item_id_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.collectible_item_id_bulk)
- Avatar Catalog Search - [`Client::avatar_catalog_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.avatar_catalog_search)
* Chat API - [`chat.roblox.com/*`]
- Fetch Unread Conversation Count - [`Client::unread_conversation_count`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.unread_conversation_count)
* Economy API - [`economy.roblox.com/*`]
- Fetch Robux Balance - [`Client::robux`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.robux)
- Fetch Resellers - [`Client::resellers`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.resellers)
- Fetch User Sales - [`Client::user_sales`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_sales)
- Put Limited On Sale - [`Client::put_limited_on_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.put_limited_on_sale)
- Take Limited Off Sale - [`Client::take_limited_off_sale`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.take_limited_off_sale)
- Purchase Tradable Limited - [`Client::purchase_tradable_limited`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.purchase_tradable_limited)
* Group API - [`groups.roblox.com/*`]
- Fetch Group Roles - [`Client::group_roles`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.group_roles)
- Fetch Group Role Members - [`Client::group_role_members`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.group_role_members)
- Set Group Member Role - [`Client::set_group_member_role`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.set_group_member_role)
* Presence API - [`presence.roblox.com/*`]
- Register Presence - [`Client::register_presence`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.register_presence)
* Private Messages API - [`privatemessages.roblox.com/*`]
- Fetch Messages - [`Client::messages`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.messages)
* Thumbnails API - [`thumbnails.roblox.com/*`]
- Fetch Thumbnail Url Bulk - [`Client::thumbnail_url_bulk`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.thumbnail_url_bulk)
- Fetch Thumbnail Url - [`Client::thumbnail_url`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.thumbnail_url)
* Trades API - [`trades.roblox.com/*`]
- Accept Trade - [`Client::accept_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.accept_trade)
- Decline Trade - [`Client::decline_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.decline_trade)
- Send Trade - [`Client::send_trade`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.send_trade)
- Fetch Trades List - [`Client::trades`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trades)
- Fetch Trade Details - [`Client::trade_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trade_details)
- Fetch Trade Count - [`Client::trade_count`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.trade_count)
* Users API - [`users.roblox.com/*`]
- Fetch User ID - [`Client::user_id`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_id)
- Fetch Username - [`Client::username`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.username)
- Fetch Display Name - [`Client::display_name`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.display_name)
- User Search - [`Client::user_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_search)
- Username User Search - [`Client::username_user_search`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.username_user_search)
- Fetch User Details - [`Client::user_details`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.user_details)
* UNDER CONSTRUCTION
- Upload Classic Clothing to Group - [`Client::upload_classic_clothing_to_group`](https://docs.rs/roboat/latest/roboat/struct.Client.html#method.upload_classic_clothing_to_group)

# Setup

Expand Down
43 changes: 43 additions & 0 deletions examples/upload_shirt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use clap::Parser;
use roboat::ClientBuilder;

#[derive(Parser, Debug)]
struct Args {
#[arg(long, short)]
roblosecurity: String,
#[arg(long, short)]
group_id: u64,
#[arg(long, short)]
name: String,
#[arg(long, short)]
description: String,
#[arg(long, short)]
image_path: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let client = ClientBuilder::new()
.roblosecurity(args.roblosecurity)
.build();

let classic_clothing_type = roboat::bedev2::ClassicClothingType::Shirt;

let result = client
.upload_classic_clothing_to_group(
args.group_id,
args.name,
args.description,
args.image_path,
classic_clothing_type,
)
.await;

match result {
Ok(()) => println!("Uploaded shirt successfully."),
Err(e) => println!("Failed to upload shirt. Reason: {}", e),
}

Ok(())
}
116 changes: 111 additions & 5 deletions src/bedev2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const COLLECTIBLE_ITEM_DETAILS_API: &str =
const PURCHASE_NON_TRADEABLE_LIMITED_API_PART_1: &str =
"https://apis.roblox.com/marketplace-sales/v1/item/";

const ASSET_UPLOAD_API: &str = "https://apis.roblox.com/assets/user-auth/v1/assets";

const PURCHASE_NON_TRADEABLE_LIMITED_API_PART_2: &str = "/purchase-item";

/// Custom Roblox errors that occur when using [`Client::purchase_non_tradable_limited`].
Expand Down Expand Up @@ -40,6 +42,24 @@ pub enum PurchaseNonTradableLimitedError {
UnknownRobloxErrorMsg(String),
}

#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, Copy,
)]
pub enum ClassicClothingType {
#[default]
Shirt,
Pants,
}

impl std::fmt::Display for ClassicClothingType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClassicClothingType::Shirt => write!(f, "Shirt"),
ClassicClothingType::Pants => write!(f, "Pants"),
}
}
}

/// A struct containing (mostly) all the fields possibly returned from <https://apis.roblox.com/marketplace-items/v1/items/details>.
///
/// Returned from [`Client::non_tradable_limited_details`].
Expand Down Expand Up @@ -225,7 +245,7 @@ impl Client {
.await?;

let collectible_product_id = details
.get(0)
.first()
.ok_or(RoboatError::MalformedResponse)?
.collectible_product_id
.clone();
Expand Down Expand Up @@ -337,7 +357,7 @@ impl Client {
.await?;

let collectible_creator_id = details
.get(0)
.first()
.ok_or(RoboatError::MalformedResponse)?
.creator_id;

Expand Down Expand Up @@ -429,17 +449,68 @@ impl Client {
},
}
}

/// Uploads classic clothing to a group. This currently only works for classic clothing and
/// for people uploading from groups. This is because the endpoint is not fully understood yet
/// and reverse engineering it is expensive.
///
/// Uses endpoint <https://apis.roblox.com/assets/user-auth/v1/assets>.
///
/// # WARNING: UNDER CONSTRUCTION
///
/// # Notes
/// * Requires a valid roblosecurity.
/// * Will repeat once if the x-csrf-token is invalid.
/// * The `image_path` must be a valid path to an image file.
pub async fn upload_classic_clothing_to_group(
&self,
group_id: u64,
name: String,
description: String,
image_path: String,
classic_clothing_type: ClassicClothingType,
) -> Result<(), RoboatError> {
match self
.upload_classic_clothing_to_group_internal(
group_id,
name.clone(),
description.clone(),
image_path.clone(),
classic_clothing_type,
)
.await
{
Ok(x) => Ok(x),
Err(e) => match e {
RoboatError::InvalidXcsrf(new_xcsrf) => {
self.set_xcsrf(new_xcsrf).await;

self.upload_classic_clothing_to_group_internal(
group_id,
name,
description,
image_path,
classic_clothing_type,
)
.await
}
_ => Err(e),
},
}
}
}

mod internal {
use std::path::Path;

use reqwest::header;

use super::{
request_types, sort_items_by_argument_order, NonTradableLimitedDetails,
PurchaseNonTradableLimitedError, COLLECTIBLE_ITEM_DETAILS_API,
request_types, sort_items_by_argument_order, ClassicClothingType,
NonTradableLimitedDetails, PurchaseNonTradableLimitedError, COLLECTIBLE_ITEM_DETAILS_API,
PURCHASE_NON_TRADEABLE_LIMITED_API_PART_1, PURCHASE_NON_TRADEABLE_LIMITED_API_PART_2,
};
use crate::{Client, RoboatError, XCSRF_HEADER};
use crate::{bedev2::ASSET_UPLOAD_API, Client, RoboatError, XCSRF_HEADER};

impl Client {
pub(super) async fn non_tradable_limited_details_internal(
Expand Down Expand Up @@ -536,6 +607,41 @@ mod internal {
)),
}
}

pub(super) async fn upload_classic_clothing_to_group_internal(
&self,
group_id: u64,
name: String,
description: String,
image_path: String,
classic_clothing_type: ClassicClothingType,
) -> Result<(), RoboatError> {
let filename = Path::new(&image_path)
.file_name()
.map(|x| x.to_string_lossy().to_string())
.ok_or(RoboatError::InvalidPath(image_path.clone()))?;

let form = reqwest::multipart::Form::new()
.part("fileContent", reqwest::multipart::Part::bytes(tokio::fs::read(image_path).await?).file_name(filename))
.text("request", format!("{{\"displayName\":\"{}\",\"description\":\"{}\",\"assetType\":\"{}\",\"creationContext\":{{\"creator\":{{\"groupId\":{}}},\"expectedPrice\":10}}}}", name, description, classic_clothing_type, group_id));

let cookie_string = self.cookie_string()?;
let xcsrf = self.xcsrf().await;

let response_result = self
.reqwest_client
.request(reqwest::Method::POST, ASSET_UPLOAD_API)
.header(header::COOKIE, cookie_string)
.header(XCSRF_HEADER, xcsrf)
.multipart(form)
.send()
.await;

let response = Self::validate_request_result(response_result).await?;
let _ = Self::parse_to_raw::<request_types::UploadClassicClothingRaw>(response).await?;

Ok(())
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/bedev2/request_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ pub(super) struct PurchaseNonTradeableLimitedRaw {
/// Error variants: null, "PriceMismatch"
pub error_message: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UploadClassicClothingRaw {
path: String,
operation_id: String,
done: bool,
}
Loading

0 comments on commit 90d7558

Please sign in to comment.