diff options
| author | salmonik <raisersoft@proton.me> | 2026-07-08 02:13:35 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-07-08 02:13:35 +0200 |
| commit | cd8ed8bc5793985df2c1692072a932ceee517b95 (patch) | |
| tree | 42d18a4140259b5c68b4dd05b7fcb398f78b8d7f /src/lang.rs | |
| parent | Merge pull request 'Voice related fixes, alongside simplifying assistant' (#5... (diff) | |
| download | senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.tar.gz senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.zip | |
OPEN THE RUST GATES
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to 'src/lang.rs')
| -rw-r--r-- | src/lang.rs | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/src/lang.rs b/src/lang.rs new file mode 100644 index 0000000..626f387 --- /dev/null +++ b/src/lang.rs | |||
| @@ -0,0 +1,387 @@ | |||
| 1 | // ┌──────────────────────────────────────────────────────────────────┐ | ||
| 2 | // │ lang.rs — All user-facing messages live here. │ | ||
| 3 | // │ Translate the bot by editing this single file. │ | ||
| 4 | // │ Naming: L_<CAT>_<NAME> for simple &str; │ | ||
| 5 | // │ fn fmt_<cat>_<name>(...) -> String for parameterized. │ | ||
| 6 | // └──────────────────────────────────────────────────────────────────┘ | ||
| 7 | |||
| 8 | // ── Error display prefixes (used in error.rs Display impl) ──────────── | ||
| 9 | |||
| 10 | pub const L_ERR_DATABASE: &str = "Database error"; | ||
| 11 | pub const L_ERR_HTTP: &str = "HTTP error"; | ||
| 12 | pub const L_ERR_DISCORD: &str = "Discord error"; | ||
| 13 | pub const L_ERR_VOICE_JOIN: &str = "Voice join error"; | ||
| 14 | pub const L_ERR_VOICE_CONTROL: &str = "Voice control error"; | ||
| 15 | pub const L_ERR_IO: &str = "IO error"; | ||
| 16 | pub const L_ERR_CRON: &str = "Cron error"; | ||
| 17 | pub const L_ERR_IMAGE: &str = "Image error"; | ||
| 18 | pub const L_ERR_QR: &str = "QR error"; | ||
| 19 | pub const L_ERR_SCRAPER: &str = "Scraper error"; | ||
| 20 | pub const L_ERR_CONFIG: &str = "Config error"; | ||
| 21 | pub const L_ERR_TASK_JOIN: &str = "Task join error"; | ||
| 22 | |||
| 23 | // ── Common / shared error messages ──────────────────────────────────── | ||
| 24 | |||
| 25 | /// "Nie jestem w gildii" — bot must be invoked inside a guild | ||
| 26 | pub const L_NOT_IN_GUILD: &str = "Nie jestem w gildii"; | ||
| 27 | /// "Nie jesteś na kanale głosowym" — user must be in a voice channel | ||
| 28 | pub const L_NOT_IN_VOICE: &str = "Nie jesteś na kanale głosowym"; | ||
| 29 | /// "Zły typ kanału" — wrong channel variant | ||
| 30 | pub const L_BAD_CHANNEL_TYPE: &str = "Zły typ kanału"; | ||
| 31 | /// "Grupa triggerów nie znaleziona" — trigger group not found | ||
| 32 | pub const L_TRIGGER_NOT_FOUND: &str = "Grupa triggerów nie znaleziona"; | ||
| 33 | |||
| 34 | // ── AI / senator command ────────────────────────────────────────────── | ||
| 35 | |||
| 36 | pub const SENATOR_SYSTEM_PROMPT: &str = "Jesteś Senatorem Steavenem Sperma Armstrongiem. Jesteś rasistą, seksistą i homofobem. Odpowiadasz w języku polskim. Odpowiedź ma maksymalnie 100 słów. Nie używaj formatowania markdown."; | ||
| 37 | |||
| 38 | pub const L_OPENROUTER_NO_KEY: &str = "Klucz API OpenRouter nie jest skonfigurowany w .env"; | ||
| 39 | |||
| 40 | // ── Cron command ────────────────────────────────────────────────────── | ||
| 41 | |||
| 42 | pub const L_CRON_QUEUED: &str = "Praca cron zakolejkowana"; | ||
| 43 | pub const L_CRON_NONE: &str = "Brak zakolejkowanych prac cron"; | ||
| 44 | pub const L_CRON_TITLE: &str = "Prace cron"; | ||
| 45 | pub const L_CRON_ACTIVE: &str = "✅ Aktywna"; | ||
| 46 | pub const L_CRON_PAUSED: &str = "⏸️ Zapauzowana"; | ||
| 47 | |||
| 48 | pub fn fmt_cron_audio_queued(cron: &str) -> String { | ||
| 49 | format!("Praca audio zakolejkowana z cron'em `{cron}`") | ||
| 50 | } | ||
| 51 | pub fn fmt_cron_label_audio(name: &str) -> String { | ||
| 52 | format!("Audio: {name}") | ||
| 53 | } | ||
| 54 | pub fn fmt_cron_label_msg(msg: &str) -> String { | ||
| 55 | format!("Wiadomość: {msg}") | ||
| 56 | } | ||
| 57 | pub fn fmt_cron_job_status(id: i64, status: &str) -> String { | ||
| 58 | format!("ID {id} — {status}") | ||
| 59 | } | ||
| 60 | pub fn fmt_cron_field(cron: &str, chan: i64, desc: &str) -> String { | ||
| 61 | format!("`{cron}` — <#{chan}>\n{desc}") | ||
| 62 | } | ||
| 63 | pub fn fmt_cron_deleted(id: i64) -> String { | ||
| 64 | format!("Praca cron z ID {id} została usunięta") | ||
| 65 | } | ||
| 66 | pub fn fmt_cron_not_found(id: i64) -> String { | ||
| 67 | format!("Praca z ID {id} nie została znaleziona") | ||
| 68 | } | ||
| 69 | pub fn fmt_cron_paused(id: i64) -> String { | ||
| 70 | format!("Praca cron z ID {id} została zapauzowana") | ||
| 71 | } | ||
| 72 | pub fn fmt_cron_resumed(id: i64) -> String { | ||
| 73 | format!("Praca cron z ID {id} została wznowiona") | ||
| 74 | } | ||
| 75 | |||
| 76 | // ── Math command ────────────────────────────────────────────────────── | ||
| 77 | |||
| 78 | pub const L_MATH_TOO_LONG: &str = "Ekspresja za długa (max 1000 znaków"; | ||
| 79 | pub const L_MATH_NOT_ASCII: &str = "Tylko znaki ASCII dozwolone"; | ||
| 80 | pub const L_MATH_UNBALANCED: &str = "Nawiasy są niezbalansowane"; | ||
| 81 | pub const L_MATH_DANGEROUS: &str = "Ekspresja zawiera groźne słowa"; | ||
| 82 | pub const L_MATH_NO_RESULT: &str = "Brak rezultatu"; | ||
| 83 | |||
| 84 | pub fn fmt_math_qalc_err(e: &dyn std::fmt::Display) -> String { | ||
| 85 | format!("qalc nie jest dostępny: {e}") | ||
| 86 | } | ||
| 87 | pub fn fmt_math_result(r: &str) -> String { | ||
| 88 | format!("```\n{r}\n```") | ||
| 89 | } | ||
| 90 | |||
| 91 | // ── Music command ───────────────────────────────────────────────────── | ||
| 92 | |||
| 93 | pub const L_MUSIC_NEED_NAME: &str = "Daj mi nazwę pliku, URL albo plik"; | ||
| 94 | pub const L_MUSIC_NO_AUDIO_ON_CP: &str = "Brak powiązanego audio na copyparty"; | ||
| 95 | pub const L_MUSIC_NO_URL_IN_RESULT: &str = "Wynik nie ma URL"; | ||
| 96 | pub const L_MUSIC_CANT_COMPRESS_LIVE: &str = | ||
| 97 | "Nie można skompresować transmisji na żywo, odtwarzam normalnie"; | ||
| 98 | pub const L_MUSIC_STOPPED: &str = "wyszed"; | ||
| 99 | pub const L_VOICE_USER_NOT_IN_VC: &str = "You are not in a voice channel"; | ||
| 100 | |||
| 101 | pub fn fmt_music_search_failed(e: &dyn std::fmt::Display) -> String { | ||
| 102 | format!("Szukanie na copyparty zawiodło: {e}") | ||
| 103 | } | ||
| 104 | pub fn fmt_music_playing(name: &str) -> String { | ||
| 105 | format!("Grajcowanie: {name}") | ||
| 106 | } | ||
| 107 | pub fn fmt_voice_joined(cid: &dyn std::fmt::Display) -> String { | ||
| 108 | format!("Dołączon <#{cid}>") | ||
| 109 | } | ||
| 110 | pub fn fmt_voice_join_failed(e: &dyn std::fmt::Display) -> String { | ||
| 111 | format!("Nie udało się połączyć z VC: {e}") | ||
| 112 | } | ||
| 113 | |||
| 114 | // ── TTS command ─────────────────────────────────────────────────────── | ||
| 115 | |||
| 116 | pub const L_TTS_SAVED: &str = "TTS zapisany"; | ||
| 117 | pub const L_TTS_PLAYING: &str = "Grom TTS na kanale głosowym"; | ||
| 118 | |||
| 119 | // ── Trigger command ─────────────────────────────────────────────────── | ||
| 120 | |||
| 121 | pub const L_TRIGGER_UPDATED: &str = "Trigger zaktualizowany"; | ||
| 122 | pub const L_TRIGGER_KW_NONE: &str = "Brak"; | ||
| 123 | |||
| 124 | pub fn fmt_trigger_created(name: &str) -> String { | ||
| 125 | format!("Grupa trigger '{name}' utworzona") | ||
| 126 | } | ||
| 127 | pub fn fmt_trigger_failed(e: &dyn std::fmt::Display) -> String { | ||
| 128 | format!("Nie udało się: {e}") | ||
| 129 | } | ||
| 130 | pub fn fmt_trigger_deleted(name: &str) -> String { | ||
| 131 | format!("Grupa trigger '{name}' usunięta") | ||
| 132 | } | ||
| 133 | pub fn fmt_trigger_title(name: &str) -> String { | ||
| 134 | format!("Trigger: {name}") | ||
| 135 | } | ||
| 136 | |||
| 137 | // ── Utility commands ────────────────────────────────────────────────── | ||
| 138 | |||
| 139 | pub const L_UTIL_NEED_INPUT: &str = "Podaj plik z copyparty lub załącznik"; | ||
| 140 | pub const L_UTIL_PICK_FROM_AUTOCOMPLETE: &str = "Wybierz plik z podpowiedzi"; | ||
| 141 | pub const L_UTIL_DOWNLOAD_FAILED: &str = "Nie udało się pobrać pliku"; | ||
| 142 | pub const L_UTIL_UNSUPPORTED_TYPE: &str = "Nieobsługiwany typ pliku"; | ||
| 143 | pub const L_UTIL_COMPRESS_FAILED: &str = "Kompresja nie powiodła się"; | ||
| 144 | pub const L_UTIL_CONVERT_FAILED: &str = "Konwersja nie powiodła się"; | ||
| 145 | pub const L_UTIL_UPLOAD_FAILED: &str = "Nie udało się wysłać na serwer"; | ||
| 146 | pub const L_UTIL_NO_ATTACHMENTS: &str = "Brak załączników w wiadomości"; | ||
| 147 | pub const L_SEARCH_NO_RESULTS: &str = "No results found"; | ||
| 148 | |||
| 149 | pub fn fmt_util_compressed(url: &str) -> String { | ||
| 150 | format!("Skompresowano\n{url}") | ||
| 151 | } | ||
| 152 | pub fn fmt_util_converted(url: &str) -> String { | ||
| 153 | format!("Skonwertowano\n{url}") | ||
| 154 | } | ||
| 155 | pub fn fmt_util_convert_err(stderr: &str) -> String { | ||
| 156 | format!("Błąd konwersji: {stderr}") | ||
| 157 | } | ||
| 158 | |||
| 159 | pub fn fmt_uploaded(url: &str) -> String { | ||
| 160 | format!("Wysłon: {url}") | ||
| 161 | } | ||
| 162 | pub fn fmt_download_uploaded(url: &str) -> String { | ||
| 163 | format!("pobioron i wysyłon: {url}") | ||
| 164 | } | ||
| 165 | pub fn fmt_upload_summary(count: usize) -> String { | ||
| 166 | format!("Wysłano {count} plików:\n") | ||
| 167 | } | ||
| 168 | pub fn fmt_upload_partial_failed(count: usize) -> String { | ||
| 169 | format!("\n\nNie wysłano {count} plików") | ||
| 170 | } | ||
| 171 | pub fn fmt_message_compressed(made: usize, total: usize) -> String { | ||
| 172 | format!("Skompresowano {made} z {total} plików:") | ||
| 173 | } | ||
| 174 | pub fn fmt_search_result(name: &str, url: &str) -> String { | ||
| 175 | format!("**{name}**\n{url}") | ||
| 176 | } | ||
| 177 | pub fn fmt_download_failed(stderr: &str) -> String { | ||
| 178 | format!( | ||
| 179 | "Pobieranie się nie powiodło: {}", | ||
| 180 | translate_ytdlp_stderr(stderr) | ||
| 181 | ) | ||
| 182 | } | ||
| 183 | pub fn fmt_ytdlp_err(e: &dyn std::fmt::Display) -> String { | ||
| 184 | format!("yt-dlp nie mógło się uruchomić: {e}") | ||
| 185 | } | ||
| 186 | pub fn fmt_ytdlp_stderr(stderr: &str) -> String { | ||
| 187 | translate_ytdlp_stderr(stderr) | ||
| 188 | } | ||
| 189 | |||
| 190 | /// Translate yt-dlp's verbose stderr into a short, user-friendly Polish | ||
| 191 | /// message. yt-dlp emits lots of `[youtube] Extracting URL…`, `WARNING:` | ||
| 192 | /// and `ERROR:` noise that is meaningless to a normal Discord user; this | ||
| 193 | /// recognises the handful of failure modes users actually hit and falls | ||
| 194 | /// back to the first `ERROR:`/`WARNING:` line (stripped of the | ||
| 195 | /// `ERROR: [extractor] id: ` prefix) when nothing matches, so we never | ||
| 196 | /// show the full multi-line trace. | ||
| 197 | pub fn translate_ytdlp_stderr(stderr: &str) -> String { | ||
| 198 | let lower = stderr.to_ascii_lowercase(); | ||
| 199 | |||
| 200 | // YouTube's anti-bot sign-in wall. yt-dlp can't bypass this without | ||
| 201 | // browser cookies configured on the host — surface that to the user | ||
| 202 | // rather than the cryptic "Sign in to confirm you're not a bot" line. | ||
| 203 | if lower.contains("sign in to confirm you") | ||
| 204 | || lower.contains("confirm you're not a bot") | ||
| 205 | || lower.contains("confirm you are not a bot") | ||
| 206 | { | ||
| 207 | return "YouTube uznaje nas za bota i blokuje pobieranie. Spróbuj ponownie później albo podaj inny link.".into(); | ||
| 208 | } | ||
| 209 | |||
| 210 | // Generic extractor fallback means the URL didn't match any known | ||
| 211 | // site — combined with "Requested format is not available" this is | ||
| 212 | // almost always "the user pasted a non-video URL into /download". | ||
| 213 | if lower.contains("requested format is not available") | ||
| 214 | || lower.contains("url could be a direct video link") | ||
| 215 | || lower.contains("falling back on generic information extractor") | ||
| 216 | { | ||
| 217 | return "Ten link nie wygląda na wideo obsługiwane przez yt-dlp. Upewnij się, że to adres do filmu (YouTube / SoundCloud / Bandcamp / Twitch).".into(); | ||
| 218 | } | ||
| 219 | |||
| 220 | if lower.contains("private video") { | ||
| 221 | return "Ten film jest prywatny i nie można go pobrać.".into(); | ||
| 222 | } | ||
| 223 | if lower.contains("video unavailable") | ||
| 224 | || lower.contains("video has been removed") | ||
| 225 | || lower.contains("this video is unavailable") | ||
| 226 | { | ||
| 227 | return "Ten film jest niedostępny lub został usunięty.".into(); | ||
| 228 | } | ||
| 229 | if lower.contains("members-only") | ||
| 230 | || lower.contains("premium-only") | ||
| 231 | || lower.contains("channel's members") | ||
| 232 | { | ||
| 233 | return "Ten film jest dostępny tylko dla subskrybujących / członków kanału.".into(); | ||
| 234 | } | ||
| 235 | if lower.contains("not available in your country") | ||
| 236 | || lower.contains("geo-restricted") | ||
| 237 | || lower.contains("geo restricted") | ||
| 238 | { | ||
| 239 | return "Ten film jest zablokowany regionalnie i nie jest dostępny w naszym regionie." | ||
| 240 | .into(); | ||
| 241 | } | ||
| 242 | if lower.contains("age-restricted") | ||
| 243 | || lower.contains("age restricted") | ||
| 244 | || lower.contains("inappropriate for some users") | ||
| 245 | { | ||
| 246 | return "Ten film jest ograniczony wiekowo i wymaga uwierzytelnienia.".into(); | ||
| 247 | } | ||
| 248 | if lower.contains("http error 404") || lower.contains(" http error 404 ") { | ||
| 249 | return "Nie znaleziono takiego adresu (404).".into(); | ||
| 250 | } | ||
| 251 | if lower.contains("unable to extract") && lower.contains("video data") { | ||
| 252 | return "Nie udało się wyciągnąć danych filmu — strona mogła się zmienić albo link jest niepełny.".into(); | ||
| 253 | } | ||
| 254 | |||
| 255 | // Fallback: keep only the first ERROR: line and strip the | ||
| 256 | // `ERROR: [extractor] id: ` prefix so the message is short and | ||
| 257 | // free of yt-dlp internals. | ||
| 258 | for line in stderr.lines() { | ||
| 259 | if let Some(rest) = line.strip_prefix("ERROR:") { | ||
| 260 | let rest = rest.trim(); | ||
| 261 | // Drop leading `[extractor] video_id: ` noise if present. | ||
| 262 | let msg = rest | ||
| 263 | .strip_prefix(|c: char| c == '[') | ||
| 264 | .and_then(|r| r.find(']').map(|i| &r[i + 1..])) | ||
| 265 | .map(|s| { | ||
| 266 | s.trim_start_matches(|c: char| { | ||
| 267 | c.is_alphanumeric() || c == '_' || c == '-' || c == ':' | ||
| 268 | }) | ||
| 269 | .trim() | ||
| 270 | }) | ||
| 271 | .filter(|s| !s.is_empty()) | ||
| 272 | .unwrap_or(rest); | ||
| 273 | return format!("Pobieranie się nie powiodło: {msg}"); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | for line in stderr.lines() { | ||
| 277 | if let Some(rest) = line.strip_prefix("WARNING:") { | ||
| 278 | return format!("Pobieranie się nie powiodło: {}", rest.trim()); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | "Pobieranie się nie powiodło.".into() | ||
| 283 | } | ||
| 284 | |||
| 285 | // ── Web command ─────────────────────────────────────────────────────── | ||
| 286 | |||
| 287 | pub const L_WEB_NO_RESULTS: &str = "Brak rezultatów"; | ||
| 288 | |||
| 289 | pub fn fmt_web_title(query: &str) -> String { | ||
| 290 | format!("Wyniki wyszukiwania dla: {query}") | ||
| 291 | } | ||
| 292 | |||
| 293 | // ── QR command ──────────────────────────────────────────────────────── | ||
| 294 | |||
| 295 | pub const L_QR_GENERATED: &str = "Kod QR wygenerowany:"; | ||
| 296 | pub const L_QR_DECODE_FAILED: &str = "Nie udało się zdekodować kodu QR."; | ||
| 297 | |||
| 298 | pub fn fmt_qr_decoded(text: &str) -> String { | ||
| 299 | format!("```\n{text}\n```") | ||
| 300 | } | ||
| 301 | |||
| 302 | // ── Copyparty client ────────────────────────────────────────────────── | ||
| 303 | |||
| 304 | pub const L_CP_UPLOAD_NO_URL: &str = "Wysłanie zawiodło, brak URL w odpowiedzi"; | ||
| 305 | |||
| 306 | pub fn fmt_cp_search_http(status: &dyn std::fmt::Display, snippet: &str) -> String { | ||
| 307 | format!("copyparty zwróciło status HTTP {status}: {snippet}") | ||
| 308 | } | ||
| 309 | pub fn fmt_cp_invalid_json(e: &dyn std::fmt::Display, snippet: &str) -> String { | ||
| 310 | format!("copyparty zwróciło nielegalny JSON. ({e}): {snippet}") | ||
| 311 | } | ||
| 312 | |||
| 313 | // ── OpenRouter ──────────────────────────────────────────────────────── | ||
| 314 | |||
| 315 | pub fn fmt_openrouter_err(status: &dyn std::fmt::Display, text: &str) -> String { | ||
| 316 | format!("Błąd API OpenRouter. {status}: {text}") | ||
| 317 | } | ||
| 318 | |||
| 319 | // ── Voice / audio decoding ──────────────────────────────────────────── | ||
| 320 | |||
| 321 | pub const L_VOICE_NO_TRACK: &str = "Brak kawałka"; | ||
| 322 | pub const L_VOICE_NO_CODEC: &str = "Brak parametrów codec'a audio"; | ||
| 323 | |||
| 324 | pub fn fmt_voice_remove_failed(e: &dyn std::fmt::Display) -> String { | ||
| 325 | format!("Nie udało się usunąć klienta głosowego: {e}") | ||
| 326 | } | ||
| 327 | pub fn fmt_voice_probe_failed(e: &dyn std::fmt::Display) -> String { | ||
| 328 | format!("Próbkowanie symphonia zawiodło: {e}") | ||
| 329 | } | ||
| 330 | pub fn fmt_voice_decoder_failed(e: &dyn std::fmt::Display) -> String { | ||
| 331 | format!("Nie udało się utworzyć dekodera audio: {e}") | ||
| 332 | } | ||
| 333 | pub fn fmt_voice_ytdlp_launch(e: &dyn std::fmt::Display) -> String { | ||
| 334 | format!("yt-dlp nie mogło się uruchomić: {e}") | ||
| 335 | } | ||
| 336 | pub fn fmt_voice_ffmpeg_launch(e: &dyn std::fmt::Display) -> String { | ||
| 337 | format!("ffmpeg nie mogło się uruchomić: {e}") | ||
| 338 | } | ||
| 339 | |||
| 340 | // ── Events ──────────────────────────────────────────────────────────── | ||
| 341 | |||
| 342 | pub const L_EV_READY: &str = "ŁUKASZ SPERMA AKTYWACJA"; | ||
| 343 | pub const L_EV_VOICE_STATE_ERROR: &str = "Błąd stanu głosu"; | ||
| 344 | pub const L_EV_MESSAGE_HANDLER_ERROR: &str = "Błąd ustanawiacza głosu"; | ||
| 345 | |||
| 346 | // ── Event TTS phrases ───────────────────────────────────────────────── | ||
| 347 | |||
| 348 | pub fn fmt_ev_joined(name: &str) -> String { | ||
| 349 | format!("{name} sie zespawnił") | ||
| 350 | } | ||
| 351 | pub fn fmt_ev_left(name: &str) -> String { | ||
| 352 | format!("{name} jest chujem") | ||
| 353 | } | ||
| 354 | pub fn fmt_ev_deafened(name: &str) -> String { | ||
| 355 | format!("{name} jest bity przez ojca") | ||
| 356 | } | ||
| 357 | pub fn fmt_ev_undeafened(name: &str) -> String { | ||
| 358 | format!("{name} uciekł od ojca") | ||
| 359 | } | ||
| 360 | pub fn fmt_ev_muted(name: &str) -> String { | ||
| 361 | format!("{name} idzie robić jajecznice") | ||
| 362 | } | ||
| 363 | pub fn fmt_ev_unmuted(name: &str) -> String { | ||
| 364 | format!("{name} zrobił jajecznice") | ||
| 365 | } | ||
| 366 | |||
| 367 | // ── AI assistant (events/message handler) ───────────────────────────── | ||
| 368 | |||
| 369 | pub const ASSISTANT_SYSTEM_PROMPT: &str = | ||
| 370 | "Jesteś asystentem. Odpowiadasz po polsku, jesteś pomocny, nie używasz emoji."; | ||
| 371 | |||
| 372 | // ── Convenience helpers for repeated Error::Custom patterns ──────────── | ||
| 373 | |||
| 374 | use crate::error::Error; | ||
| 375 | |||
| 376 | pub fn err_not_in_guild() -> Error { | ||
| 377 | Error::Custom(L_NOT_IN_GUILD.into()) | ||
| 378 | } | ||
| 379 | pub fn err_not_in_voice() -> Error { | ||
| 380 | Error::Custom(L_NOT_IN_VOICE.into()) | ||
| 381 | } | ||
| 382 | pub fn err_trigger_not_found() -> Error { | ||
| 383 | Error::Custom(L_TRIGGER_NOT_FOUND.into()) | ||
| 384 | } | ||
| 385 | pub fn err_bad_channel_type() -> Error { | ||
| 386 | Error::Custom(L_BAD_CHANNEL_TYPE.into()) | ||
| 387 | } | ||
