Implement Releaser (#1)
- Discover submissions in `Uploaded` status - Discover most recent release date per game - Shuffle order - Create release schedule hardcoded to 1 map per week, Fridays at peak hours - Confirm each schedule before release Reviewed-on: #1 Co-authored-by: Quaternions <krakow20@gmail.com> Co-committed-by: Quaternions <krakow20@gmail.com>
This commit is contained in:
164
Cargo.lock
generated
164
Cargo.lock
generated
@ -17,6 +17,21 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.19"
|
version = "0.6.19"
|
||||||
@ -133,6 +148,21 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.39"
|
version = "4.5.39"
|
||||||
@ -534,6 +564,30 @@ dependencies = [
|
|||||||
"windows-registry",
|
"windows-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -762,6 +816,15 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@ -860,6 +923,15 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
@ -884,6 +956,35 @@ version = "5.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.19"
|
version = "0.12.19"
|
||||||
@ -942,10 +1043,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rreview"
|
name = "rreview"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"rand",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"submissions-api",
|
"submissions-api",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -1153,10 +1255,11 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "submissions-api"
|
name = "submissions-api"
|
||||||
version = "0.7.2"
|
version = "0.8.1"
|
||||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||||
checksum = "f0a098bbc31ecadff0237a2979cda54b2369c76e3a1ce7b86ff4643bfbf4822f"
|
checksum = "a08deea49e9e34f2f2f23219f4a565681b4c1ae46f8012496d9a8fe10897efd3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -1521,6 +1624,41 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1677,6 +1815,26 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rreview"
|
name = "rreview"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -8,8 +8,9 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.2", features = ["derive"] }
|
clap = { version = "4.4.2", features = ["derive"] }
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
rand = "0.9.1"
|
||||||
siphasher = "1.0.1"
|
siphasher = "1.0.1"
|
||||||
submissions-api = { version = "0.7.2", registry = "strafesnet" }
|
submissions-api = { version = "0.8.1", registry = "strafesnet" }
|
||||||
tokio = { version = "1.42.0", features = ["fs", "macros", "rt-multi-thread"] }
|
tokio = { version = "1.42.0", features = ["fs", "macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
196
src/main.rs
196
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
use clap::{Args,Parser,Subcommand};
|
use clap::{Args,Parser,Subcommand};
|
||||||
use futures::{StreamExt,TryStreamExt};
|
use futures::{StreamExt,TryStreamExt};
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const READ_CONCURRENCY:usize=16;
|
const READ_CONCURRENCY:usize=16;
|
||||||
@ -15,12 +17,20 @@ struct Cli{
|
|||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands{
|
enum Commands{
|
||||||
|
Release(ReleaseCommand),
|
||||||
RepairDuplicates(RepairDuplicatesCommand),
|
RepairDuplicates(RepairDuplicatesCommand),
|
||||||
RepairPolicies(RepairPoliciesCommand),
|
RepairPolicies(RepairPoliciesCommand),
|
||||||
Review(ReviewCommand),
|
Review(ReviewCommand),
|
||||||
UploadScripts(UploadScriptsCommand),
|
UploadScripts(UploadScriptsCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct ReleaseCommand{
|
||||||
|
#[arg(long)]
|
||||||
|
session_id_file:PathBuf,
|
||||||
|
#[arg(long)]
|
||||||
|
api_url:String,
|
||||||
|
}
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
struct RepairDuplicatesCommand{
|
struct RepairDuplicatesCommand{
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@ -54,6 +64,10 @@ struct UploadScriptsCommand{
|
|||||||
async fn main(){
|
async fn main(){
|
||||||
let cli=Cli::parse();
|
let cli=Cli::parse();
|
||||||
match cli.command{
|
match cli.command{
|
||||||
|
Commands::Release(command)=>release(ReleaseConfig{
|
||||||
|
session_id:std::fs::read_to_string(command.session_id_file).unwrap(),
|
||||||
|
api_url:command.api_url,
|
||||||
|
}).await.unwrap(),
|
||||||
Commands::RepairDuplicates(command)=>repair_duplicates(RepairDuplicatesConfig{
|
Commands::RepairDuplicates(command)=>repair_duplicates(RepairDuplicatesConfig{
|
||||||
session_id:std::fs::read_to_string(command.session_id_file).unwrap(),
|
session_id:std::fs::read_to_string(command.session_id_file).unwrap(),
|
||||||
api_url:command.api_url,
|
api_url:command.api_url,
|
||||||
@ -103,13 +117,13 @@ enum ReviewError{
|
|||||||
Cookie(submissions_api::CookieError),
|
Cookie(submissions_api::CookieError),
|
||||||
Reqwest(submissions_api::ReqwestError),
|
Reqwest(submissions_api::ReqwestError),
|
||||||
GetPolicies(submissions_api::Error),
|
GetPolicies(submissions_api::Error),
|
||||||
GetScriptFromHash(submissions_api::types::SingleItemError),
|
GetScriptFromHash(submissions_api::types::ScriptSingleItemError),
|
||||||
NoScript,
|
NoScript,
|
||||||
WriteCurrent(std::io::Error),
|
WriteCurrent(std::io::Error),
|
||||||
ActionIO(std::io::Error),
|
ActionIO(std::io::Error),
|
||||||
PurgeScript(submissions_api::Error),
|
PurgeScript(submissions_api::Error),
|
||||||
ReadCurrent(std::io::Error),
|
ReadCurrent(std::io::Error),
|
||||||
DeduplicateModified(submissions_api::types::SingleItemError),
|
DeduplicateModified(submissions_api::types::ScriptSingleItemError),
|
||||||
UploadModified(submissions_api::Error),
|
UploadModified(submissions_api::Error),
|
||||||
UpdateScriptPolicy(submissions_api::Error),
|
UpdateScriptPolicy(submissions_api::Error),
|
||||||
}
|
}
|
||||||
@ -236,10 +250,10 @@ enum ScriptUploadError{
|
|||||||
AllowedMap(GetMapError),
|
AllowedMap(GetMapError),
|
||||||
ReplaceMap(GetMapError),
|
ReplaceMap(GetMapError),
|
||||||
BlockedSet(std::io::Error),
|
BlockedSet(std::io::Error),
|
||||||
GetOrCreate(GOCError),
|
GetOrCreate(GOCScriptError),
|
||||||
GetOrCreatePolicyReplace(GOCError),
|
GetOrCreatePolicyReplace(GOCScriptPolicyError),
|
||||||
GetOrCreatePolicyAllowed(GOCError),
|
GetOrCreatePolicyAllowed(GOCScriptPolicyError),
|
||||||
GetOrCreatePolicyBlocked(GOCError),
|
GetOrCreatePolicyBlocked(GOCScriptPolicyError),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dir_stream(dir:tokio::fs::ReadDir)->impl futures::stream::Stream<Item=std::io::Result<tokio::fs::DirEntry>>{
|
fn read_dir_stream(dir:tokio::fs::ReadDir)->impl futures::stream::Stream<Item=std::io::Result<tokio::fs::DirEntry>>{
|
||||||
@ -318,10 +332,11 @@ fn hash_format(hash:u64)->String{
|
|||||||
format!("{:016x}",hash)
|
format!("{:016x}",hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GOCError=submissions_api::types::SingleItemError;
|
type GOCScriptError=submissions_api::types::ScriptSingleItemError;
|
||||||
type GOCResult=Result<submissions_api::types::ScriptID,GOCError>;
|
type GOCScriptPolicyError=submissions_api::types::ScriptPolicySingleItemError;
|
||||||
|
type GOCScriptResult=Result<submissions_api::types::ScriptID,GOCScriptError>;
|
||||||
|
|
||||||
async fn get_or_create_script(api:&submissions_api::external::Context,source:&str)->GOCResult{
|
async fn get_or_create_script(api:&submissions_api::external::Context,source:&str)->GOCScriptResult{
|
||||||
let script_response=api.get_script_from_hash(submissions_api::types::HashRequest{
|
let script_response=api.get_script_from_hash(submissions_api::types::HashRequest{
|
||||||
hash:hash_format(hash_source(source)).as_str(),
|
hash:hash_format(hash_source(source)).as_str(),
|
||||||
}).await?;
|
}).await?;
|
||||||
@ -333,7 +348,7 @@ async fn get_or_create_script(api:&submissions_api::external::Context,source:&st
|
|||||||
Source:source,
|
Source:source,
|
||||||
ResourceType:submissions_api::types::ResourceType::Unknown,
|
ResourceType:submissions_api::types::ResourceType::Unknown,
|
||||||
ResourceID:None,
|
ResourceID:None,
|
||||||
}).await.map_err(GOCError::Other)?.ScriptID
|
}).await.map_err(GOCScriptError::Other)?.ScriptID
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +356,7 @@ async fn check_or_create_script_poicy(
|
|||||||
api:&submissions_api::external::Context,
|
api:&submissions_api::external::Context,
|
||||||
hash:&str,
|
hash:&str,
|
||||||
script_policy:submissions_api::types::CreateScriptPolicyRequest,
|
script_policy:submissions_api::types::CreateScriptPolicyRequest,
|
||||||
)->Result<(),GOCError>{
|
)->Result<(),GOCScriptPolicyError>{
|
||||||
let script_policy_result=api.get_script_policy_from_hash(submissions_api::types::HashRequest{
|
let script_policy_result=api.get_script_policy_from_hash(submissions_api::types::HashRequest{
|
||||||
hash,
|
hash,
|
||||||
}).await?;
|
}).await?;
|
||||||
@ -355,7 +370,7 @@ async fn check_or_create_script_poicy(
|
|||||||
},
|
},
|
||||||
None=>{
|
None=>{
|
||||||
// create a new policy
|
// create a new policy
|
||||||
api.create_script_policy(script_policy).await.map_err(GOCError::Other)?;
|
api.create_script_policy(script_policy).await.map_err(GOCScriptPolicyError::Other)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +479,7 @@ enum RepairPoliciesError{
|
|||||||
Cookie(submissions_api::CookieError),
|
Cookie(submissions_api::CookieError),
|
||||||
Reqwest(submissions_api::ReqwestError),
|
Reqwest(submissions_api::ReqwestError),
|
||||||
GetPolicies(submissions_api::Error),
|
GetPolicies(submissions_api::Error),
|
||||||
GetScripts(submissions_api::types::SingleItemError),
|
GetScripts(submissions_api::types::ScriptSingleItemError),
|
||||||
UpdateScriptPolicy(submissions_api::Error),
|
UpdateScriptPolicy(submissions_api::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,3 +592,158 @@ async fn repair_duplicates(config:RepairDuplicatesConfig)->Result<(),RepairDupli
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReleaseError{
|
||||||
|
Cookie(submissions_api::CookieError),
|
||||||
|
Reqwest(submissions_api::ReqwestError),
|
||||||
|
GetSubmissions(submissions_api::Error),
|
||||||
|
GetMaps(submissions_api::Error),
|
||||||
|
Io(std::io::Error),
|
||||||
|
Release(submissions_api::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReleaseConfig{
|
||||||
|
session_id:String,
|
||||||
|
api_url:String,
|
||||||
|
}
|
||||||
|
async fn release(config:ReleaseConfig)->Result<(),ReleaseError>{
|
||||||
|
let cookie=submissions_api::Cookie::new(&config.session_id).map_err(ReleaseError::Cookie)?;
|
||||||
|
let api=&submissions_api::external::Context::new(config.api_url,cookie).map_err(ReleaseError::Reqwest)?;
|
||||||
|
|
||||||
|
const LIMIT:u32=100;
|
||||||
|
const ONE_HOUR:i64=60*60;
|
||||||
|
const ONE_DAY:i64=24*ONE_HOUR;
|
||||||
|
const ONE_WEEK:i64=7*ONE_DAY;
|
||||||
|
const FRIDAY:i64=2*ONE_DAY;
|
||||||
|
const PEAK_HOURS:i64=-7*ONE_HOUR;
|
||||||
|
|
||||||
|
// determine maps ready to be released
|
||||||
|
let mut submissions_pending_release=std::collections::BTreeMap::new();
|
||||||
|
{
|
||||||
|
println!("Downloading submissions pending release...");
|
||||||
|
let mut page=1;
|
||||||
|
loop{
|
||||||
|
let submissions=api.get_submissions(submissions_api::types::GetSubmissionsRequest{
|
||||||
|
Page:page,
|
||||||
|
Limit:LIMIT,
|
||||||
|
DisplayName:None,
|
||||||
|
Creator:None,
|
||||||
|
GameID:None,
|
||||||
|
Sort:None,
|
||||||
|
Submitter:None,
|
||||||
|
AssetID:None,
|
||||||
|
UploadedAssetID:None,
|
||||||
|
StatusID:Some(submissions_api::types::SubmissionStatus::Uploaded),
|
||||||
|
}).await.map_err(ReleaseError::GetSubmissions)?;
|
||||||
|
let len=submissions.Submissions.len();
|
||||||
|
for submission in submissions.Submissions{
|
||||||
|
submissions_pending_release.entry(submission.GameID).or_insert(Vec::new()).push(submission);
|
||||||
|
}
|
||||||
|
if len<LIMIT as usize{
|
||||||
|
break;
|
||||||
|
}else{
|
||||||
|
page+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there is nothing to release, exit immediately
|
||||||
|
if submissions_pending_release.is_empty(){
|
||||||
|
println!("Nothing to release!");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine the most recent map release date
|
||||||
|
// if it's in the past, generate a Friday 10AM timestamp instead
|
||||||
|
let it={
|
||||||
|
println!("Determining most recent release dates...");
|
||||||
|
let mut latest_date=std::collections::HashMap::new();
|
||||||
|
let mut page=1;
|
||||||
|
loop{
|
||||||
|
let maps=api.get_maps(submissions_api::types::GetMapsRequest{
|
||||||
|
Page:page,
|
||||||
|
Limit:LIMIT,
|
||||||
|
DisplayName:None,
|
||||||
|
Creator:None,
|
||||||
|
GameID:None,
|
||||||
|
Sort:None,//TODO: sort by date to cut down requests
|
||||||
|
}).await.map_err(ReleaseError::GetMaps)?;
|
||||||
|
let len=maps.len();
|
||||||
|
for map in maps{
|
||||||
|
latest_date
|
||||||
|
.entry(map.GameID)
|
||||||
|
.and_modify(|date|
|
||||||
|
*date=map.Date.min(*date)
|
||||||
|
)
|
||||||
|
.or_insert(map.Date);
|
||||||
|
}
|
||||||
|
if len<LIMIT as usize{
|
||||||
|
break;
|
||||||
|
}else{
|
||||||
|
page+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// breaks on Sun 4 Dec 292277026596
|
||||||
|
let now=std::time::UNIX_EPOCH.elapsed().unwrap().as_secs() as i64;
|
||||||
|
|
||||||
|
// If the date is in the past, unset it
|
||||||
|
latest_date.retain(|_,&mut date|now<date);
|
||||||
|
|
||||||
|
submissions_pending_release.into_iter().map(move|(game,pending)|{
|
||||||
|
let start_date=match latest_date.get(&game){
|
||||||
|
Some(&date)=>{
|
||||||
|
// round to friday
|
||||||
|
(date+(ONE_WEEK>>1)-FRIDAY)/ONE_WEEK*ONE_WEEK+FRIDAY+PEAK_HOURS
|
||||||
|
// add a week
|
||||||
|
+ONE_WEEK
|
||||||
|
},
|
||||||
|
// find soonest friday
|
||||||
|
None=>((now-FRIDAY) as u64).next_multiple_of(ONE_WEEK as u64) as i64+FRIDAY+PEAK_HOURS
|
||||||
|
};
|
||||||
|
|
||||||
|
(game,start_date,pending)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rng=rand::rng();
|
||||||
|
|
||||||
|
for (game,start_date,mut pending) in it{
|
||||||
|
// shuffle maps
|
||||||
|
pending.shuffle(&mut rng);
|
||||||
|
|
||||||
|
// schedule one per week
|
||||||
|
let schedule:&Vec<_>=&pending.into_iter().enumerate().map(|(i,submission)|{
|
||||||
|
let release_date=(std::time::UNIX_EPOCH+std::time::Duration::from_secs((
|
||||||
|
start_date+i as i64*ONE_WEEK
|
||||||
|
) as u64)).into();
|
||||||
|
println!("Schedule {:?} {} at {}",submission.ID,submission.DisplayName,release_date);
|
||||||
|
submissions_api::types::ReleaseInfo{
|
||||||
|
Date:release_date,
|
||||||
|
SubmissionID:submission.ID,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// ask to confirm schedule
|
||||||
|
print!("Accept this release schedule for {game:?}? [y/N]: ");
|
||||||
|
std::io::stdout().flush().map_err(ReleaseError::Io)?;
|
||||||
|
|
||||||
|
let mut input=String::new();
|
||||||
|
std::io::stdin().read_line(&mut input).map_err(ReleaseError::Io)?;
|
||||||
|
match input.trim(){
|
||||||
|
"y"|"Y"=>(),
|
||||||
|
_=>{
|
||||||
|
println!("Quitting.");
|
||||||
|
return Ok(());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// send it
|
||||||
|
api.release_submissions(submissions_api::types::ReleaseRequest{
|
||||||
|
schedule,
|
||||||
|
}).await.map_err(ReleaseError::Release)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user