This script was created by Aine Caoimhe (Mata Hari).  It is for Opensim only.  Greet someone when they first enter the region and (optionally) give them a “welcome notecard”. Only that avatar will see the message (uses llRegionSayTo(UUID) to send) the message. The notecard is only given on the very first recorded visit to the sim by that avatar. After that, they’ll be greeted with a welcome back message instead.

// by Mata Hari/Aine Caoimhe March 2014
// This is written specifically for Posh and Doro but can easily be adapted/customized for other purposes. It will:
//  - Greet someone when they first enter the region and (optionally) give them a "welcome notecard". Only that avatar will see the message (uses llRegionSayTo(UUID) to send)
//    the message. The notecard is only given on the very first recorded visit to the sim by that avatar. After that, they'll be greeted with a welcome back message instead
//  - Log (in a notecard) a complete list of the avatars who have visited the sim and the date (and optionally time) of their most recent arrival
//  - Optionally invite a first-time visitor to join a specified Group.
//  - When the owner touches the prim that contains this script she/he will be given a notecard that lists everyone who has visited the region and the date (and optionally time)
//  of their most recent visit.
//  - The name that will display as the sender in messages is the name of the prim that contains this script so give the prim a "friendly" meaningful name.
//  - The visitor log is sorted in the order of most recent visit to oldest, so the people at the top of the list are the latest ones to arrive in the region
//    even if their first visit was a long time ago.
//  - Date (and time if you use that option) is based on the UTC time zone so it's roughly Greenwich Mean Time except doesn't have daylight savings time applied to it.
// REQUIRED: The following OSSL script functions must be enabled for the script-owner:
//      - osGetAvatarList       -- used to detect who is in the region
//      - osKey2Name            -- used to correctly retrieve owner's name
//      - osGetNotecard         -- used to retrieve visitor log data
//      - osMakeNotecard        -- used to store visitor log data
//      - osIsNpc               -- used to check whether a detected agent in the region is "real" or an NPC so we don't message NPCs or try to give them notecards :p
//      - osInviteToGroup       -- only needed if you enable the invite to group flag in the USER SETTINGS section
// set the following variable to whatever values suit your needs
string visitorLogNotecard="visitor log";
// visitorLogNotecard is the name to use for the notecard that stores the information about avatars who have visited the region. If the notecard doesn't exist
// it will be created automatically for you. New visitors are automatically added to the notecard to keep a running log of all the people who have ever come
// to your region. If you delete this notecard it's like resetting your log and all avatars who visit the region will be greeted again as though it's their
// first visit so don't deleted it unless you want that to happen. The script reads the names from the card each time the region starts or if the script is
// restarted (that's how it knows who it has already welcomed for the first time). If you restart the script, anyone currently in the region will be welcomed
// again so I'd recommend you not do this very frequently (there really shouldn't be any need to).
integer includeTimes=FALSE;
// includeTimes can be set to either TRUE or FALSE (must be capital letters). If set to true, the notecard will contain both date and time of their most recent visit
// but if set to FALSE it will only show the date (I find it easier to read with just the date).
string welcomeFirstTimeMessage = "Welcome, INSERT_NAME. Thank you for visiting my region in OSG";
// welcomeFirstTimeMessage is only said to the avatar the very FIRST time they visit the region (or if you've deleted the visitor log). Must be enclosed in quotes.
// if you include the string INSERT_NAME in the message, it will be replaced with the name of the avatar. In the message I added as default, if I visited
// the region it would say "Welcome, Mata Hari. Thank you for visiting my region in OSG" to me the first time I went there.
// Note: if you set the script to invite first-time visitors to join a group. make sure your welcome message encourages them to do so.
string welcomeBackMessage = "Welcome back to my OSG region, INSERT_NAME. Please enjoy your stay!";
// welcomeBackMessage is the message that gets sent to the avatar on all subsequent visits (even days later). Again, the string INSERT_NAME will be replaced with the avi name
// so if I returned to the region it would say "Welcome back, Mata Hari. Please enjoy your stay!"
float checkTime=30.0;
// checkTime is how often (in seconds) to check whether someone new has arrived in the region. This is the longest possible time it can be between their arrival and the script 
// detecting it but will often be faster. I set a default of 30 seconds because it often takes this long for someone to arrive, rez, and start to have the sim rez around them.
// If you want the sim to feel a little more "responsive" to new arrivals try shortening it to only 10 or 15 seconds but avoid very short times (less than 5 seconds) since it
// will result in unnecessary sim resource usage to do checks this frequently.
// FYI this also has a second purpose....if an avatar that was previously welcomed to the region is no longer detected during this check it is flagged as having left so when it
// returns to the region it will be welcomed back. Someone who logs out and back in very quickly might not be welcomed back (and doesn't need to be).
string welcomeNotecard="";
string welcomeLandmark="";
// welcomeNotecard amd welcomeLandmark are the exact names of a notecard and/or a landmark in the prim's inventory that you want given to a first-time visitor. If the string is
// empty (you leave it at "") it won't try to give anything, otherwise it will try to give that object if it can find it in the prim's inventory. If you only want to give
// a notecard, leave welcomeLandmark at "", or if you only want to give a landmark then leave welcomeNotecard at "". Default is to give nothing at all.
// Note that giving something to an avatar causes the script to stop working for 2 seconds so don't hand out stuff that isn't needed.
integer inviteToGroup=FALSE;
// inviteToGroup can either be TRUE or FALSE (must be capital letters). If set to TRUE, a first-time visitor to your region will be invited to join the same group that prim is
// set to (so if you enable this, make sure the prim's group is set correctly!). I'm not 100% sure how the OSSL function checks group permissions so it's probably safest to
// make sure that you have group permissions set to allow you to invite people and to allow people to join any time (not just by invitation). It might work with different group
// permission settings though...you'd have to experiment. If set to FALSE, no group invitation is sent.
integer notifyOwner=TRUE;
// notifyOwner can either be TRUE or FALSE (must be capital letters). If set to TRUE, when someone arrives in the region and you are also in the region, you will be notified
// in general chat (but only you will be able to see it).
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
list visitorLog=[];     // timestamp|name|UUID
list regionLog=[];      // UUID|pos|name

