1use chumsky::error::Simple;
4use chumsky::prelude::{any, choice, end, just, one_of, take_until};
5use chumsky::text::{digits, newline, whitespace};
6use chumsky::Parser;
7use sl_types::utils::{i64_parser, u64_parser, unsigned_f32_parser, usize_parser};
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum SystemMessage {
12 SavedSnapshot {
14 filename: std::path::PathBuf,
16 },
17 FailedToSaveSnapshotDueToMissingDestinationFolder {
19 folder: std::path::PathBuf,
21 },
22 FailedToSaveSnapshotDueToDiskSpace {
24 folder: std::path::PathBuf,
26 required_disk_space: bytesize::ByteSize,
28 free_disk_space: bytesize::ByteSize,
30 },
31 DrawDistanceSet {
33 distance: sl_types::map::Distance,
35 },
36 HomePositionSet,
38 LandDivided,
40 FailedToJoinLandDueToRegionBoundary,
42 OfferedCallingCard {
44 recipient_avatar_name: String,
46 },
47 AttachmentSavedMessage,
49 YouPaidForObject {
51 seller: sl_types::key::OwnerKey,
53 amount: sl_types::money::LindenAmount,
55 object_name: String,
57 },
58 YouPaidToCreateGroup {
60 payment_recipient: sl_types::key::AgentKey,
62 amount: sl_types::money::LindenAmount,
64 },
65 YouPaidToJoinGroup {
67 joined_group: sl_types::key::GroupKey,
69 join_fee: sl_types::money::LindenAmount,
71 },
72 YouPaidForLand {
74 previous_land_owner: sl_types::key::OwnerKey,
76 amount: sl_types::money::LindenAmount,
78 },
79 FailedToPay {
81 payment_recipient: sl_types::key::OwnerKey,
83 amount: sl_types::money::LindenAmount,
85 },
86 ObjectGrantedPermissionToTakeMoney {
88 object_name: String,
90 owner_name: String,
92 object_region: Option<sl_types::map::RegionName>,
94 object_location: Option<sl_types::map::RegionCoordinates>,
96 },
97 SentPayment {
99 recipient_key: sl_types::key::OwnerKey,
101 amount: sl_types::money::LindenAmount,
103 message: Option<String>,
105 },
106 ReceivedPayment {
108 sender_key: sl_types::key::OwnerKey,
110 amount: sl_types::money::LindenAmount,
112 message: Option<String>,
114 },
115 AddedToGroup,
117 LeftGroup {
119 group_name: String,
121 },
122 UnableToInviteUserDueToMissingGroupMembership,
125 UnableToInviteUserToGroupDueToDifferingLimitedEstate,
128 UnableToLoadNotecard,
130 UnableToLoadGesture {
132 gesture_name: String,
134 },
135 NowPlaying {
137 song_name: String,
139 },
140 TeleportCompleted {
142 origin: sl_types::map::UnconstrainedLocation,
144 },
145 RegionRestart,
147 ObjectGaveObject {
149 giving_object_name: String,
151 giving_object_location: sl_types::map::UnconstrainedLocation,
153 giving_object_owner: sl_types::key::OwnerKey,
155 given_object_name: String,
157 },
158 ObjectGaveFolder {
160 giving_object_key: sl_types::key::ObjectKey,
162 giving_object_name: String,
164 giving_object_owner: sl_types::key::OwnerKey,
166 giving_object_location: sl_types::map::Location,
168 giving_object_link_label: String,
170 folder_name: String,
172 },
173 AvatarGaveObject {
175 is_group_member: bool,
177 giving_avatar_name: String,
179 given_object_name: String,
181 },
182 DeclinedGivenObject {
184 object_name: String,
186 giver_location: sl_types::map::UnconstrainedLocation,
188 giver_name: String,
190 },
191 SelectResidentsToShareWith,
193 ItemsSuccessfullyShared,
195 ModifiedSearchQuery {
197 query: String,
199 },
200 SimulatorVersion {
202 previous_region_simulator_version: String,
204 current_region_simulator_version: String,
206 },
207 RenamedAvatar {
209 old_name: String,
211 new_name: String,
213 },
214 DoubleClickTeleport {
216 enabled: bool,
218 },
219 AlwaysRun {
221 enabled: bool,
223 },
224 AddedAsEstateManager,
226 CreatingBridge,
228 BridgeCreated,
230 BridgeCreationInProgress,
233 BridgeFailedToAttach,
235 BridgeFailedToAttachDueToBridgeAttachmentPointInUse,
238 BridgeNotCreated,
240 BridgeDetached,
242 BridgeObjectNotFoundCantProceedWithCreation,
244 FailedToPlaceObjectAtSpecifiedLocation,
246 ScriptCountChanged {
248 previous_script_count: u32,
250 current_script_count: u32,
252 change: i32,
254 },
255 MultiPersonChatMessageStillBeingProcessed,
257 ChatMessageToNoLongerExistingImSessionStillBeingProcessed,
259 ConferenceChatMessageStillBeingProcessed {
261 avatar_name: String,
263 },
264 GroupChatMessageStillBeingProcessed {
266 group_name: String,
268 },
269 AvatarDeclinedVoice {
271 avatar_name: String,
273 },
274 AudioFromDomainWillAlwaysBePlayed {
276 domain: String,
278 },
279 ObjectNotForSale,
281 CanNotCreateRequestedInventory,
283 LinkFailedDueToPieceDistance {
285 link_failed_pieces: Option<usize>,
287 total_selected_pieces: Option<usize>,
289 },
290 RezObjectFailedDueToFullParcel {
292 object_name: String,
294 parcel_name: String,
296 attempted_rez_location: sl_types::map::RegionCoordinates,
298 region_name: sl_types::map::RegionName,
300 },
301 CreateObjectFailedDueToFullRegion,
303 YourObjectHasBeenReturned {
305 object_name: String,
307 parcel_name: String,
309 location: sl_types::map::UnconstrainedLocation,
311 auto_return: bool,
313 },
314 PermissionToCreateObjectDenied,
316 PermissionToRezObjectDenied {
318 object_name: String,
320 parcel_name: String,
322 attempted_rez_location: sl_types::map::RegionCoordinates,
324 region_name: sl_types::map::RegionName,
326 },
327 PermissionToRepositionDenied,
329 PermissionToRotateDenied,
331 PermissionToRescaleDenied,
333 PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions,
335 PermissionToViewScriptDenied,
337 PermissionToViewNotecardDenied,
339 PermissionToChangeShapeDenied,
341 PermissionToEnterParcelDenied,
343 PermissionToEnterParcelDeniedDueToBan,
345 EjectedAvatar,
347 EjectedFromParcel,
349 EjectedFromParcelBecauseNoLongerAllowed,
351 BannedFromParcelTemporarily {
353 ban_duration: time::Duration,
355 },
356 BannedFromParcelIndefinitely,
358 OnlyGroupMembersCanVisitThisArea,
360 UnableToTeleportDueToRlv,
362 UnableToOpenTextureDueToRlv,
364 UnsupportedSlurl,
366 BlockedUntrustedBrowserSlurl,
368 GridStatusErrorInvalidMessageFormat,
370 ScriptInfoObjectInvalidOrOutOfRange,
372 ScriptInfo {
374 name: String,
376 running_scripts: usize,
378 total_scripts: usize,
380 allowed_memory_size_limit: bytesize::ByteSize,
382 cpu_time_consumed: time::Duration,
384 },
385 ExtendedScriptInfo {
387 object_key: sl_types::key::ObjectKey,
389 description: Option<String>,
391 root_prim: sl_types::key::ObjectKey,
393 prim_count: usize,
395 land_impact: usize,
397 inventory_items: usize,
399 velocity: sl_types::lsl::Vector,
401 position: sl_types::map::RegionCoordinates,
403 position_distance: sl_types::map::Distance,
405 rotation: sl_types::lsl::Rotation,
407 rotation_vector_degrees: sl_types::lsl::Vector,
409 angular_velocity: sl_types::lsl::Vector,
411 creator: sl_types::key::AgentKey,
413 owner: sl_types::key::OwnerKey,
415 previous_owner: Option<sl_types::key::OwnerKey>,
417 rezzed_by: sl_types::key::AgentKey,
419 group: Option<sl_types::key::GroupKey>,
421 creation_time: Option<time::OffsetDateTime>,
423 rez_time: Option<time::OffsetDateTime>,
425 pathfinding_type: sl_types::pathfinding::PathfindingType,
427 attachment_point: Option<sl_types::attachment::AttachmentPoint>,
429 temporarily_attached: bool,
431 inspecting_avatar_position: sl_types::map::RegionCoordinates,
433 },
434 DiceRollCommandUsageInstructions,
436 DiceRollResult {
438 roll_number: usize,
440 dice_faces: usize,
442 roll_result: usize,
444 },
445 DiceRollResultSum {
447 roll_count: usize,
449 dice_faces: usize,
451 result_sum: usize,
453 },
454 TextureInfoForObject {
456 object_name: String,
458 },
459 TextureInfoForFace {
461 face_number: usize,
463 texture_width: u16,
465 texture_height: u16,
467 texture_type: String,
469 },
470 FirestormMessage {
472 message_type: String,
475 message: String,
477 },
478 GridStatusEvent {
480 title: String,
482 scheduled: bool,
484 body: String,
486 incident_url: String,
488 },
489 SystemMessageWithLink {
492 message: String,
494 link: String,
496 },
497 FirestormHolidayWishes {
499 message: String,
501 },
502 PhishingWarning {
504 message: String,
506 },
507 TestMessageOfTheDay,
509 EarlyFirestormStartupMessage {
511 message: String,
513 },
514 OtherSystemMessage {
516 message: String,
518 },
519}
520
521#[must_use]
527pub fn snapshot_saved_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
528 just("Snapshot saved: ")
529 .ignore_then(
530 any()
531 .repeated()
532 .collect::<String>()
533 .map(std::path::PathBuf::from),
534 )
535 .map(|filename| SystemMessage::SavedSnapshot { filename })
536 .or(just("Failed to save snapshot to ").ignore_then(
537 take_until(just(": Directory does not exist.").ignored()).map(|(folder, _)| {
538 SystemMessage::FailedToSaveSnapshotDueToMissingDestinationFolder {
539 folder: std::path::PathBuf::from(folder.into_iter().collect::<String>()),
540 }
541 }),
542 ))
543 .or(just("Failed to save snapshot to ").ignore_then(
544 take_until(just(": Disk is full. ").ignored())
545 .map(|(folder, _)| std::path::PathBuf::from(folder.into_iter().collect::<String>()))
546 .then(u64_parser())
547 .then_ignore(just("KB is required but only "))
548 .then(u64_parser())
549 .then_ignore(just("KB is free."))
550 .map(|((folder, required), free)| {
551 let required_disk_space = bytesize::ByteSize::kib(required);
552 let free_disk_space = bytesize::ByteSize::kib(free);
553 SystemMessage::FailedToSaveSnapshotDueToDiskSpace {
554 folder,
555 required_disk_space,
556 free_disk_space,
557 }
558 }),
559 ))
560}
561
562#[must_use]
568pub fn draw_distance_set_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
569{
570 just("Draw Distance set to ")
571 .ignore_then(sl_types::map::distance_parser())
572 .then_ignore(just('.'))
573 .map(|distance| SystemMessage::DrawDistanceSet { distance })
574}
575
576#[must_use]
582pub fn home_position_set_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
583{
584 just("Home position set.").to(SystemMessage::HomePositionSet)
585}
586
587#[must_use]
593pub fn land_divided_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
594 just("Land has been divided.").to(SystemMessage::LandDivided)
595}
596
597#[must_use]
603pub fn failed_to_join_land_due_to_region_boundary_message_parser(
604) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
605 just("Selected land is not all in the same region.")
606 .ignore_then(newline())
607 .ignore_then(just(" Try selecting a smaller piece of land."))
608 .to(SystemMessage::FailedToJoinLandDueToRegionBoundary)
609}
610
611#[must_use]
617pub fn offered_calling_card_message_parser(
618) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
619 just("You have offered a calling card to ")
620 .ignore_then(take_until(just('.')).map(|(vc, _)| vc.into_iter().collect::<String>()))
621 .map(|recipient_avatar_name| SystemMessage::OfferedCallingCard {
622 recipient_avatar_name,
623 })
624}
625
626#[must_use]
632pub fn attachment_saved_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
633 just("Attachment has been saved").to(SystemMessage::AttachmentSavedMessage)
634}
635
636#[must_use]
642pub fn you_paid_for_object_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
643{
644 just("You paid ")
645 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
646 .then_ignore(whitespace())
647 .then(sl_types::money::linden_amount_parser())
648 .then_ignore(just(" for "))
649 .then(take_until(just(".")).map(|(vc, _)| vc.into_iter().collect::<String>()))
650 .map(
651 |((seller, amount), object_name)| SystemMessage::YouPaidForObject {
652 seller,
653 amount,
654 object_name,
655 },
656 )
657}
658
659#[must_use]
665pub fn sent_payment_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
666 just("You paid ").ignore_then(
667 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
668 .then_ignore(whitespace())
669 .then(sl_types::money::linden_amount_parser())
670 .then(
671 just(": ")
672 .ignore_then(any().repeated().collect::<String>())
673 .ignore_then(take_until(newline().or(end())).map(|(n, _)| Some(n)))
674 .or(just(".").map(|_| None)),
675 )
676 .map(
677 |((recipient_key, amount), message)| SystemMessage::SentPayment {
678 recipient_key,
679 amount,
680 message: message.map(|n| n.into_iter().collect()),
681 },
682 ),
683 )
684}
685
686#[must_use]
692pub fn received_payment_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
693 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
694 .then_ignore(just(" paid you "))
695 .then(sl_types::money::linden_amount_parser())
696 .then(
697 just(": ")
698 .ignore_then(any().repeated().collect::<String>())
699 .ignore_then(take_until(newline().or(end())).map(|(n, _)| Some(n)))
700 .or(just(".").map(|_| None)),
701 )
702 .map(
703 |((sender_key, amount), message)| SystemMessage::ReceivedPayment {
704 sender_key,
705 amount,
706 message: message.map(|n| n.into_iter().collect()),
707 },
708 )
709}
710
711#[must_use]
717pub fn you_paid_to_create_a_group_message_parser(
718) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
719 just("You paid ")
720 .ignore_then(sl_types::key::app_agent_uri_as_agent_key_parser())
721 .then_ignore(whitespace())
722 .then(sl_types::money::linden_amount_parser())
723 .then_ignore(just(" to create a group."))
724 .map(
725 |(payment_recipient, amount)| SystemMessage::YouPaidToCreateGroup {
726 payment_recipient,
727 amount,
728 },
729 )
730}
731
732#[must_use]
738pub fn you_paid_to_join_group_message_parser(
739) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
740 just("You paid ")
741 .ignore_then(sl_types::key::app_group_uri_as_group_key_parser())
742 .then_ignore(whitespace())
743 .then(sl_types::money::linden_amount_parser())
744 .then_ignore(just(" to join a group."))
745 .map(
746 |(joined_group, join_fee)| SystemMessage::YouPaidToJoinGroup {
747 joined_group,
748 join_fee,
749 },
750 )
751}
752
753#[must_use]
759pub fn you_paid_for_land_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
760{
761 just("You paid ")
762 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
763 .then_ignore(whitespace())
764 .then(sl_types::money::linden_amount_parser())
765 .then_ignore(just(" for a parcel of land."))
766 .map(
767 |(previous_land_owner, amount)| SystemMessage::YouPaidForLand {
768 previous_land_owner,
769 amount,
770 },
771 )
772}
773
774#[must_use]
780pub fn failed_to_pay_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
781 just("You failed to pay ")
782 .ignore_then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
783 .then_ignore(whitespace())
784 .then(sl_types::money::linden_amount_parser())
785 .then_ignore(just('.'))
786 .map(|(payment_recipient, amount)| SystemMessage::FailedToPay {
787 payment_recipient,
788 amount,
789 })
790}
791
792#[must_use]
798pub fn object_granted_permission_to_take_money_parser(
799) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
800 just('\'')
801 .ignore_then(
802 take_until(just("', an object owned by '"))
803 .map(|(vc, _)| vc.into_iter().collect::<String>()),
804 )
805 .then(take_until(just("', located in ")).map(|(vc, _)| vc.into_iter().collect::<String>()))
806 .then(
807 just("(unknown region) at ")
808 .to(None)
809 .or(take_until(just(" at ")).try_map(|(vc, _), span| {
810 Ok(Some(
811 sl_types::map::RegionName::try_new(vc.into_iter().collect::<String>())
812 .map_err(|err| {
813 Simple::custom(
814 span,
815 format!("Error creating region name: {:?}", err),
816 )
817 })?,
818 ))
819 })),
820 )
821 .then(
822 just("(unknown position)")
823 .to(None)
824 .or(sl_types::utils::f32_parser()
825 .then_ignore(just(", "))
826 .then(sl_types::utils::f32_parser())
827 .then_ignore(just(','))
828 .then(sl_types::utils::f32_parser())
829 .map(|((x, y), z)| Some(sl_types::map::RegionCoordinates::new(x, y, z)))),
830 )
831 .then_ignore(just(
832 ", has been granted permission to: Take Linden dollars (L$) from you.",
833 ))
834 .map(
835 |(((object_name, owner_name), object_region), object_location)| {
836 SystemMessage::ObjectGrantedPermissionToTakeMoney {
837 object_name,
838 owner_name,
839 object_region,
840 object_location,
841 }
842 },
843 )
844}
845
846#[must_use]
852pub fn group_membership_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
853 just("You have been added to the group.")
854 .to(SystemMessage::AddedToGroup)
855 .or(just("You have left the group '")
856 .ignore_then(take_until(just("'.")).map(|(vc, _)| vc.into_iter().collect::<String>()))
857 .map(|group_name| SystemMessage::LeftGroup { group_name }))
858}
859
860#[must_use]
867pub fn unable_to_invite_user_due_to_missing_group_membership_message_parser(
868) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
869 just("Unable to invite user because you are not in that group.")
870 .to(SystemMessage::UnableToInviteUserDueToMissingGroupMembership)
871}
872
873#[must_use]
880pub fn unable_to_invite_user_due_to_differing_limited_estate_message_parser(
881) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
882 just("Unable to invite users because at least one user is in a different")
883 .ignore_then(newline())
884 .ignore_then(just(" limited estate than the group."))
885 .to(SystemMessage::UnableToInviteUserToGroupDueToDifferingLimitedEstate)
886}
887
888#[must_use]
894pub fn unable_to_load_notecard_message_parser(
895) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
896 just("Unable to load the notecard.")
897 .then_ignore(newline())
898 .then_ignore(whitespace())
899 .then(just("Please try again."))
900 .to(SystemMessage::UnableToLoadNotecard)
901}
902
903#[must_use]
909pub fn unable_to_load_gesture_message_parser(
910) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
911 just("Unable to load gesture ")
912 .ignore_then(
913 take_until(just('.').then(end())).map(|(vc, _)| vc.into_iter().collect::<String>()),
914 )
915 .map(|gesture_name| SystemMessage::UnableToLoadGesture { gesture_name })
916}
917
918#[must_use]
924pub fn teleport_completed_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
925{
926 just("Teleport completed from http://maps.secondlife.com/secondlife/")
927 .ignore_then(sl_types::map::url_unconstrained_location_parser())
928 .try_map(|origin, _span: std::ops::Range<usize>| {
929 Ok(SystemMessage::TeleportCompleted { origin })
930 })
931}
932
933#[must_use]
939pub fn now_playing_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
940 just("Now playing: ")
941 .ignore_then(any().repeated().collect::<String>())
942 .try_map(|song_name, _span: std::ops::Range<usize>| {
943 Ok(SystemMessage::NowPlaying { song_name })
944 })
945}
946
947#[must_use]
953pub fn region_restart_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
954 just("The region you are in now is about to restart. If you stay in this region you will be logged out.")
955 .try_map(|_, _span: std::ops::Range<usize>| {
956 Ok(SystemMessage::RegionRestart)
957 })
958}
959
960#[must_use]
966pub fn object_gave_object_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
967{
968 take_until(just(" owned by "))
969 .then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
970 .then_ignore(
971 whitespace()
972 .or_not()
973 .ignore_then(just("gave you ").then(just("<nolink>'").or_not())),
974 )
975 .then(take_until(
976 just("</nolink>'")
977 .or_not()
978 .then(whitespace())
979 .then(just("( http://slurl.com/secondlife/")),
980 ))
981 .then(sl_types::map::url_unconstrained_location_parser())
982 .then_ignore(just(" )."))
983 .map(
984 |(
985 (((giving_object_name, _), giving_object_owner), (given_object_name, _)),
986 giving_object_location,
987 )| {
988 SystemMessage::ObjectGaveObject {
989 giving_object_name: giving_object_name.into_iter().collect(),
990 giving_object_owner,
991 given_object_name: given_object_name.into_iter().collect(),
992 giving_object_location,
993 }
994 },
995 )
996}
997
998#[must_use]
1004pub fn object_gave_folder_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1005{
1006 just("An object named [")
1007 .ignore_then(sl_types::viewer_uri::viewer_app_objectim_uri_parser())
1008 .then_ignore(whitespace())
1009 .then(
1010 take_until(just("] gave you this folder: '"))
1011 .map(|(vc, _)| vc.into_iter().collect::<String>()),
1012 )
1013 .then(take_until(just('\'')).map(|(vc, _)| vc.into_iter().collect::<String>()))
1014 .try_map(
1015 |((app_objectim_uri, giving_object_link_label), folder_name), span| {
1016 match app_objectim_uri {
1017 sl_types::viewer_uri::ViewerUri::ObjectInstantMessage {
1018 object_key,
1019 object_name,
1020 owner,
1021 location,
1022 } => Ok(SystemMessage::ObjectGaveFolder {
1023 giving_object_key: object_key,
1024 giving_object_name: object_name,
1025 giving_object_owner: owner,
1026 giving_object_location: location,
1027 giving_object_link_label,
1028 folder_name,
1029 }),
1030 _ => Err(Simple::custom(
1031 span,
1032 "Unexpected type of viewer URI in object gave folder message parser",
1033 )),
1034 }
1035 },
1036 )
1037}
1038
1039#[must_use]
1045pub fn avatar_gave_object_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1046{
1047 just("A group member named ")
1048 .or_not()
1049 .then(take_until(just(" gave you ")))
1050 .then(take_until(just(".")))
1051 .try_map(
1052 |((group_member, (giving_avatar_name, _)), (given_object_name, _)),
1053 _span: std::ops::Range<usize>| {
1054 Ok(SystemMessage::AvatarGaveObject {
1055 is_group_member: group_member.is_some(),
1056 giving_avatar_name: giving_avatar_name.into_iter().collect(),
1057 given_object_name: given_object_name.into_iter().collect(),
1058 })
1059 },
1060 )
1061}
1062
1063#[must_use]
1069pub fn declined_given_object_message_parser(
1070) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1071 just("You decline '")
1072 .ignore_then(
1073 take_until(just("' ( http://slurl.com/secondlife/").ignored())
1074 .map(|(vc, _)| vc.into_iter().collect::<String>()),
1075 )
1076 .then(sl_types::map::url_unconstrained_location_parser())
1077 .then_ignore(just(" ) from "))
1078 .then(
1079 any()
1080 .repeated()
1081 .collect::<String>()
1082 .map(|s| s.strip_suffix(".").map(|s| s.to_string()).unwrap_or(s)),
1083 )
1084 .map(
1085 |((object_name, giver_location), giver_name)| SystemMessage::DeclinedGivenObject {
1086 object_name,
1087 giver_location,
1088 giver_name,
1089 },
1090 )
1091}
1092
1093#[must_use]
1099pub fn select_residents_to_share_with_message_parser(
1100) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1101 just("Select residents to share with.").to(SystemMessage::SelectResidentsToShareWith)
1102}
1103
1104#[must_use]
1110pub fn items_successfully_shared_message_parser(
1111) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1112 just("Items successfully shared.").to(SystemMessage::ItemsSuccessfullyShared)
1113}
1114
1115#[must_use]
1121pub fn modified_search_query_message_parser(
1122) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1123 just("Your search query was modified and the words that were too short were removed.")
1124 .ignore_then(whitespace())
1125 .ignore_then(just("Searched for:"))
1126 .ignore_then(whitespace())
1127 .ignore_then(any().repeated().collect::<String>())
1128 .try_map(|query, _span: std::ops::Range<usize>| {
1129 Ok(SystemMessage::ModifiedSearchQuery { query })
1130 })
1131}
1132
1133#[must_use]
1139pub fn simulator_version_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1140{
1141 just("The region you have entered is running a different simulator version.")
1142 .ignore_then(whitespace())
1143 .ignore_then(just("Current simulator:"))
1144 .ignore_then(whitespace())
1145 .ignore_then(take_until(just("\n")).map(|(s, _): (Vec<char>, _)| s.into_iter().collect()))
1146 .then_ignore(whitespace())
1147 .then_ignore(just("Previous simulator:"))
1148 .then_ignore(whitespace())
1149 .then(any().repeated().collect::<String>())
1150 .try_map(
1151 |(current_region_simulator_version, previous_region_simulator_version),
1152 _span: std::ops::Range<usize>| {
1153 Ok(SystemMessage::SimulatorVersion {
1154 previous_region_simulator_version,
1155 current_region_simulator_version,
1156 })
1157 },
1158 )
1159}
1160
1161#[must_use]
1167pub fn renamed_avatar_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1168 take_until(just(" is now known as"))
1169 .map(|(s, _)| s.into_iter().collect())
1170 .then_ignore(whitespace())
1171 .then(take_until(just(".")).map(|(s, _): (Vec<char>, _)| s.into_iter().collect()))
1172 .try_map(|(old_name, new_name), _span: std::ops::Range<usize>| {
1173 Ok(SystemMessage::RenamedAvatar { old_name, new_name })
1174 })
1175}
1176
1177#[must_use]
1183pub fn doubleclick_teleport_message_parser(
1184) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1185 just("DoubleClick Teleport enabled.")
1186 .to(SystemMessage::DoubleClickTeleport { enabled: true })
1187 .or(just("DoubleClick Teleport disabled.")
1188 .to(SystemMessage::DoubleClickTeleport { enabled: false }))
1189}
1190
1191#[must_use]
1197pub fn always_run_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1198 just("Always Run enabled.")
1199 .to(SystemMessage::AlwaysRun { enabled: true })
1200 .or(just("Always Run disabled.").to(SystemMessage::AlwaysRun { enabled: false }))
1201}
1202
1203#[must_use]
1209pub fn added_as_estate_manager_message_parser(
1210) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1211 just("You have been added as an estate manager.").to(SystemMessage::AddedAsEstateManager)
1212}
1213
1214#[must_use]
1220pub fn bridge_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1221 choice([
1222 just("Creating the bridge. This might take a moment, please wait.").to(SystemMessage::CreatingBridge).boxed(),
1223 just("Creating the bridge. This might take a few moments, please wait").to(SystemMessage::CreatingBridge).boxed(),
1224 just("Bridge created.").to(SystemMessage::BridgeCreated).boxed(),
1225 just("Bridge creation in process, cannot start another. Please wait a few minutes before trying again.").to(SystemMessage::BridgeCreationInProgress).boxed(),
1226 just("Bridge object not found. Can't proceed with creation, exiting.").to(SystemMessage::BridgeObjectNotFoundCantProceedWithCreation).boxed(),
1227 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(),
1228 just("Bridge failed to attach. Something else was using the bridge attachment point. Please try to recreate the bridge.").to(SystemMessage::BridgeFailedToAttachDueToBridgeAttachmentPointInUse).boxed(),
1229 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(),
1230 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(),
1231 just("Bridge detached.").to(SystemMessage::BridgeDetached).boxed(),
1232 ])
1233}
1234
1235#[must_use]
1242pub fn failed_to_place_object_at_specified_location_message_parser(
1243) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1244 just("Failed to place object at specified location. Please try again.")
1245 .to(SystemMessage::FailedToPlaceObjectAtSpecifiedLocation)
1246}
1247
1248#[must_use]
1254pub fn region_script_count_change_message_parser(
1255) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1256 just("Total scripts in region ")
1257 .ignore_then(just("jumped from ").or(just("dropped from ")))
1258 .ignore_then(
1259 digits(10)
1260 .then_ignore(just(" to "))
1261 .then(digits(10))
1262 .then_ignore(just(" ("))
1263 .then(one_of("+-"))
1264 .then(digits(10))
1265 .then_ignore(just(")."))
1266 .try_map(
1267 |(((previous_script_count, current_script_count), sign), diff): (
1268 ((String, String), char),
1269 String,
1270 ),
1271 span: std::ops::Range<usize>| {
1272 let previous_span = span.clone();
1273 let previous_script_count =
1274 previous_script_count.parse().map_err(|err| {
1275 Simple::custom(
1276 previous_span,
1277 format!(
1278 "Could not parse previous script count ({}) as u32: {:?}",
1279 previous_script_count, err
1280 ),
1281 )
1282 })?;
1283 let current_span = span.clone();
1284 let current_script_count = current_script_count.parse().map_err(|err| {
1285 Simple::custom(
1286 current_span,
1287 format!(
1288 "Could not parse current script count ({}) as u32: {:?}",
1289 current_script_count, err
1290 ),
1291 )
1292 })?;
1293 let diff_span = span.clone();
1294 let diff: i32 = diff.parse().map_err(|err| {
1295 Simple::custom(
1296 diff_span,
1297 format!(
1298 "Could not parse changed script count ({}) as i32: {:?}",
1299 diff, err
1300 ),
1301 )
1302 })?;
1303 let change = match sign {
1304 '+' => diff,
1305 '-' => -diff,
1306 c => {
1307 return Err(Simple::custom(
1308 span,
1309 format!("Unexpected sign character for script change: {}", c),
1310 ))
1311 }
1312 };
1313 Ok(SystemMessage::ScriptCountChanged {
1314 previous_script_count,
1315 current_script_count,
1316 change,
1317 })
1318 },
1319 ),
1320 )
1321}
1322
1323#[must_use]
1329pub fn chat_message_still_being_processed_message_parser(
1330) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1331 just("The message sent to ")
1332 .ignore_then(
1333 choice([
1334 just("Multi-person chat is still being processed.").to(SystemMessage::MultiPersonChatMessageStillBeingProcessed).boxed(),
1335 just("(IM Session Doesn't Exist) is still being processed.").to(SystemMessage::ChatMessageToNoLongerExistingImSessionStillBeingProcessed).boxed(),
1336 just("Conference with ").ignore_then(take_until(just(" is still being processed.")).map(|(vc, _)| SystemMessage::ConferenceChatMessageStillBeingProcessed { avatar_name: vc.into_iter().collect::<String>() })).boxed(),
1337 take_until(just(" is still being processed.").ignored()).map(|(vc, _)| vc.into_iter().collect::<String>())
1338 .map(|group_name| {
1339 SystemMessage::GroupChatMessageStillBeingProcessed {
1340 group_name,
1341 }
1342 }).boxed(),
1343 ])
1344 )
1345 .then_ignore(newline())
1346 .then_ignore(whitespace())
1347 .then_ignore(just("If the message does not appear in the next few minutes, it may have been dropped by the server."))
1348}
1349
1350#[must_use]
1356pub fn avatar_declined_voice_call_message_parser(
1357) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1358 take_until(just(
1359 "has declined your call. You will now be reconnected to Nearby Voice Chat.",
1360 ))
1361 .map(|(vc, _)| SystemMessage::AvatarDeclinedVoice {
1362 avatar_name: vc.into_iter().collect::<String>(),
1363 })
1364}
1365
1366#[must_use]
1372pub fn audio_from_domain_will_always_be_played_message_parser(
1373) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1374 just("Audio from the domain ").ignore_then(take_until(just(" will always be played.")).map(
1375 |(vc, _)| SystemMessage::AudioFromDomainWillAlwaysBePlayed {
1376 domain: vc.into_iter().collect::<String>(),
1377 },
1378 ))
1379}
1380
1381#[must_use]
1387pub fn object_not_for_sale_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1388{
1389 just("This object is not for sale.").to(SystemMessage::ObjectNotForSale)
1390}
1391
1392#[must_use]
1398pub fn can_not_create_requested_inventory_message_parser(
1399) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1400 just("Cannot create requested inventory.").to(SystemMessage::CanNotCreateRequestedInventory)
1401}
1402
1403#[must_use]
1409pub fn link_failed_due_to_piece_distance_message_parser(
1410) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1411 just("Link failed -- Unable to link any pieces - pieces are too far apart.")
1412 .to(SystemMessage::LinkFailedDueToPieceDistance {
1413 link_failed_pieces: None,
1414 total_selected_pieces: None,
1415 })
1416 .or(just("Link failed -- Unable to link ").ignore_then(
1417 usize_parser()
1418 .then_ignore(just(" of the "))
1419 .then(usize_parser())
1420 .then_ignore(just(" selected pieces - pieces are too far apart."))
1421 .map(|(link_failed_pieces, total_selected_pieces)| {
1422 SystemMessage::LinkFailedDueToPieceDistance {
1423 link_failed_pieces: Some(link_failed_pieces),
1424 total_selected_pieces: Some(total_selected_pieces),
1425 }
1426 }),
1427 ))
1428}
1429
1430#[must_use]
1437pub fn rezzing_object_failed_due_to_full_parcel_message_parser(
1438) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1439 just("Can't rez object '").ignore_then(
1440 take_until(just("' at ").ignored())
1441 .map(|(vc, _)| vc.into_iter().collect::<String>())
1442 .then(sl_types::map::region_coordinates_parser())
1443 .then_ignore(just(" on parcel '"))
1444 .then(
1445 take_until(just("' in region ").ignored())
1446 .map(|(vc, _)| vc.into_iter().collect::<String>()),
1447 )
1448 .then(
1449 take_until(just(" because the parcel is too full").ignored())
1450 .map(|(vc, _)| vc.into_iter().collect::<String>())
1451 .try_map(|region_name, span| {
1452 sl_types::map::RegionName::try_new(®ion_name).map_err(|err| {
1453 Simple::custom(
1454 span,
1455 format!(
1456 "Could not turn parsed region name ({}) into RegionName: {:?}",
1457 region_name, err
1458 ),
1459 )
1460 })
1461 }),
1462 )
1463 .map(
1464 |(((object_name, attempted_rez_location), parcel_name), region_name)| {
1465 SystemMessage::RezObjectFailedDueToFullParcel {
1466 object_name,
1467 attempted_rez_location,
1468 parcel_name,
1469 region_name,
1470 }
1471 },
1472 ),
1473 )
1474}
1475
1476#[must_use]
1483pub fn create_object_failed_due_to_full_region_message_parser(
1484) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1485 just("Unable to create requested object. The region is full.")
1486 .to(SystemMessage::CreateObjectFailedDueToFullRegion)
1487}
1488
1489#[must_use]
1495pub fn your_object_has_been_returned_message_parser(
1496) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1497 just("Your object '")
1498 .ignore_then(
1499 take_until(just(
1500 "' has been returned to your inventory Lost and Found folder from parcel '",
1501 ))
1502 .map(|(vc, _)| vc.into_iter().collect::<String>()),
1503 )
1504 .then(take_until(just("' at ")).map(|(vc, _)| vc.into_iter().collect::<String>()))
1505 .then(sl_types::map::region_name_parser())
1506 .then_ignore(whitespace())
1507 .then(sl_types::utils::i16_parser())
1508 .then_ignore(just(", "))
1509 .then(sl_types::utils::i16_parser())
1510 .then(
1511 just(" due to parcel auto return.")
1512 .to(true)
1513 .or(just('.').to(false)),
1514 )
1515 .map(
1516 |(((((object_name, parcel_name), region_name), x), y), auto_return)| {
1517 SystemMessage::YourObjectHasBeenReturned {
1518 object_name,
1519 parcel_name,
1520 location: sl_types::map::UnconstrainedLocation::new(region_name, x, y, 0),
1521 auto_return,
1522 }
1523 },
1524 )
1525}
1526
1527#[must_use]
1533pub fn permission_to_create_object_denied_message_parser(
1534) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1535 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)
1536}
1537
1538#[must_use]
1544pub fn permission_to_rez_object_denied_message_parser(
1545) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1546 just("Can't rez object '")
1547 .ignore_then(
1548 take_until(just("' at ").ignored()).map(|(vc, _)| vc.into_iter().collect::<String>())
1549 .then(sl_types::map::region_coordinates_parser())
1550 .then_ignore(just(" on parcel '"))
1551 .then(take_until(just("' in region ").ignored()).map(|(vc, _)| vc.into_iter().collect::<String>()))
1552 .then(take_until(just(" because the owner of this land does not allow it. Use the land tool to see land ownership.").ignored()).map(|(vc, _)| vc.into_iter().collect::<String>()).try_map(|region_name, span| {
1553 sl_types::map::RegionName::try_new(®ion_name).map_err(|err| Simple::custom(span, format!("Could not turn parsed region name ({}) into RegionName: {:?}", region_name, err)))
1554 }))
1555 .map(|(((object_name, attempted_rez_location), parcel_name), region_name)| {
1556 SystemMessage::PermissionToRezObjectDenied {
1557 object_name,
1558 attempted_rez_location,
1559 parcel_name,
1560 region_name,
1561 }
1562 })
1563 )
1564}
1565
1566#[must_use]
1572pub fn permission_to_reposition_denied_message_parser(
1573) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1574 just("Can't reposition -- permission denied").to(SystemMessage::PermissionToRepositionDenied)
1575}
1576
1577#[must_use]
1583pub fn permission_to_rotate_denied_message_parser(
1584) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1585 just("Can't rotate -- permission denied").to(SystemMessage::PermissionToRotateDenied)
1586}
1587
1588#[must_use]
1594pub fn permission_to_rescale_denied_message_parser(
1595) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1596 just("Can't rescale -- permission denied").to(SystemMessage::PermissionToRescaleDenied)
1597}
1598
1599#[must_use]
1606pub fn permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser(
1607) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1608 just("Failed to unlink because you do not have permissions to build on all parcels")
1609 .to(SystemMessage::PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions)
1610}
1611
1612#[must_use]
1618pub fn permission_to_view_script_denied_message_parser(
1619) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1620 just("Insufficient permissions to view the script.")
1621 .to(SystemMessage::PermissionToViewScriptDenied)
1622}
1623
1624#[must_use]
1630pub fn permission_to_view_notecard_denied_message_parser(
1631) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1632 just("You do not have permission to view this notecard.")
1633 .to(SystemMessage::PermissionToViewNotecardDenied)
1634}
1635
1636#[must_use]
1642pub fn permission_to_change_shape_denied_message_parser(
1643) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1644 just("You are not allowed to change this shape.")
1645 .to(SystemMessage::PermissionToChangeShapeDenied)
1646}
1647
1648#[must_use]
1654pub fn permission_to_enter_parcel_denied_message_parser(
1655) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1656 just("Cannot enter parcel, you are not on the access list.")
1657 .to(SystemMessage::PermissionToEnterParcelDenied)
1658}
1659
1660#[must_use]
1666pub fn permission_to_enter_parcel_denied_due_to_ban_message_parser(
1667) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1668 just("Cannot enter parcel, you have been banned.")
1669 .to(SystemMessage::PermissionToEnterParcelDeniedDueToBan)
1670}
1671
1672#[must_use]
1678pub fn avatar_ejected_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1679 just("Avatar ejected.").to(SystemMessage::EjectedAvatar)
1680}
1681
1682#[must_use]
1688pub fn ejected_from_parcel_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1689{
1690 just("You have been ejected from this land.")
1691 .to(SystemMessage::EjectedFromParcel)
1692 .or(
1693 just("You are no longer allowed here and have been ejected.")
1694 .to(SystemMessage::EjectedFromParcelBecauseNoLongerAllowed),
1695 )
1696}
1697
1698#[must_use]
1704pub fn banned_from_parcel_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1705{
1706 just("You have been banned ").ignore_then(
1707 just("indefinitely")
1708 .to(SystemMessage::BannedFromParcelIndefinitely)
1709 .or(just("for ")
1710 .ignore_then(i64_parser().then_ignore(just(" minutes")))
1711 .map(|d| SystemMessage::BannedFromParcelTemporarily {
1712 ban_duration: time::Duration::minutes(d),
1713 })),
1714 )
1715}
1716
1717#[must_use]
1723pub fn only_group_members_can_visit_this_area_message_parser(
1724) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1725 just("Only members of a certain group can visit this area.")
1726 .to(SystemMessage::OnlyGroupMembersCanVisitThisArea)
1727}
1728
1729#[must_use]
1735pub fn unable_to_teleport_due_to_rlv_message_parser(
1736) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1737 just("Unable to initiate teleport due to RLV restrictions")
1738 .to(SystemMessage::UnableToTeleportDueToRlv)
1739}
1740
1741#[must_use]
1747pub fn unable_to_open_texture_due_to_rlv_message_parser(
1748) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1749 just("Unable to open texture due to RLV restrictions")
1750 .to(SystemMessage::UnableToOpenTextureDueToRlv)
1751}
1752
1753#[must_use]
1759pub fn unsupported_slurl_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
1760{
1761 just("The SLurl you clicked on is not supported.").to(SystemMessage::UnsupportedSlurl)
1762}
1763
1764#[must_use]
1770pub fn blocked_untrusted_browser_slurl_message_parser(
1771) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1772 just("A SLurl was received from an untrusted browser and has been blocked for your security")
1773 .to(SystemMessage::BlockedUntrustedBrowserSlurl)
1774}
1775
1776#[must_use]
1782pub fn grid_status_error_invalid_message_format_message_parser(
1783) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1784 just("SL Grid Status error: Invalid message format. Try again later.")
1785 .to(SystemMessage::GridStatusErrorInvalidMessageFormat)
1786}
1787
1788#[must_use]
1794pub fn script_info_object_invalid_or_out_of_range_message_parser(
1795) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1796 just("Script info: Object to check is invalid or out of range.")
1797 .to(SystemMessage::ScriptInfoObjectInvalidOrOutOfRange)
1798}
1799
1800#[must_use]
1806pub fn script_info_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1807 just("Script info: '").ignore_then(
1808 take_until(just("': [").ignored())
1809 .map(|(vc, _)| vc.into_iter().collect::<String>())
1810 .then(usize_parser())
1811 .then_ignore(just('/'))
1812 .then(usize_parser())
1813 .then_ignore(just("] running scripts, "))
1814 .then(u64_parser().map(bytesize::ByteSize::kb))
1815 .then_ignore(just(" KB allowed memory size limit, "))
1816 .then(unsigned_f32_parser().map(|ms| time::Duration::seconds_f32(ms / 1000f32)))
1817 .then_ignore(just(" ms of CPU time consumed."))
1818 .map(
1819 |(
1820 (((name, running_scripts), total_scripts), allowed_memory_size_limit),
1821 cpu_time_consumed,
1822 )| {
1823 SystemMessage::ScriptInfo {
1824 name,
1825 running_scripts,
1826 total_scripts,
1827 allowed_memory_size_limit,
1828 cpu_time_consumed,
1829 }
1830 },
1831 ),
1832 )
1833}
1834
1835#[must_use]
1843pub fn extended_script_info_message_parser(
1844) -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1845 just("Object ID: ")
1846 .ignore_then(sl_types::key::object_key_parser())
1847 .then_ignore(newline())
1848 .then_ignore(just(" Description:"))
1849 .then_ignore(just(" ").or_not())
1850 .then(just("(No Description)").then_ignore(newline()).to(None).or(
1851 take_until(newline().ignored()).map(|(vc, _)| Some(vc.into_iter().collect::<String>())),
1852 ))
1853 .then_ignore(just(" Root prim: "))
1854 .then(sl_types::key::object_key_parser())
1855 .then_ignore(newline())
1856 .then_ignore(just(" Prim count: "))
1857 .then(sl_types::utils::usize_parser())
1858 .then_ignore(newline())
1859 .then_ignore(just(" Land impact: "))
1860 .then(sl_types::utils::usize_parser())
1861 .then_ignore(newline())
1862 .then_ignore(just(" Inventory items: "))
1863 .then(sl_types::utils::usize_parser())
1864 .then_ignore(newline())
1865 .then_ignore(just(" Velocity: "))
1866 .then(sl_types::lsl::vector_parser())
1867 .then_ignore(newline())
1868 .then_ignore(just(" Position: "))
1869 .then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
1870 .then_ignore(whitespace())
1871 .then(sl_types::map::distance_parser().delimited_by(just('('), just(')')))
1872 .then_ignore(newline())
1873 .then_ignore(just(" Rotation: "))
1874 .then(sl_types::lsl::rotation_parser())
1875 .then_ignore(whitespace())
1876 .then(sl_types::lsl::vector_parser().delimited_by(just('('), just(')')))
1877 .then_ignore(newline())
1878 .then_ignore(just(" Angular velocity: "))
1879 .then(sl_types::lsl::vector_parser())
1880 .then_ignore(whitespace())
1881 .then_ignore(just("(radians per second)"))
1882 .then_ignore(newline())
1883 .then_ignore(just(" Creator: "))
1884 .then(sl_types::key::app_agent_uri_as_agent_key_parser())
1885 .then_ignore(newline())
1886 .then_ignore(just(" Owner: "))
1887 .then(sl_types::key::app_agent_or_group_uri_as_owner_key_parser())
1888 .then_ignore(newline())
1889 .then_ignore(just(" Previous owner: "))
1890 .then(
1891 sl_types::key::app_agent_or_group_uri_as_owner_key_parser()
1892 .map(Some)
1893 .or(just("---").to(None)),
1894 )
1895 .then_ignore(newline())
1896 .then_ignore(just(" Rezzed by: "))
1897 .then(sl_types::key::agent_key_parser())
1898 .then_ignore(newline())
1899 .then_ignore(just(" Group: "))
1900 .then(
1901 sl_types::key::app_group_uri_as_group_key_parser()
1902 .map(Some)
1903 .or(just("---").to(None)),
1904 )
1905 .then_ignore(newline())
1906 .then_ignore(just(" Creation time:"))
1907 .then_ignore(just(' ').or_not())
1908 .then(crate::utils::offset_datetime_parser().or_not())
1909 .then_ignore(newline())
1910 .then_ignore(just(" Rez time:"))
1911 .then_ignore(just(' ').or_not())
1912 .then(crate::utils::offset_datetime_parser().or_not())
1913 .then_ignore(newline())
1914 .then_ignore(just(" Pathfinding type: "))
1915 .then(sl_types::pathfinding::int_as_pathfinding_type_parser())
1916 .then_ignore(newline())
1917 .then_ignore(just(" Attachment point: "))
1918 .then(
1919 sl_types::attachment::attachment_point_parser()
1920 .map(Some)
1921 .or(just("---").to(None)),
1922 )
1923 .then_ignore(newline())
1924 .then_ignore(just(" Temporarily attached: "))
1925 .then(just("Yes").to(true).or(just("No").to(false)))
1926 .then_ignore(newline())
1927 .then_ignore(just(" Your current position: "))
1928 .then(sl_types::lsl::vector_parser().map(sl_types::map::RegionCoordinates::from))
1929 .map(
1930 |((((((((((((((((((((((
1931 object_key,
1932 description),
1933 root_prim),
1934 prim_count),
1935 land_impact),
1936 inventory_items),
1937 velocity),
1938 position),
1939 position_distance),
1940 rotation),
1941 rotation_vector_degrees),
1942 angular_velocity),
1943 creator),
1944 owner),
1945 previous_owner),
1946 rezzed_by),
1947 group),
1948 creation_time),
1949 rez_time),
1950 pathfinding_type),
1951 attachment_point),
1952 temporarily_attached),
1953 inspecting_avatar_position,
1954 )| {
1955 SystemMessage::ExtendedScriptInfo {
1956 object_key,
1957 description,
1958 root_prim,
1959 prim_count,
1960 land_impact,
1961 inventory_items,
1962 velocity,
1963 position,
1964 position_distance,
1965 rotation,
1966 rotation_vector_degrees,
1967 angular_velocity,
1968 creator,
1969 owner,
1970 previous_owner,
1971 rezzed_by,
1972 group,
1973 creation_time,
1974 rez_time,
1975 pathfinding_type,
1976 attachment_point,
1977 temporarily_attached,
1978 inspecting_avatar_position,
1979 }
1980 },
1981 )
1982}
1983
1984#[must_use]
1990pub fn dice_roll_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
1991 choice([
1992 just("You must provide positive values for dice (max 100) and faces (max 1000).")
1993 .to(SystemMessage::DiceRollCommandUsageInstructions)
1994 .boxed(),
1995 just('#')
1996 .ignore_then(usize_parser())
1997 .then_ignore(whitespace())
1998 .then_ignore(just("1d"))
1999 .then(usize_parser())
2000 .then_ignore(just(":"))
2001 .then_ignore(whitespace())
2002 .then(usize_parser())
2003 .then_ignore(just('.'))
2004 .map(
2005 |((roll_number, dice_faces), roll_result)| SystemMessage::DiceRollResult {
2006 roll_number,
2007 dice_faces,
2008 roll_result,
2009 },
2010 )
2011 .boxed(),
2012 just("Total result for ")
2013 .ignore_then(usize_parser())
2014 .then_ignore(just('d'))
2015 .then(usize_parser())
2016 .then_ignore(just(':'))
2017 .then_ignore(whitespace())
2018 .then(usize_parser())
2019 .then_ignore(just('.'))
2020 .map(
2021 |((roll_count, dice_faces), result_sum)| SystemMessage::DiceRollResultSum {
2022 roll_count,
2023 dice_faces,
2024 result_sum,
2025 },
2026 )
2027 .boxed(),
2028 ])
2029}
2030
2031#[must_use]
2037pub fn texture_info_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
2038 choice([
2039 just("Texture info for: ")
2040 .ignore_then(take_until(newline().or(end())).map(|(vc, _)| {
2041 SystemMessage::TextureInfoForObject {
2042 object_name: vc.into_iter().collect::<String>(),
2043 }
2044 }))
2045 .boxed(),
2046 sl_types::utils::u16_parser()
2047 .then_ignore(just('x'))
2048 .then(sl_types::utils::u16_parser())
2049 .then_ignore(whitespace())
2050 .then(just("opaque").or(just("alpha")))
2051 .then_ignore(just(" on face "))
2052 .then(usize_parser())
2053 .map(
2054 |(((texture_width, texture_height), texture_type), face_number)| {
2055 SystemMessage::TextureInfoForFace {
2056 face_number,
2057 texture_width,
2058 texture_height,
2059 texture_type: texture_type.to_owned(),
2060 }
2061 },
2062 )
2063 .boxed(),
2064 ])
2065}
2066
2067#[must_use]
2073pub fn firestorm_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
2074 just("Firestorm ").ignore_then(
2075 take_until(just("!").ignored())
2076 .map(|(message_type, _)| message_type.into_iter().collect::<String>())
2077 .then(any().repeated().collect::<String>())
2078 .map(|(message_type, message)| SystemMessage::FirestormMessage {
2079 message_type,
2080 message,
2081 }),
2082 )
2083}
2084
2085#[must_use]
2091pub fn grid_status_event_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>>
2092{
2093 just("[ ").ignore_then(
2094 take_until(just(" ] "))
2095 .map(|(vc, _)| vc.into_iter().collect::<String>())
2096 .then(
2097 just("THIS IS A SCHEDULED EVENT ")
2098 .or_not()
2099 .map(|s| s.is_some()),
2100 )
2101 .then(
2102 take_until(just(" [ https://status.secondlifegrid.net/incidents/").ignored())
2103 .map(|(vc, _)| vc.into_iter().collect::<String>()),
2104 )
2105 .then(take_until(just(' ').ignored()).map(|(vc, _)| vc.into_iter().collect::<String>()))
2106 .then_ignore(just("]"))
2107 .map(
2108 |(((title, scheduled), body), url_fragment)| SystemMessage::GridStatusEvent {
2109 title,
2110 scheduled,
2111 body,
2112 incident_url: format!(
2113 "https://status.secondlifegird.net/incidents/{}",
2114 url_fragment
2115 ),
2116 },
2117 ),
2118 )
2119}
2120
2121#[must_use]
2127pub fn system_message_parser() -> impl Parser<char, SystemMessage, Error = Simple<char>> {
2128 choice([
2129 snapshot_saved_message_parser().boxed(),
2130 attachment_saved_message_parser().boxed(),
2131 draw_distance_set_message_parser().boxed(),
2132 home_position_set_message_parser().boxed(),
2133 land_divided_message_parser().boxed(),
2134 failed_to_join_land_due_to_region_boundary_message_parser().boxed(),
2135 offered_calling_card_message_parser().boxed(),
2136 you_paid_for_object_message_parser().boxed(),
2137 you_paid_to_create_a_group_message_parser().boxed(),
2138 you_paid_to_join_group_message_parser().boxed(),
2139 you_paid_for_land_message_parser().boxed(),
2140 failed_to_pay_message_parser().boxed(),
2141 object_granted_permission_to_take_money_parser().boxed(),
2142 sent_payment_message_parser().boxed(),
2143 received_payment_message_parser().boxed(),
2144 group_membership_message_parser().boxed(),
2145 unable_to_invite_user_due_to_missing_group_membership_message_parser().boxed(),
2146 unable_to_invite_user_due_to_differing_limited_estate_message_parser().boxed(),
2147 unable_to_load_notecard_message_parser().boxed(),
2148 unable_to_load_gesture_message_parser().boxed(),
2149 teleport_completed_message_parser().boxed(),
2150 now_playing_message_parser().boxed(),
2151 region_restart_message_parser().boxed(),
2152 object_gave_object_message_parser().boxed(),
2153 object_gave_folder_message_parser().boxed(),
2154 declined_given_object_message_parser().boxed(),
2155 select_residents_to_share_with_message_parser().boxed(),
2156 items_successfully_shared_message_parser().boxed(),
2157 modified_search_query_message_parser().boxed(),
2158 avatar_gave_object_message_parser().boxed(),
2159 simulator_version_message_parser().boxed(),
2160 renamed_avatar_message_parser().boxed(),
2161 doubleclick_teleport_message_parser().boxed(),
2162 always_run_message_parser().boxed(),
2163 added_as_estate_manager_message_parser().boxed(),
2164 bridge_message_parser().boxed(),
2165 failed_to_place_object_at_specified_location_message_parser().boxed(),
2166 region_script_count_change_message_parser().boxed(),
2167 chat_message_still_being_processed_message_parser().boxed(),
2168 avatar_declined_voice_call_message_parser().boxed(),
2169 audio_from_domain_will_always_be_played_message_parser().boxed(),
2170 object_not_for_sale_message_parser().boxed(),
2171 can_not_create_requested_inventory_message_parser().boxed(),
2172 link_failed_due_to_piece_distance_message_parser().boxed(),
2173 rezzing_object_failed_due_to_full_parcel_message_parser().boxed(),
2174 create_object_failed_due_to_full_region_message_parser().boxed(),
2175 your_object_has_been_returned_message_parser().boxed(),
2176 permission_to_create_object_denied_message_parser().boxed(),
2177 permission_to_rez_object_denied_message_parser().boxed(),
2178 permission_to_reposition_denied_message_parser().boxed(),
2179 permission_to_rotate_denied_message_parser().boxed(),
2180 permission_to_rescale_denied_message_parser().boxed(),
2181 permission_to_unlink_denied_due_to_missing_parcel_build_permissions_message_parser()
2182 .boxed(),
2183 permission_to_view_script_denied_message_parser().boxed(),
2184 permission_to_view_notecard_denied_message_parser().boxed(),
2185 permission_to_change_shape_denied_message_parser().boxed(),
2186 permission_to_enter_parcel_denied_message_parser().boxed(),
2187 permission_to_enter_parcel_denied_due_to_ban_message_parser().boxed(),
2188 avatar_ejected_message_parser().boxed(),
2189 ejected_from_parcel_message_parser().boxed(),
2190 banned_from_parcel_message_parser().boxed(),
2191 only_group_members_can_visit_this_area_message_parser().boxed(),
2192 unable_to_teleport_due_to_rlv_message_parser().boxed(),
2193 unable_to_open_texture_due_to_rlv_message_parser().boxed(),
2194 unsupported_slurl_message_parser().boxed(),
2195 blocked_untrusted_browser_slurl_message_parser().boxed(),
2196 grid_status_error_invalid_message_format_message_parser().boxed(),
2197 script_info_object_invalid_or_out_of_range_message_parser().boxed(),
2198 script_info_message_parser().boxed(),
2199 extended_script_info_message_parser().boxed(),
2200 dice_roll_message_parser().boxed(),
2201 texture_info_message_parser().boxed(),
2202 firestorm_message_parser().boxed(),
2203 grid_status_event_message_parser().boxed(),
2204 take_until(just("https").or(just("http")).or(just("Http")))
2205 .map(|(message, scheme)| (message.into_iter().collect::<String>(), scheme))
2206 .then(take_until(newline().or(end())).map(|(vc, _)| vc.into_iter().collect::<String>()))
2207 .map(
2208 |((message, scheme), rest_of_url)| SystemMessage::SystemMessageWithLink {
2209 message,
2210 link: format!("{}://{}", scheme, rest_of_url),
2211 },
2212 )
2213 .boxed(),
2214 take_until(just("www."))
2215 .map(|(message, subdomain)| (message.into_iter().collect::<String>(), subdomain))
2216 .then(take_until(newline().or(end())).map(|(vc, _)| vc.into_iter().collect::<String>()))
2217 .map(
2218 |((message, subdomain), rest_of_url)| SystemMessage::SystemMessageWithLink {
2219 message,
2220 link: format!("{}{}", subdomain, rest_of_url),
2221 },
2222 )
2223 .boxed(),
2224 any()
2225 .repeated()
2226 .collect::<String>()
2227 .map(|message| {
2228 if message.contains("Firestorm") && (message.contains("holiday") || message.contains("Happy New Year")) {
2229 SystemMessage::FirestormHolidayWishes { message }
2230 } else if message.contains("phishing") {
2231 SystemMessage::PhishingWarning { message }
2232 } 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." {
2233 SystemMessage::TestMessageOfTheDay
2234 } 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") {
2235 SystemMessage::EarlyFirestormStartupMessage { message }
2236 } else if message.contains("wiki.phoenixviewer.com/firestorm_classes") {
2237 SystemMessage::FirestormMessage {
2238 message_type: "Classes".to_string(),
2239 message,
2240 }
2241 } else if message.contains("BETA TESTERS") || message.contains("Beta Testers") {
2242 SystemMessage::FirestormMessage {
2243 message_type: "Beta Test".to_string(),
2244 message,
2245 }
2246 } else {
2247 SystemMessage::OtherSystemMessage { message }
2248 }
2249 })
2250 .boxed(),
2251 ])
2252}
2253
2254#[cfg(test)]
2255mod test {
2256 use super::*;
2257 use pretty_assertions::assert_eq;
2258
2259 #[test]
2260 fn test_teleport_completed() -> Result<(), Box<dyn std::error::Error>> {
2261 assert_eq!(
2262 Ok(SystemMessage::TeleportCompleted {
2263 origin: sl_types::map::UnconstrainedLocation {
2264 region_name: sl_types::map::RegionName::try_new("Fudo")?,
2265 x: 30,
2266 y: 169,
2267 z: 912
2268 }
2269 }),
2270 teleport_completed_message_parser().parse(
2271 "Teleport completed from http://maps.secondlife.com/secondlife/Fudo/30/169/912"
2272 )
2273 );
2274 Ok(())
2275 }
2276
2277 #[test]
2278 fn test_teleport_completed_extra_short() -> Result<(), Box<dyn std::error::Error>> {
2279 assert_eq!(
2280 Ok(SystemMessage::TeleportCompleted {
2281 origin: sl_types::map::UnconstrainedLocation {
2282 region_name: sl_types::map::RegionName::try_new("AA")?,
2283 x: 78,
2284 y: 83,
2285 z: 26
2286 }
2287 }),
2288 teleport_completed_message_parser()
2289 .parse("Teleport completed from http://maps.secondlife.com/secondlife/AA/78/83/26")
2290 );
2291 Ok(())
2292 }
2293
2294 #[test]
2295 fn test_cant_rez_object() -> Result<(), Box<dyn std::error::Error>> {
2296 assert_eq!(
2297 Ok(SystemMessage::PermissionToRezObjectDenied {
2298 object_name: "Foo2".to_string(),
2299 attempted_rez_location: sl_types::map::RegionCoordinates::new(63.0486, 45.2515, 1501.08),
2300 parcel_name: "The Foo Bar".to_string(),
2301 region_name: sl_types::map::RegionName::try_new("Fudo")?,
2302 }),
2303 permission_to_rez_object_denied_message_parser()
2304 .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.")
2305 );
2306 Ok(())
2307 }
2308}