1#[cfg(feature = "chumsky")]
6use chumsky::{Parser, prelude::just};
7#[cfg(feature = "chumsky")]
8use std::ops::Deref as _;
9
10#[cfg(feature = "chumsky")]
11use crate::utils::url_text_component_parser;
12
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, strum::FromRepr, strum::EnumIs)]
16pub enum ScriptTriggerMode {
17 FirstPerson = 0,
19 ThirdPerson = 1,
21 EditAvatar = 2,
23 Sitting = 3,
25}
26
27#[cfg(feature = "chumsky")]
33#[must_use]
34pub fn script_trigger_mode_parser<'src>()
35-> impl Parser<'src, &'src str, ScriptTriggerMode, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
36{
37 just("first_person")
38 .to(ScriptTriggerMode::FirstPerson)
39 .or(just("third_person").to(ScriptTriggerMode::ThirdPerson))
40 .or(just("edit_avatar").to(ScriptTriggerMode::EditAvatar))
41 .or(just("sitting").to(ScriptTriggerMode::Sitting))
42 .labelled("script trigger mode")
43}
44
45impl std::fmt::Display for ScriptTriggerMode {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::FirstPerson => write!(f, "first_person"),
49 Self::ThirdPerson => write!(f, "third_person"),
50 Self::EditAvatar => write!(f, "edit_avatar"),
51 Self::Sitting => write!(f, "sitting"),
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct ScriptTriggerModeParseError {
59 value: String,
61}
62
63impl std::fmt::Display for ScriptTriggerModeParseError {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "Could not parse as ScriptTriggerMode: {}", self.value)
66 }
67}
68
69impl std::error::Error for ScriptTriggerModeParseError {}
70
71impl std::str::FromStr for ScriptTriggerMode {
72 type Err = ScriptTriggerModeParseError;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
75 match s {
76 "first_person" | "0" => Ok(Self::FirstPerson),
77 "third_person" | "1" => Ok(Self::ThirdPerson),
78 "edit_avatar" | "2" => Ok(Self::EditAvatar),
79 "sitting" | "3" => Ok(Self::Sitting),
80 _ => Err(ScriptTriggerModeParseError {
81 value: s.to_owned(),
82 }),
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, strum::EnumIs)]
89pub enum ViewerUri {
90 Location(crate::map::Location),
92 AgentAbout(crate::key::AgentKey),
94 AgentInspect(crate::key::AgentKey),
96 AgentInstantMessage(crate::key::AgentKey),
98 AgentOfferTeleport(crate::key::AgentKey),
100 AgentPay(crate::key::AgentKey),
102 AgentRequestFriend(crate::key::AgentKey),
104 AgentMute(crate::key::AgentKey),
106 AgentUnmute(crate::key::AgentKey),
108 AgentCompleteName(crate::key::AgentKey),
110 AgentDisplayName(crate::key::AgentKey),
112 AgentUsername(crate::key::AgentKey),
114 AppearanceShow,
116 BalanceRequest,
118 Chat {
120 channel: crate::chat::ChatChannel,
122 text: String,
124 },
125 ClassifiedAbout(crate::key::ClassifiedKey),
127 EventAbout(crate::key::EventKey),
129 ExperienceProfile(crate::key::ExperienceKey),
131 GroupAbout(crate::key::GroupKey),
133 GroupInspect(crate::key::GroupKey),
135 GroupCreate,
137 GroupListShow,
139 Help {
141 help_query: Option<String>,
143 },
144 InventorySelect(crate::key::InventoryKey),
146 InventoryShow,
148 KeyBindingMovementWalkTo,
150 KeyBindingMovementTeleportTo,
152 KeyBindingMovementPushForward,
154 KeyBindingMovementPushBackward,
156 KeyBindingMovementTurnLeft,
158 KeyBindingMovementTurnRight,
160 KeyBindingMovementSlideLeft,
162 KeyBindingMovementSlideRight,
164 KeyBindingMovementJump,
166 KeyBindingMovementPushDown,
168 KeyBindingMovementRunForward,
170 KeyBindingMovementRunBackward,
172 KeyBindingMovementRunLeft,
174 KeyBindingMovementRunRight,
176 KeyBindingMovementToggleRun,
178 KeyBindingMovementToggleFly,
180 KeyBindingMovementToggleSit,
182 KeyBindingMovementStopMoving,
184 KeyBindingCameraLookUp,
186 KeyBindingCameraLookDown,
188 KeyBindingCameraMoveForward,
190 KeyBindingCameraMoveBackward,
192 KeyBindingCameraMoveForwardFast,
194 KeyBindingCameraMoveBackwardFast,
196 KeyBindingCameraSpinOver,
198 KeyBindingCameraSpinUnder,
200 KeyBindingCameraPanUp,
202 KeyBindingCameraPanDown,
204 KeyBindingCameraPanLeft,
206 KeyBindingCameraPanRight,
208 KeyBindingCameraPanIn,
210 KeyBindingCameraPanOut,
212 KeyBindingCameraSpinAroundCounterClockwise,
214 KeyBindingCameraSpinAroundClockwise,
216 KeyBindingCameraMoveForwardSitting,
218 KeyBindingCameraMoveBackwardSitting,
220 KeyBindingCameraSpinOverSitting,
222 KeyBindingCameraSpinUnderSitting,
224 KeyBindingCameraSpinAroundCounterClockwiseSitting,
226 KeyBindingCameraSpinAroundClockwiseSitting,
228 KeyBindingEditingAvatarSpinCounterClockwise,
230 KeyBindingEditingAvatarSpinClockwise,
232 KeyBindingEditingAvatarSpinOver,
234 KeyBindingEditingAvatarSpinUnder,
236 KeyBindingEditingAvatarMoveForward,
238 KeyBindingEditingAvatarMoveBackward,
240 KeyBindingSoundAndMediaTogglePauseMedia,
242 KeyBindingSoundAndMediaToggleEnableMedia,
244 KeyBindingSoundAndMediaVoiceFollowKey,
246 KeyBindingSoundAndMediaToggleVoice,
248 KeyBindingStartChat,
250 KeyBindingStartGesture,
252 KeyBindingScriptTriggerLButton(ScriptTriggerMode),
254 Login {
256 first_name: String,
258 last_name: String,
260 session: String,
262 login_location: Option<String>,
264 },
265 MapTrackAvatar(crate::key::FriendKey),
267 ObjectInstantMessage {
269 object_key: crate::key::ObjectKey,
271 object_name: String,
273 owner: crate::key::OwnerKey,
275 location: crate::map::Location,
277 },
278 OpenFloater(String),
280 Parcel(crate::key::ParcelKey),
282 Search {
284 category: crate::search::SearchCategory,
286 search_term: String,
288 },
289 ShareWithAvatar(crate::key::AgentKey),
291 Teleport(crate::map::Location),
293 VoiceCallAvatar(crate::key::AgentKey),
295 WearFolderByInventoryFolderKey(crate::key::InventoryFolderKey),
297 WearFolderByLibraryFolderName(String),
299 WorldMap(crate::map::Location),
301}
302
303impl ViewerUri {
304 #[must_use]
307 pub const fn internal_only(&self) -> bool {
308 matches!(self, Self::Location(_) | Self::Login { .. })
309 }
310}
311
312impl std::fmt::Display for ViewerUri {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 match self {
315 Self::Location(location) => {
316 write!(
317 f,
318 "secondlife:///{}/{}/{}/{}",
319 percent_encoding::percent_encode(
320 location.region_name().as_ref().as_bytes(),
321 percent_encoding::NON_ALPHANUMERIC
322 ),
323 location.x(),
324 location.y(),
325 location.z()
326 )
327 }
328 Self::AgentAbout(agent_key) => {
329 write!(f, "secondlife:///app/agent/{agent_key}/about")
330 }
331 Self::AgentInspect(agent_key) => {
332 write!(f, "secondlife:///app/agent/{agent_key}/inspect")
333 }
334 Self::AgentInstantMessage(agent_key) => {
335 write!(f, "secondlife:///app/agent/{agent_key}/im")
336 }
337 Self::AgentOfferTeleport(agent_key) => {
338 write!(f, "secondlife:///app/agent/{agent_key}/offerteleport")
339 }
340 Self::AgentPay(agent_key) => {
341 write!(f, "secondlife:///app/agent/{agent_key}/pay")
342 }
343 Self::AgentRequestFriend(agent_key) => {
344 write!(f, "secondlife:///app/agent/{agent_key}/requestfriend")
345 }
346 Self::AgentMute(agent_key) => {
347 write!(f, "secondlife:///app/agent/{agent_key}/mute")
348 }
349 Self::AgentUnmute(agent_key) => {
350 write!(f, "secondlife:///app/agent/{agent_key}/unmute")
351 }
352 Self::AgentCompleteName(agent_key) => {
353 write!(f, "secondlife:///app/agent/{agent_key}/completename")
354 }
355 Self::AgentDisplayName(agent_key) => {
356 write!(f, "secondlife:///app/agent/{agent_key}/displayname")
357 }
358 Self::AgentUsername(agent_key) => {
359 write!(f, "secondlife:///app/agent/{agent_key}/username")
360 }
361 Self::AppearanceShow => {
362 write!(f, "secondlife:///app/appearance/show")
363 }
364 Self::BalanceRequest => {
365 write!(f, "secondlife:///app/balance/request")
366 }
367 Self::Chat { channel, text } => {
368 write!(
369 f,
370 "secondlife:///app/chat/{}/{}",
371 channel,
372 percent_encoding::percent_encode(
373 text.as_bytes(),
374 percent_encoding::NON_ALPHANUMERIC
375 )
376 )
377 }
378 Self::ClassifiedAbout(classified_key) => {
379 write!(f, "secondlife:///app/classified/{classified_key}/about")
380 }
381 Self::EventAbout(event_key) => {
382 write!(f, "secondlife:///app/event/{event_key}/about")
383 }
384 Self::ExperienceProfile(experience_key) => {
385 write!(f, "secondlife:///app/experience/{experience_key}/profile")
386 }
387 Self::GroupAbout(group_key) => {
388 write!(f, "secondlife:///app/group/{group_key}/about")
389 }
390 Self::GroupInspect(group_key) => {
391 write!(f, "secondlife:///app/group/{group_key}/inspect")
392 }
393 Self::GroupCreate => {
394 write!(f, "secondlife:///app/group/create")
395 }
396 Self::GroupListShow => {
397 write!(f, "secondlife:///app/group/list/show")
398 }
399 Self::Help { help_query } => {
400 if let Some(help_query) = help_query {
401 write!(
402 f,
403 "secondlife:///app/help/{}",
404 percent_encoding::percent_encode(
405 help_query.as_bytes(),
406 percent_encoding::NON_ALPHANUMERIC
407 )
408 )
409 } else {
410 write!(f, "secondlife:///app/help")
411 }
412 }
413 Self::InventorySelect(inventory_key) => {
414 write!(f, "secondlife:///app/inventory/{inventory_key}/select")
415 }
416 Self::InventoryShow => {
417 write!(f, "secondlife:///app/inventory/show")
418 }
419 Self::KeyBindingMovementWalkTo => {
420 write!(f, "secondlife:///app/keybinding/walk_to")
421 }
422 Self::KeyBindingMovementTeleportTo => {
423 write!(f, "secondlife:///app/keybinding/teleport_to")
424 }
425 Self::KeyBindingMovementPushForward => {
426 write!(f, "secondlife:///app/keybinding/push_forward")
427 }
428 Self::KeyBindingMovementPushBackward => {
429 write!(f, "secondlife:///app/keybinding/push_backward")
430 }
431 Self::KeyBindingMovementTurnLeft => {
432 write!(f, "secondlife:///app/keybinding/turn_left")
433 }
434 Self::KeyBindingMovementTurnRight => {
435 write!(f, "secondlife:///app/keybinding/turn_right")
436 }
437 Self::KeyBindingMovementSlideLeft => {
438 write!(f, "secondlife:///app/keybinding/slide_left")
439 }
440 Self::KeyBindingMovementSlideRight => {
441 write!(f, "secondlife:///app/keybinding/slide_right")
442 }
443 Self::KeyBindingMovementJump => {
444 write!(f, "secondlife:///app/keybinding/jump")
445 }
446 Self::KeyBindingMovementPushDown => {
447 write!(f, "secondlife:///app/keybinding/push_down")
448 }
449 Self::KeyBindingMovementRunForward => {
450 write!(f, "secondlife:///app/keybinding/run_forward")
451 }
452 Self::KeyBindingMovementRunBackward => {
453 write!(f, "secondlife:///app/keybinding/run_backward")
454 }
455 Self::KeyBindingMovementRunLeft => {
456 write!(f, "secondlife:///app/keybinding/run_left")
457 }
458 Self::KeyBindingMovementRunRight => {
459 write!(f, "secondlife:///app/keybinding/run_right")
460 }
461 Self::KeyBindingMovementToggleRun => {
462 write!(f, "secondlife:///app/keybinding/toggle_run")
463 }
464 Self::KeyBindingMovementToggleFly => {
465 write!(f, "secondlife:///app/keybinding/toggle_fly")
466 }
467 Self::KeyBindingMovementToggleSit => {
468 write!(f, "secondlife:///app/keybinding/toggle_sit")
469 }
470 Self::KeyBindingMovementStopMoving => {
471 write!(f, "secondlife:///app/keybinding/stop_moving")
472 }
473 Self::KeyBindingCameraLookUp => {
474 write!(f, "secondlife:///app/keybinding/look_up")
475 }
476 Self::KeyBindingCameraLookDown => {
477 write!(f, "secondlife:///app/keybinding/look_down")
478 }
479 Self::KeyBindingCameraMoveForward => {
480 write!(f, "secondlife:///app/keybinding/move_forward")
481 }
482 Self::KeyBindingCameraMoveBackward => {
483 write!(f, "secondlife:///app/keybinding/move_backward")
484 }
485 Self::KeyBindingCameraMoveForwardFast => {
486 write!(f, "secondlife:///app/keybinding/move_forward_fast")
487 }
488 Self::KeyBindingCameraMoveBackwardFast => {
489 write!(f, "secondlife:///app/keybinding/move_backward_fast")
490 }
491 Self::KeyBindingCameraSpinOver => {
492 write!(f, "secondlife:///app/keybinding/spin_over")
493 }
494 Self::KeyBindingCameraSpinUnder => {
495 write!(f, "secondlife:///app/keybinding/spin_under")
496 }
497 Self::KeyBindingCameraPanUp => {
498 write!(f, "secondlife:///app/keybinding/pan_up")
499 }
500 Self::KeyBindingCameraPanDown => {
501 write!(f, "secondlife:///app/keybinding/pan_down")
502 }
503 Self::KeyBindingCameraPanLeft => {
504 write!(f, "secondlife:///app/keybinding/pan_left")
505 }
506 Self::KeyBindingCameraPanRight => {
507 write!(f, "secondlife:///app/keybinding/pan_right")
508 }
509 Self::KeyBindingCameraPanIn => {
510 write!(f, "secondlife:///app/keybinding/pan_in")
511 }
512 Self::KeyBindingCameraPanOut => {
513 write!(f, "secondlife:///app/keybinding/pan_out")
514 }
515 Self::KeyBindingCameraSpinAroundCounterClockwise => {
516 write!(f, "secondlife:///app/keybinding/spin_around_ccw")
517 }
518 Self::KeyBindingCameraSpinAroundClockwise => {
519 write!(f, "secondlife:///app/keybinding/spin_around_cw")
520 }
521 Self::KeyBindingCameraMoveForwardSitting => {
522 write!(f, "secondlife:///app/keybinding/move_forward_sitting")
523 }
524 Self::KeyBindingCameraMoveBackwardSitting => {
525 write!(f, "secondlife:///app/keybinding/move_backward_sitting")
526 }
527 Self::KeyBindingCameraSpinOverSitting => {
528 write!(f, "secondlife:///app/keybinding/spin_over_sitting")
529 }
530 Self::KeyBindingCameraSpinUnderSitting => {
531 write!(f, "secondlife:///app/keybinding/spin_under_sitting")
532 }
533 Self::KeyBindingCameraSpinAroundCounterClockwiseSitting => {
534 write!(f, "secondlife:///app/keybinding/spin_around_ccw_sitting")
535 }
536 Self::KeyBindingCameraSpinAroundClockwiseSitting => {
537 write!(f, "secondlife:///app/keybinding/spin_around_cw_sitting")
538 }
539 Self::KeyBindingEditingAvatarSpinCounterClockwise => {
540 write!(f, "secondlife:///app/keybinding/avatar_spin_ccw")
541 }
542 Self::KeyBindingEditingAvatarSpinClockwise => {
543 write!(f, "secondlife:///app/keybinding/avatar_spin_cw")
544 }
545 Self::KeyBindingEditingAvatarSpinOver => {
546 write!(f, "secondlife:///app/keybinding/avatar_spin_over")
547 }
548 Self::KeyBindingEditingAvatarSpinUnder => {
549 write!(f, "secondlife:///app/keybinding/avatar_spin_under")
550 }
551 Self::KeyBindingEditingAvatarMoveForward => {
552 write!(f, "secondlife:///app/keybinding/avatar_move_forward")
553 }
554 Self::KeyBindingEditingAvatarMoveBackward => {
555 write!(f, "secondlife:///app/keybinding/avatar_move_backward")
556 }
557 Self::KeyBindingSoundAndMediaTogglePauseMedia => {
558 write!(f, "secondlife:///app/keybinding/toggle_pause_media")
559 }
560 Self::KeyBindingSoundAndMediaToggleEnableMedia => {
561 write!(f, "secondlife:///app/keybinding/toggle_enable_media")
562 }
563 Self::KeyBindingSoundAndMediaVoiceFollowKey => {
564 write!(f, "secondlife:///app/keybinding/voice_follow_key")
565 }
566 Self::KeyBindingSoundAndMediaToggleVoice => {
567 write!(f, "secondlife:///app/keybinding/toggle_voice")
568 }
569 Self::KeyBindingStartChat => {
570 write!(f, "secondlife:///app/keybinding/start_chat")
571 }
572 Self::KeyBindingStartGesture => {
573 write!(f, "secondlife:///app/keybinding/start_gesture")
574 }
575 Self::KeyBindingScriptTriggerLButton(script_trigger_mode) => {
576 write!(
577 f,
578 "secondlife:///app/keybinding/script_trigger_lbutton?mode={script_trigger_mode}"
579 )
580 }
581 Self::Login {
582 first_name,
583 last_name,
584 session,
585 login_location,
586 } => {
587 write!(
588 f,
589 "secondlife::///app/login?first={}&last={}&session={}{}",
590 percent_encoding::percent_encode(
591 first_name.as_bytes(),
592 percent_encoding::NON_ALPHANUMERIC
593 ),
594 percent_encoding::percent_encode(
595 last_name.as_bytes(),
596 percent_encoding::NON_ALPHANUMERIC
597 ),
598 session,
599 if let Some(login_location) = login_location {
600 format!(
601 "&location={}",
602 percent_encoding::percent_encode(
603 login_location.as_bytes(),
604 percent_encoding::NON_ALPHANUMERIC
605 ),
606 )
607 } else {
608 "".to_string()
609 },
610 )
611 }
612 Self::MapTrackAvatar(friend_key) => {
613 write!(f, "secondlife:///app/maptrackavatar/{friend_key}")
614 }
615 Self::ObjectInstantMessage {
616 object_key,
617 object_name,
618 owner,
619 location,
620 } => {
621 write!(
622 f,
623 "secondlife::///app/objectim/{}/?object_name={}&{}&slurl={}/{}/{}/{}",
624 object_key,
625 percent_encoding::percent_encode(
626 object_name.as_bytes(),
627 percent_encoding::NON_ALPHANUMERIC
628 ),
629 match owner {
630 crate::key::OwnerKey::Agent(agent_key) => {
631 format!("owner={agent_key}")
632 }
633 crate::key::OwnerKey::Group(group_key) => {
634 format!("owner={group_key}?groupowned=true")
635 }
636 },
637 percent_encoding::percent_encode(
638 location.region_name.as_ref().as_bytes(),
639 percent_encoding::NON_ALPHANUMERIC
640 ),
641 location.x,
642 location.y,
643 location.z,
644 )
645 }
646 Self::OpenFloater(floater_name) => {
647 write!(
648 f,
649 "secondlife:///app/openfloater/{}",
650 percent_encoding::percent_encode(
651 floater_name.as_bytes(),
652 percent_encoding::NON_ALPHANUMERIC
653 )
654 )
655 }
656 Self::Parcel(parcel_key) => {
657 write!(f, "secondlife:///app/parcel/{parcel_key}/about")
658 }
659 Self::Search {
660 category,
661 search_term,
662 } => {
663 write!(
664 f,
665 "secondlife:///app/search/{}/{}",
666 category,
667 percent_encoding::percent_encode(
668 search_term.as_bytes(),
669 percent_encoding::NON_ALPHANUMERIC
670 )
671 )
672 }
673 Self::ShareWithAvatar(agent_key) => {
674 write!(f, "secondlife::///app/sharewithavatar/{agent_key}")
675 }
676 Self::Teleport(location) => {
677 write!(
678 f,
679 "secondlife:///teleport/{}/{}/{}/{}",
680 percent_encoding::percent_encode(
681 location.region_name().as_ref().as_bytes(),
682 percent_encoding::NON_ALPHANUMERIC
683 ),
684 location.x(),
685 location.y(),
686 location.z()
687 )
688 }
689 Self::VoiceCallAvatar(agent_key) => {
690 write!(f, "secondlife:///app/voicecallavatar/{agent_key}")
691 }
692 Self::WearFolderByInventoryFolderKey(inventory_folder_key) => {
693 write!(
694 f,
695 "secondlife:///app/wear_folder?folder_id={inventory_folder_key}"
696 )
697 }
698 Self::WearFolderByLibraryFolderName(library_folder_name) => {
699 write!(
700 f,
701 "secondlife:///app/wear_folder?folder_name={}",
702 percent_encoding::percent_encode(
703 library_folder_name.as_bytes(),
704 percent_encoding::NON_ALPHANUMERIC
705 ),
706 )
707 }
708 Self::WorldMap(location) => {
709 write!(
710 f,
711 "secondlife:///app/worldmap/{}/{}/{}/{}",
712 percent_encoding::percent_encode(
713 location.region_name().as_ref().as_bytes(),
714 percent_encoding::NON_ALPHANUMERIC
715 ),
716 location.x(),
717 location.y(),
718 location.z()
719 )
720 }
721 }
722 }
723}
724
725#[cfg(feature = "chumsky")]
733#[must_use]
734pub fn viewer_app_agent_uri_parser<'src>()
735-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
736 just("secondlife:///app/agent/")
737 .ignore_then(
738 crate::key::agent_key_parser()
739 .then_ignore(just("/about"))
740 .map(ViewerUri::AgentAbout)
741 .or(crate::key::agent_key_parser()
742 .then_ignore(just("/inspect"))
743 .map(ViewerUri::AgentInspect))
744 .or(crate::key::agent_key_parser()
745 .then_ignore(just("/im"))
746 .map(ViewerUri::AgentInstantMessage))
747 .or(crate::key::agent_key_parser()
748 .then_ignore(just("/offerteleport"))
749 .map(ViewerUri::AgentOfferTeleport))
750 .or(crate::key::agent_key_parser()
751 .then_ignore(just("/pay"))
752 .map(ViewerUri::AgentPay))
753 .or(crate::key::agent_key_parser()
754 .then_ignore(just("/requestfriend"))
755 .map(ViewerUri::AgentRequestFriend))
756 .or(crate::key::agent_key_parser()
757 .then_ignore(just("/mute"))
758 .map(ViewerUri::AgentMute))
759 .or(crate::key::agent_key_parser()
760 .then_ignore(just("/unmute"))
761 .map(ViewerUri::AgentUnmute))
762 .or(crate::key::agent_key_parser()
763 .then_ignore(just("/completename"))
764 .map(ViewerUri::AgentCompleteName))
765 .or(crate::key::agent_key_parser()
766 .then_ignore(just("/displayname"))
767 .map(ViewerUri::AgentDisplayName))
768 .or(crate::key::agent_key_parser()
769 .then_ignore(just("/username"))
770 .map(ViewerUri::AgentUsername)),
771 )
772 .labelled("viewer app agent URI")
773}
774
775#[cfg(feature = "chumsky")]
781#[must_use]
782pub fn viewer_app_appearance_uri_parser<'src>()
783-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
784 just("secondlife:///app/appearance/show")
785 .to(ViewerUri::AppearanceShow)
786 .labelled("viewer app appearance URI")
787}
788
789#[cfg(feature = "chumsky")]
795#[must_use]
796pub fn viewer_app_balance_uri_parser<'src>()
797-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
798 just("secondlife:///app/balance/request")
799 .to(ViewerUri::BalanceRequest)
800 .labelled("viewer app balance URI")
801}
802
803#[cfg(feature = "chumsky")]
809#[must_use]
810pub fn viewer_app_chat_uri_parser<'src>()
811-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
812 just("secondlife:///app/chat/")
813 .ignore_then(
814 crate::chat::chat_channel_parser()
815 .then_ignore(just('/'))
816 .then(url_text_component_parser()),
817 )
818 .map(|(channel, text)| ViewerUri::Chat {
819 channel,
820 text: text.to_string(),
821 })
822 .labelled("viewer app chat URI")
823}
824
825#[cfg(feature = "chumsky")]
831#[must_use]
832pub fn viewer_app_classified_uri_parser<'src>()
833-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
834 just("secondlife:///app/classified/")
835 .ignore_then(
836 crate::key::classified_key_parser()
837 .then_ignore(just("/about"))
838 .map(ViewerUri::ClassifiedAbout),
839 )
840 .labelled("viewer app classified URI")
841}
842
843#[cfg(feature = "chumsky")]
849#[must_use]
850pub fn viewer_app_event_uri_parser<'src>()
851-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
852 just("secondlife:///app/event/")
853 .ignore_then(
854 crate::key::event_key_parser()
855 .then_ignore(just("/about"))
856 .map(ViewerUri::EventAbout),
857 )
858 .labelled("viewer app event URI")
859}
860
861#[cfg(feature = "chumsky")]
867#[must_use]
868pub fn viewer_app_experience_uri_parser<'src>()
869-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
870 just("secondlife:///app/experience/")
871 .ignore_then(
872 crate::key::experience_key_parser()
873 .then_ignore(just("/profile"))
874 .map(ViewerUri::ExperienceProfile),
875 )
876 .labelled("viewer app experience URI")
877}
878
879#[cfg(feature = "chumsky")]
885#[must_use]
886pub fn viewer_app_group_uri_parser<'src>()
887-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
888 just("secondlife:///app/group/")
889 .ignore_then(
890 crate::key::group_key_parser()
891 .then_ignore(just("/about"))
892 .map(ViewerUri::GroupAbout)
893 .or(crate::key::group_key_parser()
894 .then_ignore(just("/inspect"))
895 .map(ViewerUri::GroupInspect))
896 .or(just("create").to(ViewerUri::GroupCreate))
897 .or(just("list/show").to(ViewerUri::GroupListShow)),
898 )
899 .labelled("viewer app group URI")
900}
901
902#[cfg(feature = "chumsky")]
908#[must_use]
909pub fn viewer_app_help_uri_parser<'src>()
910-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
911 just("secondlife:///app/help/")
912 .ignore_then(just('/').ignore_then(url_text_component_parser()).or_not())
913 .map(|help_query| ViewerUri::Help { help_query })
914 .labelled("viewer app help URI")
915}
916
917#[cfg(feature = "chumsky")]
923#[must_use]
924pub fn viewer_app_inventory_uri_parser<'src>()
925-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
926 just("secondlife:///app/inventory/")
927 .ignore_then(
928 crate::key::inventory_key_parser()
929 .then_ignore(just("/select"))
930 .map(ViewerUri::InventorySelect)
931 .or(just("/show").to(ViewerUri::InventoryShow)),
932 )
933 .labelled("viewer app inventory URI")
934}
935
936#[cfg(feature = "chumsky")]
942#[must_use]
943pub fn viewer_app_keybinding_uri_parser<'src>()
944-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
945 just("secondlife:///app/keybinding/")
946 .ignore_then(
947 url_text_component_parser()
948 .try_map(|s, span| match s.deref() {
949 "walk_to" => Ok(ViewerUri::KeyBindingMovementWalkTo),
950 "teleport_to" => Ok(ViewerUri::KeyBindingMovementTeleportTo),
951 "push_forward" => Ok(ViewerUri::KeyBindingMovementPushForward),
952 "push_backward" => Ok(ViewerUri::KeyBindingMovementPushBackward),
953 "turn_left" => Ok(ViewerUri::KeyBindingMovementTurnLeft),
954 "turn_right" => Ok(ViewerUri::KeyBindingMovementTurnRight),
955 "slide_left" => Ok(ViewerUri::KeyBindingMovementSlideLeft),
956 "slide_right" => Ok(ViewerUri::KeyBindingMovementSlideRight),
957 "jump" => Ok(ViewerUri::KeyBindingMovementJump),
958 "push_down" => Ok(ViewerUri::KeyBindingMovementPushDown),
959 "run_forward" => Ok(ViewerUri::KeyBindingMovementRunForward),
960 "run_backward" => Ok(ViewerUri::KeyBindingMovementRunBackward),
961 "run_left" => Ok(ViewerUri::KeyBindingMovementRunLeft),
962 "run_right" => Ok(ViewerUri::KeyBindingMovementRunRight),
963 "toggle_run" => Ok(ViewerUri::KeyBindingMovementToggleRun),
964 "toggle_fly" => Ok(ViewerUri::KeyBindingMovementToggleFly),
965 "toggle_sit" => Ok(ViewerUri::KeyBindingMovementToggleSit),
966 "stop_moving" => Ok(ViewerUri::KeyBindingMovementStopMoving),
967 "look_up" => Ok(ViewerUri::KeyBindingCameraLookUp),
968 "look_down" => Ok(ViewerUri::KeyBindingCameraLookDown),
969 "move_forward_fast" => Ok(ViewerUri::KeyBindingCameraMoveForwardFast),
970 "move_backward_fast" => Ok(ViewerUri::KeyBindingCameraMoveBackwardFast),
971 "move_forward_sitting" => Ok(ViewerUri::KeyBindingCameraMoveForwardSitting),
972 "move_backward_sittingk" => Ok(ViewerUri::KeyBindingCameraMoveBackwardSitting),
973 "move_forward" => Ok(ViewerUri::KeyBindingCameraMoveForward),
974 "move_backward" => Ok(ViewerUri::KeyBindingCameraMoveBackward),
975 "spin_over_sitting" => Ok(ViewerUri::KeyBindingCameraSpinOverSitting),
976 "spin_under_sitting" => Ok(ViewerUri::KeyBindingCameraSpinUnderSitting),
977 "spin_over" => Ok(ViewerUri::KeyBindingCameraSpinOver),
978 "spin_under" => Ok(ViewerUri::KeyBindingCameraSpinUnder),
979 "pan_up" => Ok(ViewerUri::KeyBindingCameraPanUp),
980 "pan_down" => Ok(ViewerUri::KeyBindingCameraPanDown),
981 "pan_left" => Ok(ViewerUri::KeyBindingCameraPanLeft),
982 "pan_right" => Ok(ViewerUri::KeyBindingCameraPanRight),
983 "pan_in" => Ok(ViewerUri::KeyBindingCameraPanIn),
984 "pan_out" => Ok(ViewerUri::KeyBindingCameraPanOut),
985 "spin_around_ccw_sitting" => {
986 Ok(ViewerUri::KeyBindingCameraSpinAroundCounterClockwiseSitting)
987 }
988 "spin_around_cw_sitting" => {
989 Ok(ViewerUri::KeyBindingCameraSpinAroundClockwiseSitting)
990 }
991 "spin_around_ccw" => Ok(ViewerUri::KeyBindingCameraSpinAroundCounterClockwise),
992 "spin_around_cw" => Ok(ViewerUri::KeyBindingCameraSpinAroundClockwise),
993 "edit_avatar_spin_ccw" => {
994 Ok(ViewerUri::KeyBindingEditingAvatarSpinCounterClockwise)
995 }
996 "edit_avatar_spin_cw" => Ok(ViewerUri::KeyBindingEditingAvatarSpinClockwise),
997 "edit_avatar_spin_over" => Ok(ViewerUri::KeyBindingEditingAvatarSpinOver),
998 "edit_avatar_spin_under" => Ok(ViewerUri::KeyBindingEditingAvatarSpinUnder),
999 "edit_avatar_move_forward" => Ok(ViewerUri::KeyBindingEditingAvatarMoveForward),
1000 "edit_avatar_move_backward" => {
1001 Ok(ViewerUri::KeyBindingEditingAvatarMoveBackward)
1002 }
1003 "toggle_pause_media" => Ok(ViewerUri::KeyBindingSoundAndMediaTogglePauseMedia),
1004 "toggle_enable_media" => {
1005 Ok(ViewerUri::KeyBindingSoundAndMediaToggleEnableMedia)
1006 }
1007 "voice_follow_key" => Ok(ViewerUri::KeyBindingSoundAndMediaVoiceFollowKey),
1008 "toggle_voice" => Ok(ViewerUri::KeyBindingSoundAndMediaToggleVoice),
1009 "start_chat" => Ok(ViewerUri::KeyBindingStartChat),
1010 "start_gesture" => Ok(ViewerUri::KeyBindingStartGesture),
1011 _ => Err(chumsky::error::Rich::custom(
1012 span,
1013 format!("Not a valid keybinding: {s}"),
1014 )),
1015 })
1016 .or(just("/script_trigger_lbutton")
1017 .ignore_then(script_trigger_mode_parser())
1018 .map(ViewerUri::KeyBindingScriptTriggerLButton)),
1019 )
1020 .labelled("viewer app keybinding URI")
1021}
1022
1023#[cfg(feature = "chumsky")]
1029#[must_use]
1030pub fn viewer_app_login_uri_parser<'src>()
1031-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1032 just("secondlife:///app/login?first=")
1033 .ignore_then(url_text_component_parser())
1034 .then(just("?last=").ignore_then(url_text_component_parser()))
1035 .then(just("?session=").ignore_then(url_text_component_parser()))
1036 .then(
1037 just("?location=")
1038 .ignore_then(url_text_component_parser())
1039 .or_not(),
1040 )
1041 .map(
1042 |(((first_name, last_name), session), login_location)| ViewerUri::Login {
1043 first_name,
1044 last_name,
1045 session,
1046 login_location,
1047 },
1048 )
1049 .labelled("viewer app login URI")
1050}
1051
1052#[cfg(feature = "chumsky")]
1058#[must_use]
1059pub fn viewer_app_maptrackavatar_uri_parser<'src>()
1060-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1061 just("secondlife:///app/maptrackavatar/")
1062 .ignore_then(crate::key::friend_key_parser().map(ViewerUri::MapTrackAvatar))
1063 .labelled("viewer app maptrackavatar URI")
1064}
1065
1066#[cfg(feature = "chumsky")]
1072#[must_use]
1073pub fn viewer_app_objectim_uri_parser<'src>()
1074-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1075 just("secondlife:///app/objectim/")
1076 .ignore_then(crate::key::object_key_parser())
1077 .then_ignore(just('/').or_not())
1078 .then(just("?name=").ignore_then(url_text_component_parser()))
1079 .then(
1080 just("&owner=")
1081 .ignore_then(crate::key::group_key_parser())
1082 .then_ignore(just("&groupowned=true"))
1083 .map(crate::key::OwnerKey::Group)
1084 .or(just("&owner=")
1085 .ignore_then(crate::key::agent_key_parser())
1086 .map(crate::key::OwnerKey::Agent)),
1087 )
1088 .then(just("&slurl=").ignore_then(crate::map::url_encoded_location_parser()))
1089 .map(
1090 |(((object_key, object_name), owner), location)| ViewerUri::ObjectInstantMessage {
1091 object_key,
1092 object_name,
1093 owner,
1094 location,
1095 },
1096 )
1097 .labelled("viewer app objectim URI")
1098}
1099
1100#[cfg(feature = "chumsky")]
1106#[must_use]
1107pub fn viewer_app_openfloater_uri_parser<'src>()
1108-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1109 just("secondlife:///app/openfloater/")
1110 .ignore_then(url_text_component_parser().map(ViewerUri::OpenFloater))
1111 .labelled("viewer app openfloater URI")
1112}
1113
1114#[cfg(feature = "chumsky")]
1120#[must_use]
1121pub fn viewer_app_parcel_uri_parser<'src>()
1122-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1123 just("secondlife:///app/parcel/")
1124 .ignore_then(crate::key::parcel_key_parser().map(ViewerUri::Parcel))
1125 .labelled("viewer app parcel URI")
1126}
1127
1128#[cfg(feature = "chumsky")]
1134#[must_use]
1135pub fn viewer_app_search_uri_parser<'src>()
1136-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1137 just("secondlife:///app/search/")
1138 .ignore_then(crate::search::search_category_parser())
1139 .then_ignore(just('/'))
1140 .then(url_text_component_parser())
1141 .map(|(category, search_term)| ViewerUri::Search {
1142 category,
1143 search_term,
1144 })
1145 .labelled("viewer app search URI")
1146}
1147
1148#[cfg(feature = "chumsky")]
1154#[must_use]
1155pub fn viewer_app_sharewithavatar_uri_parser<'src>()
1156-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1157 just("secondlife:///app/sharewithavatar/")
1158 .ignore_then(crate::key::agent_key_parser().map(ViewerUri::ShareWithAvatar))
1159 .labelled("viewer app sharewithavatar URI")
1160}
1161#[cfg(feature = "chumsky")]
1167#[must_use]
1168pub fn viewer_app_teleport_uri_parser<'src>()
1169-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1170 just("secondlife:///app/teleport/")
1171 .ignore_then(crate::map::url_location_parser().map(ViewerUri::Teleport))
1172 .labelled("viewer app teleport URI")
1173}
1174
1175#[cfg(feature = "chumsky")]
1181#[must_use]
1182pub fn viewer_app_voicecallavatar_uri_parser<'src>()
1183-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1184 just("secondlife:///app/voicecallavatar/")
1185 .ignore_then(crate::key::agent_key_parser().map(ViewerUri::VoiceCallAvatar))
1186 .labelled("viewer app voicecallavatar URI")
1187}
1188
1189#[cfg(feature = "chumsky")]
1195#[must_use]
1196pub fn viewer_app_wear_folder_uri_parser<'src>()
1197-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1198 just("secondlife:///app/wear_folder")
1199 .ignore_then(
1200 just("?folder_id=")
1201 .ignore_then(crate::key::inventory_folder_key_parser())
1202 .map(ViewerUri::WearFolderByInventoryFolderKey)
1203 .or(just("?folder_name=")
1204 .ignore_then(url_text_component_parser())
1205 .map(ViewerUri::WearFolderByLibraryFolderName)),
1206 )
1207 .labelled("viewer app wear_folder URI")
1208}
1209
1210#[cfg(feature = "chumsky")]
1216#[must_use]
1217pub fn viewer_app_worldmap_uri_parser<'src>()
1218-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1219 just("secondlife:///app/worldmap/")
1220 .ignore_then(crate::map::url_location_parser().map(ViewerUri::WorldMap))
1221 .labelled("viewer app worldmap URI")
1222}
1223
1224#[cfg(feature = "chumsky")]
1230#[must_use]
1231pub fn viewer_app_uri_parser<'src>()
1232-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1233 viewer_app_agent_uri_parser()
1234 .or(viewer_app_appearance_uri_parser())
1235 .or(viewer_app_balance_uri_parser())
1236 .or(viewer_app_chat_uri_parser())
1237 .or(viewer_app_classified_uri_parser())
1238 .or(viewer_app_event_uri_parser())
1239 .or(viewer_app_experience_uri_parser())
1240 .or(viewer_app_group_uri_parser())
1241 .or(viewer_app_help_uri_parser())
1242 .or(viewer_app_inventory_uri_parser())
1243 .or(viewer_app_keybinding_uri_parser())
1244 .or(viewer_app_login_uri_parser())
1245 .or(viewer_app_maptrackavatar_uri_parser())
1246 .or(viewer_app_objectim_uri_parser())
1247 .or(viewer_app_openfloater_uri_parser())
1248 .or(viewer_app_parcel_uri_parser())
1249 .or(viewer_app_search_uri_parser())
1250 .or(viewer_app_sharewithavatar_uri_parser())
1251 .or(viewer_app_teleport_uri_parser())
1252 .or(viewer_app_voicecallavatar_uri_parser())
1253 .or(viewer_app_wear_folder_uri_parser())
1254 .or(viewer_app_worldmap_uri_parser())
1255 .labelled("viewer app URI")
1256}
1257
1258#[cfg(feature = "chumsky")]
1264#[must_use]
1265pub fn viewer_location_uri_parser<'src>()
1266-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1267 just("secondlife:///")
1268 .ignore_then(crate::map::url_location_parser())
1269 .map(ViewerUri::Location)
1270 .labelled("viewer location URI")
1271}
1272
1273#[cfg(feature = "chumsky")]
1279#[must_use]
1280#[expect(
1281 clippy::module_name_repetitions,
1282 reason = "the parse is used outside this module"
1283)]
1284pub fn viewer_uri_parser<'src>()
1285-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1286 viewer_app_uri_parser()
1287 .or(viewer_location_uri_parser())
1288 .labelled("viewer URI")
1289}