welcome(key who,string name)
    // called when a new key is detected in the region
    // skip if this is the UUID of an NPC
    if (osIsNpc(who)) return;
    // find the index of the visitor in the visitor log
    integer visitorIndex=llListFindList(visitorLog,[who]);
    string strToSay=welcomeBackMessage;        // welcome message we're going to send - set to welcome back as default
    if (visitorIndex==-1)
        // this is a first time visitor and needs to be added to the visitor log
        if (includeTimes) visitorLog+=[llGetTimestamp()];  // user wants timestamp
        else visitorLog+=[llGetDate()];                    // user just wants data
        // and we want to send the first-time visitor message instead of the welcome back one
        // don't send anything to them yet though....need to wait until we've sent the welcome message
        // this is a repeat visitor so we need to update the timestamp instead
        if (includeTimes) llListReplaceList(visitorLog,[llGetTimestamp()],visitorIndex-2,visitorIndex-2);   // user wants timestamp
        else llListReplaceList(visitorLog,[llGetDate()],visitorIndex-2,visitorIndex-2);                     // user just wants date
    // sort the list and then store it
    if (llGetInventoryType(visitorLogNotecard)==INVENTORY_NOTECARD)
        llSleep(0.2);   // need to delay briefly to give it time to remove the old one
    // build new string to store
    string strToStore;
    integer i;
    integer l=llGetListLength(visitorLog);
    while (i<l)
        strToStore+=llDumpList2String(llList2List(visitorLog,i,i+2),"|") + "\n";        // can't use tabs to store and format nicely because names could include 4 spaces which is treated as a tab
    //store it
    // now send welcome message to the avi after parsing it to replace INSERT_NAME with the name
    while (llSubStringIndex(strToSay,"INSERT_NAME")>-1)
        integer ind=llSubStringIndex(strToSay,"INSERT_NAME");
    // if owner wants to be notified of visitors, send message (as long as it's not the owner being greeted)
    if (notifyOwner && (who!=llGetOwner()) && (llGetAgentSize(llGetOwner())!=ZERO_VECTOR)) llRegionSayTo(llGetOwner(),0,name+" has entered the region and been greeted");
    // if this is a first-time visitor we also may need to send a notecard and/or a landmark and/or a group invite
    if (visitorIndex==-1)
        if ((welcomeNotecard!="") && (llGetInventoryType(welcomeNotecard)==INVENTORY_NOTECARD)) llGiveInventory(who,welcomeNotecard);
        if ((welcomeLandmark!="") && (llGetInventoryType(welcomeLandmark)==INVENTORY_LANDMARK)) llGiveInventory(who,welcomeLandmark);
        if (inviteToGroup)
            key groupKey=llList2Key(llGetObjectDetails(llGetKey(),[OBJECT_GROUP]),0);
            if (groupKey==NULL_KEY) llOwnerSay("Cannot send a group invite because there is no group set for this prim");
            else osInviteToGroup(who);
        if(llGetObjectDesc() == "<disable>") return;   //Add "<disable>" to the object description to disable the script

        // zero the running visitor log and then read the stored notecard from memory if it exists to get previous visitors
        string logData;
        if (llGetInventoryType(visitorLogNotecard)==INVENTORY_NOTECARD) logData=osGetNotecard(visitorLogNotecard);
        // zero the in-region log as well
        // start the timer
    on_rez(integer start)
        // on first rez remove any existing visitor log
        if (llGetInventoryType(visitorLogNotecard)==INVENTORY_NOTECARD) llRemoveInventory(visitorLogNotecard);
    changed (integer change)
        // restart if the owner changes or any time the region is restarted
        if (change & CHANGED_OWNER)
            // on owner change remove any existing visitor log
            if (llGetInventoryType(visitorLogNotecard)==INVENTORY_NOTECARD) llRemoveInventory(visitorLogNotecard);
        else if (change & CHANGED_REGION_START) llResetScript();
    touch_start(integer num)
        if (llDetectedKey(0)!=llGetOwner()) return;
        if (llGetInventoryType(visitorLogNotecard)==INVENTORY_NOTECARD)
            llOwnerSay("Sending you the up to date visitor log");
        else llOwnerSay("ERROR! I could not find a stored visitor log in the prim's inventory. Did you delete it by mistake?");
        // update the region log to reflect who is currently in the region. OSSL function doesn't include owner so also add her/him if present
        list oldRegionLog=regionLog;
        if (llGetAgentSize(llGetOwner())!=ZERO_VECTOR) regionLog+=[llGetOwner(),<1,2,3>,osKey2Name(llGetOwner())];   // we don't do anything with position so just give it any value
        // see if anyone new is in the updated log
        integer checking;
        integer stop=llGetListLength(regionLog);
        while (checking<stop)
            key who=llList2Key(regionLog,checking); // UUID of person to check against the old log
            if (llListFindList(oldRegionLog,[who])==-1) welcome(who,llList2String(regionLog,checking+2));    // not in previous log so welcome them by passing UUID and name to UDF
            checking +=3;   // stride of the regionLog list