pikadick/commands/
uwuify.rs

1use crate::checks::ENABLED_CHECK;
2use rand::prelude::SliceRandom;
3use serenity::{
4    client::Context,
5    framework::standard::{
6        macros::command,
7        Args,
8        CommandResult,
9    },
10    model::channel::Message,
11};
12
13const FACES: &[&str] = &["(・`ω´・)", ";;w;;", "owo", "UwU", ">w<", "^w^"];
14
15#[command]
16#[description("UwUify as phrase")]
17#[usage("\"<phrase>\"")]
18#[example("\"Hello World!\"")]
19#[min_args(1)]
20#[max_args(1)]
21#[checks(Enabled)]
22#[bucket("default")]
23pub async fn uwuify(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
24    let phrase = args.single_quoted::<String>()?;
25    msg.channel_id.say(&ctx.http, uwuify_str(&phrase)).await?;
26    Ok(())
27}
28
29/// A rust-optimized version of:
30/// ```javascript
31/// /// Taken from: https://honk.moe/tools/owo.html
32/// var faces = ["(・`ω´・)", ";;w;;", "owo", "UwU", ">w<", "^w^"];
33/// function OwoifyText(){
34///     v = document.getElementById("textarea").value
35///     v = v.replace(/(?:r|l)/g, "w");
36///     v = v.replace(/(?:R|L)/g, "W");
37///     v = v.replace(/n([aeiou])/g, 'ny$1');
38///     v = v.replace(/N([aeiou])/g, 'Ny$1');
39///     v = v.replace(/N([AEIOU])/g, 'Ny$1');
40///     v = v.replace(/ove/g, "uv");
41///     v = v.replace(/\!+/g, " " + faces[Math.floor(Math.random() * faces.length)] + " ");
42///     document.getElementById("textarea").value = v
43///  };
44/// ```
45/// This version doesn't use regexes and completes the uwufication in 1 iteration with a lookahead buffer of 2 elements.
46/// NOTE: It may be buggy due to its complexity and discrepancies with the js version should be reported on the issue tracker.
47pub fn uwuify_str(input: &str) -> String {
48    let mut iter = input.chars().peekable();
49    let mut buf = None;
50    let mut output = String::with_capacity(input.len());
51
52    // Buf has 1 cap so it must be empty in each match arm since we try to fetch a value from it here.
53    // We can then treat peek/next as the first value from here on.
54    while let Some(c) = buf.take().or_else(|| iter.next()) {
55        match c {
56            'r' | 'l' => {
57                output.push('w');
58            }
59            'R' | 'L' => {
60                output.push('W');
61            }
62            'n' => {
63                if let Some(c) = iter.peek().copied() {
64                    if matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
65                        let c = iter.next().unwrap();
66
67                        output.reserve(3);
68                        output.push_str("ny");
69                        output.push(c);
70                    } else {
71                        output.push('n');
72                    }
73                } else {
74                    output.push('n');
75                }
76            }
77            'N' => {
78                if let Some(c) = iter.peek().copied().map(|c| c.to_ascii_lowercase()) {
79                    if matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
80                        let c = iter.next().unwrap();
81
82                        output.reserve(3);
83                        output.push_str("Ny");
84                        output.push(c);
85                    } else {
86                        output.push('N');
87                    }
88                } else {
89                    output.push('N');
90                }
91            }
92            'o' => {
93                if let Some(c) = iter.peek().copied() {
94                    if c == 'v' {
95                        let _ = iter.next().unwrap();
96
97                        if let Some(c) = iter.peek().copied() {
98                            if c == 'e' {
99                                let _ = iter.next().unwrap();
100                                output.push_str("ove");
101                            } else {
102                                output.push('o');
103                                buf = Some('v');
104                                // e is still in iter peek buffer
105                            }
106                        } else {
107                            output.push('o');
108                            buf = Some('v');
109                        }
110                    } else {
111                        output.push('o');
112                        // v is in iter peek buffer
113                    }
114                } else {
115                    output.push('o');
116                }
117            }
118            '!' => {
119                while let Some('!') = iter.next() {
120                    // Consume all '!'
121                    // TODO: Some variants add a face per '!',
122                    // it might make sense to add a feature that does that here
123                }
124
125                let face = FACES.choose(&mut rand::thread_rng()).expect("Valid Face");
126                output.reserve(face.len() + 2);
127
128                output.push(' ');
129                output.push_str(face);
130                output.push(' ');
131            }
132            c => output.push(c),
133        }
134    }
135
136    output
137}