1use chumsky::IterParser as _;
4use chumsky::Parser;
5use chumsky::prelude::{any, choice, end, just, one_of};
6use chumsky::text::{digits, newline, whitespace};
7use sl_types::utils::{i64_parser, u64_parser, unsigned_f32_parser, usize_parser};
8
9use crate::take_until;
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum SystemMessage {
14 SavedSnapshot {
16 filename: std::path::PathBuf,
18 },
19 FailedToSaveSnapshotDueToMissingDestinationFolder {
21 folder: std::path::PathBuf,
23 },
24 FailedToSaveSnapshotDueToDiskSpace {
26 folder: std::path::PathBuf,
28 required_disk_space: bytesize::ByteSize,
30 free_disk_space: bytesize::ByteSize,
32 },
33 DrawDistanceSet {
35 distance: sl_types::map::Distance,
37 },
38 HomePositionSet,
40 LandDivided,
42 FailedToJoinLandDueToRegionBoundary,
44 OfferedCallingCard {
46 recipient_avatar_name: String,
48 },
49 AttachmentSavedMessage,
51 YouPaidForObject {
53 seller: sl_types::key::OwnerKey,
55 amount: sl_types::money::LindenAmount,
57 object_name: String,
59 },
60 YouPaidToCreateGroup {
62 payment_recipient: sl_types::key::AgentKey,
64 amount: sl_types::money::LindenAmount,
66 },
67 YouPaidToJoinGroup {
69 joined_group: sl_types::key::GroupKey,
71 join_fee: sl_types::money::LindenAmount,
73 },
74 YouPaidForLand {
76 previous_land_owner: sl_types::key::OwnerKey,
78 amount: sl_types::money::LindenAmount,
80 },
81 FailedToPay {
83 payment_recipient: sl_types::key::OwnerKey,
85 amount: sl_types::money::LindenAmount,
87 },
88 ObjectGrantedPermissionToTakeMoney {
90 object_name: String,
92 owner_name: String,
94 object_region: Option<sl_types::map::RegionName>,
96 object_location: Option<sl_types::map::RegionCoordinates>,
98 },
99 SentPayment {
101 recipient_key: sl_types::key::OwnerKey,
103 amount: sl_types::money::LindenAmount,
105 message: Option<String>,
107 },
108 ReceivedPayment {
110 sender_key: sl_types::key::OwnerKey,
112 amount: sl_types::money::LindenAmount,
114 message: Option<String>,
116 },
117 AddedToGroup,
119 LeftGroup {
121 group_name: String,
123 },
124 UnableToInviteUserDueToMissingGroupMembership,
127 UnableToInviteUserToGroupDueToDifferingLimitedEstate,
130 UnableToLoadNotecard,
132 UnableToLoadGesture {
134 gesture_name: String,
136 },
137 NowPlaying {
139 song_name: String,
141 },
142 TeleportCompleted {
144 origin: sl_types::map::UnconstrainedLocation,
146 },
147 RegionRestart,
149 ObjectGaveObject {
151 giving_object_name: String,
153 giving_object_location: sl_types::map::UnconstrainedLocation,
155 giving_object_owner: sl_types::key::OwnerKey,
157 given_object_name: String,
159 },
160 ObjectGaveFolder {
162 giving_object_key: sl_types::key::ObjectKey,
164 giving_object_name: String,
166 giving_object_owner: sl_types::key::OwnerKey,
168 giving_object_location: sl_types::map::Location,
170 giving_object_link_label: String,
172 folder_name: String,
174 },
175 AvatarGaveObject {
177 is_group_member: bool,
179 giving_avatar_name: String,
181 given_object_name: String,
183 },
184 DeclinedGivenObject {
186 object_name: String,
188 giver_location: sl_types::map::UnconstrainedLocation,
190 giver_name: String,
192 },
193 SelectResidentsToShareWith,
195 ItemsSuccessfullyShared,
197 ModifiedSearchQuery {
199 query: String,
201 },
202 SimulatorVersion {
204 previous_region_simulator_version: String,
206 current_region_simulator_version: String,
208 },
209 RenamedAvatar {
211 old_name: String,
213 new_name: String,
215 },
216 DoubleClickTeleport {
218 enabled: bool,
220 },
221 AlwaysRun {
223 enabled: bool,
225 },
226 AddedAsEstateManager,
228 CreatingBridge,
230 BridgeCreated,
232 BridgeCreationInProgress,
235 BridgeFailedToAttach,
237 BridgeFailedToAttachDueToBridgeAttachmentPointInUse,
240 BridgeNotCreated,
242 BridgeDetached,
244 BridgeObjectNotFoundCantProceedWithCreation,
246 FailedToPlaceObjectAtSpecifiedLocation,
248 ScriptCountChanged {
250 previous_script_count: u32,
252 current_script_count: u32,
254 change: i32,
256 },
257 MultiPersonChatMessageStillBeingProcessed,
259 ChatMessageToNoLongerExistingImSessionStillBeingProcessed,
261 ConferenceChatMessageStillBeingProcessed {
263 avatar_name: String,
265 },
266 GroupChatMessageStillBeingProcessed {
268 group_name: String,
270 },
271 AvatarDeclinedVoice {
273 avatar_name: String,
275 },
276 AvatarUnavailableForVoice {
278 avatar_name: String,
280 },
281 AudioFromDomainWillAlwaysBePlayed {
283 domain: String,
285 },
286 ObjectNotForSale,
288 CanNotCreateRequestedInventory,
290 LinkFailedDueToPieceDistance {
292 link_failed_pieces: Option<usize>,
294 total_selected_pieces: Option<usize>,
296 },
297 RezObjectFailedDueToFullParcel {
299 object_name: String,
301 parcel_name: String,
303 attempted_rez_location: sl_types::map::RegionCoordinates,
305 region_name: sl_types::map::RegionName,
307 },
308 CreateObjectFailedDueToFullRegion,
310 YourObjectHasBeenReturned {
312 object_name: String,
314 parcel_name: String,
316 location: sl_types::map::UnconstrainedLocation,
318 auto_return: bool,
320 },
321 PermissionToCreateObjectDenied,
323 PermissionToRezObjectDenied {
325 object_name: String,
327 parcel_name: String,
329 attempted_rez_location: sl_types::map::RegionCoordinates,
331 region_name: sl_types::map::RegionName,
333 },
334 PermissionToRepositionDenied,
336 PermissionToRotateDenied,
338 PermissionToRescaleDenied,
340 PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions,
342 PermissionToViewScriptDenied,
344 PermissionToViewNotecardDenied,
346 PermissionToChangeShapeDenied,
348 PermissionToEnterParcelDenied,
350 PermissionToEnterParcelDeniedDueToBan,
352 EjectedAvatar,
354 EjectedFromParcel,
356 EjectedFromParcelBecauseNoLongerAllowed,
358 BannedFromParcelTemporarily {
360 ban_duration: time::Duration,
362 },
363 BannedFromParcelIndefinitely,
365 OnlyGroupMembersCanVisitThisArea,
367 UnableToTeleportDueToRlv,
369 UnableToOpenTextureDueToRlv,
371 UnsupportedSlurl,
373 BlockedUntrustedBrowserSlurl,
375 GridStatusErrorInvalidMessageFormat,
377 ScriptInfoObjectInvalidOrOutOfRange,
379 ScriptInfo {
381 name: String,
383 running_scripts: usize,
385 total_scripts: usize,
387 allowed_memory_size_limit: bytesize::ByteSize,
389 cpu_time_consumed: time::Duration,
391 },
392 ExtendedScriptInfo {
394 object_key: sl_types::key::ObjectKey,
396 description: Option<String>,
398 root_prim: sl_types::key::ObjectKey,
400 prim_count: usize,
402 land_impact: usize,
404 inventory_items: usize,
406 velocity: sl_types::lsl::Vector,
408 position: sl_types::map::RegionCoordinates,
410 position_distance: sl_types::map::Distance,
412 rotation: sl_types::lsl::Rotation,
414 rotation_vector_degrees: sl_types::lsl::Vector,
416 angular_velocity: sl_types::lsl::Vector,
418 creator: sl_types::key::AgentKey,
420 owner: Option<sl_types::key::OwnerKey>,
422 previous_owner: Option<sl_types::key::OwnerKey>,
424 rezzed_by: sl_types::key::AgentKey,
426 group: Option<sl_types::key::GroupKey>,
428 creation_time: Option<time::OffsetDateTime>,
430 rez_time: Option<time::OffsetDateTime>,
432 pathfinding_type: sl_types::pathfinding::PathfindingType,
434 attachment_point: Option<sl_types::attachment::AttachmentPoint>,
436 temporarily_attached: bool,
438 inspecting_avatar_position: sl_types::map::RegionCoordinates,
440 },
441 DiceRollCommandUsageInstructions,
443 DiceRollResult {
445 roll_number: usize,
447 dice_faces: usize,
449 roll_result: usize,
451 },
452 DiceRollResultSum {
454 roll_count: usize,
456 dice_faces: usize,
458 result_sum: usize,
460 },
461 TextureInfoForObject {
463 object_name: String,
465 },
466 TextureInfoForFace {
468 face_number: usize,
470 texture_width: u16,
472 texture_height: u16,
474 texture_type: String,
476 },
477 FirestormMessage {
479 message_type: String,
482 message: String,
484 },
485 GridStatusEvent {
487 title: String,
489 scheduled: bool,
491 body: String,
493 incident_url: String,
495 },
496 SystemMessageWithLink {
499 message: String,
501 link: String,
503 },
504 FirestormHolidayWishes {
506 message: String,
508 },
509 PhishingWarning {
511 message: String,
513 },
514 TestMessageOfTheDay,
516 EarlyFirestormStartupMessage {
518 message: String,
520 },
521 OtherSystemMessage {
523 message: String,
525 },
526}
527
528#[must_use]
534pub fn snapshot_saved_message_parser<'src>()
535-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
536{
537 just("Snapshot saved: ")
538 .ignore_then(
539 any()
540 .repeated()
541 .collect::<String>()
542 .map(std::path::PathBuf::from),
543 )
544 .map(|filename| SystemMessage::SavedSnapshot { filename })
545 .or(just("Failed to save snapshot to ").ignore_then(
546 take_until!(just(": Directory does not exist.").ignored()).map(|(folder, ())| {
547 SystemMessage::FailedToSaveSnapshotDueToMissingDestinationFolder {
548 folder: std::path::PathBuf::from(folder),
549 }
550 }),
551 ))
552 .or(just("Failed to save snapshot to ").ignore_then(
553 take_until!(just(": Disk is full. ").ignored())
554 .map(|(folder, ())| std::path::PathBuf::from(folder))
555 .then(u64_parser())
556 .then_ignore(just("KB is required but only "))
557 .then(u64_parser())
558 .then_ignore(just("KB is free."))
559 .map(|((folder, required), free)| {
560 let required_disk_space = bytesize::ByteSize::kib(required);
561 let free_disk_space = bytesize::ByteSize::kib(free);
562 SystemMessage::FailedToSaveSnapshotDueToDiskSpace {
563 folder,
564 required_disk_space,
565 free_disk_space,
566 }
567 }),
568 ))
569}
570
571#[must_use]
577pub fn draw_distance_set_message_parser<'src>()
578-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
579{
580 just("Draw Distance set to ")
581 .ignore_then(sl_types::map::distance_parser())
582 .then_ignore(just('.'))
583 .map(|distance| SystemMessage::DrawDistanceSet { distance })
584 .labelled("draw distance set")
585}
586
587#[must_use]
593pub fn home_position_set_message_parser<'src>()
594-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
595{
596 just("Home position set.")
597 .to(SystemMessage::HomePositionSet)
598 .labelled("home position set")
599}
600
601#[must_use]
607pub fn land_divided_message_parser<'src>()
608-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
609{
610 just("Land has been divided.")
611 .to(SystemMessage::LandDivided)
612 .labelled("land has been divided")
613}
614
615#[must_use]
621pub fn failed_to_join_land_due_to_region_boundary_message_parser<'src>()
622-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
623{
624 just("Selected land is not all in the same region.")
625 .ignore_then(newline())
626 .ignore_then(just(" Try selecting a smaller piece of land."))
627 .to(SystemMessage::FailedToJoinLandDueToRegionBoundary)
628 .labelled("selected labe is not all in the same region")
629}
630
631#[must_use]
637pub fn offered_calling_card_message_parser<'src>()
638-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
639{
640 just("You have offered a calling card to ")
641 .ignore_then(take_until!(just('.')))
642 .map(
643 |(recipient_avatar_name, _)| SystemMessage::OfferedCallingCard {
644 recipient_avatar_name,
645 },
646 )
647 .labelled("offered calling card")
648}
649
650#[must_use]
656pub fn attachment_saved_message_parser<'src>()
657-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
658{
659 just("Attachment has been saved.")
660 .to(SystemMessage::AttachmentSavedMessage)
661 .labelled("attachment has been saved")
662}
663
664#[must_use]
670pub fn you_paid_for_object_message_parser<'src>()
671-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
672{
673 just("You paid ")
674 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
675 .then_ignore(whitespace())
676 .then(sl_types::money::linden_amount_parser())
677 .then_ignore(just(" for "))
678 .then(take_until!(just(".").ignore_then(end())))
679 .map(
680 |((seller, amount), (object_name, ()))| SystemMessage::YouPaidForObject {
681 seller,
682 amount,
683 object_name,
684 },
685 )
686 .labelled("you paid for object")
687}
688
689#[must_use]
695pub fn sent_payment_message_parser<'src>()
696-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
697{
698 just("You paid ")
699 .ignore_then(
700 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
701 .then_ignore(whitespace())
702 .then(sl_types::money::linden_amount_parser())
703 .then(
704 just(": ")
705 .ignore_then(any().repeated().collect::<String>())
706 .ignore_then(take_until!(newline().or(end())).map(|(n, ())| Some(n)))
707 .or(just(".").map(|_| None)),
708 )
709 .map(
710 |((recipient_key, amount), message)| SystemMessage::SentPayment {
711 recipient_key,
712 amount,
713 message,
714 },
715 ),
716 )
717 .labelled("you paid avatar")
718}
719
720#[must_use]
726pub fn received_payment_message_parser<'src>()
727-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
728{
729 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
730 .then_ignore(just(" paid you "))
731 .then(sl_types::money::linden_amount_parser())
732 .then(
733 just(": ")
734 .ignore_then(any().repeated().collect::<String>())
735 .ignore_then(take_until!(newline().or(end())).map(|(n, ())| Some(n)))
736 .or(just(".").map(|_| None)),
737 )
738 .map(
739 |((sender_key, amount), message)| SystemMessage::ReceivedPayment {
740 sender_key,
741 amount,
742 message,
743 },
744 )
745 .labelled("received payment")
746}
747
748#[must_use]
754pub fn you_paid_to_create_a_group_message_parser<'src>()
755-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
756{
757 just("You paid ")
758 .ignore_then(sl_types::key::app_agent_uri_as_agent_key_parser())
759 .then_ignore(whitespace())
760 .then(sl_types::money::linden_amount_parser())
761 .then_ignore(just(" to create a group."))
762 .map(
763 |(payment_recipient, amount)| SystemMessage::YouPaidToCreateGroup {
764 payment_recipient,
765 amount,
766 },
767 )
768 .labelled("you paid to create a group")
769}
770
771#[must_use]
777pub fn you_paid_to_join_group_message_parser<'src>()
778-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
779{
780 just("You paid ")
781 .ignore_then(sl_types::key::app_group_uri_as_group_key_parser())
782 .then_ignore(whitespace())
783 .then(sl_types::money::linden_amount_parser())
784 .then_ignore(just(" to join a group."))
785 .map(
786 |(joined_group, join_fee)| SystemMessage::YouPaidToJoinGroup {
787 joined_group,
788 join_fee,
789 },
790 )
791 .labelled("you paid to join a group")
792}
793
794#[must_use]
800pub fn you_paid_for_land_message_parser<'src>()
801-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
802{
803 just("You paid ")
804 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
805 .then_ignore(whitespace())
806 .then(sl_types::money::linden_amount_parser())
807 .then_ignore(just(" for a parcel of land."))
808 .map(
809 |(previous_land_owner, amount)| SystemMessage::YouPaidForLand {
810 previous_land_owner,
811 amount,
812 },
813 )
814 .labelled("you paid for a parcel of land")
815}
816
817#[must_use]
823pub fn failed_to_pay_message_parser<'src>()
824-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
825{
826 just("You failed to pay ")
827 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
828 .then_ignore(whitespace())
829 .then(sl_types::money::linden_amount_parser())
830 .then_ignore(just('.'))
831 .map(|(payment_recipient, amount)| SystemMessage::FailedToPay {
832 payment_recipient,
833 amount,
834 })
835 .labelled("you failed to pay")
836}
837
838#[must_use]
844pub fn object_granted_permission_to_take_money_parser<'src>()
845-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
846{
847 just('\'')
848 .ignore_then(take_until!(just("', an object owned by '")))
849 .then(take_until!(just("', located in ")))
850 .then(
851 just("(unknown region) at ")
852 .to(None)
853 .or(take_until!(just(" at ")).try_map(|(vc, _), span| {
854 Ok(Some(sl_types::map::RegionName::try_new(vc).map_err(
855 |err| {
856 chumsky::error::Rich::custom(
857 span,
858 format!("Error creating region name: {err:?}"),
859 )
860 },
861 )?))
862 })),
863 )
864 .then(
865 just("(unknown position)")
866 .to(None)
867 .or(sl_types::utils::f32_parser()
868 .then_ignore(just(", "))
869 .then(sl_types::utils::f32_parser())
870 .then_ignore(just(','))
871 .then(sl_types::utils::f32_parser())
872 .map(|((x, y), z)| Some(sl_types::map::RegionCoordinates::new(x, y, z)))),
873 )
874 .then_ignore(just(
875 ", has been granted permission to: Take Linden dollars (L$) from you.",
876 ))
877 .map(
878 |((((object_name, _), (owner_name, _)), object_region), object_location)| {
879 SystemMessage::ObjectGrantedPermissionToTakeMoney {
880 object_name,
881 owner_name,
882 object_region,
883 object_location,
884 }
885 },
886 )
887 .labelled("object granted permission to take money")
888}
889
890#[must_use]
896pub fn group_membership_message_parser<'src>()
897-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
898{
899 just("You have been added to the group.")
900 .to(SystemMessage::AddedToGroup)
901 .or(just("You have left the group '")
902 .ignore_then(take_until!(just("'.")))
903 .map(|(group_name, _)| SystemMessage::LeftGroup { group_name }))
904 .labelled("you have been added to the group")
905}
906
907#[must_use]
914pub fn unable_to_invite_user_due_to_missing_group_membership_message_parser<'src>()
915-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
916{
917 just("Unable to invite user because you are not in that group.")
918 .to(SystemMessage::UnableToInviteUserDueToMissingGroupMembership)
919 .labelled("unable to invite user because you are not in that group")
920}
921
922#[must_use]
929pub fn unable_to_invite_user_due_to_differing_limited_estate_message_parser<'src>()
930-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
931{
932 just("Unable to invite users because at least one user is in a different")
933 .ignore_then(newline())
934 .ignore_then(just(" limited estate than the group.").then_ignore(whitespace().then(end())))
935 .to(SystemMessage::UnableToInviteUserToGroupDueToDifferingLimitedEstate)
936 .labelled("unable to invite users because of differing limited estate")
937}
938
939#[must_use]
945pub fn unable_to_load_notecard_message_parser<'src>()
946-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
947{
948 just("Unable to load the notecard.")
949 .then_ignore(newline())
950 .then_ignore(whitespace())
951 .then(just("Please try again."))
952 .to(SystemMessage::UnableToLoadNotecard)
953 .labelled("unable to load notecard")
954}
955
956#[must_use]
962pub fn unable_to_load_gesture_message_parser<'src>()
963-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
964{
965 just("Unable to load gesture ")
966 .ignore_then(take_until!(just('.').then(end())))
967 .map(|(gesture_name, _)| SystemMessage::UnableToLoadGesture { gesture_name })
968 .labelled("unable to load gesture")
969}
970
971#[must_use]
977pub fn teleport_completed_message_parser<'src>()
978-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
979{
980 just("Teleport completed from http://maps.secondlife.com/secondlife/")
981 .ignore_then(sl_types::map::url_unconstrained_location_parser())
982 .try_map(|origin, _span: chumsky::span::SimpleSpan| {
983 Ok(SystemMessage::TeleportCompleted { origin })
984 })
985 .labelled("teleport completed")
986}
987
988#[must_use]
994pub fn now_playing_message_parser<'src>()
995-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
996{
997 just("Now playing: ")
998 .ignore_then(any().repeated().collect::<String>())
999 .try_map(|song_name, _span: chumsky::span::SimpleSpan| {
1000 Ok(SystemMessage::NowPlaying { song_name })
1001 })
1002 .labelled("now playing")
1003}
1004
1005#[must_use]
1011pub fn region_restart_message_parser<'src>()
1012-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1013{
1014 just("The region you are in now is about to restart. If you stay in this region you will be logged out.")
1015 .try_map(|_, _span: chumsky::span::SimpleSpan| {
1016 Ok(SystemMessage::RegionRestart)
1017 })
1018 .labelled("region about to restart")
1019}
1020
1021#[must_use]
1027pub fn object_gave_object_message_parser<'src>()
1028-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1029{
1030 take_until!(just(" owned by "))
1031 .then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
1032 .then_ignore(
1033 whitespace()
1034 .or_not()
1035 .ignore_then(just("gave you ").then(just("<nolink>'").or_not())),
1036 )
1037 .then(take_until!(
1038 just("</nolink>'")
1039 .or_not()
1040 .then(whitespace())
1041 .then(just("( http://slurl.com/secondlife/"))
1042 ))
1043 .then(sl_types::map::url_unconstrained_location_parser())
1044 .then_ignore(just(" ).").ignore_then(end()))
1045 .map(
1046 |(
1047 (((giving_object_name, _), giving_object_owner), (given_object_name, _)),
1048 giving_object_location,
1049 )| {
1050 SystemMessage::ObjectGaveObject {
1051 giving_object_name,
1052 giving_object_owner,
1053 given_object_name,
1054 giving_object_location,
1055 }
1056 },
1057 )
1058 .labelled("object gave you object")
1059}
1060
1061#[must_use]
1067pub fn object_gave_folder_message_parser<'src>()
1068-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1069{
1070 just("An object named [")
1071 .ignore_then(sl_types::viewer_uri::viewer_app_objectim_uri_parser())
1072 .then_ignore(whitespace())
1073 .then(take_until!(just("] gave you this folder: '")))
1074 .then(take_until!(just('\'').ignore_then(end())))
1075 .try_map(
1076 |((app_objectim_uri, (giving_object_link_label, _)), (folder_name, ())), span| {
1077 match app_objectim_uri {
1078 sl_types::viewer_uri::ViewerUri::ObjectInstantMessage {
1079 object_key,
1080 object_name,
1081 owner,
1082 location,
1083 } => Ok(SystemMessage::ObjectGaveFolder {
1084 giving_object_key: object_key,
1085 giving_object_name: object_name,
1086 giving_object_owner: owner,
1087 giving_object_location: location,
1088 giving_object_link_label,
1089 folder_name,
1090 }),
1091 _ => Err(chumsky::error::Rich::custom(
1092 span,
1093 "Unexpected type of viewer URI in object gave folder message parser",
1094 )),
1095 }
1096 },
1097 )
1098 .labelled("object gave you folder")
1099}
1100
1101#[must_use]
1107pub fn avatar_gave_object_message_parser<'src>()
1108-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1109{
1110 just("A group member named ")
1111 .or_not()
1112 .then(take_until!(just(" gave you ")))
1113 .then(take_until!(just(".").ignore_then(end())))
1114 .try_map(
1115 |((group_member, (giving_avatar_name, _)), (given_object_name, ())),
1116 _span: chumsky::span::SimpleSpan| {
1117 Ok(SystemMessage::AvatarGaveObject {
1118 is_group_member: group_member.is_some(),
1119 giving_avatar_name,
1120 given_object_name,
1121 })
1122 },
1123 )
1124 .labelled("group member gave you object")
1125}
1126
1127#[must_use]
1133pub fn declined_given_object_message_parser<'src>()
1134-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1135{
1136 just("You decline '")
1137 .ignore_then(take_until!(
1138 just("' ( http://slurl.com/secondlife/").ignored()
1139 ))
1140 .then(sl_types::map::url_unconstrained_location_parser())
1141 .then_ignore(just(" ) from "))
1142 .then(
1143 any()
1144 .repeated()
1145 .collect::<String>()
1146 .map(|s| s.strip_suffix(".").map(|s| s.to_string()).unwrap_or(s)),
1147 )
1148 .map(|(((object_name, ()), giver_location), giver_name)| {
1149 SystemMessage::DeclinedGivenObject {
1150 object_name,
1151 giver_location,
1152 giver_name,
1153 }
1154 })
1155 .labelled("you decline given object")
1156}
1157
1158#[must_use]
1164pub fn select_residents_to_share_with_message_parser<'src>()
1165-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1166{
1167 just("Select residents to share with.")
1168 .to(SystemMessage::SelectResidentsToShareWith)
1169 .labelled("select residents to share with")
1170}
1171
1172#[must_use]
1178pub fn items_successfully_shared_message_parser<'src>()
1179-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1180{
1181 just("Items successfully shared.")
1182 .to(SystemMessage::ItemsSuccessfullyShared)
1183 .labelled("items successfully shared")
1184}
1185
1186#[must_use]
1192pub fn modified_search_query_message_parser<'src>()
1193-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1194{
1195 just("Your search query was modified and the words that were too short were removed.")
1196 .ignore_then(whitespace())
1197 .ignore_then(just("Searched for:"))
1198 .ignore_then(whitespace())
1199 .ignore_then(any().repeated().collect::<String>())
1200 .try_map(|query, _span: chumsky::span::SimpleSpan| {
1201 Ok(SystemMessage::ModifiedSearchQuery { query })
1202 })
1203 .labelled("your search query was modified")
1204}
1205
1206#[must_use]
1212pub fn simulator_version_message_parser<'src>()
1213-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1214{
1215 just("The region you have entered is running a different simulator version.")
1216 .ignore_then(whitespace())
1217 .ignore_then(just("Current simulator:"))
1218 .ignore_then(whitespace())
1219 .ignore_then(take_until!(just("\n")))
1220 .then_ignore(whitespace())
1221 .then_ignore(just("Previous simulator:"))
1222 .then_ignore(whitespace())
1223 .then(any().repeated().collect::<String>())
1224 .try_map(
1225 |((current_region_simulator_version, _), previous_region_simulator_version),
1226 _span: chumsky::span::SimpleSpan| {
1227 Ok(SystemMessage::SimulatorVersion {
1228 previous_region_simulator_version,
1229 current_region_simulator_version,
1230 })
1231 },
1232 )
1233 .labelled("region running different simulator version")
1234}
1235
1236#[must_use]
1242pub fn renamed_avatar_message_parser<'src>()
1243-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1244{
1245 take_until!(just(" is now known as"))
1246 .then_ignore(whitespace())
1247 .then(take_until!(just(".").ignore_then(end())))
1248 .try_map(
1249 |((old_name, _), (new_name, ())), _span: chumsky::span::SimpleSpan| {
1250 Ok(SystemMessage::RenamedAvatar { old_name, new_name })
1251 },
1252 )
1253 .labelled("avatar is now known as")
1254}
1255
1256#[must_use]
1262pub fn doubleclick_teleport_message_parser<'src>()
1263-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1264{
1265 just("DoubleClick Teleport enabled.")
1266 .to(SystemMessage::DoubleClickTeleport { enabled: true })
1267 .or(just("DoubleClick Teleport disabled.")
1268 .to(SystemMessage::DoubleClickTeleport { enabled: false }))
1269 .labelled("double click teleport enabled/disabled")
1270}
1271
1272#[must_use]
1278pub fn always_run_message_parser<'src>()
1279-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1280{
1281 just("Always Run enabled.")
1282 .to(SystemMessage::AlwaysRun { enabled: true })
1283 .or(just("Always Run disabled.").to(SystemMessage::AlwaysRun { enabled: false }))
1284 .labelled("always run enabled/disabled")
1285}
1286
1287#[must_use]
1293pub fn added_as_estate_manager_message_parser<'src>()
1294-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1295{
1296 just("You have been added as an estate manager.")
1297 .to(SystemMessage::AddedAsEstateManager)
1298 .labelled("you have been added as estate manager")
1299}
1300
1301#[must_use]
1307pub fn bridge_message_parser<'src>()
1308-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1309{
1310 choice([
1311 just("Creating the bridge. This might take a moment, please wait.").to(SystemMessage::CreatingBridge).boxed(),
1312 just("Creating the bridge. This might take a few moments, please wait").to(SystemMessage::CreatingBridge).boxed(),
1313 just("Bridge created.").to(SystemMessage::BridgeCreated).boxed(),
1314 just("Bridge creation in process, cannot start another. Please wait a few minutes before trying again.").to(SystemMessage::BridgeCreationInProgress).boxed(),
1315 just("Bridge object not found. Can't proceed with creation, exiting.").to(SystemMessage::BridgeObjectNotFoundCantProceedWithCreation).boxed(),
1316 just("Bridge failed to attach. This is not the current bridge version. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeFailedToAttach).boxed(),
1317 just("Bridge failed to attach. Something else was using the bridge attachment point. Please try to recreate the bridge.").to(SystemMessage::BridgeFailedToAttachDueToBridgeAttachmentPointInUse).boxed(),
1318 just("Bridge failed to attach. Something else was using the bridge attachment point. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeFailedToAttachDueToBridgeAttachmentPointInUse).boxed(),
1319 just("Bridge not created. The bridge couldn't be found in inventory. Please use the Firestorm 'Avatar/Avatar Health/Recreate Bridge' menu option to recreate the bridge.").to(SystemMessage::BridgeNotCreated).boxed(),
1320 just("Bridge detached.").to(SystemMessage::BridgeDetached).labelled("bridge message").boxed(),
1321])
1322}
1323
1324#[must_use]
1331pub fn failed_to_place_object_at_specified_location_message_parser<'src>()
1332-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1333{
1334 just("Failed to place object at specified location. Please try again.")
1335 .to(SystemMessage::FailedToPlaceObjectAtSpecifiedLocation)
1336 .labelled("failed to place object at specified location")
1337}
1338
1339#[must_use]
1345pub fn region_script_count_change_message_parser<'src>()
1346-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1347{
1348 just("Total scripts in region ")
1349 .ignore_then(just("jumped from ").or(just("dropped from ")))
1350 .ignore_then(
1351 digits(10).collect::<String>()
1352 .then_ignore(just(" to "))
1353 .then(digits(10).collect::<String>())
1354 .then_ignore(just(" ("))
1355 .then(one_of("+-"))
1356 .then(digits(10).collect::<String>())
1357 .then_ignore(just(")."))
1358 .try_map(
1359 |(((previous_script_count, current_script_count), sign), diff): (
1360 ((String, String), char),
1361 String,
1362 ),
1363 span: chumsky::span::SimpleSpan| {
1364 let previous_span = span;
1365 let previous_script_count =
1366 previous_script_count.parse().map_err(|err| {
1367 chumsky::error::Rich::custom(previous_span, format!(
1368 "Could not parse previous script count ({previous_script_count}) as u32: {err:?}"
1369 ))
1370 })?;
1371 let current_span = span;
1372 let current_script_count = current_script_count.parse().map_err(|err| {
1373 chumsky::error::Rich::custom(current_span, format!(
1374 "Could not parse current script count ({current_script_count}) as u32: {err:?}"
1375 ))
1376 })?;
1377 let diff_span = span;
1378 let diff: i32 = diff.parse().map_err(|err| {
1379 chumsky::error::Rich::custom(diff_span, format!(
1380 "Could not parse changed script count ({diff}) as i32: {err:?}"
1381 ))
1382 })?;
1383 let change = match sign {
1384 '+' => diff,
1385 '-' => {
1386 #[expect(clippy::arithmetic_side_effects, reason = "this just switches the sign of a positive value to a negative one but only the opposite switch is panic-prone at i32::MIN")]
1387 -diff
1388 }
1389 c => {
1390 return Err(chumsky::error::Rich::custom(span, format!("Unexpected sign character for script change: {c}")))
1391 }
1392 };
1393 Ok(SystemMessage::ScriptCountChanged {
1394 previous_script_count,
1395 current_script_count,
1396 change,
1397 })
1398 },
1399 ),
1400 )
1401 .labelled("region script count changed")
1402}
1403
1404#[must_use]
1410pub fn chat_message_still_being_processed_message_parser<'src>()
1411-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1412{
1413 just("The message sent to ")
1414 .ignore_then(
1415 choice([
1416 just("Multi-person chat is still being processed.").to(SystemMessage::MultiPersonChatMessageStillBeingProcessed).boxed(),
1417 just("(IM Session Doesn't Exist) is still being processed.").to(SystemMessage::ChatMessageToNoLongerExistingImSessionStillBeingProcessed).boxed(),
1418 just("Conference with ").ignore_then(take_until!(just(" is still being processed.")).map(|(avatar_name, _)| SystemMessage::ConferenceChatMessageStillBeingProcessed { avatar_name })).boxed(),
1419 take_until!(just(" is still being processed.").ignored())
1420 .map(|(group_name, ())| {
1421 SystemMessage::GroupChatMessageStillBeingProcessed {
1422 group_name,
1423 }
1424 }).boxed(),
1425 ])
1426 )
1427 .then_ignore(newline())
1428 .then_ignore(whitespace())
1429 .then_ignore(just("If the message does not appear in the next few minutes, it may have been dropped by the server."))
1430 .labelled("chat message still being processed")
1431}
1432
1433#[must_use]
1439pub fn avatar_declined_voice_call_message_parser<'src>()
1440-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1441{
1442 take_until!(just(
1443 "has declined your call. You will now be reconnected to Nearby Voice Chat.",
1444 ))
1445 .map(|(avatar_name, _)| SystemMessage::AvatarDeclinedVoice { avatar_name })
1446 .labelled("avatar declined voice call")
1447}
1448
1449#[must_use]
1455pub fn avatar_unavailable_for_voice_call_message_parser<'src>()
1456-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1457{
1458 take_until!(just(
1459 "is not available to take your call. You will now be reconnected to Nearby Voice Chat.",
1460 ))
1461 .map(|(avatar_name, _)| SystemMessage::AvatarUnavailableForVoice { avatar_name })
1462 .labelled("avatar unavailable for voice call")
1463}
1464
1465#[must_use]
1471pub fn audio_from_domain_will_always_be_played_message_parser<'src>()
1472-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1473{
1474 just("Audio from the domain ")
1475 .ignore_then(
1476 take_until!(just(" will always be played."))
1477 .map(|(domain, _)| SystemMessage::AudioFromDomainWillAlwaysBePlayed { domain }),
1478 )
1479 .labelled("audio from domain will always be played")
1480}
1481
1482#[must_use]
1488pub fn object_not_for_sale_message_parser<'src>()
1489-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1490{
1491 just("This object is not for sale.")
1492 .to(SystemMessage::ObjectNotForSale)
1493 .labelled("object not for sale")
1494}
1495
1496#[must_use]
1502pub fn can_not_create_requested_inventory_message_parser<'src>()
1503-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1504{
1505 just("Cannot create requested inventory.")
1506 .to(SystemMessage::CanNotCreateRequestedInventory)
1507 .labelled("cannot created requested inventory")
1508}
1509
1510#[must_use]
1516pub fn link_failed_due_to_piece_distance_message_parser<'src>()
1517-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1518{
1519 just("Link failed -- Unable to link any pieces - pieces are too far apart.")
1520 .to(SystemMessage::LinkFailedDueToPieceDistance {
1521 link_failed_pieces: None,
1522 total_selected_pieces: None,
1523 })
1524 .or(just("Link failed -- Unable to link ").ignore_then(
1525 usize_parser()
1526 .then_ignore(just(" of the "))
1527 .then(usize_parser())
1528 .then_ignore(just(" selected pieces - pieces are too far apart."))
1529 .map(|(link_failed_pieces, total_selected_pieces)| {
1530 SystemMessage::LinkFailedDueToPieceDistance {
1531 link_failed_pieces: Some(link_failed_pieces),
1532 total_selected_pieces: Some(total_selected_pieces),
1533 }
1534 }),
1535 ))
1536 .labelled("link failed due to distance")
1537}
1538
1539#[must_use]
1546pub fn rezzing_object_failed_due_to_full_parcel_message_parser<'src>()
1547-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1548{
1549 just("Can't rez object '").ignore_then(
1550 take_until!(just("' at ").ignored())
1551 .then(sl_types::map::region_coordinates_parser())
1552 .then_ignore(just(" on parcel '"))
1553 .then(
1554 take_until!(just("' in region ").ignored())
1555 )
1556 .then(
1557 take_until!(just(" because the parcel is too full.").ignored())
1558 .try_map(|(region_name, ()), span| {
1559 sl_types::map::RegionName::try_new(®ion_name).map_err(|err| {
1560 chumsky::error::Rich::custom(span, format!(
1561 "Could not turn parsed region name ({region_name}) into RegionName: {err:?}"
1562 ))
1563 })
1564 }),
1565 )
1566 .map(
1567 |((((object_name, ()), attempted_rez_location), (parcel_name, ())), region_name)| {
1568 SystemMessage::RezObjectFailedDueToFullParcel {
1569 object_name,
1570 attempted_rez_location,
1571 parcel_name,
1572 region_name,
1573 }
1574 },
1575 ),
1576 ).labelled("rezzing failed due to full parcel")
1577}
1578
1579#[must_use]
1586pub fn create_object_failed_due_to_full_region_message_parser<'src>()
1587-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1588{
1589 just("Unable to create requested object. The region is full.")
1590 .to(SystemMessage::CreateObjectFailedDueToFullRegion)
1591 .labelled("create object failed due to full region")
1592}
1593
1594#[must_use]
1600pub fn your_object_has_been_returned_message_parser<'src>()
1601-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1602{
1603 just("Your object '")
1604 .ignore_then(take_until!(just(
1605 "' has been returned to your inventory Lost and Found folder from parcel '",
1606 )))
1607 .then(take_until!(just("' at ")))
1608 .then(sl_types::map::region_name_parser())
1609 .then_ignore(whitespace())
1610 .then(sl_types::utils::i16_parser())
1611 .then_ignore(just(", "))
1612 .then(sl_types::utils::i16_parser())
1613 .then(
1614 just(" due to parcel auto return.")
1615 .to(true)
1616 .or(just('.').to(false)),
1617 )
1618 .map(
1619 |((((((object_name, _), (parcel_name, _)), region_name), x), y), auto_return)| {
1620 SystemMessage::YourObjectHasBeenReturned {
1621 object_name,
1622 parcel_name,
1623 location: sl_types::map::UnconstrainedLocation::new(region_name, x, y, 0),
1624 auto_return,
1625 }
1626 },
1627 )
1628 .labelled("your object has been returned")
1629}
1630
1631#[must_use]
1637pub fn permission_to_create_object_denied_message_parser<'src>()
1638-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1639{
1640 just("You cannot create objects here. The owner of this land does not allow it. Use the land tool to see land ownership.").to(SystemMessage::PermissionToCreateObjectDenied).labelled("permission to create object denied")
1641}
1642
1643#[must_use]
1649pub fn permission_to_rez_object_denied_message_parser<'src>()
1650-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1651{
1652 just("Can't rez object '")
1653 .ignore_then(
1654 take_until!(just("' at ").ignored())
1655 .then(sl_types::map::region_coordinates_parser())
1656 .then_ignore(just(" on parcel '"))
1657 .then(take_until!(just("' in region ").ignored()))
1658 .then(take_until!(just(" because the owner of this land does not allow it. Use the land tool to see land ownership.").ignored()).try_map(|(region_name, ()), span| {
1659 sl_types::map::RegionName::try_new(®ion_name).map_err(|err| chumsky::error::Rich::custom(span, format!("Could not turn parsed region name ({region_name}) into RegionName: {err:?}")))
1660 }))
1661 .map(|((((object_name, ()), attempted_rez_location), (parcel_name, ())), region_name)| {
1662 SystemMessage::PermissionToRezObjectDenied {
1663 object_name,
1664 attempted_rez_location,
1665 parcel_name,
1666 region_name,
1667 }
1668 })
1669 )
1670 .labelled("permission to rez object denied")
1671}
1672
1673#[must_use]
1679pub fn permission_to_reposition_denied_message_parser<'src>()
1680-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1681{
1682 just("Can't reposition -- permission denied")
1683 .to(SystemMessage::PermissionToRepositionDenied)
1684 .labelled("permission to reposition denied")
1685}
1686
1687#[must_use]
1693pub fn permission_to_rotate_denied_message_parser<'src>()
1694-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1695{
1696 just("Can't rotate -- permission denied")
1697 .to(SystemMessage::PermissionToRotateDenied)
1698 .labelled("permission to rotate denied")
1699}
1700
1701#[must_use]
1707pub fn permission_to_rescale_denied_message_parser<'src>()
1708-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1709{
1710 just("Can't rescale -- permission denied")
1711 .to(SystemMessage::PermissionToRescaleDenied)
1712 .labelled("permission to rescale denied")
1713}
1714
1715#[must_use]
1722pub fn permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser<'src>()
1723-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1724{
1725 just("Failed to unlink because you do not have permissions to build on all parcels")
1726 .to(SystemMessage::PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions)
1727 .labelled("permission to unlink denied due to parcel build permissions")
1728}
1729
1730#[must_use]
1736pub fn permission_to_view_script_denied_message_parser<'src>()
1737-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1738{
1739 just("Insufficient permissions to view the script.")
1740 .to(SystemMessage::PermissionToViewScriptDenied)
1741 .labelled("permission to view script denied")
1742}
1743
1744#[must_use]
1750pub fn permission_to_view_notecard_denied_message_parser<'src>()
1751-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1752{
1753 just("You do not have permission to view this notecard.")
1754 .to(SystemMessage::PermissionToViewNotecardDenied)
1755 .labelled("permission to view notecard denied")
1756}
1757
1758#[must_use]
1764pub fn permission_to_change_shape_denied_message_parser<'src>()
1765-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1766{
1767 just("You are not allowed to change this shape.")
1768 .to(SystemMessage::PermissionToChangeShapeDenied)
1769 .labelled("permission to change shape denied")
1770}
1771
1772#[must_use]
1778pub fn permission_to_enter_parcel_denied_message_parser<'src>()
1779-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1780{
1781 just("Cannot enter parcel, you are not on the access list.")
1782 .to(SystemMessage::PermissionToEnterParcelDenied)
1783 .labelled("permission to enter parcel denied")
1784}
1785
1786#[must_use]
1792pub fn permission_to_enter_parcel_denied_due_to_ban_message_parser<'src>()
1793-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1794{
1795 just("Cannot enter parcel, you have been banned.")
1796 .to(SystemMessage::PermissionToEnterParcelDeniedDueToBan)
1797 .labelled("permission to enter parcel denied due to ban")
1798}
1799
1800#[must_use]
1806pub fn avatar_ejected_message_parser<'src>()
1807-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1808{
1809 just("Avatar ejected.")
1810 .to(SystemMessage::EjectedAvatar)
1811 .labelled("avatar ejected")
1812}
1813
1814#[must_use]
1820pub fn ejected_from_parcel_message_parser<'src>()
1821-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1822{
1823 just("You have been ejected from this land.")
1824 .to(SystemMessage::EjectedFromParcel)
1825 .or(
1826 just("You are no longer allowed here and have been ejected.")
1827 .to(SystemMessage::EjectedFromParcelBecauseNoLongerAllowed),
1828 )
1829 .labelled("ejected from parcel")
1830}
1831
1832#[must_use]
1838pub fn banned_from_parcel_message_parser<'src>()
1839-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1840{
1841 just("You have been banned ")
1842 .ignore_then(
1843 just("indefinitely")
1844 .to(SystemMessage::BannedFromParcelIndefinitely)
1845 .or(just("for ")
1846 .ignore_then(i64_parser().then_ignore(just(" minutes")))
1847 .map(|d| SystemMessage::BannedFromParcelTemporarily {
1848 ban_duration: time::Duration::minutes(d),
1849 })),
1850 )
1851 .labelled("you have been banned")
1852}
1853
1854#[must_use]
1860pub fn only_group_members_can_visit_this_area_message_parser<'src>()
1861-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1862{
1863 just("Only members of a certain group can visit this area.")
1864 .to(SystemMessage::OnlyGroupMembersCanVisitThisArea)
1865 .labelled("only group members allowed here")
1866}
1867
1868#[must_use]
1874pub fn unable_to_teleport_due_to_rlv_message_parser<'src>()
1875-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1876{
1877 just("Unable to initiate teleport due to RLV restrictions")
1878 .to(SystemMessage::UnableToTeleportDueToRlv)
1879 .labelled("unable to teleport due to RLV")
1880}
1881
1882#[must_use]
1888pub fn unable_to_open_texture_due_to_rlv_message_parser<'src>()
1889-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1890{
1891 just("Unable to open texture due to RLV restrictions")
1892 .to(SystemMessage::UnableToOpenTextureDueToRlv)
1893 .labelled("unable to open texture due to RLV")
1894}
1895
1896#[must_use]
1902pub fn unsupported_slurl_message_parser<'src>()
1903-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1904{
1905 just("The SLurl you clicked on is not supported.")
1906 .to(SystemMessage::UnsupportedSlurl)
1907 .labelled("unsupported slurl")
1908}
1909
1910#[must_use]
1916pub fn blocked_untrusted_browser_slurl_message_parser<'src>()
1917-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1918{
1919 just("A SLurl was received from an untrusted browser and has been blocked for your security.")
1920 .to(SystemMessage::BlockedUntrustedBrowserSlurl)
1921 .labelled("blocked untrusted browser")
1922}
1923
1924#[must_use]
1930pub fn grid_status_error_invalid_message_format_message_parser<'src>()
1931-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1932{
1933 just("SL Grid Status error: Invalid message format. Try again later.")
1934 .to(SystemMessage::GridStatusErrorInvalidMessageFormat)
1935 .labelled("sl grid status invalid message format")
1936}
1937
1938#[must_use]
1944pub fn script_info_object_invalid_or_out_of_range_message_parser<'src>()
1945-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1946{
1947 just("Script info: Object to check is invalid or out of range.")
1948 .to(SystemMessage::ScriptInfoObjectInvalidOrOutOfRange)
1949 .labelled("script info object invalid or out of range")
1950}
1951
1952#[must_use]
1958pub fn script_info_message_parser<'src>()
1959-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
1960{
1961 just("Script info: '")
1962 .ignore_then(
1963 take_until!(just("': [").ignored())
1964 .then(usize_parser())
1965 .then_ignore(just('/'))
1966 .then(usize_parser())
1967 .then_ignore(just("] running scripts, "))
1968 .then(u64_parser().map(bytesize::ByteSize::kb))
1969 .then_ignore(just(" KB allowed memory size limit, "))
1970 .then(unsigned_f32_parser().map(|ms| time::Duration::seconds_f32(ms / 1000f32)))
1971 .then_ignore(just(" ms of CPU time consumed."))
1972 .map(
1973 |(
1974 ((((name, ()), running_scripts), total_scripts), allowed_memory_size_limit),
1975 cpu_time_consumed,
1976 )| {
1977 SystemMessage::ScriptInfo {
1978 name,
1979 running_scripts,
1980 total_scripts,
1981 allowed_memory_size_limit,
1982 cpu_time_consumed,
1983 }
1984 },
1985 ),
1986 )
1987 .labelled("script info")
1988}
1989
1990#[must_use]
1998pub fn extended_script_info_message_parser<'src>()
1999-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2000{
2001 just("Object ID: ")
2002 .ignore_then(sl_types::key::object_key_parser())
2003 .then_ignore(newline())
2004 .then_ignore(just(" Description:"))
2005 .then_ignore(just(" ").or_not())
2006 .then(just("(No Description)").then_ignore(newline()).to(None).or(
2007 take_until!(newline().ignored()).map(|(vc, ())| Some(vc)),
2008 ))
2009 .then_ignore(just(" Root prim: "))
2010 .then(sl_types::key::object_key_parser())
2011 .then_ignore(newline())
2012 .then_ignore(just(" Prim count: "))
2013 .then(sl_types::utils::usize_parser())
2014 .then_ignore(newline())
2015 .then_ignore(just(" Land impact: "))
2016 .then(sl_types::utils::usize_parser())
2017 .then_ignore(newline())
2018 .then_ignore(just(" Inventory items: "))
2019 .then(sl_types::utils::usize_parser())
2020 .then_ignore(newline())
2021 .then_ignore(just(" Velocity: "))
2022 .then(sl_types::lsl::vector_parser())
2023 .then_ignore(newline())
2024 .then_ignore(just(" Position: "))
2025 .then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
2026 .then_ignore(whitespace())
2027 .then(sl_types::map::distance_parser().delimited_by(just('('), just(')')))
2028 .then_ignore(newline())
2029 .then_ignore(just(" Rotation: "))
2030 .then(sl_types::lsl::rotation_parser())
2031 .then_ignore(whitespace())
2032 .then(sl_types::lsl::vector_parser().delimited_by(just('('), just(')')))
2033 .then_ignore(newline())
2034 .then_ignore(just(" Angular velocity: "))
2035 .then(sl_types::lsl::vector_parser())
2036 .then_ignore(whitespace())
2037 .then_ignore(just("(radians per second)"))
2038 .then_ignore(newline())
2039 .then_ignore(just(" Creator: "))
2040 .then(sl_types::key::app_agent_uri_as_agent_key_parser())
2041 .then_ignore(newline())
2042 .then_ignore(just(" Owner: "))
2043 .then(just("Group Owned").to(None).or(sl_types::key::app_agent_or_group_uri_as_owner_key_parser().map(Some)))
2044 .then_ignore(newline())
2045 .then_ignore(just(" Previous owner: "))
2046 .then(
2047 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
2048 .map(Some)
2049 .or(just("---").to(None)),
2050 )
2051 .then_ignore(newline())
2052 .then_ignore(just(" Rezzed by: "))
2053 .then(sl_types::key::agent_key_parser())
2054 .then_ignore(newline())
2055 .then_ignore(just(" Group: "))
2056 .then(
2057 sl_types::key::app_group_uri_as_group_key_parser()
2058 .map(Some)
2059 .or(just("---").to(None)),
2060 )
2061 .then_ignore(newline())
2062 .then_ignore(just(" Creation time:"))
2063 .then_ignore(just(' ').or_not())
2064 .then(crate::utils::offset_datetime_parser().or_not())
2065 .then_ignore(newline())
2066 .then_ignore(just(" Rez time:"))
2067 .then_ignore(just(' ').or_not())
2068 .then(crate::utils::offset_datetime_parser().or_not())
2069 .then_ignore(newline())
2070 .then_ignore(just(" Pathfinding type: "))
2071 .then(sl_types::pathfinding::int_as_pathfinding_type_parser())
2072 .then_ignore(newline())
2073 .then_ignore(just(" Attachment point: "))
2074 .then(
2075 sl_types::attachment::attachment_point_parser()
2076 .map(Some)
2077 .or(just("---").to(None)),
2078 )
2079 .then_ignore(newline())
2080 .then_ignore(just(" Temporarily attached: "))
2081 .then(just("Yes").to(true).or(just("No").to(false)))
2082 .then_ignore(newline())
2083 .then_ignore(just(" Your current position: "))
2084 .then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
2085 .map(
2086 |((((((((((((((((((((((
2087 object_key,
2088 description),
2089 root_prim),
2090 prim_count),
2091 land_impact),
2092 inventory_items),
2093 velocity),
2094 position),
2095 position_distance),
2096 rotation),
2097 rotation_vector_degrees),
2098 angular_velocity),
2099 creator),
2100 owner),
2101 previous_owner),
2102 rezzed_by),
2103 group),
2104 creation_time),
2105 rez_time),
2106 pathfinding_type),
2107 attachment_point),
2108 temporarily_attached),
2109 inspecting_avatar_position,
2110 )| {
2111 SystemMessage::ExtendedScriptInfo {
2112 object_key,
2113 description,
2114 root_prim,
2115 prim_count,
2116 land_impact,
2117 inventory_items,
2118 velocity,
2119 position,
2120 position_distance,
2121 rotation,
2122 rotation_vector_degrees,
2123 angular_velocity,
2124 creator,
2125 owner,
2126 previous_owner,
2127 rezzed_by,
2128 group,
2129 creation_time,
2130 rez_time,
2131 pathfinding_type,
2132 attachment_point,
2133 temporarily_attached,
2134 inspecting_avatar_position,
2135 }
2136 },
2137 ).labelled("extended script info")
2138}
2139
2140#[must_use]
2146pub fn dice_roll_message_parser<'src>()
2147-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2148{
2149 choice([
2150 just("You must provide positive values for dice (max 100) and faces (max 1000).")
2151 .to(SystemMessage::DiceRollCommandUsageInstructions)
2152 .boxed(),
2153 just('#')
2154 .ignore_then(usize_parser())
2155 .then_ignore(whitespace())
2156 .then_ignore(just("1d"))
2157 .then(usize_parser())
2158 .then_ignore(just(":"))
2159 .then_ignore(whitespace())
2160 .then(usize_parser())
2161 .then_ignore(just('.'))
2162 .map(
2163 |((roll_number, dice_faces), roll_result)| SystemMessage::DiceRollResult {
2164 roll_number,
2165 dice_faces,
2166 roll_result,
2167 },
2168 )
2169 .boxed(),
2170 just("Total result for ")
2171 .ignore_then(usize_parser())
2172 .then_ignore(just('d'))
2173 .then(usize_parser())
2174 .then_ignore(just(':'))
2175 .then_ignore(whitespace())
2176 .then(usize_parser())
2177 .then_ignore(just('.'))
2178 .map(
2179 |((roll_count, dice_faces), result_sum)| SystemMessage::DiceRollResultSum {
2180 roll_count,
2181 dice_faces,
2182 result_sum,
2183 },
2184 )
2185 .boxed(),
2186 ])
2187 .labelled("dice roll")
2188}
2189
2190#[must_use]
2196pub fn texture_info_message_parser<'src>()
2197-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2198{
2199 choice([
2200 just("Texture info for: ")
2201 .ignore_then(
2202 take_until!(newline().or(end()))
2203 .map(|(object_name, ())| SystemMessage::TextureInfoForObject { object_name }),
2204 )
2205 .boxed(),
2206 sl_types::utils::u16_parser()
2207 .then_ignore(just('x'))
2208 .then(sl_types::utils::u16_parser())
2209 .then_ignore(whitespace())
2210 .then(just("opaque").or(just("alpha")))
2211 .then_ignore(just(" on face "))
2212 .then(usize_parser())
2213 .map(
2214 |(((texture_width, texture_height), texture_type), face_number)| {
2215 SystemMessage::TextureInfoForFace {
2216 face_number,
2217 texture_width,
2218 texture_height,
2219 texture_type: texture_type.to_owned(),
2220 }
2221 },
2222 )
2223 .boxed(),
2224 ])
2225 .labelled("texture info")
2226}
2227
2228#[must_use]
2234pub fn firestorm_message_parser<'src>()
2235-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2236{
2237 just("Firestorm ")
2238 .ignore_then(
2239 take_until!(just("!").ignored())
2240 .map(|(message_type, ())| message_type)
2241 .then(any().repeated().collect::<String>())
2242 .map(|(message_type, message)| SystemMessage::FirestormMessage {
2243 message_type,
2244 message,
2245 }),
2246 )
2247 .labelled("firestorm message")
2248}
2249
2250#[must_use]
2256pub fn grid_status_event_message_parser<'src>()
2257-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2258{
2259 just("[ ")
2260 .ignore_then(
2261 take_until!(just(" ] "))
2262 .map(|(vc, _)| vc)
2263 .then(
2264 just("THIS IS A SCHEDULED EVENT ")
2265 .or_not()
2266 .map(|s| s.is_some()),
2267 )
2268 .then(
2269 take_until!(just(" [ https://status.secondlifegrid.net/incidents/").ignored())
2270 .map(|(vc, ())| vc),
2271 )
2272 .then(take_until!(just(' ').ignored()).map(|(vc, ())| vc))
2273 .then_ignore(just("]"))
2274 .map(
2275 |(((title, scheduled), body), url_fragment)| SystemMessage::GridStatusEvent {
2276 title,
2277 scheduled,
2278 body,
2279 incident_url: format!(
2280 "https://status.secondlifegird.net/incidents/{url_fragment}"
2281 ),
2282 },
2283 ),
2284 )
2285 .labelled("grid status event")
2286}
2287
2288#[must_use]
2294pub fn system_message_parser<'src>()
2295-> impl Parser<'src, &'src str, SystemMessage, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
2296{
2297 choice([
2298 snapshot_saved_message_parser().boxed(),
2299 attachment_saved_message_parser().boxed(),
2300 draw_distance_set_message_parser().boxed(),
2301 home_position_set_message_parser().boxed(),
2302 land_divided_message_parser().boxed(),
2303 failed_to_join_land_due_to_region_boundary_message_parser().boxed(),
2304 offered_calling_card_message_parser().boxed(),
2305 you_paid_for_object_message_parser().boxed(),
2306 you_paid_to_create_a_group_message_parser().boxed(),
2307 you_paid_to_join_group_message_parser().boxed(),
2308 you_paid_for_land_message_parser().boxed(),
2309 failed_to_pay_message_parser().boxed(),
2310 object_granted_permission_to_take_money_parser().boxed(),
2311 sent_payment_message_parser().boxed(),
2312 received_payment_message_parser().boxed(),
2313 group_membership_message_parser().boxed(),
2314 unable_to_invite_user_due_to_missing_group_membership_message_parser().boxed(),
2315 unable_to_invite_user_due_to_differing_limited_estate_message_parser().boxed(),
2316 unable_to_load_notecard_message_parser().boxed(),
2317 unable_to_load_gesture_message_parser().boxed(),
2318 teleport_completed_message_parser().boxed(),
2319 now_playing_message_parser().boxed(),
2320 region_restart_message_parser().boxed(),
2321 object_gave_object_message_parser().boxed(),
2322 object_gave_folder_message_parser().boxed(),
2323 declined_given_object_message_parser().boxed(),
2324 select_residents_to_share_with_message_parser().boxed(),
2325 items_successfully_shared_message_parser().boxed(),
2326 modified_search_query_message_parser().boxed(),
2327 avatar_gave_object_message_parser().boxed(),
2328 simulator_version_message_parser().boxed(),
2329 renamed_avatar_message_parser().boxed(),
2330 doubleclick_teleport_message_parser().boxed(),
2331 always_run_message_parser().boxed(),
2332 added_as_estate_manager_message_parser().boxed(),
2333 bridge_message_parser().boxed(),
2334 failed_to_place_object_at_specified_location_message_parser().boxed(),
2335 region_script_count_change_message_parser().boxed(),
2336 chat_message_still_being_processed_message_parser().boxed(),
2337 avatar_declined_voice_call_message_parser().boxed(),
2338 avatar_unavailable_for_voice_call_message_parser().boxed(),
2339 audio_from_domain_will_always_be_played_message_parser().boxed(),
2340 object_not_for_sale_message_parser().boxed(),
2341 can_not_create_requested_inventory_message_parser().boxed(),
2342 link_failed_due_to_piece_distance_message_parser().boxed(),
2343 rezzing_object_failed_due_to_full_parcel_message_parser().boxed(),
2344 create_object_failed_due_to_full_region_message_parser().boxed(),
2345 your_object_has_been_returned_message_parser().boxed(),
2346 permission_to_create_object_denied_message_parser().boxed(),
2347 permission_to_rez_object_denied_message_parser().boxed(),
2348 permission_to_reposition_denied_message_parser().boxed(),
2349 permission_to_rotate_denied_message_parser().boxed(),
2350 permission_to_rescale_denied_message_parser().boxed(),
2351 permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser()
2352 .boxed(),
2353 permission_to_view_script_denied_message_parser().boxed(),
2354 permission_to_view_notecard_denied_message_parser().boxed(),
2355 permission_to_change_shape_denied_message_parser().boxed(),
2356 permission_to_enter_parcel_denied_message_parser().boxed(),
2357 permission_to_enter_parcel_denied_due_to_ban_message_parser().boxed(),
2358 avatar_ejected_message_parser().boxed(),
2359 ejected_from_parcel_message_parser().boxed(),
2360 banned_from_parcel_message_parser().boxed(),
2361 only_group_members_can_visit_this_area_message_parser().boxed(),
2362 unable_to_teleport_due_to_rlv_message_parser().boxed(),
2363 unable_to_open_texture_due_to_rlv_message_parser().boxed(),
2364 unsupported_slurl_message_parser().boxed(),
2365 blocked_untrusted_browser_slurl_message_parser().boxed(),
2366 grid_status_error_invalid_message_format_message_parser().boxed(),
2367 script_info_object_invalid_or_out_of_range_message_parser().boxed(),
2368 script_info_message_parser().boxed(),
2369 extended_script_info_message_parser().boxed(),
2370 dice_roll_message_parser().boxed(),
2371 texture_info_message_parser().boxed(),
2372 firestorm_message_parser().boxed(),
2373 grid_status_event_message_parser().boxed(),
2374 take_until!(just("https").or(just("http")).or(just("Http")))
2375 .then(take_until!(newline().or(end())).map(|(vc, ())| vc))
2376 .map(
2377 |((message, scheme), rest_of_url)| SystemMessage::SystemMessageWithLink {
2378 message,
2379 link: format!("{scheme}://{rest_of_url}"),
2380 },
2381 )
2382 .boxed(),
2383 take_until!(just("www."))
2384 .then(take_until!(newline().or(end())).map(|(vc, ())| vc))
2385 .map(
2386 |((message, subdomain), rest_of_url)| SystemMessage::SystemMessageWithLink {
2387 message,
2388 link: format!("{subdomain}{rest_of_url}"),
2389 },
2390 )
2391 .boxed(),
2392 any()
2393 .repeated()
2394 .collect::<String>()
2395 .map(|message| {
2396 if message.contains("Firestorm") && (message.contains("holiday") || message.contains("Happy New Year")) {
2397 SystemMessage::FirestormHolidayWishes { message }
2398 } else if message.contains("phishing") {
2399 SystemMessage::PhishingWarning { message }
2400 } else if message == "This is a test version of Firestorm. If this were an actual release version, a real message of the day would be here. This is only a test." {
2401 SystemMessage::TestMessageOfTheDay
2402 } else if (message.ends_with("...") && (message.starts_with("Loading") || message.starts_with("Initializing") || message.starts_with("Downloading") || message.starts_with("Verifying") || message.starts_with("Loading") || message.starts_with("Connecting") || message.starts_with("Decoding") || message.starts_with("Waiting"))) || message == "Welcome to Advertisement-Free Firestorm" || message.starts_with("Logging in") {
2403 SystemMessage::EarlyFirestormStartupMessage { message }
2404 } else if message.contains("wiki.phoenixviewer.com/firestorm_classes") {
2405 SystemMessage::FirestormMessage {
2406 message_type: "Classes".to_string(),
2407 message,
2408 }
2409 } else if message.contains("BETA TESTERS") || message.contains("Beta Testers") {
2410 SystemMessage::FirestormMessage {
2411 message_type: "Beta Test".to_string(),
2412 message,
2413 }
2414 } else {
2415 SystemMessage::OtherSystemMessage { message }
2416 }
2417 })
2418 .boxed(),
2419])
2420}
2421
2422#[cfg(test)]
2423mod test {
2424 use super::*;
2425 use pretty_assertions::assert_eq;
2426
2427 #[test]
2428 fn test_teleport_completed() -> Result<(), Box<dyn std::error::Error>> {
2429 assert_eq!(
2430 Ok(SystemMessage::TeleportCompleted {
2431 origin: sl_types::map::UnconstrainedLocation {
2432 region_name: sl_types::map::RegionName::try_new("Fudo")?,
2433 x: 30,
2434 y: 169,
2435 z: 912
2436 }
2437 }),
2438 teleport_completed_message_parser()
2439 .parse(
2440 "Teleport completed from http://maps.secondlife.com/secondlife/Fudo/30/169/912"
2441 )
2442 .into_result()
2443 );
2444 Ok(())
2445 }
2446
2447 #[test]
2448 fn test_teleport_completed_extra_short() -> Result<(), Box<dyn std::error::Error>> {
2449 assert_eq!(
2450 Ok(SystemMessage::TeleportCompleted {
2451 origin: sl_types::map::UnconstrainedLocation {
2452 region_name: sl_types::map::RegionName::try_new("AA")?,
2453 x: 78,
2454 y: 83,
2455 z: 26
2456 }
2457 }),
2458 teleport_completed_message_parser()
2459 .parse("Teleport completed from http://maps.secondlife.com/secondlife/AA/78/83/26")
2460 .into_result()
2461 );
2462 Ok(())
2463 }
2464
2465 #[test]
2466 fn test_cant_rez_object() -> Result<(), Box<dyn std::error::Error>> {
2467 assert_eq!(
2468 Ok(SystemMessage::PermissionToRezObjectDenied {
2469 object_name: "Foo2".to_string(),
2470 attempted_rez_location: sl_types::map::RegionCoordinates::new(63.0486, 45.2515, 1501.08),
2471 parcel_name: "The Foo Bar".to_string(),
2472 region_name: sl_types::map::RegionName::try_new("Fudo")?,
2473 }),
2474 permission_to_rez_object_denied_message_parser()
2475 .parse("Can't rez object 'Foo2' at { 63.0486, 45.2515, 1501.08 } on parcel 'The Foo Bar' in region Fudo because the owner of this land does not allow it. Use the land tool to see land ownership.").into_result()
2476 );
2477 Ok(())
2478 }
2479}