Want to let raid parties ping your group for combat?
This script will allow a group of 3 or more people from the same group to periodically send a mention in a custom message to your Discord server.
This article provides all of the necessary documentation to implement, maintain, and customize the Discord Raid Alerts script.
You must have the Manage Webhooks permission in your Discord server to create a webhook.
// Raid Alert written by Thunder Rahja
// Version 1; last updated 2019-06-13
// Documentation and licensing: https://www.vosimperium.com/docs/raidalert.html
/* ==== BEGIN ADVANCED SETTINGS AND CUSTOMIZABLE FUNCTIONS ==== */
string NOTECARD = "Raid Alert Settings";
integer INDICATOR_LINK = 2; // Link number of the status indicator prim.
integer touchTimeout = 1800; // in seconds
integer partySize = 3;
string webhookUrl = "";
list whiteList;
list blackList;
Busy()
{
llSetLinkColor(INDICATOR_LINK, <1,0.5,0>, ALL_SIDES);
llSetLinkPrimitiveParamsFast(INDICATOR_LINK, [PRIM_GLOW, ALL_SIDES, 0, PRIM_FULLBRIGHT, ALL_SIDES, FALSE,
PRIM_TEXT, "Raid Alert\nPlease wait...", <1,1,1>, 1]);
}
Ready()
{
llSetLinkColor(INDICATOR_LINK, <0,1,0>, ALL_SIDES);
llSetLinkPrimitiveParamsFast(INDICATOR_LINK, [PRIM_GLOW, ALL_SIDES, 0.1, PRIM_FULLBRIGHT, ALL_SIDES, TRUE,
PRIM_TEXT, "Looking for combat?\nClick to ping our Discord server", <1,1,1>, 1]);
}
ClickAccepted(key agent)
{
llRegionSayTo(agent, 0, "Discord alert sent. Please allow up to 5 minutes for combatants to arrive.");
}
ClickRefused(key agent, integer reason)
{
string answer;
if (reason == 1) answer = "You or your group are not allowed to use this system.";
else if (reason == 2) answer = "Your party must have at least " + (string)partySize + " members to send an alert."
+ " Make sure that all members of your party have the same group active.";
else if (reason == 3) answer = "There was a problem sending the alert to Discord. Please try again later.";
llRegionSayTo(agent, 0, answer);
}
string DiscordMessage(string agentName, string raiderCount)
{
string message = "ALERT: " + agentName + " wants to raid " + llGetRegionName() + " with a party of " +
raiderCount + " or more. @here";
return llList2Json(JSON_OBJECT, ["content", message]);
// Must return a valid JSON. See https://discord.com/developers/docs/resources/webhook#execute-webhook
}
/* ==== END AVANCED SETTINGS AND CUSTOMIZABLE FUNCTIONS ==== */
key lastAgent;
key notecardId;
integer notecardLine;
key notecardQuery;
string setting;
key webRequest;
integer busy;
key GetAgentGroup(key agent)
{
list attachedList = llGetAttachedList(agent);
if (attachedList)
{
return llList2Key(llGetObjectDetails(llList2Key(attachedList, 0), (list)OBJECT_GROUP), 0);
}
return "";
}
LoadNotecard()
{
if (llGetInventoryType(NOTECARD) != INVENTORY_NOTECARD) return;
key newNotecardId = llGetInventoryKey(NOTECARD);
if (newNotecardId != notecardId)
{
if (newNotecardId) notecardId = newNotecardId;
busy = TRUE;
Busy();
whiteList = [];
blackList = [];
webhookUrl = "";
touchTimeout = 1800;
partySize = 3;
setting = "";
notecardLine = 0;
notecardQuery = llGetNotecardLine(NOTECARD, notecardLine);
}
}
string Name2Username(string name)
{
list nameParts = llParseString2List(llToLower(name), [" ", "."], []);
if (llGetListLength(nameParts) > 2) return "";
if (llList2String(nameParts, 1) == "resident") return llList2String(nameParts, 0);
return llDumpList2String(nameParts, ".");
}
default
{
state_entry()
{
LoadNotecard();
llSetObjectDesc("Discord Raid Alerts by Thunder Rahja: https://www.vosimperium.com/docs/raidalert.html");
llSetLinkPrimitiveParamsFast(LINK_SET, [PRIM_TEXT, "", ZERO_VECTOR, 0]);
}
changed(integer change)
{
if (change & CHANGED_INVENTORY)
{
LoadNotecard();
}
}
dataserver(key id, string data)
{
if (id == notecardQuery)
{
if (data != EOF)
{
notecardQuery = llGetNotecardLine(NOTECARD, ++notecardLine);
if (data = llStringTrim(data, STRING_TRIM))
{
if (llGetSubString(data, 0, 1) != "//")
{
integer index = llSubStringIndex(data, " //");
if (index != -1)
{
data = llGetSubString(data, 0, index - 1);
}
string value;
if ((index = llSubStringIndex(data, "=")) != -1)
{
setting = llToLower(llGetSubString(data, 0, index - 1));
value = llDeleteSubString(data, 0, index);
}
else value = data;
if (value)
{
if (setting == "whitelist")
{
if (id = (key)value) whiteList += (key)value;
else if (value = Name2Username(value)) whiteList += value;
return;
}
else if (setting == "blacklist")
{
if (id = (key)value) blackList += (key)value;
else if (value = Name2Username(value)) blackList += value;
return;
}
else if (setting == "webhook")
{
webhookUrl = value;
}
else if (setting == "timeout")
{
touchTimeout = (integer)value * 60;
if (touchTimeout <= 0) touchTimeout = 1800;
}
else if (setting == "partysize")
{
partySize = (integer)value;
if (partySize < 0) partySize = 0;
}
setting = "";
}
}
}
}
else
{
setting = "";
busy = FALSE;
Ready();
}
}
}
touch_start(integer n)
{
if (busy) return;
key agentKey = llDetectedKey(0);
string agentName = Name2Username(llDetectedName(0));
if (llListFindList(blackList, (list)agentName) != -1)
{
ClickRefused(agentKey, 1);
return;
}
key agentGroup = GetAgentGroup(agentKey);
if (whiteList)
{
if (llListFindList(whiteList, (list)agentName) == -1)
{
if (agentGroup)
{
if (llListFindList(whiteList, (list)agentGroup) == -1)
{
ClickRefused(agentKey, 1);
return;
}
}
else
{
ClickRefused(agentKey, 1);
return;
}
}
}
else if (llListFindList(blackList, (list)agentGroup) != -1)
{
ClickRefused(agentKey, 1);
return;
}
if (agentGroup)
{
list agentList = llGetAgentList(AGENT_LIST_REGION, []);
integer count;
integer n = llGetListLength(agentList);
while (n--)
{
if (GetAgentGroup(llList2Key(agentList, n)) == agentGroup) count++;
}
if (count >= partySize)
{
busy = TRUE;
Busy();
llSetTimerEvent(touchTimeout);
agentName = llDetectedName(0);
if (llGetSubString(agentName, -9, -1) == "Resident")
agentName = llDeleteSubString(agentName, -9, -1);
string body = DiscordMessage(agentName, (string)count);
webRequest = llHTTPRequest(webhookUrl, [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/json"], body);
return;
}
}
ClickRefused(agentKey, 2);
busy = FALSE;
Ready();
}
timer()
{
llSetTimerEvent(0);
busy = FALSE;
Ready();
lastAgent = "";
}
http_response(key id, integer status, list meta, string body)
{
if (id != webRequest) return;
if (status < 300)
{
ClickAccepted(lastAgent);
}
else if (status == 499)
{
llInstantMessage(llGetOwner(), "Second Life failed to send an HTTP request. " +
"A region restart may be required.");
ClickRefused(lastAgent, 3);
busy = FALSE;
Ready();
}
else if (status >= 500)
{
llOwnerSay("There was a server error when sending a Discord message. Status: " + (string)status);
ClickRefused(lastAgent, 3);
busy = FALSE;
Ready();
}
else if (status >= 400)
{
llInstantMessage(llGetOwner(), "There was a problem with the request when sending a Discord message. " +
"Make sure that the webhook is valid. Status: " + (string)status);
ClickRefused(lastAgent, 3);
busy = FALSE;
Ready();
}
else
{
llInstantMessage(llGetOwner(), "There was an unknown problem when sending a Discord message. Status: " +
(string)status);
}
}
}
The Raid Alert Settings notecard uses an intuitive "key=value" format. Lines that do not begin with a setting name are assumed to be a continuiation of the last setting specified. Extra spaces are removed before importing each line, and any text after two slashes "//", either at the beginning of the line or after a space, is also removed, so you may use tabs, spaces, and comments to better organize the notecard.
The webhook setting specifies the URL to send messages to. Discord server members with the Manage Webhooks role can create webhooks and retrieve webhook URLs. Webhook URLs look something like the following:
The timeout setting specifies the number of minutes after an alert before another alert may be sent. By default, this is 30 minutes.
The partysize setting determines the minimum number of people that must be in a raid party to send an alert. When clicked, the Raid Alert script counts the number of people in the region who have the same group active as the person who clicked it. If that count is less than the partysize setting, then an alert is not sent.
When the whitelist setting is used, everyone who tries to send an alert must have either their names or their active group keys somewhere in the whitelist. The whitelist accepts group keys in the 8-4-4-12 UUID format or names in any format (except display names, which will not work). When there is a conflict between the whitelist and blacklist, names take priority over group keys. Here is an example whitelist:
whitelist= 87824e8b-4734-4ba1-8cf9-b2cf8e1499d5 // Vos Imperium Thunder Rahja hokusai.otsuka Hurwyn
When the blacklist setting is used, everyone who tries to send an alert must not have their names or their active group keys in the blacklist. The same formatting rules as the whitelist apply here. If somehow a name or group key is in both the whitelist and the blacklist, the whitelist takes priority.
It is recommended to have your own group key(s) in the blacklist to prevent accidental alerts.
If you have some scripting experience, you can customize the script to better suit your needs. Open the script and look for the appropriately-marked section where you can change the behavior of the script during certain events. The following variables and functions are located near the top of the script for editing: