~aleteoryx/pjsekai_emote_packs

ref: c1b79d4c44d1d1ebdfbdecd4d654defdebe0e9a2 pjsekai_emote_packs/src/main.rs -rw-r--r-- 5.1 KiB
c1b79d4cAleteoryx that's that 10 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#![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(&region);
  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());
}