diff --git a/Cargo.lock b/Cargo.lock index 4d33664b5f..3b7e70c186 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -204,6 +204,7 @@ dependencies = [ "parse-display", "pretty_assertions", "rand", + "regex", "schemars", "sea-orm", "serde", diff --git a/Cargo.toml b/Cargo.toml index 2c208fe845..993fa06924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ napi = { version = "2.16.2", default-features = false } napi-derive = "2.16.2" napi-build = "2.1.2" -async-trait = "0.1.79" +async-trait = "0.1.80" basen = "0.1.0" cfg-if = "1.0.0" chrono = "0.4.37" @@ -20,8 +20,9 @@ once_cell = "1.19.0" parse-display = "0.9.0" pretty_assertions = "1.4.0" proc-macro2 = "1.0.79" -quote = "1.0.35" +quote = "1.0.36" rand = "0.8.5" +regex = "1.10.4" schemars = "0.8.16" sea-orm = "0.12.15" serde = "1.0.197" diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml index 12762ec101..48bdebc470 100644 --- a/packages/backend-rs/Cargo.toml +++ b/packages/backend-rs/Cargo.toml @@ -26,6 +26,7 @@ jsonschema = { workspace = true } once_cell = { workspace = true } parse-display = { workspace = true } rand = { workspace = true } +regex = { workspace = true } schemars = { workspace = true, features = ["chrono"] } sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] } serde = { workspace = true, features = ["derive"] } diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 723a6a7e48..7b66fdb6de 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,8 +310,9 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr, IdConvertType, convertId } = nativeBinding +const { nyaify, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr, IdConvertType, convertId } = nativeBinding +module.exports.nyaify = nyaify module.exports.AntennaSrcEnum = AntennaSrcEnum module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum module.exports.NoteVisibilityEnum = NoteVisibilityEnum diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs index fcb2323380..06a09e64ac 100644 --- a/packages/backend-rs/src/lib.rs +++ b/packages/backend-rs/src/lib.rs @@ -2,6 +2,7 @@ pub use macro_rs::napi as export; pub mod database; pub mod macros; +pub mod misc; pub mod model; pub mod util; diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs new file mode 100644 index 0000000000..6c5d7c4f2e --- /dev/null +++ b/packages/backend-rs/src/misc/mod.rs @@ -0,0 +1 @@ +pub mod nyaify; diff --git a/packages/backend-rs/src/misc/nyaify.rs b/packages/backend-rs/src/misc/nyaify.rs new file mode 100644 index 0000000000..9ce25b8b4a --- /dev/null +++ b/packages/backend-rs/src/misc/nyaify.rs @@ -0,0 +1,96 @@ +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; + +#[cfg_attr(feature = "napi", crate::export)] +pub fn nyaify(text: &str, lang: Option<&str>) -> String { + let mut to_return = text.to_owned(); + + { + static RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(?i-u)(non)([bcdfghjklmnpqrstvwxyz])").unwrap()); + to_return = RE + .replace_all(&to_return, |caps: &Captures<'_>| { + format!( + "{}{}", + match &caps[1] { + "non" => "nyan", + "Non" => "Nyan", + "NON" => "NYAN", + _ => &caps[1], + }, + &caps[2] + ) + }) + .to_string(); + } + + { + static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"다([..。…??!!\s]|$)").unwrap()); + to_return = RE.replace_all(&to_return, r"다냥$1").to_string(); + } + + { + static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"야([??\s]|$)").unwrap()); + to_return = RE.replace_all(&to_return, r"냥$1").to_string(); + } + + { + static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"([나-낳])").unwrap()); + to_return = RE + .replace_all(&to_return, |caps: &Captures<'_>| { + format!( + "{}", + char::from_u32( + caps[0].chars().next().unwrap() as u32 + 56 /* = '냐' - '나' */ + ) + .unwrap() + ) + }) + .to_string(); + } + + if lang.is_some() && lang.unwrap().starts_with("zh") { + static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[妙庙描渺瞄秒苗藐廟]").unwrap()); + to_return = RE.replace_all(&to_return, "喵").to_string(); + } + + let simple_rules = [ + ("な", "にゃ"), + ("ナ", "ニャ"), + ("ナ", "ニャ"), + ("na", "nya"), + ("NA", "NYA"), + ("Na", "Nya"), + ("morning", "mornyan"), + ("Morning", "Mornyan"), + ("MORNING", "MORNYAN"), + ("everyone", "everynyan"), + ("Everyone", "Everynyan"), + ("EVERYONE", "EVERYNYAN"), + ("να", "νια"), + ("ΝΑ", "ΝΙΑ"), + ("Να", "Νια"), + ]; + + simple_rules.into_iter().for_each(|(from, to)| { + to_return = to_return.replace(from, to); + }); + + to_return +} + +#[cfg(test)] +mod unit_test { + use super::nyaify; + + #[test] + fn can_nyaify() { + assert_eq!(nyaify("Hello everyone!", Some("en")), "Hello everynyan!"); + assert_eq!(nyaify("Nonbinary people", None), "Nyanbinyary people"); + assert_eq!(nyaify("1分鐘是60秒", Some("zh-TW")), "1分鐘是60喵"); + assert_eq!(nyaify("1分間は60秒です", Some("ja-JP")), "1分間は60秒です"); + assert_eq!(nyaify("あなたは誰ですか", None), "あにゃたは誰ですか"); + assert_eq!(nyaify("Ναυτικός", Some("el-GR")), "Νιαυτικός"); + assert_eq!(nyaify("일어나다", None), "일어냐다냥"); + } +} diff --git a/packages/backend/src/misc/nyaify.ts b/packages/backend/src/misc/nyaify.ts deleted file mode 100644 index 5829461779..0000000000 --- a/packages/backend/src/misc/nyaify.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function nyaify(text: string, lang?: string): string { - text = text - // ja-JP - .replaceAll("な", "にゃ") - .replaceAll("ナ", "ニャ") - .replaceAll("ナ", "ニャ") - // en-US - .replaceAll("na", "nya") - .replaceAll("Na", "Nya") - .replaceAll("NA", "NYA") - .replace(/(?<=morn)ing/gi, (x) => (x === "ING" ? "YAN" : "yan")) - .replace(/(?<=every)one/gi, (x) => (x === "ONE" ? "NYAN" : "nyan")) - .replace(/non(?=[bcdfghjklmnpqrstvwxyz])/gi, (x) => - x === "NON" ? "NYAN" : "nyan", - ) - // ko-KR - .replace(/[나-낳]/g, (match) => - String.fromCharCode( - match.charCodeAt(0)! + "냐".charCodeAt(0) - "나".charCodeAt(0), - ), - ) - .replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, "다냥") - .replace(/(야(?=\?))|(야$)|(야(?= ))/gm, "냥") - // el-GR - .replaceAll("να", "νια") - .replaceAll("ΝΑ", "ΝΙΑ") - .replaceAll("Να", "Νια"); - - // zh-CN, zh-TW - if (lang === "zh") text = text.replace(/(妙|庙|描|渺|瞄|秒|苗|藐|廟)/g, "喵"); - - return text; -} diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 2921f68be3..ed6ecc4a5c 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -12,7 +12,7 @@ import { Channels, } from "../index.js"; import type { Packed } from "@/misc/schema.js"; -import { nyaify } from "@/misc/nyaify.js"; +import { nyaify } from "backend-rs"; import { awaitAll } from "@/prelude/await-all.js"; import { convertReactions, decodeReaction } from "@/misc/reaction-lib.js"; import type { NoteReaction } from "@/models/entities/note-reaction.js";