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(
16 Debug,
17 Clone,
18 PartialEq,
19 Eq,
20 PartialOrd,
21 Ord,
22 strum::FromRepr,
23 strum::EnumIs,
24 serde::Serialize,
25 serde::Deserialize,
26)]
27pub enum ScriptTriggerMode {
28 FirstPerson = 0,
30 ThirdPerson = 1,
32 EditAvatar = 2,
34 Sitting = 3,
36}
37
38#[cfg(feature = "chumsky")]
44#[must_use]
45pub fn script_trigger_mode_parser<'src>()
46-> impl Parser<'src, &'src str, ScriptTriggerMode, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
47{
48 just("first_person")
49 .to(ScriptTriggerMode::FirstPerson)
50 .or(just("third_person").to(ScriptTriggerMode::ThirdPerson))
51 .or(just("edit_avatar").to(ScriptTriggerMode::EditAvatar))
52 .or(just("sitting").to(ScriptTriggerMode::Sitting))
53 .labelled("script trigger mode")
54}
55
56impl std::fmt::Display for ScriptTriggerMode {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 Self::FirstPerson => write!(f, "first_person"),
60 Self::ThirdPerson => write!(f, "third_person"),
61 Self::EditAvatar => write!(f, "edit_avatar"),
62 Self::Sitting => write!(f, "sitting"),
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct ScriptTriggerModeParseError {
70 value: String,
72}
73
74impl std::fmt::Display for ScriptTriggerModeParseError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(f, "Could not parse as ScriptTriggerMode: {}", self.value)
77 }
78}
79
80impl std::error::Error for ScriptTriggerModeParseError {}
81
82impl std::str::FromStr for ScriptTriggerMode {
83 type Err = ScriptTriggerModeParseError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 match s {
87 "first_person" | "0" => Ok(Self::FirstPerson),
88 "third_person" | "1" => Ok(Self::ThirdPerson),
89 "edit_avatar" | "2" => Ok(Self::EditAvatar),
90 "sitting" | "3" => Ok(Self::Sitting),
91 _ => Err(ScriptTriggerModeParseError {
92 value: s.to_owned(),
93 }),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, strum::EnumIs, serde::Serialize, serde::Deserialize)]
100pub enum ViewerUri {
101 Location(crate::map::Location),
103 AgentAbout(crate::key::AgentKey),
105 AgentInspect(crate::key::AgentKey),
107 AgentInstantMessage(crate::key::AgentKey),
109 AgentOfferTeleport(crate::key::AgentKey),
111 AgentPay(crate::key::AgentKey),
113 AgentRequestFriend(crate::key::AgentKey),
115 AgentMute(crate::key::AgentKey),
117 AgentUnmute(crate::key::AgentKey),
119 AgentCompleteName(crate::key::AgentKey),
121 AgentDisplayName(crate::key::AgentKey),
123 AgentUsername(crate::key::AgentKey),
125 AppearanceShow,
127 BalanceRequest,
129 Chat {
131 channel: crate::chat::ChatChannel,
133 text: String,
135 },
136 ClassifiedAbout(crate::key::ClassifiedKey),
138 EventAbout(crate::search::EventId),
140 ExperienceProfile(crate::key::ExperienceKey),
142 GroupAbout(crate::key::GroupKey),
144 GroupInspect(crate::key::GroupKey),
146 GroupCreate,
148 GroupListShow,
150 Help {
152 help_query: Option<String>,
154 },
155 InventorySelect(crate::key::InventoryKey),
157 InventoryShow,
159 KeyBindingMovementWalkTo,
161 KeyBindingMovementTeleportTo,
163 KeyBindingMovementPushForward,
165 KeyBindingMovementPushBackward,
167 KeyBindingMovementTurnLeft,
169 KeyBindingMovementTurnRight,
171 KeyBindingMovementSlideLeft,
173 KeyBindingMovementSlideRight,
175 KeyBindingMovementJump,
177 KeyBindingMovementPushDown,
179 KeyBindingMovementRunForward,
181 KeyBindingMovementRunBackward,
183 KeyBindingMovementRunLeft,
185 KeyBindingMovementRunRight,
187 KeyBindingMovementToggleRun,
189 KeyBindingMovementToggleFly,
191 KeyBindingMovementToggleSit,
193 KeyBindingMovementStopMoving,
195 KeyBindingCameraLookUp,
197 KeyBindingCameraLookDown,
199 KeyBindingCameraMoveForward,
201 KeyBindingCameraMoveBackward,
203 KeyBindingCameraMoveForwardFast,
205 KeyBindingCameraMoveBackwardFast,
207 KeyBindingCameraSpinOver,
209 KeyBindingCameraSpinUnder,
211 KeyBindingCameraPanUp,
213 KeyBindingCameraPanDown,
215 KeyBindingCameraPanLeft,
217 KeyBindingCameraPanRight,
219 KeyBindingCameraPanIn,
221 KeyBindingCameraPanOut,
223 KeyBindingCameraSpinAroundCounterClockwise,
225 KeyBindingCameraSpinAroundClockwise,
227 KeyBindingCameraMoveForwardSitting,
229 KeyBindingCameraMoveBackwardSitting,
231 KeyBindingCameraSpinOverSitting,
233 KeyBindingCameraSpinUnderSitting,
235 KeyBindingCameraSpinAroundCounterClockwiseSitting,
237 KeyBindingCameraSpinAroundClockwiseSitting,
239 KeyBindingEditingAvatarSpinCounterClockwise,
241 KeyBindingEditingAvatarSpinClockwise,
243 KeyBindingEditingAvatarSpinOver,
245 KeyBindingEditingAvatarSpinUnder,
247 KeyBindingEditingAvatarMoveForward,
249 KeyBindingEditingAvatarMoveBackward,
251 KeyBindingSoundAndMediaTogglePauseMedia,
253 KeyBindingSoundAndMediaToggleEnableMedia,
255 KeyBindingSoundAndMediaVoiceFollowKey,
257 KeyBindingSoundAndMediaToggleVoice,
259 KeyBindingStartChat,
261 KeyBindingStartGesture,
263 KeyBindingScriptTriggerLButton(ScriptTriggerMode),
265 Login {
267 first_name: String,
269 last_name: String,
271 session: String,
273 login_location: Option<String>,
275 },
276 MapTrackAvatar(crate::key::FriendKey),
278 ObjectInstantMessage {
280 object_key: crate::key::ObjectKey,
282 object_name: String,
284 owner: crate::key::OwnerKey,
286 location: crate::map::Location,
288 },
289 OpenFloater(String),
291 Parcel(crate::key::ParcelKey),
293 Search {
295 category: crate::search::SearchCategory,
297 search_term: String,
299 },
300 ShareWithAvatar(crate::key::AgentKey),
302 Teleport(crate::map::Location),
304 VoiceCallAvatar(crate::key::AgentKey),
306 WearFolderByInventoryFolderKey(crate::key::InventoryFolderKey),
308 WearFolderByLibraryFolderName(String),
310 WorldMap(crate::map::Location),
312}
313
314impl ViewerUri {
315 #[must_use]
318 pub const fn internal_only(&self) -> bool {
319 matches!(self, Self::Location(_) | Self::Login { .. })
320 }
321}
322
323impl std::fmt::Display for ViewerUri {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 match self {
326 Self::Location(location) => {
327 write!(
328 f,
329 "secondlife:///{}/{}/{}/{}",
330 percent_encoding::percent_encode(
331 location.region_name().as_ref().as_bytes(),
332 percent_encoding::NON_ALPHANUMERIC
333 ),
334 location.x(),
335 location.y(),
336 location.z()
337 )
338 }
339 Self::AgentAbout(agent_key) => {
340 write!(f, "secondlife:///app/agent/{agent_key}/about")
341 }
342 Self::AgentInspect(agent_key) => {
343 write!(f, "secondlife:///app/agent/{agent_key}/inspect")
344 }
345 Self::AgentInstantMessage(agent_key) => {
346 write!(f, "secondlife:///app/agent/{agent_key}/im")
347 }
348 Self::AgentOfferTeleport(agent_key) => {
349 write!(f, "secondlife:///app/agent/{agent_key}/offerteleport")
350 }
351 Self::AgentPay(agent_key) => {
352 write!(f, "secondlife:///app/agent/{agent_key}/pay")
353 }
354 Self::AgentRequestFriend(agent_key) => {
355 write!(f, "secondlife:///app/agent/{agent_key}/requestfriend")
356 }
357 Self::AgentMute(agent_key) => {
358 write!(f, "secondlife:///app/agent/{agent_key}/mute")
359 }
360 Self::AgentUnmute(agent_key) => {
361 write!(f, "secondlife:///app/agent/{agent_key}/unmute")
362 }
363 Self::AgentCompleteName(agent_key) => {
364 write!(f, "secondlife:///app/agent/{agent_key}/completename")
365 }
366 Self::AgentDisplayName(agent_key) => {
367 write!(f, "secondlife:///app/agent/{agent_key}/displayname")
368 }
369 Self::AgentUsername(agent_key) => {
370 write!(f, "secondlife:///app/agent/{agent_key}/username")
371 }
372 Self::AppearanceShow => {
373 write!(f, "secondlife:///app/appearance/show")
374 }
375 Self::BalanceRequest => {
376 write!(f, "secondlife:///app/balance/request")
377 }
378 Self::Chat { channel, text } => {
379 write!(
380 f,
381 "secondlife:///app/chat/{}/{}",
382 channel,
383 percent_encoding::percent_encode(
384 text.as_bytes(),
385 percent_encoding::NON_ALPHANUMERIC
386 )
387 )
388 }
389 Self::ClassifiedAbout(classified_key) => {
390 write!(f, "secondlife:///app/classified/{classified_key}/about")
391 }
392 Self::EventAbout(event_id) => {
393 write!(f, "secondlife:///app/event/{event_id}/about")
394 }
395 Self::ExperienceProfile(experience_key) => {
396 write!(f, "secondlife:///app/experience/{experience_key}/profile")
397 }
398 Self::GroupAbout(group_key) => {
399 write!(f, "secondlife:///app/group/{group_key}/about")
400 }
401 Self::GroupInspect(group_key) => {
402 write!(f, "secondlife:///app/group/{group_key}/inspect")
403 }
404 Self::GroupCreate => {
405 write!(f, "secondlife:///app/group/create")
406 }
407 Self::GroupListShow => {
408 write!(f, "secondlife:///app/group/list/show")
409 }
410 Self::Help { help_query } => {
411 if let Some(help_query) = help_query {
412 write!(
413 f,
414 "secondlife:///app/help/{}",
415 percent_encoding::percent_encode(
416 help_query.as_bytes(),
417 percent_encoding::NON_ALPHANUMERIC
418 )
419 )
420 } else {
421 write!(f, "secondlife:///app/help")
422 }
423 }
424 Self::InventorySelect(inventory_key) => {
425 write!(f, "secondlife:///app/inventory/{inventory_key}/select")
426 }
427 Self::InventoryShow => {
428 write!(f, "secondlife:///app/inventory/show")
429 }
430 Self::KeyBindingMovementWalkTo => {
431 write!(f, "secondlife:///app/keybinding/walk_to")
432 }
433 Self::KeyBindingMovementTeleportTo => {
434 write!(f, "secondlife:///app/keybinding/teleport_to")
435 }
436 Self::KeyBindingMovementPushForward => {
437 write!(f, "secondlife:///app/keybinding/push_forward")
438 }
439 Self::KeyBindingMovementPushBackward => {
440 write!(f, "secondlife:///app/keybinding/push_backward")
441 }
442 Self::KeyBindingMovementTurnLeft => {
443 write!(f, "secondlife:///app/keybinding/turn_left")
444 }
445 Self::KeyBindingMovementTurnRight => {
446 write!(f, "secondlife:///app/keybinding/turn_right")
447 }
448 Self::KeyBindingMovementSlideLeft => {
449 write!(f, "secondlife:///app/keybinding/slide_left")
450 }
451 Self::KeyBindingMovementSlideRight => {
452 write!(f, "secondlife:///app/keybinding/slide_right")
453 }
454 Self::KeyBindingMovementJump => {
455 write!(f, "secondlife:///app/keybinding/jump")
456 }
457 Self::KeyBindingMovementPushDown => {
458 write!(f, "secondlife:///app/keybinding/push_down")
459 }
460 Self::KeyBindingMovementRunForward => {
461 write!(f, "secondlife:///app/keybinding/run_forward")
462 }
463 Self::KeyBindingMovementRunBackward => {
464 write!(f, "secondlife:///app/keybinding/run_backward")
465 }
466 Self::KeyBindingMovementRunLeft => {
467 write!(f, "secondlife:///app/keybinding/run_left")
468 }
469 Self::KeyBindingMovementRunRight => {
470 write!(f, "secondlife:///app/keybinding/run_right")
471 }
472 Self::KeyBindingMovementToggleRun => {
473 write!(f, "secondlife:///app/keybinding/toggle_run")
474 }
475 Self::KeyBindingMovementToggleFly => {
476 write!(f, "secondlife:///app/keybinding/toggle_fly")
477 }
478 Self::KeyBindingMovementToggleSit => {
479 write!(f, "secondlife:///app/keybinding/toggle_sit")
480 }
481 Self::KeyBindingMovementStopMoving => {
482 write!(f, "secondlife:///app/keybinding/stop_moving")
483 }
484 Self::KeyBindingCameraLookUp => {
485 write!(f, "secondlife:///app/keybinding/look_up")
486 }
487 Self::KeyBindingCameraLookDown => {
488 write!(f, "secondlife:///app/keybinding/look_down")
489 }
490 Self::KeyBindingCameraMoveForward => {
491 write!(f, "secondlife:///app/keybinding/move_forward")
492 }
493 Self::KeyBindingCameraMoveBackward => {
494 write!(f, "secondlife:///app/keybinding/move_backward")
495 }
496 Self::KeyBindingCameraMoveForwardFast => {
497 write!(f, "secondlife:///app/keybinding/move_forward_fast")
498 }
499 Self::KeyBindingCameraMoveBackwardFast => {
500 write!(f, "secondlife:///app/keybinding/move_backward_fast")
501 }
502 Self::KeyBindingCameraSpinOver => {
503 write!(f, "secondlife:///app/keybinding/spin_over")
504 }
505 Self::KeyBindingCameraSpinUnder => {
506 write!(f, "secondlife:///app/keybinding/spin_under")
507 }
508 Self::KeyBindingCameraPanUp => {
509 write!(f, "secondlife:///app/keybinding/pan_up")
510 }
511 Self::KeyBindingCameraPanDown => {
512 write!(f, "secondlife:///app/keybinding/pan_down")
513 }
514 Self::KeyBindingCameraPanLeft => {
515 write!(f, "secondlife:///app/keybinding/pan_left")
516 }
517 Self::KeyBindingCameraPanRight => {
518 write!(f, "secondlife:///app/keybinding/pan_right")
519 }
520 Self::KeyBindingCameraPanIn => {
521 write!(f, "secondlife:///app/keybinding/pan_in")
522 }
523 Self::KeyBindingCameraPanOut => {
524 write!(f, "secondlife:///app/keybinding/pan_out")
525 }
526 Self::KeyBindingCameraSpinAroundCounterClockwise => {
527 write!(f, "secondlife:///app/keybinding/spin_around_ccw")
528 }
529 Self::KeyBindingCameraSpinAroundClockwise => {
530 write!(f, "secondlife:///app/keybinding/spin_around_cw")
531 }
532 Self::KeyBindingCameraMoveForwardSitting => {
533 write!(f, "secondlife:///app/keybinding/move_forward_sitting")
534 }
535 Self::KeyBindingCameraMoveBackwardSitting => {
536 write!(f, "secondlife:///app/keybinding/move_backward_sitting")
537 }
538 Self::KeyBindingCameraSpinOverSitting => {
539 write!(f, "secondlife:///app/keybinding/spin_over_sitting")
540 }
541 Self::KeyBindingCameraSpinUnderSitting => {
542 write!(f, "secondlife:///app/keybinding/spin_under_sitting")
543 }
544 Self::KeyBindingCameraSpinAroundCounterClockwiseSitting => {
545 write!(f, "secondlife:///app/keybinding/spin_around_ccw_sitting")
546 }
547 Self::KeyBindingCameraSpinAroundClockwiseSitting => {
548 write!(f, "secondlife:///app/keybinding/spin_around_cw_sitting")
549 }
550 Self::KeyBindingEditingAvatarSpinCounterClockwise => {
551 write!(f, "secondlife:///app/keybinding/avatar_spin_ccw")
552 }
553 Self::KeyBindingEditingAvatarSpinClockwise => {
554 write!(f, "secondlife:///app/keybinding/avatar_spin_cw")
555 }
556 Self::KeyBindingEditingAvatarSpinOver => {
557 write!(f, "secondlife:///app/keybinding/avatar_spin_over")
558 }
559 Self::KeyBindingEditingAvatarSpinUnder => {
560 write!(f, "secondlife:///app/keybinding/avatar_spin_under")
561 }
562 Self::KeyBindingEditingAvatarMoveForward => {
563 write!(f, "secondlife:///app/keybinding/avatar_move_forward")
564 }
565 Self::KeyBindingEditingAvatarMoveBackward => {
566 write!(f, "secondlife:///app/keybinding/avatar_move_backward")
567 }
568 Self::KeyBindingSoundAndMediaTogglePauseMedia => {
569 write!(f, "secondlife:///app/keybinding/toggle_pause_media")
570 }
571 Self::KeyBindingSoundAndMediaToggleEnableMedia => {
572 write!(f, "secondlife:///app/keybinding/toggle_enable_media")
573 }
574 Self::KeyBindingSoundAndMediaVoiceFollowKey => {
575 write!(f, "secondlife:///app/keybinding/voice_follow_key")
576 }
577 Self::KeyBindingSoundAndMediaToggleVoice => {
578 write!(f, "secondlife:///app/keybinding/toggle_voice")
579 }
580 Self::KeyBindingStartChat => {
581 write!(f, "secondlife:///app/keybinding/start_chat")
582 }
583 Self::KeyBindingStartGesture => {
584 write!(f, "secondlife:///app/keybinding/start_gesture")
585 }
586 Self::KeyBindingScriptTriggerLButton(script_trigger_mode) => {
587 write!(
588 f,
589 "secondlife:///app/keybinding/script_trigger_lbutton?mode={script_trigger_mode}"
590 )
591 }
592 Self::Login {
593 first_name,
594 last_name,
595 session,
596 login_location,
597 } => {
598 write!(
599 f,
600 "secondlife::///app/login?first={}&last={}&session={}{}",
601 percent_encoding::percent_encode(
602 first_name.as_bytes(),
603 percent_encoding::NON_ALPHANUMERIC
604 ),
605 percent_encoding::percent_encode(
606 last_name.as_bytes(),
607 percent_encoding::NON_ALPHANUMERIC
608 ),
609 session,
610 if let Some(login_location) = login_location {
611 format!(
612 "&location={}",
613 percent_encoding::percent_encode(
614 login_location.as_bytes(),
615 percent_encoding::NON_ALPHANUMERIC
616 ),
617 )
618 } else {
619 "".to_string()
620 },
621 )
622 }
623 Self::MapTrackAvatar(friend_key) => {
624 write!(f, "secondlife:///app/maptrackavatar/{friend_key}")
625 }
626 Self::ObjectInstantMessage {
627 object_key,
628 object_name,
629 owner,
630 location,
631 } => {
632 write!(
633 f,
634 "secondlife::///app/objectim/{}/?object_name={}&{}&slurl={}/{}/{}/{}",
635 object_key,
636 percent_encoding::percent_encode(
637 object_name.as_bytes(),
638 percent_encoding::NON_ALPHANUMERIC
639 ),
640 match owner {
641 crate::key::OwnerKey::Agent(agent_key) => {
642 format!("owner={agent_key}")
643 }
644 crate::key::OwnerKey::Group(group_key) => {
645 format!("owner={group_key}?groupowned=true")
646 }
647 },
648 percent_encoding::percent_encode(
649 location.region_name.as_ref().as_bytes(),
650 percent_encoding::NON_ALPHANUMERIC
651 ),
652 location.x,
653 location.y,
654 location.z,
655 )
656 }
657 Self::OpenFloater(floater_name) => {
658 write!(
659 f,
660 "secondlife:///app/openfloater/{}",
661 percent_encoding::percent_encode(
662 floater_name.as_bytes(),
663 percent_encoding::NON_ALPHANUMERIC
664 )
665 )
666 }
667 Self::Parcel(parcel_key) => {
668 write!(f, "secondlife:///app/parcel/{parcel_key}/about")
669 }
670 Self::Search {
671 category,
672 search_term,
673 } => {
674 write!(
675 f,
676 "secondlife:///app/search/{}/{}",
677 category,
678 percent_encoding::percent_encode(
679 search_term.as_bytes(),
680 percent_encoding::NON_ALPHANUMERIC
681 )
682 )
683 }
684 Self::ShareWithAvatar(agent_key) => {
685 write!(f, "secondlife::///app/sharewithavatar/{agent_key}")
686 }
687 Self::Teleport(location) => {
688 write!(
689 f,
690 "secondlife:///teleport/{}/{}/{}/{}",
691 percent_encoding::percent_encode(
692 location.region_name().as_ref().as_bytes(),
693 percent_encoding::NON_ALPHANUMERIC
694 ),
695 location.x(),
696 location.y(),
697 location.z()
698 )
699 }
700 Self::VoiceCallAvatar(agent_key) => {
701 write!(f, "secondlife:///app/voicecallavatar/{agent_key}")
702 }
703 Self::WearFolderByInventoryFolderKey(inventory_folder_key) => {
704 write!(
705 f,
706 "secondlife:///app/wear_folder?folder_id={inventory_folder_key}"
707 )
708 }
709 Self::WearFolderByLibraryFolderName(library_folder_name) => {
710 write!(
711 f,
712 "secondlife:///app/wear_folder?folder_name={}",
713 percent_encoding::percent_encode(
714 library_folder_name.as_bytes(),
715 percent_encoding::NON_ALPHANUMERIC
716 ),
717 )
718 }
719 Self::WorldMap(location) => {
720 write!(
721 f,
722 "secondlife:///app/worldmap/{}/{}/{}/{}",
723 percent_encoding::percent_encode(
724 location.region_name().as_ref().as_bytes(),
725 percent_encoding::NON_ALPHANUMERIC
726 ),
727 location.x(),
728 location.y(),
729 location.z()
730 )
731 }
732 }
733 }
734}
735
736#[cfg(feature = "chumsky")]
744#[must_use]
745pub fn viewer_app_agent_uri_parser<'src>()
746-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
747 just("secondlife:///app/agent/")
748 .ignore_then(
749 crate::key::agent_key_parser()
750 .then_ignore(just("/about"))
751 .map(ViewerUri::AgentAbout)
752 .or(crate::key::agent_key_parser()
753 .then_ignore(just("/inspect"))
754 .map(ViewerUri::AgentInspect))
755 .or(crate::key::agent_key_parser()
756 .then_ignore(just("/im"))
757 .map(ViewerUri::AgentInstantMessage))
758 .or(crate::key::agent_key_parser()
759 .then_ignore(just("/offerteleport"))
760 .map(ViewerUri::AgentOfferTeleport))
761 .or(crate::key::agent_key_parser()
762 .then_ignore(just("/pay"))
763 .map(ViewerUri::AgentPay))
764 .or(crate::key::agent_key_parser()
765 .then_ignore(just("/requestfriend"))
766 .map(ViewerUri::AgentRequestFriend))
767 .or(crate::key::agent_key_parser()
768 .then_ignore(just("/mute"))
769 .map(ViewerUri::AgentMute))
770 .or(crate::key::agent_key_parser()
771 .then_ignore(just("/unmute"))
772 .map(ViewerUri::AgentUnmute))
773 .or(crate::key::agent_key_parser()
774 .then_ignore(just("/completename"))
775 .map(ViewerUri::AgentCompleteName))
776 .or(crate::key::agent_key_parser()
777 .then_ignore(just("/displayname"))
778 .map(ViewerUri::AgentDisplayName))
779 .or(crate::key::agent_key_parser()
780 .then_ignore(just("/username"))
781 .map(ViewerUri::AgentUsername)),
782 )
783 .labelled("viewer app agent URI")
784}
785
786#[cfg(feature = "chumsky")]
792#[must_use]
793pub fn viewer_app_appearance_uri_parser<'src>()
794-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
795 just("secondlife:///app/appearance/show")
796 .to(ViewerUri::AppearanceShow)
797 .labelled("viewer app appearance URI")
798}
799
800#[cfg(feature = "chumsky")]
806#[must_use]
807pub fn viewer_app_balance_uri_parser<'src>()
808-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
809 just("secondlife:///app/balance/request")
810 .to(ViewerUri::BalanceRequest)
811 .labelled("viewer app balance URI")
812}
813
814#[cfg(feature = "chumsky")]
820#[must_use]
821pub fn viewer_app_chat_uri_parser<'src>()
822-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
823 just("secondlife:///app/chat/")
824 .ignore_then(
825 crate::chat::chat_channel_parser()
826 .then_ignore(just('/'))
827 .then(url_text_component_parser()),
828 )
829 .map(|(channel, text)| ViewerUri::Chat {
830 channel,
831 text: text.to_string(),
832 })
833 .labelled("viewer app chat URI")
834}
835
836#[cfg(feature = "chumsky")]
842#[must_use]
843pub fn viewer_app_classified_uri_parser<'src>()
844-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
845 just("secondlife:///app/classified/")
846 .ignore_then(
847 crate::key::classified_key_parser()
848 .then_ignore(just("/about"))
849 .map(ViewerUri::ClassifiedAbout),
850 )
851 .labelled("viewer app classified URI")
852}
853
854#[cfg(feature = "chumsky")]
860#[must_use]
861pub fn viewer_app_event_uri_parser<'src>()
862-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
863 just("secondlife:///app/event/")
864 .ignore_then(
865 crate::search::event_id_parser()
866 .then_ignore(just("/about"))
867 .map(ViewerUri::EventAbout),
868 )
869 .labelled("viewer app event URI")
870}
871
872#[cfg(feature = "chumsky")]
878#[must_use]
879pub fn viewer_app_experience_uri_parser<'src>()
880-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
881 just("secondlife:///app/experience/")
882 .ignore_then(
883 crate::key::experience_key_parser()
884 .then_ignore(just("/profile"))
885 .map(ViewerUri::ExperienceProfile),
886 )
887 .labelled("viewer app experience URI")
888}
889
890#[cfg(feature = "chumsky")]
896#[must_use]
897pub fn viewer_app_group_uri_parser<'src>()
898-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
899 just("secondlife:///app/group/")
900 .ignore_then(
901 crate::key::group_key_parser()
902 .then_ignore(just("/about"))
903 .map(ViewerUri::GroupAbout)
904 .or(crate::key::group_key_parser()
905 .then_ignore(just("/inspect"))
906 .map(ViewerUri::GroupInspect))
907 .or(just("create").to(ViewerUri::GroupCreate))
908 .or(just("list/show").to(ViewerUri::GroupListShow)),
909 )
910 .labelled("viewer app group URI")
911}
912
913#[cfg(feature = "chumsky")]
919#[must_use]
920pub fn viewer_app_help_uri_parser<'src>()
921-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
922 just("secondlife:///app/help/")
923 .ignore_then(just('/').ignore_then(url_text_component_parser()).or_not())
924 .map(|help_query| ViewerUri::Help { help_query })
925 .labelled("viewer app help URI")
926}
927
928#[cfg(feature = "chumsky")]
934#[must_use]
935pub fn viewer_app_inventory_uri_parser<'src>()
936-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
937 just("secondlife:///app/inventory/")
938 .ignore_then(
939 crate::key::inventory_key_parser()
940 .then_ignore(just("/select"))
941 .map(ViewerUri::InventorySelect)
942 .or(just("/show").to(ViewerUri::InventoryShow)),
943 )
944 .labelled("viewer app inventory URI")
945}
946
947#[cfg(feature = "chumsky")]
953#[must_use]
954pub fn viewer_app_keybinding_uri_parser<'src>()
955-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
956 just("secondlife:///app/keybinding/")
957 .ignore_then(
958 url_text_component_parser()
959 .try_map(|s, span| match s.deref() {
960 "walk_to" => Ok(ViewerUri::KeyBindingMovementWalkTo),
961 "teleport_to" => Ok(ViewerUri::KeyBindingMovementTeleportTo),
962 "push_forward" => Ok(ViewerUri::KeyBindingMovementPushForward),
963 "push_backward" => Ok(ViewerUri::KeyBindingMovementPushBackward),
964 "turn_left" => Ok(ViewerUri::KeyBindingMovementTurnLeft),
965 "turn_right" => Ok(ViewerUri::KeyBindingMovementTurnRight),
966 "slide_left" => Ok(ViewerUri::KeyBindingMovementSlideLeft),
967 "slide_right" => Ok(ViewerUri::KeyBindingMovementSlideRight),
968 "jump" => Ok(ViewerUri::KeyBindingMovementJump),
969 "push_down" => Ok(ViewerUri::KeyBindingMovementPushDown),
970 "run_forward" => Ok(ViewerUri::KeyBindingMovementRunForward),
971 "run_backward" => Ok(ViewerUri::KeyBindingMovementRunBackward),
972 "run_left" => Ok(ViewerUri::KeyBindingMovementRunLeft),
973 "run_right" => Ok(ViewerUri::KeyBindingMovementRunRight),
974 "toggle_run" => Ok(ViewerUri::KeyBindingMovementToggleRun),
975 "toggle_fly" => Ok(ViewerUri::KeyBindingMovementToggleFly),
976 "toggle_sit" => Ok(ViewerUri::KeyBindingMovementToggleSit),
977 "stop_moving" => Ok(ViewerUri::KeyBindingMovementStopMoving),
978 "look_up" => Ok(ViewerUri::KeyBindingCameraLookUp),
979 "look_down" => Ok(ViewerUri::KeyBindingCameraLookDown),
980 "move_forward_fast" => Ok(ViewerUri::KeyBindingCameraMoveForwardFast),
981 "move_backward_fast" => Ok(ViewerUri::KeyBindingCameraMoveBackwardFast),
982 "move_forward_sitting" => Ok(ViewerUri::KeyBindingCameraMoveForwardSitting),
983 "move_backward_sittingk" => Ok(ViewerUri::KeyBindingCameraMoveBackwardSitting),
984 "move_forward" => Ok(ViewerUri::KeyBindingCameraMoveForward),
985 "move_backward" => Ok(ViewerUri::KeyBindingCameraMoveBackward),
986 "spin_over_sitting" => Ok(ViewerUri::KeyBindingCameraSpinOverSitting),
987 "spin_under_sitting" => Ok(ViewerUri::KeyBindingCameraSpinUnderSitting),
988 "spin_over" => Ok(ViewerUri::KeyBindingCameraSpinOver),
989 "spin_under" => Ok(ViewerUri::KeyBindingCameraSpinUnder),
990 "pan_up" => Ok(ViewerUri::KeyBindingCameraPanUp),
991 "pan_down" => Ok(ViewerUri::KeyBindingCameraPanDown),
992 "pan_left" => Ok(ViewerUri::KeyBindingCameraPanLeft),
993 "pan_right" => Ok(ViewerUri::KeyBindingCameraPanRight),
994 "pan_in" => Ok(ViewerUri::KeyBindingCameraPanIn),
995 "pan_out" => Ok(ViewerUri::KeyBindingCameraPanOut),
996 "spin_around_ccw_sitting" => {
997 Ok(ViewerUri::KeyBindingCameraSpinAroundCounterClockwiseSitting)
998 }
999 "spin_around_cw_sitting" => {
1000 Ok(ViewerUri::KeyBindingCameraSpinAroundClockwiseSitting)
1001 }
1002 "spin_around_ccw" => Ok(ViewerUri::KeyBindingCameraSpinAroundCounterClockwise),
1003 "spin_around_cw" => Ok(ViewerUri::KeyBindingCameraSpinAroundClockwise),
1004 "edit_avatar_spin_ccw" => {
1005 Ok(ViewerUri::KeyBindingEditingAvatarSpinCounterClockwise)
1006 }
1007 "edit_avatar_spin_cw" => Ok(ViewerUri::KeyBindingEditingAvatarSpinClockwise),
1008 "edit_avatar_spin_over" => Ok(ViewerUri::KeyBindingEditingAvatarSpinOver),
1009 "edit_avatar_spin_under" => Ok(ViewerUri::KeyBindingEditingAvatarSpinUnder),
1010 "edit_avatar_move_forward" => Ok(ViewerUri::KeyBindingEditingAvatarMoveForward),
1011 "edit_avatar_move_backward" => {
1012 Ok(ViewerUri::KeyBindingEditingAvatarMoveBackward)
1013 }
1014 "toggle_pause_media" => Ok(ViewerUri::KeyBindingSoundAndMediaTogglePauseMedia),
1015 "toggle_enable_media" => {
1016 Ok(ViewerUri::KeyBindingSoundAndMediaToggleEnableMedia)
1017 }
1018 "voice_follow_key" => Ok(ViewerUri::KeyBindingSoundAndMediaVoiceFollowKey),
1019 "toggle_voice" => Ok(ViewerUri::KeyBindingSoundAndMediaToggleVoice),
1020 "start_chat" => Ok(ViewerUri::KeyBindingStartChat),
1021 "start_gesture" => Ok(ViewerUri::KeyBindingStartGesture),
1022 _ => Err(chumsky::error::Rich::custom(
1023 span,
1024 format!("Not a valid keybinding: {s}"),
1025 )),
1026 })
1027 .or(just("/script_trigger_lbutton")
1028 .ignore_then(script_trigger_mode_parser())
1029 .map(ViewerUri::KeyBindingScriptTriggerLButton)),
1030 )
1031 .labelled("viewer app keybinding URI")
1032}
1033
1034#[cfg(feature = "chumsky")]
1040#[must_use]
1041pub fn viewer_app_login_uri_parser<'src>()
1042-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1043 just("secondlife:///app/login?first=")
1044 .ignore_then(url_text_component_parser())
1045 .then(just("?last=").ignore_then(url_text_component_parser()))
1046 .then(just("?session=").ignore_then(url_text_component_parser()))
1047 .then(
1048 just("?location=")
1049 .ignore_then(url_text_component_parser())
1050 .or_not(),
1051 )
1052 .map(
1053 |(((first_name, last_name), session), login_location)| ViewerUri::Login {
1054 first_name,
1055 last_name,
1056 session,
1057 login_location,
1058 },
1059 )
1060 .labelled("viewer app login URI")
1061}
1062
1063#[cfg(feature = "chumsky")]
1069#[must_use]
1070pub fn viewer_app_maptrackavatar_uri_parser<'src>()
1071-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1072 just("secondlife:///app/maptrackavatar/")
1073 .ignore_then(crate::key::friend_key_parser().map(ViewerUri::MapTrackAvatar))
1074 .labelled("viewer app maptrackavatar URI")
1075}
1076
1077#[cfg(feature = "chumsky")]
1083#[must_use]
1084pub fn viewer_app_objectim_uri_parser<'src>()
1085-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1086 just("secondlife:///app/objectim/")
1087 .ignore_then(crate::key::object_key_parser())
1088 .then_ignore(just('/').or_not())
1089 .then(just("?name=").ignore_then(url_text_component_parser()))
1090 .then(
1091 just("&owner=")
1092 .ignore_then(crate::key::group_key_parser())
1093 .then_ignore(just("&groupowned=true"))
1094 .map(crate::key::OwnerKey::Group)
1095 .or(just("&owner=")
1096 .ignore_then(crate::key::agent_key_parser())
1097 .map(crate::key::OwnerKey::Agent)),
1098 )
1099 .then(just("&slurl=").ignore_then(crate::map::url_encoded_location_parser()))
1100 .map(
1101 |(((object_key, object_name), owner), location)| ViewerUri::ObjectInstantMessage {
1102 object_key,
1103 object_name,
1104 owner,
1105 location,
1106 },
1107 )
1108 .labelled("viewer app objectim URI")
1109}
1110
1111#[cfg(feature = "chumsky")]
1117#[must_use]
1118pub fn viewer_app_openfloater_uri_parser<'src>()
1119-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1120 just("secondlife:///app/openfloater/")
1121 .ignore_then(url_text_component_parser().map(ViewerUri::OpenFloater))
1122 .labelled("viewer app openfloater URI")
1123}
1124
1125#[cfg(feature = "chumsky")]
1131#[must_use]
1132pub fn viewer_app_parcel_uri_parser<'src>()
1133-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1134 just("secondlife:///app/parcel/")
1135 .ignore_then(crate::key::parcel_key_parser().map(ViewerUri::Parcel))
1136 .labelled("viewer app parcel URI")
1137}
1138
1139#[cfg(feature = "chumsky")]
1145#[must_use]
1146pub fn viewer_app_search_uri_parser<'src>()
1147-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1148 just("secondlife:///app/search/")
1149 .ignore_then(crate::search::search_category_parser())
1150 .then_ignore(just('/'))
1151 .then(url_text_component_parser())
1152 .map(|(category, search_term)| ViewerUri::Search {
1153 category,
1154 search_term,
1155 })
1156 .labelled("viewer app search URI")
1157}
1158
1159#[cfg(feature = "chumsky")]
1165#[must_use]
1166pub fn viewer_app_sharewithavatar_uri_parser<'src>()
1167-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1168 just("secondlife:///app/sharewithavatar/")
1169 .ignore_then(crate::key::agent_key_parser().map(ViewerUri::ShareWithAvatar))
1170 .labelled("viewer app sharewithavatar URI")
1171}
1172#[cfg(feature = "chumsky")]
1178#[must_use]
1179pub fn viewer_app_teleport_uri_parser<'src>()
1180-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1181 just("secondlife:///app/teleport/")
1182 .ignore_then(crate::map::url_location_parser().map(ViewerUri::Teleport))
1183 .labelled("viewer app teleport URI")
1184}
1185
1186#[cfg(feature = "chumsky")]
1192#[must_use]
1193pub fn viewer_app_voicecallavatar_uri_parser<'src>()
1194-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1195 just("secondlife:///app/voicecallavatar/")
1196 .ignore_then(crate::key::agent_key_parser().map(ViewerUri::VoiceCallAvatar))
1197 .labelled("viewer app voicecallavatar URI")
1198}
1199
1200#[cfg(feature = "chumsky")]
1206#[must_use]
1207pub fn viewer_app_wear_folder_uri_parser<'src>()
1208-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1209 just("secondlife:///app/wear_folder")
1210 .ignore_then(
1211 just("?folder_id=")
1212 .ignore_then(crate::key::inventory_folder_key_parser())
1213 .map(ViewerUri::WearFolderByInventoryFolderKey)
1214 .or(just("?folder_name=")
1215 .ignore_then(url_text_component_parser())
1216 .map(ViewerUri::WearFolderByLibraryFolderName)),
1217 )
1218 .labelled("viewer app wear_folder URI")
1219}
1220
1221#[cfg(feature = "chumsky")]
1227#[must_use]
1228pub fn viewer_app_worldmap_uri_parser<'src>()
1229-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1230 just("secondlife:///app/worldmap/")
1231 .ignore_then(crate::map::url_location_parser().map(ViewerUri::WorldMap))
1232 .labelled("viewer app worldmap URI")
1233}
1234
1235#[cfg(feature = "chumsky")]
1241#[must_use]
1242pub fn viewer_app_uri_parser<'src>()
1243-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1244 viewer_app_agent_uri_parser()
1245 .or(viewer_app_appearance_uri_parser())
1246 .or(viewer_app_balance_uri_parser())
1247 .or(viewer_app_chat_uri_parser())
1248 .or(viewer_app_classified_uri_parser())
1249 .or(viewer_app_event_uri_parser())
1250 .or(viewer_app_experience_uri_parser())
1251 .or(viewer_app_group_uri_parser())
1252 .or(viewer_app_help_uri_parser())
1253 .or(viewer_app_inventory_uri_parser())
1254 .or(viewer_app_keybinding_uri_parser())
1255 .or(viewer_app_login_uri_parser())
1256 .or(viewer_app_maptrackavatar_uri_parser())
1257 .or(viewer_app_objectim_uri_parser())
1258 .or(viewer_app_openfloater_uri_parser())
1259 .or(viewer_app_parcel_uri_parser())
1260 .or(viewer_app_search_uri_parser())
1261 .or(viewer_app_sharewithavatar_uri_parser())
1262 .or(viewer_app_teleport_uri_parser())
1263 .or(viewer_app_voicecallavatar_uri_parser())
1264 .or(viewer_app_wear_folder_uri_parser())
1265 .or(viewer_app_worldmap_uri_parser())
1266 .labelled("viewer app URI")
1267}
1268
1269#[cfg(feature = "chumsky")]
1275#[must_use]
1276pub fn viewer_location_uri_parser<'src>()
1277-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1278 just("secondlife:///")
1279 .ignore_then(crate::map::url_location_parser())
1280 .map(ViewerUri::Location)
1281 .labelled("viewer location URI")
1282}
1283
1284#[cfg(feature = "chumsky")]
1290#[must_use]
1291#[expect(
1292 clippy::module_name_repetitions,
1293 reason = "the parse is used outside this module"
1294)]
1295pub fn viewer_uri_parser<'src>()
1296-> impl Parser<'src, &'src str, ViewerUri, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
1297 viewer_app_uri_parser()
1298 .or(viewer_location_uri_parser())
1299 .labelled("viewer URI")
1300}