diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9fc1a6a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +indent_size = 4 + +[*.rs] +max_line_length = 100 + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 193d30e..61ae5aa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,3 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb - - - -# Added by cargo - -/target diff --git a/Cargo.toml b/Cargo.toml index c724abf..0f63d94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "emoji-gen" description = "Emoji import file generator for Firefish" -license = "MIT" +license = "BSD" readme = "README.md" homepage = "https://git.joinfirefish.org/firefish/emoji-gen" repository = "https://git.joinfirefish.org/firefish/emoji-gen" authors = ["cutestnekoaqua ", "Mehdi Benadel "] -version = "0.3.3" +version = "0.3.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -29,6 +29,8 @@ env_logger = "0.10.0" log = "0.4.19" indicatif = {version = "0.17.5", features = ["rayon"]} rayon = "1.7.0" +datetime = "0.5.2" +chrono = "0.4.26" [dev-dependencies] env_logger = "0.10.0" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d5075a3..0000000 --- a/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -Permission is hereby granted, without written agreement and without -license or royalty fees, to use, copy, modify, and distribute this -software and its documentation for any purpose, provided that the -above copyright notice and the following two paragraphs appear in -all copies of this software. - -IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR -DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN -IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS -ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO -PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c5a43cc --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,28 @@ +# BSD 3-Clause License + +_Copyright (c) 2023 April John, Mehdi Benadel_ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 25b014e..3986e1b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,14 @@ A script written in Rust to generate a ZIP with right formating for a emoji pack Put all emojis you want to import in a folder, use subfolders for different groups. ```a -cargo install emoji-gen -emoji-gen --folder . [optionally add --group "GroupName"] +git clone https://git.joinfirefish.org/firefish/emoji-gen.git + +cargo install --path . + +# To create an import zip from an emoji folder +emoji-gen local --folder [--output ] + +# To create an import zip from a remote instance +emoji-gen crawl --host https://firefish.social [--output ] ``` -upload the zip file to Firefish and import it. \ No newline at end of file +upload the zip file to Firefish and import it. diff --git a/src/main.rs b/src/main.rs index 1414f7f..9ef92c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,18 +26,20 @@ use zip::write::FileOptions; use env_logger; use log::{debug, warn, error}; +use chrono::Local; + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Cli { /// Option #[command(subcommand)] - command: Command, + command: Command, } #[derive(Debug, Subcommand)] enum Command { - /// Help message for read. - Local { + /// Create a zip from a local folder + Local { /// Output file path #[arg(short = 'o', long = "output", default_value_t = String::from("generated_emojis"))] outputFilepath: String, @@ -45,25 +47,25 @@ enum Command { /// Folder with the custom emojis to generate the pack from. #[arg(short, long)] folder: String, - + /// Origin Host of the emoji #[arg(short = 'h', long = "host", default_value_t = String::from("https://git.joinfirefish.org/firefish/emoji-gen"))] originHost: String, - + /// Name for the pack #[arg(short, long, default_value_t = ("Custom").to_string())] group: String, - }, - /// Help message for write. - Crawl { + }, + /// Create a zip from a remote instance content + Crawl { /// Output file path #[arg(short = 'o', long = "output", default_value_t = String::from("generated_emojis"))] outputFilepath: String, /// Host to crawl emojis from - #[arg(short = 'h', long = "host", default_value_t = String::from("https://firefish.social"))] + #[arg(short, long)] host: String, - }, + }, } #[derive(Serialize, Deserialize)] @@ -78,9 +80,9 @@ struct Meta { } #[derive(Serialize, Deserialize)] struct EmojiResponse { - shortcode: Option, - url: Option, - static_url: String, + shortcode: Option, + url: Option, + static_url: String, category: Option, } @@ -100,24 +102,24 @@ struct EmojiData { fn getTypename(typeEnum: imghdr::Type) -> &'static str { return match typeEnum { - imghdr::Type::Gif => "gif", - imghdr::Type::Tiff => "tiff", - imghdr::Type::Rast => "rast", - imghdr::Type::Xbm => "xbm", - imghdr::Type::Jpeg => "jpg", - imghdr::Type::Bmp => "bmp", - imghdr::Type::Png => "png", - imghdr::Type::Webp => "webp", - imghdr::Type::Exr => "exr", + imghdr::Type::Avif => "avif", imghdr::Type::Bgp => "bgp", + imghdr::Type::Bmp => "bmp", + imghdr::Type::Exr => "exr", + imghdr::Type::Flif => "flif", + imghdr::Type::Gif => "gif", + imghdr::Type::Ico => "ico", + imghdr::Type::Jpeg => "jpg", imghdr::Type::Pbm => "pbm", imghdr::Type::Pgm => "pgm", + imghdr::Type::Png => "png", imghdr::Type::Ppm => "ppm", + imghdr::Type::Rast => "rast", imghdr::Type::Rgb => "rgb", imghdr::Type::Rgbe => "rgbe", - imghdr::Type::Flif => "flif", - imghdr::Type::Ico => "ico", - imghdr::Type::Avif => "avif", + imghdr::Type::Tiff => "tiff", + imghdr::Type::Webp => "webp", + imghdr::Type::Xbm => "xbm", }; } @@ -180,7 +182,7 @@ fn process( hostUrl = host; } } - + generate_meta( hostUrl, emojis, @@ -197,7 +199,7 @@ fn process( fn prepare_zip_filepath (outputFilepath: String) -> Result { let mut zipFilepath = PathBuf::from(outputFilepath.as_str()); - + zipFilepath.set_extension("zip"); if zipFilepath.exists() { @@ -212,10 +214,10 @@ fn get_host_emojis(host: &Url, tmpFolder: &Path) -> Result, EmojiGenE println!("Getting all the fine emojis from Url '{}'...", host.as_str()); let hostUrl = &host.join("/api/v1/custom_emojis").unwrap(); - - let emojis = match reqwest::blocking::get(hostUrl.clone()) { - Ok(response) => match response.json::>() { - Ok(emojiRes) => { + + let emojis = match reqwest::blocking::get(hostUrl.clone()) { + Ok(response) => match response.json::>() { + Ok(emojiRes) => { let emojos: Vec = emojiRes; let iter = emojos.iter(); Ok(iter.progress_count(emojos.len() as u64).map(| res | @@ -227,12 +229,12 @@ fn get_host_emojis(host: &Url, tmpFolder: &Path) -> Result, EmojiGenE ) ).filter_map(|r| r.ok()).collect::>()) }, - Err(_) => { + Err(_) => { Err(report!(EmojiGenError::ImageFetchingFailed) .attach_printable(format!("Could not get emoji list from url '{}'.", hostUrl.as_str()))) }, }, - Err(_) => { + Err(_) => { Err(report!(EmojiGenError::ImageFetchingFailed) .attach_printable(format!("Could not get response from url '{}'.", hostUrl.as_str()))) }, @@ -245,12 +247,12 @@ fn get_host_emoji_data(fileUrl: Url, name: String, category: String, tmpFolder: debug!("{}", fileUrl.to_string()); let newFilename = get_image_from_url(&fileUrl, tmpFolder, name.clone())?; - + let data: EmojiData = EmojiData{ name: name, category: category.to_string(), aliases: Vec::::new()}; - + Ok(Emoji { downloaded: true, fileName: newFilename, @@ -259,13 +261,13 @@ fn get_host_emoji_data(fileUrl: Url, name: String, category: String, tmpFolder: } fn get_image_from_url(fileUrl: &Url, tmpFolder: &Path, filename: String) -> Result { - + let img_bytes = &reqwest::blocking::get(fileUrl.clone()) .map_err(|_| report!(EmojiGenError::ImageFetchingFailed) .attach_printable(format!("Could not get image file from url '{}'.", fileUrl.as_str())) )?.bytes().unwrap(); - + let mut tmpFilepath: PathBuf = tmpFolder.join(filename); match imghdr::from_bytes(img_bytes) { @@ -274,7 +276,7 @@ fn get_image_from_url(fileUrl: &Url, tmpFolder: &Path, filename: String) -> Resu }; println!("Creating image file at path '{}'...", tmpFilepath.display()); - + let mut imageFile = File::create(tmpFilepath.as_os_str()) .map_err(|_| report!(EmojiGenError::ImageFetchingFailed) @@ -307,7 +309,7 @@ fn get_local_emojis(folder: &Path, group: String, tmpFolder: &Path) -> Result::new(); let iter = WalkDir::new(folder).into_iter(); @@ -327,7 +329,7 @@ fn get_local_emojis(folder: &Path, group: String, tmpFolder: &Path) -> Result Result Result { emojis.push(emoji); @@ -360,17 +362,17 @@ fn get_local_emojis(folder: &Path, group: String, tmpFolder: &Path) -> Result, tmpFolder: &Path) -> Result { debug!("{}", file.path().display()); - + let fileName = String::from(file.file_name().to_str().unwrap()); let name = String::from(file.path().file_stem().unwrap().to_str().unwrap()).replace(&[' ', '-'][..], "_"); get_image_from_path(&file.path(), tmpFolder)?; - + let data = EmojiData{ name: name, category: original_category.to_string(), aliases: Vec::::new()}; - + Ok(Emoji { downloaded: true, fileName, @@ -379,7 +381,7 @@ fn get_local_emoji_data(file: DirEntry, original_category: Rc, tmpFolder } fn get_image_from_path(filePath: &Path, tmpFolder: &Path) -> Result<(), EmojiGenError> { - + let filename = filePath.file_name().unwrap(); let img_data = std::fs::read(filePath.as_os_str()) .map_err(|_| @@ -388,7 +390,7 @@ fn get_image_from_path(filePath: &Path, tmpFolder: &Path) -> Result<(), EmojiGe )?; let img_bytes = img_data.as_slice(); - + let imageFilePath = &tmpFolder.join(filename); let mut imageFile = File::create(imageFilePath) .map_err(|_| @@ -413,7 +415,7 @@ fn generate_meta( let meta = Meta { metaVersion: 1, host: host, - exportedAt: "".to_string(), + exportedAt: Local::now().to_rfc3339(), emojis: emojis, }; @@ -451,10 +453,10 @@ fn zip( return Err(report!(EmojiGenError::ZipCreationFailed) .attach_printable(format!("Could not find folder '{}'.", src_dir.display()))) } - + println!("Creating zip file at path '{}'...", dst_file.display()); - - let zipFile = &File::create(dst_file).map_err(|_| + + let zipFile = &File::create(dst_file).map_err(|_| report!(EmojiGenError::ZipCreationFailed) .attach_printable(format!("Could not create file '{}'.", dst_file.display())) )?; @@ -465,15 +467,15 @@ fn zip( let iter = WalkDir::new(src_dir).into_iter(); let count = WalkDir::new(src_dir).into_iter().count() as u64; - + let mut buffer = Vec::new(); for entryRes in iter.progress_count(count) { - let entry = entryRes.map_err(|_| + let entry = entryRes.map_err(|_| report!(EmojiGenError::ZipCreationFailed) .attach_printable(format!("Could get path to a file in folder '{}'.", src_dir.display())) )?; let path = entry.path(); - let name = path.strip_prefix(src_dir).map_err(|_| + let name = path.strip_prefix(src_dir).map_err(|_| report!(EmojiGenError::ZipCreationFailed) .attach_printable(format!("Could not strip prefix on file path '{}'.", path.display())) )?; @@ -492,7 +494,7 @@ fn zip( report!(EmojiGenError::ZipCreationFailed) .attach_printable(format!("Could not read file '{}'.", path.display())) )?; - + f.read_to_end(&mut buffer) .map_err(|_| report!(EmojiGenError::ZipCreationFailed) @@ -522,4 +524,4 @@ fn zip( .attach_printable(format!("Could not close zip file '{:?}'.", zipFile)) )?; Ok(()) -} \ No newline at end of file +}