sl_chat_log_parser/
system_messages.rs

1//! Types and parsers for system messages in the chat log
2
3use 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/// represents a Second Life system message
10#[derive(Debug, Clone, PartialEq)]
11pub enum SystemMessage {
12    /// message about a saved snapshot
13    SavedSnapshot {
14        /// the snapshot filename
15        filename: std::path::PathBuf,
16    },
17    /// message about a failure to save a snapshot due to missing destination folder
18    FailedToSaveSnapshotDueToMissingDestinationFolder {
19        /// the snapshot folder
20        folder: std::path::PathBuf,
21    },
22    /// message about a failure to save a snapshot due to disk space
23    FailedToSaveSnapshotDueToDiskSpace {
24        /// the snapshot folder
25        folder: std::path::PathBuf,
26        /// the amount of space required
27        required_disk_space: bytesize::ByteSize,
28        /// the amount of free space reported
29        free_disk_space: bytesize::ByteSize,
30    },
31    /// message about the draw distance being set to a specific value
32    DrawDistanceSet {
33        /// the distance the draw distance was set to
34        distance: sl_types::map::Distance,
35    },
36    /// message about the home position being set
37    HomePositionSet,
38    /// message about land being divided
39    LandDivided,
40    /// message about a failure to join land due to region boundary
41    FailedToJoinLandDueToRegionBoundary,
42    /// message about offering a calling card
43    OfferedCallingCard {
44        /// the name of the avatar we offered a calling card to
45        recipient_avatar_name: String,
46    },
47    /// message about a saved attachment
48    AttachmentSavedMessage,
49    /// message about paying for an object
50    YouPaidForObject {
51        /// the seller avatar or group
52        seller: sl_types::key::OwnerKey,
53        /// the amount paid
54        amount: sl_types::money::LindenAmount,
55        /// the name of the object you paid for
56        object_name: String,
57    },
58    /// message about paying to create a group
59    YouPaidToCreateGroup {
60        /// the agent you paid
61        payment_recipient: sl_types::key::AgentKey,
62        /// the amount you paid
63        amount: sl_types::money::LindenAmount,
64    },
65    /// message about paying to join a group
66    YouPaidToJoinGroup {
67        /// the group key for the joined group
68        joined_group: sl_types::key::GroupKey,
69        /// the amount paid to join
70        join_fee: sl_types::money::LindenAmount,
71    },
72    /// message about paying for a parcel of land
73    YouPaidForLand {
74        /// previous land owner
75        previous_land_owner: sl_types::key::OwnerKey,
76        /// the amount paid
77        amount: sl_types::money::LindenAmount,
78    },
79    /// message about a failed payment
80    FailedToPay {
81        /// payment recipient
82        payment_recipient: sl_types::key::OwnerKey,
83        /// the amount that could not be paid
84        amount: sl_types::money::LindenAmount,
85    },
86    /// message about an object being granted permission to take L$
87    ObjectGrantedPermissionToTakeMoney {
88        /// the name of the object
89        object_name: String,
90        /// the owner of the object
91        owner_name: String,
92        /// the region where the object is located
93        object_region: Option<sl_types::map::RegionName>,
94        /// the coordinates within that region
95        object_location: Option<sl_types::map::RegionCoordinates>,
96    },
97    /// message about a sent payment
98    SentPayment {
99        /// the recipient avatar or group key
100        recipient_key: sl_types::key::OwnerKey,
101        /// the amount sent
102        amount: sl_types::money::LindenAmount,
103        /// an optional message
104        message: Option<String>,
105    },
106    /// message about a received payment
107    ReceivedPayment {
108        /// the sender avatar or group key
109        sender_key: sl_types::key::OwnerKey,
110        /// the amount received
111        amount: sl_types::money::LindenAmount,
112        /// an optional message
113        message: Option<String>,
114    },
115    /// message that you have been added to a group
116    AddedToGroup,
117    /// message that you left a group
118    LeftGroup {
119        /// the name of the group left
120        group_name: String,
121    },
122    /// message that you are unable to invite a user to a group because you
123    /// are not in the group
124    UnableToInviteUserDueToMissingGroupMembership,
125    /// message that you are unable to invite a user to a group because the
126    /// user is in a different limited estate than the group
127    UnableToInviteUserToGroupDueToDifferingLimitedEstate,
128    /// message that loading a notecard failed
129    UnableToLoadNotecard,
130    /// message that loading a gesture failed
131    UnableToLoadGesture {
132        /// name of the gesture that could not be loaded
133        gesture_name: String,
134    },
135    /// message about a song playing on stream
136    NowPlaying {
137        /// the song name
138        song_name: String,
139    },
140    /// message about a completed teleport
141    TeleportCompleted {
142        /// teleported originated at this location
143        origin: sl_types::map::UnconstrainedLocation,
144    },
145    /// message about a region restart of the region that the avatar is in
146    RegionRestart,
147    /// message about an object giving the current avatar an object
148    ObjectGaveObject {
149        /// the giving object name
150        giving_object_name: String,
151        /// the giving object location
152        giving_object_location: sl_types::map::UnconstrainedLocation,
153        /// the giving object owner
154        giving_object_owner: sl_types::key::OwnerKey,
155        /// the name of the given object
156        given_object_name: String,
157    },
158    /// message about an object giving the current avatar a folder
159    ObjectGaveFolder {
160        /// key of the object
161        giving_object_key: sl_types::key::ObjectKey,
162        /// name of the object
163        giving_object_name: String,
164        /// owner of the object
165        giving_object_owner: sl_types::key::OwnerKey,
166        /// object location
167        giving_object_location: sl_types::map::Location,
168        /// giving object link label
169        giving_object_link_label: String,
170        /// given folder name
171        folder_name: String,
172    },
173    /// message about an avatar giving the current avatar an object
174    AvatarGaveObject {
175        /// is the giving avatar a group member
176        is_group_member: bool,
177        /// the giving avatar name
178        giving_avatar_name: String,
179        /// the name of the given object
180        given_object_name: String,
181    },
182    /// message about you declining an object given to you
183    DeclinedGivenObject {
184        /// the name of the declined object
185        object_name: String,
186        /// the location of the giver
187        giver_location: sl_types::map::UnconstrainedLocation,
188        /// the name of the giver
189        giver_name: String,
190    },
191    /// message asking to select residents to share with
192    SelectResidentsToShareWith,
193    /// message about successfully shared items
194    ItemsSuccessfullyShared,
195    /// message about a modified search query
196    ModifiedSearchQuery {
197        /// the modified query
198        query: String,
199    },
200    /// message about different simulator version
201    SimulatorVersion {
202        /// the previous region simulator version
203        previous_region_simulator_version: String,
204        /// the current region simulator version
205        current_region_simulator_version: String,
206    },
207    /// message about a renamed avatar
208    RenamedAvatar {
209        /// the old name
210        old_name: String,
211        /// the new name
212        new_name: String,
213    },
214    /// message about enabling or disabling double-click teleports
215    DoubleClickTeleport {
216        /// whether this event enables or disables double-click teleports
217        enabled: bool,
218    },
219    /// message about enabling or disabling always run
220    AlwaysRun {
221        /// whether this event enables or disables always run
222        enabled: bool,
223    },
224    /// message about being added as an estate manager
225    AddedAsEstateManager,
226    /// message that the bridge creation started
227    CreatingBridge,
228    /// message that the bridge was created
229    BridgeCreated,
230    /// message that the bridge creation is still in progress and another one
231    /// can not be created simultaneously
232    BridgeCreationInProgress,
233    /// message that the bridge failed to attach
234    BridgeFailedToAttach,
235    /// message that the bridge failed to attach because something else is using
236    /// the bridge attachment point
237    BridgeFailedToAttachDueToBridgeAttachmentPointInUse,
238    /// message that the bridge was not created
239    BridgeNotCreated,
240    /// message that the bridge was detached
241    BridgeDetached,
242    /// message that the bridge object was not found and the creation was aborted
243    BridgeObjectNotFoundCantProceedWithCreation,
244    /// failed to place object at specified location, please try again
245    FailedToPlaceObjectAtSpecifiedLocation,
246    /// script count changed
247    ScriptCountChanged {
248        /// script count before
249        previous_script_count: u32,
250        /// script count now
251        current_script_count: u32,
252        /// change
253        change: i32,
254    },
255    /// the chat message to a multi-person chat is still being processed
256    MultiPersonChatMessageStillBeingProcessed,
257    /// the chat message to an im session that no longer exists is still being processed
258    ChatMessageToNoLongerExistingImSessionStillBeingProcessed,
259    /// the chat message to a conference is still being processed
260    ConferenceChatMessageStillBeingProcessed {
261        /// the name of the avatar whose conference it is
262        avatar_name: String,
263    },
264    /// the group chat message is still being processed
265    GroupChatMessageStillBeingProcessed {
266        /// the name of the group
267        group_name: String,
268    },
269    /// avatar has declined voice call
270    AvatarDeclinedVoice {
271        /// the avatar who declined our voice call
272        avatar_name: String,
273    },
274    /// audio from a specific domain will always be played (on the audio stream)
275    AudioFromDomainWillAlwaysBePlayed {
276        /// the domain whose audio will always be played
277        domain: String,
278    },
279    /// the object is not for sale
280    ObjectNotForSale,
281    /// cannot created requested inventory
282    CanNotCreateRequestedInventory,
283    /// link failed because pieces being too far apart
284    LinkFailedDueToPieceDistance {
285        /// link failed for this many pieces
286        link_failed_pieces: Option<usize>,
287        /// total selected pieces
288        total_selected_pieces: Option<usize>,
289    },
290    /// rezzing an object failed because the parcel is full
291    RezObjectFailedDueToFullParcel {
292        /// name of the object
293        object_name: String,
294        /// name of the parcel
295        parcel_name: String,
296        /// attempted rez location
297        attempted_rez_location: sl_types::map::RegionCoordinates,
298        /// name of the region where the rez failed
299        region_name: sl_types::map::RegionName,
300    },
301    /// creating an object failed because the region is full
302    CreateObjectFailedDueToFullRegion,
303    /// your object has been returned to your inventory Lost and Found folder
304    YourObjectHasBeenReturned {
305        /// name of the returned object
306        object_name: String,
307        /// from parcel name
308        parcel_name: String,
309        /// at location
310        location: sl_types::map::UnconstrainedLocation,
311        /// due to parcel auto return?
312        auto_return: bool,
313    },
314    /// permission to create an object denied
315    PermissionToCreateObjectDenied,
316    /// permission to rez an object denied
317    PermissionToRezObjectDenied {
318        /// name of the object
319        object_name: String,
320        /// name of the parcel
321        parcel_name: String,
322        /// attempted rez location
323        attempted_rez_location: sl_types::map::RegionCoordinates,
324        /// name of the region where the rez failed
325        region_name: sl_types::map::RegionName,
326    },
327    /// permission to reposition an object denied
328    PermissionToRepositionDenied,
329    /// permission to rotate an object denied
330    PermissionToRotateDenied,
331    /// permission to rescale an object denied
332    PermissionToRescaleDenied,
333    /// permission to unlink denied due to missing build permissions on at least one parcel
334    PermissionToUnlinkDeniedDueToMissingParcelBuildPermissions,
335    /// permission to view script denied
336    PermissionToViewScriptDenied,
337    /// permission to view notecard denied
338    PermissionToViewNotecardDenied,
339    /// permission to change shape denied
340    PermissionToChangeShapeDenied,
341    /// permission to enter parcel denied
342    PermissionToEnterParcelDenied,
343    /// permission to enter parcel denied due to ban
344    PermissionToEnterParcelDeniedDueToBan,
345    /// we ejected an avatar
346    EjectedAvatar,
347    /// ejected from parcel
348    EjectedFromParcel,
349    /// no longer allowed and ejected
350    EjectedFromParcelBecauseNoLongerAllowed,
351    /// banned temporarily
352    BannedFromParcelTemporarily {
353        /// How long the ban lasts
354        ban_duration: time::Duration,
355    },
356    /// banned indefinitely
357    BannedFromParcelIndefinitely,
358    /// only group members can visit this area
359    OnlyGroupMembersCanVisitThisArea,
360    /// unable to teleport due to RLV restriction
361    UnableToTeleportDueToRlv,
362    /// unable to open texture due to RLV restriction
363    UnableToOpenTextureDueToRlv,
364    /// unsupported SLurl
365    UnsupportedSlurl,
366    /// SLurl from untrusted browser blocked
367    BlockedUntrustedBrowserSlurl,
368    /// grid status error invalid message format
369    GridStatusErrorInvalidMessageFormat,
370    /// script info object is invalid or out of range
371    ScriptInfoObjectInvalidOrOutOfRange,
372    /// script info
373    ScriptInfo {
374        /// name of the object or avatar whose script info this is
375        name: String,
376        /// running scripts
377        running_scripts: usize,
378        /// total scripts
379        total_scripts: usize,
380        /// allowed memory size limit
381        allowed_memory_size_limit: bytesize::ByteSize,
382        /// CPU time consumed
383        cpu_time_consumed: time::Duration,
384    },
385    /// Firestorm extended script info
386    ExtendedScriptInfo {
387        /// object key
388        object_key: sl_types::key::ObjectKey,
389        /// description of the inspected object
390        description: Option<String>,
391        /// key of the room prim
392        root_prim: sl_types::key::ObjectKey,
393        /// prim count
394        prim_count: usize,
395        /// land impact
396        land_impact: usize,
397        /// number of items in the inspect object's inventory
398        inventory_items: usize,
399        /// velocity
400        velocity: sl_types::lsl::Vector,
401        /// position in the region
402        position: sl_types::map::RegionCoordinates,
403        /// distance from inspecting avatar to position of inspected object
404        position_distance: sl_types::map::Distance,
405        /// rotation of the inspected object as a quaternion
406        rotation: sl_types::lsl::Rotation,
407        /// rotation of the inspected object as a vector of angles in degrees
408        rotation_vector_degrees: sl_types::lsl::Vector,
409        /// angular velocity of the inspected object in radians per second
410        angular_velocity: sl_types::lsl::Vector,
411        /// creator
412        creator: sl_types::key::AgentKey,
413        /// owner
414        owner: sl_types::key::OwnerKey,
415        /// previous owner
416        previous_owner: Option<sl_types::key::OwnerKey>,
417        /// rezzed by
418        rezzed_by: sl_types::key::AgentKey,
419        /// group
420        group: Option<sl_types::key::GroupKey>,
421        /// creation time
422        creation_time: Option<time::OffsetDateTime>,
423        /// rez time
424        rez_time: Option<time::OffsetDateTime>,
425        /// pathfinding type
426        pathfinding_type: sl_types::pathfinding::PathfindingType,
427        /// attachment point
428        attachment_point: Option<sl_types::attachment::AttachmentPoint>,
429        /// temporarily attached
430        temporarily_attached: bool,
431        /// inspecting avatar position
432        inspecting_avatar_position: sl_types::map::RegionCoordinates,
433    },
434    /// usage instruction for dice roll command
435    DiceRollCommandUsageInstructions,
436    /// dice roll result
437    DiceRollResult {
438        /// which dice roll (when multiple rolls were requested)
439        roll_number: usize,
440        /// how many faces on the dice rolled
441        dice_faces: usize,
442        /// roll result
443        roll_result: usize,
444    },
445    /// dice roll result sum
446    DiceRollResultSum {
447        /// number of rolls
448        roll_count: usize,
449        /// how many faces on the dice rolled
450        dice_faces: usize,
451        /// result sum
452        result_sum: usize,
453    },
454    /// texture info for object (followed by one or more of the below)
455    TextureInfoForObject {
456        /// name of the object
457        object_name: String,
458    },
459    /// texture info for one face
460    TextureInfoForFace {
461        /// number of the face this line is about
462        face_number: usize,
463        /// width of the texture
464        texture_width: u16,
465        /// height of the texture
466        texture_height: u16,
467        /// type of texture, e.g. opaque, alpha
468        texture_type: String,
469    },
470    /// a message from the Firestorm developers
471    FirestormMessage {
472        /// the type of message, basically whatever follows the initial
473        /// Firestorm up until the exclamation mark (e.g. Tip, Help, Classes,...)
474        message_type: String,
475        /// the actual message, everything after the exclamation mark
476        message: String,
477    },
478    /// message about a grid status event
479    GridStatusEvent {
480        /// event title
481        title: String,
482        /// is this a scheduled event
483        scheduled: bool,
484        /// event body
485        body: String,
486        /// event URL
487        incident_url: String,
488    },
489    /// message with a link at the end (mostly announcements of events or
490    /// similar message of the day style stuff)
491    SystemMessageWithLink {
492        /// the message before the link
493        message: String,
494        /// the link
495        link: String,
496    },
497    /// Firestorm holiday wishes
498    FirestormHolidayWishes {
499        /// the message
500        message: String,
501    },
502    /// Warning about phishing
503    PhishingWarning {
504        /// the message
505        message: String,
506    },
507    /// Test MOTD
508    TestMessageOfTheDay,
509    /// Early Firestorm startup message
510    EarlyFirestormStartupMessage {
511        /// the message
512        message: String,
513    },
514    /// other system message
515    OtherSystemMessage {
516        /// the raw message
517        message: String,
518    },
519}
520
521/// parse a system message about a saved snapshot
522///
523/// # Errors
524///
525/// returns an error if the string could not be parsed
526#[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/// parse a system message about the draw distance being set to a specific distance
563///
564/// # Errors
565///
566/// returns an error if the string could not be parsed
567#[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/// parse a system message about the home position being set
577///
578/// # Errors
579///
580/// returns an error if the string could not be parsed
581#[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/// parse a system message about land being divided
588///
589/// # Errors
590///
591/// returns an error if the string could not be parsed
592#[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/// parse a system message about a failure to join land due to a region boundary
598///
599/// # Errors
600///
601/// returns an error if the string could not be parsed
602#[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/// parse a system message about offering a calling card to an avatar
612///
613/// # Errors
614///
615/// returns an error if the string could not be parsed
616#[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/// parse a system message about a saved attachment
627///
628/// # Errors
629///
630/// returns an error if the string could not be parsed
631#[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/// parse a system message about a sent payment
637///
638/// # Errors
639///
640/// returns an error if the string could not be parsed
641#[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/// parse a system message about a sent payment
660///
661/// # Errors
662///
663/// returns an error if the string could not be parsed
664#[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/// parse a system message about a received payment
687///
688/// # Errors
689///
690/// returns an error if the string could not be parsed
691#[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/// parse a system message about paying to create a group
712///
713/// # Errors
714///
715/// returns an error if the string could not be parsed
716#[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/// parse a system message about paying to join a group
733///
734/// # Errors
735///
736/// returns an error if the string could not be parsed
737#[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/// parse a system message about paying for land
754///
755/// # Errors
756///
757/// returns an error if the string could not be parsed
758#[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/// parse a system message about a failure to pay
775///
776/// # Errors
777///
778/// returns an error if the string could not be parsed
779#[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/// parse a system message about an object being granted permission to take L$
793///
794/// # Errors
795///
796/// returns an error if the string could not be parsed
797#[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/// parse a system message about being added or leaving a group
847///
848/// # Errors
849///
850/// returns an error if the string could not be parsed
851#[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/// parse a system message about the inability to invite a user to a group
861/// you yourself are not a member of
862///
863/// # Errors
864///
865/// returns an error if the string could not be parsed
866#[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/// parse a system message about the inability to invite a user to a group
874/// due to a difference in limited estate between user and group
875///
876/// # Errors
877///
878/// returns an error if the string could not be parsed
879#[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/// parse a system message about the inability to load a notecard
889///
890/// # Errors
891///
892/// returns an error if the string could not be parsed
893#[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/// parse a system message about the inability to load a gesture
904///
905/// # Errors
906///
907/// returns an error if the string could not be parsed
908#[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/// parse a system message about a completed teleport
919///
920/// # Errors
921///
922/// returns an error if the string could not be parsed
923#[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/// parse a system message about a now playing song
934///
935/// # Errors
936///
937/// returns an error if the string could not be parsed
938#[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/// parse a system message about a region restart
948///
949/// # Errors
950///
951/// returns an error if the string could not be parsed
952#[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/// parse a system message about an object giving the current avatar an object
961///
962/// # Errors
963///
964/// returns an error if the string could not be parsed
965#[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/// parse a system message about an object giving the current avatar a folder
999///
1000/// # Errors
1001///
1002/// returns an error if the string could not be parsed
1003#[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/// parse a system message about an avatar giving the current avatar an object
1040///
1041/// # Errors
1042///
1043/// returns an error if the string could not be parsed
1044#[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/// parse a system message about declining an object given to you
1064///
1065/// # Errors
1066///
1067/// returns an error if the string could not be parsed
1068#[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/// parse a system message asking to select residents to share with
1094///
1095/// # Errors
1096///
1097/// returns an error if the string could not be parsed
1098#[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/// parse a system message about items being successfully shared
1105///
1106/// # Errors
1107///
1108/// returns an error if the string could not be parsed
1109#[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/// parse a system message about a modified search query
1116///
1117/// # Errors
1118///
1119/// returns an error if the string could not be parsed
1120#[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/// parse a system message about a different simulator version
1134///
1135/// # Errors
1136///
1137/// returns an error if the string could not be parsed
1138#[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/// parse a system message about a renamed avatar
1162///
1163/// # Errors
1164///
1165/// returns an error if the string could not be parsed
1166#[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/// parse a system message about enabling or disabling of double-click teleports
1178///
1179/// # Errors
1180///
1181/// returns an error if the string could not be parsed
1182#[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/// parse a system message about enabling or disabling of always run
1192///
1193/// # Errors
1194///
1195/// returns an error if the string could not be parsed
1196#[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/// parse a system message about being added as an estate manager
1204///
1205/// # Errors
1206///
1207/// returns an error if the string could not be parsed
1208#[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/// parse a system message about the LSL viewer bridge
1215///
1216/// # Errors
1217///
1218/// returns an error if the string could not be parsed
1219#[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/// parse a system message about a failure to place an object a a specified
1236/// location
1237///
1238/// # Errors
1239///
1240/// returns an error if the string could not be parsed
1241#[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/// parse a system message about a changed script count in the current region
1249///
1250/// # Errors
1251///
1252/// returns an error if the string could not be parsed
1253#[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/// parse a system message about a chat message still being processed
1324///
1325/// # Errors
1326///
1327/// returns an error if the string could not be parsed
1328#[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/// parse a system message about an avatar declining a voice call
1351///
1352/// # Errors
1353///
1354/// returns an error if the string could not be parsed
1355#[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/// parse a system message about audio from a domain always being played
1367///
1368/// # Errors
1369///
1370/// returns an error if the string could not be parsed
1371#[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/// parse a system message about an object not being for sale
1382///
1383/// # Errors
1384///
1385/// returns an error if the string could not be parsed
1386#[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/// parse a system message cannot create requested inventory
1393///
1394/// # Errors
1395///
1396/// returns an error if the string could not be parsed
1397#[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/// parse a system message about a failed link due to piece distance
1404///
1405/// # Errors
1406///
1407/// returns an error if the string could not be parsed
1408#[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/// parse a system message about the failure to rez an object due to a full
1431/// parcel
1432///
1433/// # Errors
1434///
1435/// returns an error if the string could not be parsed
1436#[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(&region_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/// parse a system message about the failure to create an object due to a full
1477/// region
1478///
1479/// # Errors
1480///
1481/// returns an error if the string could not be parsed
1482#[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/// parse a system message about an object being returned to your inventory
1490///
1491/// # Errors
1492///
1493/// returns an error if the string could not be parsed
1494#[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/// parse a system message about the denial of permission to create an object
1528///
1529/// # Errors
1530///
1531/// returns an error if the string could not be parsed
1532#[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/// parse a system message about the denial of permission to rez an object
1539///
1540/// # Errors
1541///
1542/// returns an error if the string could not be parsed
1543#[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(&region_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/// parse a system message about the denial of permission to reposition an object
1567///
1568/// # Errors
1569///
1570/// returns an error if the string could not be parsed
1571#[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/// parse a system message about the denial of permission to rotate an object
1578///
1579/// # Errors
1580///
1581/// returns an error if the string could not be parsed
1582#[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/// parse a system message about the denial of permission to rescale an object
1589///
1590/// # Errors
1591///
1592/// returns an error if the string could not be parsed
1593#[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/// parse a system message about the denial of permission to unlink an object
1600/// because build permissions are missing on at least one parcel
1601///
1602/// # Errors
1603///
1604/// returns an error if the string could not be parsed
1605#[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/// parse a system message about the denial of permission to view a script
1613///
1614/// # Errors
1615///
1616/// returns an error if the string could not be parsed
1617#[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/// parse a system message about the denial of permission to view a notecard
1625///
1626/// # Errors
1627///
1628/// returns an error if the string could not be parsed
1629#[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/// parse a system message about the denial of permission to change a shape
1637///
1638/// # Errors
1639///
1640/// returns an error if the string could not be parsed
1641#[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/// parse a system message about the denial of permission to enter a parcel
1649///
1650/// # Errors
1651///
1652/// returns an error if the string could not be parsed
1653#[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/// parse a system message about the denial of permission to enter a parcel due to ban
1661///
1662/// # Errors
1663///
1664/// returns an error if the string could not be parsed
1665#[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/// parse a system message about ejecting an avatar from a parcel
1673///
1674/// # Errors
1675///
1676/// returns an error if the string could not be parsed
1677#[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/// parse a system message about being ejected from a parcel
1683///
1684/// # Errors
1685///
1686/// returns an error if the string could not be parsed
1687#[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/// parse a system message about being banned from a parcel
1699///
1700/// # Errors
1701///
1702/// returns an error if the string could not be parsed
1703#[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/// parse a system message about only group members being able to visit an area
1718///
1719/// # Errors
1720///
1721/// returns an error if the string could not be parsed
1722#[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/// parse a system message about teleports being RLV restricted
1730///
1731/// # Errors
1732///
1733/// returns an error if the string could not be parsed
1734#[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/// parse a system message about opening textures being RLV restricted
1742///
1743/// # Errors
1744///
1745/// returns an error if the string could not be parsed
1746#[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/// parse a system message about unsupported SLurl
1754///
1755/// # Errors
1756///
1757/// returns an error if the string could not be parsed
1758#[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/// parse a system message about a SLurl from an untrusted browser being blocked
1765///
1766/// # Errors
1767///
1768/// returns an error if the string could not be parsed
1769#[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/// parse a system message about a grid status error about an invalid message format
1777///
1778/// # Errors
1779///
1780/// returns an error if the string could not be parsed
1781#[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/// parse a system message about a script info object being invalid or out of range
1789///
1790/// # Errors
1791///
1792/// returns an error if the string could not be parsed
1793#[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/// parse a system message about script info
1801///
1802/// # Errors
1803///
1804/// returns an error if the string could not be parsed
1805#[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/// parse a system message with extended script info
1836/// usually this should follow a line with regular script info containing the
1837/// object name
1838///
1839/// # Errors
1840///
1841/// returns an error if the string could not be parsed
1842#[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/// parse a system message about dice rolls
1985///
1986/// # Errors
1987///
1988/// returns an error if the string could not be parsed
1989#[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/// parse a system message about object textures on faces
2032///
2033/// # Errors
2034///
2035/// returns an error if the string could not be parsed
2036#[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/// parse a system message by the Firestorm developers
2068///
2069/// # Errors
2070///
2071/// returns an error if the string could not be parsed
2072#[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/// parse a system message about a grid status event
2086///
2087/// # Errors
2088///
2089/// returns an error if the string could not be parsed
2090#[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/// parse a Second Life system message
2122///
2123/// # Errors
2124///
2125/// returns an error if the string could not be parsed
2126#[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}