#![feature(iter_intersperse)]
mod en;
mod model;
mod slug;
use std::collections::{BTreeMap, HashSet};
use std::fs::{File, self};
use std::io::Write;
use std::path::{Path, PathBuf};
use bytes::Bytes;
use reqwest::blocking::Client;
use strum::{EnumIs, EnumProperty};
use zip::write::{SimpleFileOptions, ZipWriter};
const BASE_DOWNLOAD_PATH: &'static str = "https://aleteoryx.me/downloads2/emotes/pleroma/";
trait Helpers {
fn escape_name(name: &str) -> String;
}
#[derive(Default, serde::Serialize)]
struct PackMetadata {
name: String,
description: String,
homepage: String,
files: String,
src: String,
src_sha256: String,
license: String
}
fn get_stamp_asset_png(client: &Client, id: &str) -> Bytes {
client
.get(format!("https://storage.sekai.best/sekai-en-assets/stamp/{id}_rip/{id}.png"))
.send()
.expect("Couldn't download stamp asset")
.error_for_status()
.expect("Couldn't download stamp asset")
.bytes()
.expect("Couldn't download stamp asset")
}
fn main() {
let repo_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_owned()).into();
let client = Client::new();
// English region (global)
handle_region::<en::Helpers>(&client, &repo_dir, "en");
}
fn handle_region<H: Helpers>(client: &Client, repo_dir: &Path, region: &'static str) {
let sekai_dir = repo_dir.join(format!("sekai_world/db_{region}_diff"));
let stamps =
serde_json::from_reader::<_, Vec<model::RawStamp>>(
File::open(sekai_dir.join("stamps.json"))
.expect("Couldn't open stamps.json"))
.expect("stamps.json invalid")
.into_iter()
.map(|a| a.to_stamp())
.collect::<Vec<_>>();
let cache_dir = repo_dir.join("stamps_cache").join(®ion);
fs::create_dir_all(&cache_dir);
for stamp in &stamps {
let download_path = cache_dir.join(&stamp.assetbundle_name).with_extension("png");
if !fs::exists(&download_path).unwrap() {
println!("Downlading asset {}...", &stamp.assetbundle_name);
fs::write(&download_path, get_stamp_asset_png(&client, &stamp.assetbundle_name))
.expect("Couldn't write out stamp cache file");
}
}
let character_stamps =
stamps.clone().into_iter()
.filter(|a| a.character.is_solo() || a.character.is_solo_extra())
.collect::<Vec<_>>();
let text_stamps =
stamps.clone().into_iter()
.filter(|a| a.character.is_nobody())
.collect::<Vec<_>>();
let name_stamps =
stamps.clone().into_iter()
.filter(|a| a.character.is_gcuid())
.collect::<Vec<_>>();
let pair_stamps =
stamps.clone().into_iter()
.filter(|a| a.character.is_duo())
.collect::<Vec<_>>();
let packs_dir = repo_dir.join("stamp_packs");
let out_dir = packs_dir.join("Project SEKAI").join(region);
fs::create_dir_all(&out_dir);
let mut packs_list: BTreeMap<String, PackMetadata> = BTreeMap::new();
let character_out_dir = out_dir.join("characters");
fs::create_dir_all(&character_out_dir);
let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
for cid in 1..=26 {
let character = model::Character::from(cid);
let character_slug = character.get_str("prefix").unwrap();
let mut emotes: BTreeMap<String, String> = BTreeMap::new();
let zip_path = character_out_dir.join(&character_slug).with_extension("zip");
{
let mut writer =
ZipWriter::new(
File::create(&zip_path).unwrap());
println!("Generating character stamp pack for {}...", character);
for stamp in &character_stamps {
match stamp.character {
model::StampCharacter::Solo{ character_id } |
model::StampCharacter::SoloExtra{ character_id, .. } if character_id as u8 == cid => {
let character_slug = stamp.character.to_string() + "_" + &H::escape_name(&stamp.name);
writer.start_file(character_slug.clone() + ".png", options);
writer.write_all(&fs::read(cache_dir.join(&stamp.assetbundle_name).with_extension("png")).unwrap());
emotes.insert(character_slug.clone(), character_slug + ".png");
},
_ => ()
}
}
writer.finish().unwrap();
}
File::create(
character_out_dir
.join(&character_slug)
.with_extension("json"))
.unwrap()
.write_all(&serde_json::to_vec(&emotes).unwrap());
let pack_path = format!("Project SEKAI/{region}/characters/{character_slug}");
let pack_uri_path = pack_path.replace(" ", "%20");
packs_list.insert(
pack_path.clone(),
PackMetadata {
name: format!("Project SEKAI ({region}): {}", character.get_str(&format!("name_{region}")).unwrap()),
description: "Made possible by the sekai-viewer project. <https:///sekai.best>".into(),
homepage: "https://git.amehut.dev/~aleteoryx/pjsekai_emote_packs".into(),
src: BASE_DOWNLOAD_PATH.to_string() + &pack_uri_path + ".zip",
src_sha256: sha256::try_digest(&zip_path).unwrap(),
files: pack_uri_path + ".json",
license: "N/A".into()
});
}
File::create(packs_dir.join("manifest.json"))
.unwrap()
.write_all(&serde_json::to_vec_pretty(&packs_list).unwrap());
}