8 Commits

Author SHA1 Message Date
4cd8c4b888 update deps 2025-11-27 15:59:00 -08:00
1e82ad6af8 add unicode-perl feature to regex 2025-11-15 10:25:08 -08:00
4587ff89ee update deps 2025-11-15 10:20:38 -08:00
d27cf2d69e drop lazy_static dep 2025-11-09 06:03:17 -08:00
1f8a66638f rewrite get_full_name function 2025-03-27 12:20:29 -07:00
0995ced783 skip non-files (directories) 2025-02-12 13:47:59 -08:00
49d071fd56 move writeattributes to map fixer 2025-01-27 07:06:24 -08:00
d9a39cc046 print output on failure 2024-12-23 19:53:08 -08:00
3 changed files with 433 additions and 1615 deletions

1717
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,11 @@ edition = "2021"
[dependencies] [dependencies]
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.31" rbx_binary = "2.0.0"
lazy-regex = "3.1.0" rbx_dom_weak = "4.0.0"
rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_reflection_database = "2.0.1"
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet"} rbx_xml = "2.0.0"
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet"} regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
rbx_xml = { version = "0.13.3", registry = "strafesnet"}
siphasher = "1.0.1"
submissions-api = { path = "../maps-service/validation/api", features = ["external"], default-features = false}
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "fs"] }
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -1,24 +1,25 @@
use std::{io::{Read, Seek}, path::PathBuf}; use std::{io::{Read, Seek}, path::PathBuf};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use anyhow::Result as AResult; use anyhow::Result as AResult;
use futures::{StreamExt,TryStreamExt};
#[derive(Parser)] #[derive(Parser)]
#[command(author,version,about,long_about=None)] #[command(author, version, about, long_about = None)]
#[command(propagate_version=true)] #[command(propagate_version = true)]
struct Cli{ struct Cli {
#[arg(long)]
path:Option<PathBuf>,
#[command(subcommand)] #[command(subcommand)]
command:Commands, command: Commands,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
enum Commands{ enum Commands {
ExtractScripts(PathBufList), ExtractScripts(PathBufList),
Interactive, Interactive,
Replace, Replace,
Scan, Scan,
Upload, Upload,
UploadScripts(UploadScriptsCommand) WriteAttributes,
} }
#[derive(Args)] #[derive(Args)]
@@ -31,14 +32,7 @@ struct MapList {
maps: Vec<u64>, maps: Vec<u64>,
} }
#[derive(Args)] fn main() -> AResult<()> {
struct UploadScriptsCommand{
#[arg(long)]
session_id:PathBuf,
}
#[tokio::main]
async fn main() -> AResult<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match cli.command { match cli.command {
Commands::ExtractScripts(pathlist)=>extract_scripts(pathlist.paths), Commands::ExtractScripts(pathlist)=>extract_scripts(pathlist.paths),
@@ -46,7 +40,7 @@ async fn main() -> AResult<()> {
Commands::Replace=>replace(), Commands::Replace=>replace(),
Commands::Scan=>scan(), Commands::Scan=>scan(),
Commands::Upload=>upload(), Commands::Upload=>upload(),
Commands::UploadScripts(command)=>upload_scripts(command.session_id).await, Commands::WriteAttributes=>write_attributes(),
} }
} }
@@ -54,7 +48,7 @@ fn class_is_a(class: &str, superclass: &str) -> bool {
if class==superclass { if class==superclass {
return true return true
} }
let class_descriptor=rbx_reflection_database::get().classes.get(class); let class_descriptor=rbx_reflection_database::get().unwrap().classes.get(class);
if let Some(descriptor) = &class_descriptor { if let Some(descriptor) = &class_descriptor {
if let Some(class_super) = &descriptor.superclass { if let Some(class_super) = &descriptor.superclass {
return class_is_a(&class_super, superclass) return class_is_a(&class_super, superclass)
@@ -72,15 +66,16 @@ fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types:
} }
} }
} }
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance) -> String{
let mut full_name=instance.name.clone(); fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut pref=instance.parent(); let mut names:Vec<_>=core::iter::successors(
while let Some(parent)=dom.get_by_ref(pref){ Some(instance),
full_name.insert(0, '.'); |i|dom.get_by_ref(i.parent())
full_name.insert_str(0, &parent.name); ).map(
pref=parent.parent(); |i|i.name.as_str()
} ).collect();
full_name names.reverse();
names.join(".")
} }
//scan (scripts) //scan (scripts)
@@ -179,7 +174,17 @@ fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&'a rbx_dom
None None
} }
fn get_mapinfo(dom:&rbx_dom_weak::WeakDom) -> AResult<(String,String,String,rbx_dom_weak::types::Ref)>{ struct GetMapInfoUstrs{
value:rbx_dom_weak::Ustr,
}
impl GetMapInfoUstrs{
fn new()->Self{
let value=rbx_dom_weak::ustr("Value");
Self{value}
}
}
fn get_mapinfo(ustrs:&GetMapInfoUstrs,dom:&rbx_dom_weak::WeakDom)->AResult<(String,String,String,rbx_dom_weak::types::Ref)>{
let workspace_children=dom.root().children(); let workspace_children=dom.root().children();
if workspace_children.len()!=1{ if workspace_children.len()!=1{
return Err(anyhow::Error::msg("there can only be one model")); return Err(anyhow::Error::msg("there can only be one model"));
@@ -190,8 +195,8 @@ fn get_mapinfo(dom:&rbx_dom_weak::WeakDom) -> AResult<(String,String,String,rbx_
Some(rbx_dom_weak::types::Variant::String(creator_string)), Some(rbx_dom_weak::types::Variant::String(creator_string)),
Some(rbx_dom_weak::types::Variant::String(displayname_string)) Some(rbx_dom_weak::types::Variant::String(displayname_string))
)=( )=(
creator.properties.get("Value"), creator.properties.get(&ustrs.value),
displayname.properties.get("Value") displayname.properties.get(&ustrs.value)
){ ){
return Ok((model_instance.name.clone(),creator_string.clone(),displayname_string.clone(),displayname.referent())); return Ok((model_instance.name.clone(),creator_string.clone(),displayname_string.clone(),displayname.referent()));
} }
@@ -207,6 +212,7 @@ enum Scan{
} }
fn scan() -> AResult<()>{ fn scan() -> AResult<()>{
let source_ustr=rbx_dom_weak::ustr("Source");
let mut id = get_id()?; let mut id = get_id()?;
//Construct allowed scripts //Construct allowed scripts
let allowed_set = get_allowed_set()?; let allowed_set = get_allowed_set()?;
@@ -225,7 +231,7 @@ fn scan() -> AResult<()>{
let mut fail_type=Scan::Passed; let mut fail_type=Scan::Passed;
for &script_ref in script_refs.iter() { for &script_ref in script_refs.iter() {
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(s)) = script.properties.get("Source") { if let Some(rbx_dom_weak::types::Variant::String(s)) = script.properties.get(&source_ustr) {
//flag keywords and instantly fail //flag keywords and instantly fail
if check_source_illegal_keywords(s){ if check_source_illegal_keywords(s){
println!("{:?} - flagged.",file_thing.file_name()); println!("{:?} - flagged.",file_thing.file_name());
@@ -266,6 +272,7 @@ fn scan() -> AResult<()>{
} }
fn extract_scripts(paths: Vec<PathBuf>) -> AResult<()>{ fn extract_scripts(paths: Vec<PathBuf>) -> AResult<()>{
let source_ustr=rbx_dom_weak::ustr("Source");
let mut id = 0; let mut id = 0;
//Construct allowed scripts //Construct allowed scripts
let mut script_set = std::collections::HashSet::<String>::new(); let mut script_set = std::collections::HashSet::<String>::new();
@@ -281,7 +288,7 @@ fn extract_scripts(paths: Vec<PathBuf>) -> AResult<()>{
//extract scribb //extract scribb
for &script_ref in script_refs.iter() { for &script_ref in script_refs.iter() {
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(s)) = script.properties.get("Source") { if let Some(rbx_dom_weak::types::Variant::String(s))=script.properties.get(&source_ustr){
if script_set.contains(s) { if script_set.contains(s) {
continue; continue;
}else{ }else{
@@ -301,6 +308,7 @@ fn extract_scripts(paths: Vec<PathBuf>) -> AResult<()>{
Ok(()) Ok(())
} }
fn replace() -> AResult<()>{ fn replace() -> AResult<()>{
let source_ustr=rbx_dom_weak::ustr("Source");
let allowed_map=get_allowed_map()?; let allowed_map=get_allowed_map()?;
let replace_map=get_replace_map()?; let replace_map=get_replace_map()?;
@@ -316,12 +324,12 @@ fn replace() -> AResult<()>{
let mut any_failed=false; let mut any_failed=false;
for &script_ref in script_refs.iter() { for &script_ref in script_refs.iter() {
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source)) = script.properties.get("Source") { if let Some(rbx_dom_weak::types::Variant::String(source)) = script.properties.get(&source_ustr) {
if let (Some(replace_id),Some(replace_script))=(replace_map.get(source),dom.get_by_ref_mut(script.referent())) { if let (Some(replace_id),Some(replace_script))=(replace_map.get(source),dom.get_by_ref_mut(script.referent())) {
println!("replace {}",replace_id); println!("replace {}",replace_id);
//replace the source //replace the source
if let Some(replace_source)=allowed_map.get(replace_id){ if let Some(replace_source)=allowed_map.get(replace_id){
replace_script.properties.insert("Source".to_string(), rbx_dom_weak::types::Variant::String(replace_source.clone())); replace_script.properties.insert(source_ustr, rbx_dom_weak::types::Variant::String(replace_source.clone()));
}else{ }else{
println!("failed to get replacement source {}",replace_id); println!("failed to get replacement source {}",replace_id);
any_failed=true; any_failed=true;
@@ -379,14 +387,19 @@ impl std::str::FromStr for UploadAction {
} }
fn upload() -> AResult<()>{ fn upload() -> AResult<()>{
let gmi_ustrs=GetMapInfoUstrs::new();
//interactive prompt per upload: //interactive prompt per upload:
for entry in std::fs::read_dir("maps/passed")? { for entry in std::fs::read_dir("maps/passed")?{
let file_thing=entry?; let file_thing=entry?;
if !file_thing.file_type()?.is_file(){
println!("skipping non-file: {:?}",file_thing.file_name());
continue;
}
println!("map file: {:?}",file_thing.file_name()); println!("map file: {:?}",file_thing.file_name());
let mut input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?); let mut input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?);
let dom = load_dom(&mut input)?; let dom = load_dom(&mut input)?;
let (modelname,creator,displayname,_) = get_mapinfo(&dom)?; let (modelname,creator,displayname,_)=get_mapinfo(&gmi_ustrs,&dom)?;
//Creator: [auto fill creator] //Creator: [auto fill creator]
//DisplayName: [auto fill DisplayName] //DisplayName: [auto fill DisplayName]
@@ -405,7 +418,7 @@ fn upload() -> AResult<()>{
} }
match upload_action { match upload_action {
UploadAction::Upload(asset_id) => { UploadAction::Upload(asset_id) => {
let status=std::process::Command::new("asset-tool") let output=std::process::Command::new("asset-tool")
.args([ .args([
"upload-asset", "upload-asset",
"--cookie-envvar","RBXCOOKIE", "--cookie-envvar","RBXCOOKIE",
@@ -413,16 +426,17 @@ fn upload() -> AResult<()>{
]) ])
.arg("--asset-id").arg(asset_id.to_string()) .arg("--asset-id").arg(asset_id.to_string())
.arg("--input-file").arg(file_thing.path().into_os_string().into_string().unwrap()) .arg("--input-file").arg(file_thing.path().into_os_string().into_string().unwrap())
.status()?; .output()?;
match status.code() { match output.status.code() {
Some(0)=>{ Some(0)=>{
//move file //move file
let mut dest=PathBuf::from("maps/uploaded"); let mut dest=PathBuf::from("maps/uploaded");
dest.push(file_thing.file_name()); dest.push(file_thing.file_name());
std::fs::rename(file_thing.path(), dest)?; std::fs::rename(file_thing.path(), dest)?;
} }
Some(code)=>println!("upload failed! code={}",code), other=>{
None => println!("no status code!"), println!("upload failed! code={:?}\noutput={}\nerr={}",other,String::from_utf8_lossy(&output.stdout),String::from_utf8_lossy(&output.stderr));
},
} }
} }
UploadAction::Skip => continue, UploadAction::Skip => continue,
@@ -444,9 +458,10 @@ fn upload() -> AResult<()>{
let mut dest=PathBuf::from("maps/uploaded"); let mut dest=PathBuf::from("maps/uploaded");
dest.push(file_thing.file_name()); dest.push(file_thing.file_name());
std::fs::rename(file_thing.path(), dest)?; std::fs::rename(file_thing.path(), dest)?;
} },
Some(code)=>println!("upload failed! code={}",code), other=>{
None => println!("no status code!"), println!("upload failed! code={:?}\noutput={}\nerr={}",other,String::from_utf8_lossy(&output.stdout),String::from_utf8_lossy(&output.stderr));
},
} }
} }
UploadAction::Delete => std::fs::remove_file(file_thing.path())?, UploadAction::Delete => std::fs::remove_file(file_thing.path())?,
@@ -495,11 +510,20 @@ fn is_first_letter_lowercase(s:&str)->bool{
s.chars().next().map(char::is_lowercase).unwrap_or(false) s.chars().next().map(char::is_lowercase).unwrap_or(false)
} }
macro_rules! lazy_regex{
($r:literal)=>{{
use regex::Regex;
use std::sync::LazyLock;
static RE:LazyLock<Regex>=LazyLock::new(||Regex::new($r).unwrap());
&RE
}};
}
fn is_title_case(display_name:&str)->bool{ fn is_title_case(display_name:&str)->bool{
display_name.len()!=0 display_name.len()!=0
&&!is_first_letter_lowercase(display_name) &&!is_first_letter_lowercase(display_name)
&&{ &&{
let display_name_pattern=lazy_regex::regex!(r"\b\S+"); let display_name_pattern=lazy_regex!(r"\b\S+");
display_name_pattern.find_iter(display_name) display_name_pattern.find_iter(display_name)
.all(|capture|match capture.as_str(){ .all(|capture|match capture.as_str(){
"a"=>true, "a"=>true,
@@ -513,20 +537,23 @@ fn is_title_case(display_name:&str)->bool{
} }
fn interactive() -> AResult<()>{ fn interactive() -> AResult<()>{
let gmi_utrs=GetMapInfoUstrs::new();
let value_ustr=rbx_dom_weak::ustr("Value");
let source_ustr=rbx_dom_weak::ustr("Source");
let mut id=get_id()?; let mut id=get_id()?;
//Construct allowed scripts //Construct allowed scripts
let mut allowed_set=get_allowed_set()?; let mut allowed_set=get_allowed_set()?;
let mut allowed_map=get_allowed_map()?; let mut allowed_map=get_allowed_map()?;
let mut replace_map=get_replace_map()?; let mut replace_map=get_replace_map()?;
let mut blocked = get_blocked()?; let mut blocked = get_blocked()?;
let model_name_pattern=lazy_regex::regex!(r"^[a-z0-9_]+$"); let model_name_pattern=lazy_regex!(r"^[a-z0-9_]+$");
'map_loop: for entry in std::fs::read_dir("maps/unprocessed")? { 'map_loop: for entry in std::fs::read_dir("maps/unprocessed")? {
let file_thing=entry?; let file_thing=entry?;
println!("processing map={:?}",file_thing.file_name()); println!("processing map={:?}",file_thing.file_name());
let mut input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?); let mut input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?);
let mut dom = load_dom(&mut input)?; let mut dom = load_dom(&mut input)?;
let (modelname,creator,displayname,displayname_ref)=get_mapinfo(&dom)?; let (modelname,creator,displayname,displayname_ref)=get_mapinfo(&gmi_utrs,&dom)?;
let mut script_count=0; let mut script_count=0;
let mut replace_count=0; let mut replace_count=0;
@@ -598,7 +625,7 @@ fn interactive() -> AResult<()>{
} }
} }
let displayname_instance=dom.get_by_ref_mut(displayname_ref).unwrap(); let displayname_instance=dom.get_by_ref_mut(displayname_ref).unwrap();
assert!(displayname_instance.properties.insert("Value".to_owned(),new_display_name.into()).is_some(),"StringValue we have a problem"); assert!(displayname_instance.properties.insert(value_ustr,new_display_name.into()).is_some(),"StringValue we have a problem");
//mark file as edited so a new file is generated //mark file as edited so a new file is generated
replace_count+=1; replace_count+=1;
} }
@@ -617,7 +644,7 @@ fn interactive() -> AResult<()>{
let mut fail_type=Interactive::Passed; let mut fail_type=Interactive::Passed;
for (script_ref,script_full_name) in script_refs{ for (script_ref,script_full_name) in script_refs{
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source)) = script.properties.get("Source") { if let Some(rbx_dom_weak::types::Variant::String(source)) = script.properties.get(&source_ustr) {
script_count+=1; script_count+=1;
let source_action=if check_source_illegal_keywords(source) { let source_action=if check_source_illegal_keywords(source) {
ScriptAction::Flag//script triggers flagging -> Flag ScriptAction::Flag//script triggers flagging -> Flag
@@ -695,7 +722,7 @@ fn interactive() -> AResult<()>{
if let (Some(replace_source),Some(replace_script))=(allowed_map.get(&replace_id),dom.get_by_ref_mut(script.referent())){ if let (Some(replace_source),Some(replace_script))=(allowed_map.get(&replace_id),dom.get_by_ref_mut(script.referent())){
replace_count+=1; replace_count+=1;
println!("replaced source id={} location={}",replace_id,location); println!("replaced source id={} location={}",replace_id,location);
replace_script.properties.insert("Source".to_string(), rbx_dom_weak::types::Variant::String(replace_source.clone())); replace_script.properties.insert(source_ustr,rbx_dom_weak::types::Variant::String(replace_source.clone()));
}else{ }else{
panic!("failed to get replacement source id={} location={}",replace_id,location); panic!("failed to get replacement source id={} location={}",replace_id,location);
} }
@@ -762,143 +789,65 @@ fn interactive() -> AResult<()>{
Ok(()) Ok(())
} }
fn hash_source(source:&str)->u64{ fn recursive_collect_regex(objects:&mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,regex:&regex::Regex){
let mut hasher=siphasher::sip::SipHasher::new(); for &referent in instance.children(){
std::hash::Hasher::write(&mut hasher,source.as_bytes()); if let Some(c)=dom.get_by_ref(referent){
std::hash::Hasher::finish(&hasher) if regex.captures(c.name.as_str()).is_some(){
} objects.push(c.referent());//copy ref
}
fn hash_format(hash:u64)->String{ recursive_collect_regex(objects,dom,c,regex);
format!("{:016x}",hash)
}
type GOCError=submissions_api::types::SingleItemError;
type GOCResult=Result<submissions_api::types::ScriptID,GOCError>;
async fn get_or_create_script(api:&submissions_api::external::Context,source:&str)->GOCResult{
let script_response=api.get_script_from_hash(submissions_api::types::HashRequest{
hash:hash_format(hash_source(source)).as_str(),
}).await?;
Ok(match script_response{
Some(script_response)=>script_response.ID,
None=>api.create_script(submissions_api::types::CreateScriptRequest{
Name:"Script",
Source:source,
SubmissionID:None,
}).await.map_err(GOCError::Other)?.ID
})
}
async fn check_or_create_script_poicy(
api:&submissions_api::external::Context,
hash:&str,
script_policy:submissions_api::types::CreateScriptPolicyRequest,
)->Result<(),GOCError>{
let script_policy_result=api.get_script_policy_from_hash(submissions_api::types::HashRequest{
hash,
}).await?;
match script_policy_result{
Some(script_policy_reponse)=>{
// check that everything matches the expectation
assert!(hash==script_policy_reponse.FromScriptHash);
assert!(script_policy.ToScriptID==script_policy_reponse.ToScriptID);
assert!(script_policy.Policy==script_policy_reponse.Policy);
},
None=>{
// create a new policy
api.create_script_policy(script_policy).await.map_err(GOCError::Other)?;
} }
} }
}
Ok(()) fn get_button_refs(dom:&rbx_dom_weak::WeakDom) -> Vec<rbx_dom_weak::types::Ref>{
} let mut buttons = std::vec::Vec::new();
recursive_collect_regex(&mut buttons, dom, dom.root(),lazy_regex!(r"Button(\d+)$"));
async fn do_policy( buttons
api:&submissions_api::external::Context, }
script_ids:&std::collections::HashMap<&str,submissions_api::types::ScriptID>,
source:&str, fn write_attributes() -> AResult<()>{
to_script_id:submissions_api::types::ScriptID, let attributes_ustr=rbx_dom_weak::ustr("Attributes");
policy:submissions_api::types::Policy, for entry in std::fs::read_dir("maps/unprocessed")? {
)->Result<(),GOCError>{ let file_thing=entry?;
let hash=hash_format(hash_source(source)); println!("processing map={:?}",file_thing.file_name());
check_or_create_script_poicy(api,hash.as_str(),submissions_api::types::CreateScriptPolicyRequest{ let mut input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?);
FromScriptID:script_ids[source], let mut dom = load_dom(&mut input)?;
ToScriptID:to_script_id,
Policy:policy, let button_refs = get_button_refs(&dom);
}).await
} for &button_ref in &button_refs {
if let Some(button)=dom.get_by_ref_mut(button_ref){
async fn upload_scripts(session_id:PathBuf)->AResult<()>{ match button.properties.get_mut(&attributes_ustr){
let cookie={ Some(rbx_dom_weak::types::Variant::Attributes(attributes))=>{
let mut cookie=String::new(); println!("Appending Ref={} to existing attributes for {}",button_ref,button.name);
std::fs::File::open(session_id)?.read_to_string(&mut cookie)?; attributes.insert("Ref".to_string(),rbx_dom_weak::types::Variant::String(button_ref.to_string()));
submissions_api::Cookie::new(&cookie)? },
}; None=>{
let api=&submissions_api::external::Context::new("http://localhost:8083".to_owned(),cookie)?; println!("Creating new attributes with Ref={} for {}",button_ref,button.name);
let mut attributes=rbx_dom_weak::types::Attributes::new();
let allowed_set=get_allowed_set()?; attributes.insert("Ref".to_string(),rbx_dom_weak::types::Variant::String(button_ref.to_string()));
let allowed_map=get_allowed_map()?; button.properties.insert(attributes_ustr,rbx_dom_weak::types::Variant::Attributes(attributes));
let replace_map=get_replace_map()?; }
let blocked=get_blocked()?; _=>unreachable!("Fetching attributes did not return attributes."),
}
// create a unified deduplicated set of all scripts }
let script_set:std::collections::HashSet<&str>=allowed_set.iter() }
.map(|s|s.as_str()) let mut dest={
.chain( let mut dest=PathBuf::from("maps/attributes");
replace_map.keys().map(|s|s.as_str()) dest.push(file_thing.file_name());
).chain( let output = std::io::BufWriter::new(std::fs::File::create(dest)?);
blocked.iter().map(|s|s.as_str()) //write workspace:GetChildren()[1]
).collect(); let workspace_children=dom.root().children();
if workspace_children.len()!=1{
// get or create every unique script return Err(anyhow::Error::msg("there can only be one model"));
let script_ids:std::collections::HashMap<&str,submissions_api::types::ScriptID>= }
futures::stream::iter(script_set) rbx_binary::to_writer(output, &dom, &[workspace_children[0]])?;
.map(|source|async move{ //move original to processed folder
let script_id=get_or_create_script(api,source).await?; PathBuf::from("maps/unaltered")
Ok::<_,GOCError>((source,script_id)) };
}) dest.push(file_thing.file_name());
.buffer_unordered(16) std::fs::rename(file_thing.path(), dest)?;
.try_collect().await?; }
// get or create policy for each script in each category
//
// replace
futures::stream::iter(replace_map.iter().map(Ok))
.try_for_each_concurrent(Some(16),|(source,id)|async{
do_policy(
api,
&script_ids,
source,
script_ids[allowed_map[id].as_str()],
submissions_api::types::Policy::Replace
).await
}).await?;
// allowed
futures::stream::iter(allowed_set.iter().map(Ok))
.try_for_each_concurrent(Some(16),|source|async{
do_policy(
api,
&script_ids,
source,
script_ids[source.as_str()],
submissions_api::types::Policy::Allowed
).await
}).await?;
// blocked
futures::stream::iter(blocked.iter().map(Ok))
.try_for_each_concurrent(Some(16),|source|async{
do_policy(
api,
&script_ids,
source,
script_ids[source.as_str()],
submissions_api::types::Policy::Blocked
).await
}).await?;
Ok(()) Ok(())
} }