First Omnifilter implementation - Can block chat, dialogs, teleport offers, friendship, etc. based on content rather than just owner or sender.
parent
4b4e3df6de
commit
67813450af
|
|
@ -187,6 +187,8 @@ set(viewer_SOURCE_FILES
|
||||||
lfsimfeaturehandler.cpp
|
lfsimfeaturehandler.cpp
|
||||||
llflickrconnect.cpp
|
llflickrconnect.cpp
|
||||||
llfloaterflickr.cpp
|
llfloaterflickr.cpp
|
||||||
|
omnifilter.cpp
|
||||||
|
omnifilterengine.cpp
|
||||||
fsprimfeedconnect.cpp
|
fsprimfeedconnect.cpp
|
||||||
fsfloaterprimfeed.cpp
|
fsfloaterprimfeed.cpp
|
||||||
llpanelopenregionsettings.cpp
|
llpanelopenregionsettings.cpp
|
||||||
|
|
@ -1036,6 +1038,8 @@ set(viewer_HEADER_FILES
|
||||||
fsfloaterprimfeed.h
|
fsfloaterprimfeed.h
|
||||||
# <FS:Ansariel> [Legacy Bake]
|
# <FS:Ansariel> [Legacy Bake]
|
||||||
llagentwearablesfetch.h
|
llagentwearablesfetch.h
|
||||||
|
omnifilter.h
|
||||||
|
omnifilterengine.h
|
||||||
vjlocalmesh.h
|
vjlocalmesh.h
|
||||||
vjfloaterlocalmesh.h
|
vjfloaterlocalmesh.h
|
||||||
vjlocalmeshimportdae.h
|
vjlocalmeshimportdae.h
|
||||||
|
|
|
||||||
|
|
@ -680,4 +680,15 @@
|
||||||
is_running_parameters="performance"
|
is_running_parameters="performance"
|
||||||
checkbox_control="AutoTuneFPS"
|
checkbox_control="AutoTuneFPS"
|
||||||
/>
|
/>
|
||||||
|
<command name="omnifilter"
|
||||||
|
available_in_toybox="true"
|
||||||
|
icon="Command_Omnifilter_Icon"
|
||||||
|
label_ref="Command_Omnifilter_Label"
|
||||||
|
tooltip_ref="Command_Omnifilter_Tooltip"
|
||||||
|
execute_function="Floater.ToggleOrBringToFront"
|
||||||
|
execute_parameters="omnifilter"
|
||||||
|
is_running_function="Floater.IsOpen"
|
||||||
|
is_running_parameters="omnifilter"
|
||||||
|
checkbox_control="OmnifilterEnabled"
|
||||||
|
/>
|
||||||
</commands>
|
</commands>
|
||||||
|
|
|
||||||
|
|
@ -27193,5 +27193,16 @@ Change of this parameter will affect the layout of buttons in notification toast
|
||||||
<key>Value</key>
|
<key>Value</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</map>
|
</map>
|
||||||
|
<key>OmnifilterEnabled</key>
|
||||||
|
<map>
|
||||||
|
<key>Comment</key>
|
||||||
|
<string>The operating state of the Omnifilter.</string>
|
||||||
|
<key>Persist</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>Value</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</map>
|
||||||
</map>
|
</map>
|
||||||
</llsd>
|
</llsd>
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@
|
||||||
#include "llgiveinventory.h"
|
#include "llgiveinventory.h"
|
||||||
#include "lllandmarkactions.h"
|
#include "lllandmarkactions.h"
|
||||||
#include "llviewernetwork.h"
|
#include "llviewernetwork.h"
|
||||||
|
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
|
||||||
#include "sound_ids.h"
|
#include "sound_ids.h"
|
||||||
#include "NACLantispam.h"
|
#include "NACLantispam.h"
|
||||||
|
|
||||||
|
|
@ -721,6 +722,100 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
|
||||||
*-----------------------------------------------------
|
*-----------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// <FS:Zi> Omnifilter support
|
||||||
|
OmnifilterEngine::Haystack haystack;
|
||||||
|
haystack.mContent = message;
|
||||||
|
haystack.mSenderName = agentName;
|
||||||
|
haystack.mOwnerID = from_id;
|
||||||
|
|
||||||
|
switch (dialog)
|
||||||
|
{
|
||||||
|
case IM_NOTHING_SPECIAL: // this is the type for regular IMs
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::InstantMessage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_SESSION_SEND: // this is the type for regular group IMs
|
||||||
|
case IM_SESSION_INVITE: // this is the type for group IM sessions started by ourselves or conferences
|
||||||
|
case IM_DO_NOT_DISTURB_AUTO_RESPONSE:
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::GroupChat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_FROM_TASK:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Object name
|
||||||
|
// haystack.mOwnerID = from_id; // object owner UUID
|
||||||
|
// haystack.mContent = message; // IM content
|
||||||
|
haystack.mType = OmnifilterEngine::eType::ObjectInstantMessage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_GROUP_INVITATION:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Inviter "first.last"
|
||||||
|
// haystack.mOwnerID = from_id; // group ID
|
||||||
|
haystack.mType = OmnifilterEngine::eType::GroupInvite;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_INVENTORY_OFFERED:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Sender "First Last"
|
||||||
|
// haystack.mOwnerID = from_id; // Sender UUID
|
||||||
|
// haystack.mContent = message; // item/folder name
|
||||||
|
haystack.mType = OmnifilterEngine::eType::InventoryOffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_FRIENDSHIP_OFFERED:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Sender "First Last"
|
||||||
|
// haystack.mOwnerID = from_id; // Sender UUID
|
||||||
|
// haystack.mContent = message; // friendship message
|
||||||
|
haystack.mType = OmnifilterEngine::eType::FriendshipOffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_LURE_USER:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Sender "First Last"
|
||||||
|
// haystack.mOwnerID = from_id; // Sender UUID
|
||||||
|
// haystack.mContent = message; // teleport message
|
||||||
|
haystack.mType = OmnifilterEngine::eType::Lure;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_TELEPORT_REQUEST:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Sender "First Last"
|
||||||
|
// haystack.mOwnerID = from_id; // Sender UUID
|
||||||
|
// haystack.mContent = message; // teleport message
|
||||||
|
haystack.mType = OmnifilterEngine::eType::TeleportRequest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IM_GROUP_NOTICE:
|
||||||
|
{
|
||||||
|
// haystack.mSenderName = agentName; // Notice Sender "First Last"
|
||||||
|
// haystack.mOwnerID = from_id; // Notice Sender UUID
|
||||||
|
// haystack.mContent = message; // notice message
|
||||||
|
haystack.mType = OmnifilterEngine::eType::GroupNotice;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "unhandled IM type " << (U32)dialog << LL_ENDL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
|
||||||
|
if (needle)
|
||||||
|
{
|
||||||
|
if (needle->mChatReplace.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = needle->mChatReplace;
|
||||||
|
}
|
||||||
|
// </FS:Zi>
|
||||||
|
|
||||||
std::string notice_name;
|
std::string notice_name;
|
||||||
LLSD notice_args;
|
LLSD notice_args;
|
||||||
if (metadata.has("notice"))
|
if (metadata.has("notice"))
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
#include "dialogstack.h"
|
#include "dialogstack.h"
|
||||||
#include "llbutton.h"
|
#include "llbutton.h"
|
||||||
#include "llnavigationbar.h"
|
#include "llnavigationbar.h"
|
||||||
|
#include "omnifilterengine.h" // Omnifilter support
|
||||||
// </FS:Zi>
|
// </FS:Zi>
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
@ -473,6 +474,74 @@ void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id)
|
||||||
// get scripted Object's ID
|
// get scripted Object's ID
|
||||||
LLUUID object_id = notification_id_to_object_id(notification_id);
|
LLUUID object_id = notification_id_to_object_id(notification_id);
|
||||||
|
|
||||||
|
// <FS:Zi> Omnifilter support
|
||||||
|
LLNotificationPtr notification = LLNotifications::instance().find(notification_id);
|
||||||
|
|
||||||
|
OmnifilterEngine::Haystack haystack;
|
||||||
|
haystack.mContent = notification->getMessage();
|
||||||
|
|
||||||
|
if(notification->getName() == "ScriptDialog") // ScriptDialogGroup seems not to be in use anymore?
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::ScriptDialog;
|
||||||
|
haystack.mSenderName = notification->getPayload()["object_name"].asString();
|
||||||
|
haystack.mOwnerID = notification->getPayload()["owner_id"];
|
||||||
|
}
|
||||||
|
else if(notification->getName() == "ObjectGiveItem") // what about OwnObjectGiveItem?
|
||||||
|
{
|
||||||
|
// "description":"\'Object Name\' ( http://slurl.com/secondlife/Region%20Name/x/y/z )"
|
||||||
|
std::vector<std::string> params;
|
||||||
|
std::string description = notification->asLLSD()["responder_sd"]["description"].asString();
|
||||||
|
if (!description.empty())
|
||||||
|
{
|
||||||
|
LLStringUtil::getTokens(description, params, "/");
|
||||||
|
haystack.mRegionName = LLURI::unescape(params.at(params.size() - 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
haystack.mType = OmnifilterEngine::eType::URLRequest;
|
||||||
|
haystack.mSenderName = notification->asLLSD()["responder_sd"]["from_name"].asString();
|
||||||
|
haystack.mOwnerID = notification->getPayload()["from_id"];
|
||||||
|
}
|
||||||
|
else if(notification->getName() == "LoadWebPage")
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::ScriptDialog;
|
||||||
|
haystack.mSenderName = notification->getPayload()["object_name"].asString();
|
||||||
|
haystack.mOwnerID = notification->getPayload()["owner_id"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LL_WARNS("Omnifilter") << "unknown notification name: " << notification->getName() << LL_ENDL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
|
||||||
|
if(needle)
|
||||||
|
{
|
||||||
|
LLSD response = notification->getResponseTemplate();
|
||||||
|
|
||||||
|
if(response.has(TEXTBOX_MAGIC_TOKEN))
|
||||||
|
{
|
||||||
|
response[TEXTBOX_MAGIC_TOKEN] = needle->mTextBoxReply;
|
||||||
|
if (response[TEXTBOX_MAGIC_TOKEN].asString().empty())
|
||||||
|
{
|
||||||
|
// so we can distinguish between a successfully
|
||||||
|
// submitted blank textbox, and an ignored toast
|
||||||
|
response[TEXTBOX_MAGIC_TOKEN] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!needle->mButtonReply.empty())
|
||||||
|
{
|
||||||
|
response[needle->mButtonReply] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will result in DialogStack complaining that there is no matching dialog to remove
|
||||||
|
// but that should not break anything
|
||||||
|
notification->respond(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// </FS:Zi>
|
||||||
|
|
||||||
// Need to indicate of "new message" for object chiclets according to requirements
|
// Need to indicate of "new message" for object chiclets according to requirements
|
||||||
// specified in the Message Bar design specification. See EXT-3142.
|
// specified in the Message Bar design specification. See EXT-3142.
|
||||||
bool set_new_message = false;
|
bool set_new_message = false;
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,7 @@
|
||||||
#include "llprogressview.h"
|
#include "llprogressview.h"
|
||||||
#include "lltoolbarview.h"
|
#include "lltoolbarview.h"
|
||||||
#include "NACLantispam.h"
|
#include "NACLantispam.h"
|
||||||
|
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
|
||||||
#include "streamtitledisplay.h"
|
#include "streamtitledisplay.h"
|
||||||
#include "tea.h"
|
#include "tea.h"
|
||||||
|
|
||||||
|
|
@ -1281,6 +1282,8 @@ bool idle_startup()
|
||||||
}
|
}
|
||||||
show_release_notes_if_required();
|
show_release_notes_if_required();
|
||||||
|
|
||||||
|
OmnifilterEngine::getInstance()->init(); // <FS:Zi> Omnifilter support
|
||||||
|
|
||||||
if (show_connect_box)
|
if (show_connect_box)
|
||||||
{
|
{
|
||||||
LL_DEBUGS("AppInit") << "show_connect_box on" << LL_ENDL;
|
LL_DEBUGS("AppInit") << "show_connect_box on" << LL_ENDL;
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@
|
||||||
#include "quickprefs.h"
|
#include "quickprefs.h"
|
||||||
#include "vjfloaterlocalmesh.h" // local mesh
|
#include "vjfloaterlocalmesh.h" // local mesh
|
||||||
#include "fsfloaterwhitelisthelper.h" // fs whitelist helper
|
#include "fsfloaterwhitelisthelper.h" // fs whitelist helper
|
||||||
|
#include "omnifilter.h" // Omnifilter support
|
||||||
|
|
||||||
// handle secondlife:///app/openfloater/{NAME} URLs
|
// handle secondlife:///app/openfloater/{NAME} URLs
|
||||||
const std::string FLOATER_PROFILE("profile");
|
const std::string FLOATER_PROFILE("profile");
|
||||||
|
|
@ -667,6 +667,7 @@ void LLViewerFloaterReg::registerFloaters()
|
||||||
LLFloaterReg::add("lgg_beamshape", "floater_beamshape.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<lggBeamMapFloater>);
|
LLFloaterReg::add("lgg_beamshape", "floater_beamshape.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<lggBeamMapFloater>);
|
||||||
LLFloaterReg::add("media_lists", "floater_media_lists.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FloaterMediaLists>);
|
LLFloaterReg::add("media_lists", "floater_media_lists.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FloaterMediaLists>);
|
||||||
LLFloaterReg::add("money_tracker", "floater_fs_money_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSMoneyTracker>);
|
LLFloaterReg::add("money_tracker", "floater_fs_money_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSMoneyTracker>);
|
||||||
|
LLFloaterReg::add("omnifilter", "floater_omnifilter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<Omnifilter>); // <FS:Zi> Omnifilter support
|
||||||
LLFloaterReg::add("particle_editor","floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ParticleEditor>);
|
LLFloaterReg::add("particle_editor","floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ParticleEditor>);
|
||||||
LLFloaterReg::add("performance", "floater_fs_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterPerformance>);
|
LLFloaterReg::add("performance", "floater_fs_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterPerformance>);
|
||||||
// <FS:William_W:FixPhototoolsTypo> [PhotoTools] Corrected typo in Phototools floater registration - using string literal instead of PHOTOTOOLS_FLOATER constant (likely intended).
|
// <FS:William_W:FixPhototoolsTypo> [PhotoTools] Corrected typo in Phototools floater registration - using string literal instead of PHOTOTOOLS_FLOATER constant (likely intended).
|
||||||
|
|
|
||||||
|
|
@ -8559,6 +8559,13 @@ void handle_edit_shape()
|
||||||
LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_shape"));
|
LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_shape"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// <FS:Zi> Omnifilter support
|
||||||
|
void handle_omnifilter()
|
||||||
|
{
|
||||||
|
LLFloaterReg::showInstance("omnifilter");
|
||||||
|
}
|
||||||
|
// </FS:Zi>
|
||||||
|
|
||||||
void handle_hover_height()
|
void handle_hover_height()
|
||||||
{
|
{
|
||||||
LLFloaterReg::showInstance("edit_hover_height");
|
LLFloaterReg::showInstance("edit_hover_height");
|
||||||
|
|
@ -12638,6 +12645,7 @@ void initialize_menus()
|
||||||
commit.add("NowWearing", boost::bind(&handle_now_wearing));
|
commit.add("NowWearing", boost::bind(&handle_now_wearing));
|
||||||
commit.add("EditOutfit", boost::bind(&handle_edit_outfit));
|
commit.add("EditOutfit", boost::bind(&handle_edit_outfit));
|
||||||
commit.add("EditShape", boost::bind(&handle_edit_shape));
|
commit.add("EditShape", boost::bind(&handle_edit_shape));
|
||||||
|
commit.add("Omnifilter", boost::bind(&handle_omnifilter)); // <FS:Zi> Omnifilter support
|
||||||
commit.add("HoverHeight", boost::bind(&handle_hover_height));
|
commit.add("HoverHeight", boost::bind(&handle_hover_height));
|
||||||
commit.add("EditPhysics", boost::bind(&handle_edit_physics));
|
commit.add("EditPhysics", boost::bind(&handle_edit_physics));
|
||||||
// <FS:TT> Client LSL Bridge
|
// <FS:TT> Client LSL Bridge
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@
|
||||||
#include "llfloaterbump.h"
|
#include "llfloaterbump.h"
|
||||||
#include "llfloaterreg.h"
|
#include "llfloaterreg.h"
|
||||||
#include "llfriendcard.h"
|
#include "llfriendcard.h"
|
||||||
|
#include "omnifilterengine.h" // <FS:Zi> Omnifilter support
|
||||||
#include "permissionstracker.h" // <FS:Zi> Permissions Tracker
|
#include "permissionstracker.h" // <FS:Zi> Permissions Tracker
|
||||||
#include "tea.h" // <FS:AW opensim currency support>
|
#include "tea.h" // <FS:AW opensim currency support>
|
||||||
#include "NACLantispam.h"
|
#include "NACLantispam.h"
|
||||||
|
|
@ -3251,6 +3252,69 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
|
||||||
chat.mText += mesg;
|
chat.mText += mesg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// <FS:Zi> Omnifilter support
|
||||||
|
OmnifilterEngine::Haystack haystack;
|
||||||
|
haystack.mContent = chat.mText;
|
||||||
|
haystack.mSenderName = from_name; // we don't use chat.mFromName here because that will include display names etc.
|
||||||
|
haystack.mOwnerID = chat.mFromID;
|
||||||
|
|
||||||
|
switch (chat.mChatType)
|
||||||
|
{
|
||||||
|
case CHAT_TYPE_WHISPER:
|
||||||
|
case CHAT_TYPE_NORMAL:
|
||||||
|
case CHAT_TYPE_SHOUT:
|
||||||
|
case CHAT_TYPE_DEBUG_MSG:
|
||||||
|
case CHAT_TYPE_REGION:
|
||||||
|
case CHAT_TYPE_OWNER:
|
||||||
|
case CHAT_TYPE_DIRECT:
|
||||||
|
{
|
||||||
|
switch (chat.mSourceType)
|
||||||
|
{
|
||||||
|
case CHAT_SOURCE_AGENT: // this is the type for regular chat
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::NearbyChat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CHAT_SOURCE_OBJECT: // this is the type for object chat
|
||||||
|
{
|
||||||
|
haystack.mType = OmnifilterEngine::eType::ObjectChat;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "unhandled source type " << (U32)chat.mSourceType << LL_ENDL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const OmnifilterEngine::Needle* needle = OmnifilterEngine::getInstance()->match(haystack);
|
||||||
|
|
||||||
|
if (needle)
|
||||||
|
{
|
||||||
|
// we need to make sure to put the typing stopped flag on the chatting avatar (if it is an avatar)
|
||||||
|
if (chatter && chatter->isAvatar())
|
||||||
|
{
|
||||||
|
LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false);
|
||||||
|
((LLVOAvatar*)chatter)->stopTyping();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needle->mChatReplace.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chat.mText = needle->mChatReplace;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "unhandled chat type " << (U32)chat.mChatType << LL_ENDL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// </FS:Zi>
|
||||||
|
|
||||||
// We have a real utterance now, so can stop showing "..." and proceed.
|
// We have a real utterance now, so can stop showing "..." and proceed.
|
||||||
if (chatter && chatter->isAvatar())
|
if (chatter && chatter->isAvatar())
|
||||||
{
|
{
|
||||||
|
|
@ -8284,6 +8348,7 @@ void process_script_dialog(LLMessageSystem* msg, void**)
|
||||||
payload["object_id"] = object_id;
|
payload["object_id"] = object_id;
|
||||||
payload["chat_channel"] = chat_channel;
|
payload["chat_channel"] = chat_channel;
|
||||||
payload["object_name"] = object_name;
|
payload["object_name"] = object_name;
|
||||||
|
payload["owner_id"] = owner_id; // <FS:Zi> Omnifilter support
|
||||||
|
|
||||||
// <FS:Ansariel> FIRE-17158: Remove "block" button for script dialog of own objects
|
// <FS:Ansariel> FIRE-17158: Remove "block" button for script dialog of own objects
|
||||||
bool own_object = false;
|
bool own_object = false;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
/**
|
||||||
|
* @file omnifilter.cpp
|
||||||
|
* @brief The Omnifilter editor
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2025, Zi Ree @ Second Life
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "omnifilter.h"
|
||||||
|
|
||||||
|
#include "llbutton.h"
|
||||||
|
#include "llcheckboxctrl.h"
|
||||||
|
#include "llcombobox.h"
|
||||||
|
#include "lllineeditor.h"
|
||||||
|
#include "lltexteditor.h"
|
||||||
|
#include "lltrans.h"
|
||||||
|
|
||||||
|
#include "fsscrolllistctrl.h"
|
||||||
|
|
||||||
|
static constexpr S32 NEEDLE_CHECK_COLUMN = 0;
|
||||||
|
static constexpr S32 NEEDLE_NAME_COLUMN = 1;
|
||||||
|
|
||||||
|
static constexpr S32 LOG_DATE_COLUMN = 0;
|
||||||
|
static constexpr S32 LOG_CONTENT_COLUMN = 1;
|
||||||
|
|
||||||
|
Omnifilter::Omnifilter(const LLSD& key) :
|
||||||
|
LLFloater(key)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Omnifilter::~Omnifilter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LLScrollListItem* Omnifilter::addNeedle(const std::string& needle_name, const OmnifilterEngine::Needle& needle)
|
||||||
|
{
|
||||||
|
LLSD row;
|
||||||
|
row["columns"][NEEDLE_CHECK_COLUMN]["column"] = "enabled";
|
||||||
|
row["columns"][NEEDLE_CHECK_COLUMN]["type"] = "checkbox";
|
||||||
|
row["columns"][NEEDLE_CHECK_COLUMN]["value"] = false;
|
||||||
|
|
||||||
|
row["columns"][NEEDLE_NAME_COLUMN]["column"] = "needle_name";
|
||||||
|
row["columns"][NEEDLE_NAME_COLUMN]["type"] = "text";
|
||||||
|
row["columns"][NEEDLE_NAME_COLUMN]["value"] = needle_name;
|
||||||
|
|
||||||
|
LLScrollListItem* item = mNeedleListCtrl->addElement(row);
|
||||||
|
item->getColumn(NEEDLE_CHECK_COLUMN)->setValue(needle.mEnabled);
|
||||||
|
|
||||||
|
LLScrollListCheck* scroll_list_check = dynamic_cast<LLScrollListCheck*>(item->getColumn(NEEDLE_CHECK_COLUMN));
|
||||||
|
if (scroll_list_check)
|
||||||
|
{
|
||||||
|
LLCheckBoxCtrl* check_box = scroll_list_check->getCheckBox();
|
||||||
|
check_box->setCommitCallback(boost::bind(&Omnifilter::onNeedleCheckboxChanged, this, _1));
|
||||||
|
}
|
||||||
|
|
||||||
|
mNeedleListCtrl->refreshLineHeight();
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::Needle* Omnifilter::getSelectedNeedle()
|
||||||
|
{
|
||||||
|
const LLScrollListItem* needle_item = mNeedleListCtrl->getFirstSelected();
|
||||||
|
if (needle_item)
|
||||||
|
{
|
||||||
|
const LLScrollListCell* needle_name_cell = needle_item->getColumn(NEEDLE_NAME_COLUMN);
|
||||||
|
if (needle_name_cell)
|
||||||
|
{
|
||||||
|
const std::string& needle_name = needle_name_cell->getValue().asString();
|
||||||
|
if (!needle_name.empty())
|
||||||
|
{
|
||||||
|
return &OmnifilterEngine::getInstance()->getNeedleList().at(needle_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mPanelDetails->setVisible(false);
|
||||||
|
mRemoveNeedleBtn->setEnabled(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onSelectNeedle()
|
||||||
|
{
|
||||||
|
const OmnifilterEngine::Needle* needle = getSelectedNeedle();
|
||||||
|
if (!needle)
|
||||||
|
{
|
||||||
|
mPanelDetails->setVisible(false);
|
||||||
|
mRemoveNeedleBtn->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mNeedleListCtrl->getItemCount())
|
||||||
|
{
|
||||||
|
mPanelDetails->setVisible(false);
|
||||||
|
mRemoveNeedleBtn->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPanelDetails->setVisible(true);
|
||||||
|
mRemoveNeedleBtn->setEnabled(true);
|
||||||
|
|
||||||
|
mNeedleNameCtrl->setText(mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN));
|
||||||
|
mSenderNameCtrl->setText(needle->mSenderName);
|
||||||
|
mSenderCaseSensitiveCheck->setValue(!needle->mSenderNameCaseInsensitive);
|
||||||
|
mSenderMatchTypeCombo->selectByValue(needle->mSenderNameMatchType);
|
||||||
|
mContentCtrl->setText(needle->mContent);
|
||||||
|
mContentCaseSensitiveCheck->setValue(!needle->mContentCaseInsensitive);
|
||||||
|
mContentMatchTypeCombo->selectByValue(needle->mContentMatchType);
|
||||||
|
mRegionNameCtrl->setText(needle->mRegionName);
|
||||||
|
mOwnerCtrl->clear();
|
||||||
|
if (needle->mOwnerID.notNull())
|
||||||
|
{
|
||||||
|
mOwnerCtrl->setText(needle->mOwnerID.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
mTypeNearbyBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::NearbyChat) != needle->mTypes.end());
|
||||||
|
mTypeIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::InstantMessage) != needle->mTypes.end());
|
||||||
|
mTypeGroupIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupChat) != needle->mTypes.end());
|
||||||
|
mTypeObjectChatBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ObjectChat) != needle->mTypes.end());
|
||||||
|
mTypeObjectIMBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ObjectInstantMessage) != needle->mTypes.end());
|
||||||
|
mTypeScriptErrorBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ScriptError) != needle->mTypes.end());
|
||||||
|
mTypeDialogBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::ScriptDialog) != needle->mTypes.end());
|
||||||
|
mTypeOfferBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::InventoryOffer) != needle->mTypes.end());
|
||||||
|
mTypeInviteBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupInvite) != needle->mTypes.end());
|
||||||
|
mTypeLureBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::Lure) != needle->mTypes.end());
|
||||||
|
mTypeLoadURLBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::URLRequest) != needle->mTypes.end());
|
||||||
|
mTypeFriendshipOfferBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::FriendshipOffer) != needle->mTypes.end());
|
||||||
|
mTypeTeleportRequestBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::TeleportRequest) != needle->mTypes.end());
|
||||||
|
mTypeGroupNoticeBtn->setToggleState(needle->mTypes.find(OmnifilterEngine::eType::GroupNotice) != needle->mTypes.end());
|
||||||
|
|
||||||
|
mChatReplaceCtrl->setText(needle->mChatReplace);
|
||||||
|
mButtonReplyCtrl->setText(needle->mButtonReply);
|
||||||
|
mTextBoxReplyCtrl->setText(needle->mTextBoxReply);
|
||||||
|
|
||||||
|
mContentCtrl->setFocus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onNeedleChanged()
|
||||||
|
{
|
||||||
|
OmnifilterEngine::Needle* needle = getSelectedNeedle();
|
||||||
|
if (!needle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
needle->mSenderName = mSenderNameCtrl->getValue().asString();
|
||||||
|
needle->mSenderNameCaseInsensitive = !mSenderCaseSensitiveCheck->getValue();
|
||||||
|
needle->mSenderNameMatchType = static_cast<OmnifilterEngine::eMatchType>(mSenderMatchTypeCombo->getValue().asInteger());
|
||||||
|
needle->mContent = mContentCtrl->getValue().asString();
|
||||||
|
needle->mContentCaseInsensitive = !mContentCaseSensitiveCheck->getValue();
|
||||||
|
needle->mContentMatchType = static_cast<OmnifilterEngine::eMatchType>(mContentMatchTypeCombo->getValue().asInteger());
|
||||||
|
needle->mRegionName = mRegionNameCtrl->getValue().asString();
|
||||||
|
needle->mOwnerID.set(mOwnerCtrl->getValue().asString());
|
||||||
|
|
||||||
|
needle->mTypes.clear();
|
||||||
|
|
||||||
|
if (mTypeNearbyBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::NearbyChat);
|
||||||
|
if (mTypeIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::InstantMessage);
|
||||||
|
if (mTypeGroupIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupChat);
|
||||||
|
if (mTypeObjectChatBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ObjectChat);
|
||||||
|
if (mTypeObjectIMBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ObjectInstantMessage);
|
||||||
|
if (mTypeScriptErrorBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ScriptError);
|
||||||
|
if (mTypeDialogBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::ScriptDialog);
|
||||||
|
if (mTypeOfferBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::InventoryOffer);
|
||||||
|
if (mTypeInviteBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupInvite);
|
||||||
|
if (mTypeLureBtn->getToggleState()) needle->mTypes.insert(OmnifilterEngine::eType::Lure);
|
||||||
|
if (mTypeLoadURLBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::URLRequest);
|
||||||
|
if (mTypeFriendshipOfferBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::FriendshipOffer);
|
||||||
|
if (mTypeTeleportRequestBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::TeleportRequest);
|
||||||
|
if (mTypeGroupNoticeBtn->getValue()) needle->mTypes.insert(OmnifilterEngine::eType::GroupNotice);
|
||||||
|
|
||||||
|
needle->mChatReplace = mChatReplaceCtrl->getValue().asString();
|
||||||
|
needle->mButtonReply = mButtonReplyCtrl->getValue().asString();
|
||||||
|
needle->mTextBoxReply = mTextBoxReplyCtrl->getValue().asString();
|
||||||
|
|
||||||
|
OmnifilterEngine::getInstance()->setDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onAddNeedleClicked()
|
||||||
|
{
|
||||||
|
std::string new_needle_string = LLTrans::getString("OmnifilterNewNeedle");
|
||||||
|
if (!mNeedleListCtrl->selectItemByLabel(new_needle_string, true, NEEDLE_NAME_COLUMN))
|
||||||
|
{
|
||||||
|
OmnifilterEngine::Needle& new_needle = OmnifilterEngine::getInstance()->newNeedle(new_needle_string);
|
||||||
|
addNeedle(new_needle_string, new_needle)->setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectNeedle();
|
||||||
|
mNeedleNameCtrl->setFocus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onRemoveNeedleClicked()
|
||||||
|
{
|
||||||
|
S32 index = mNeedleListCtrl->getItemIndex(mNeedleListCtrl->getFirstSelected());
|
||||||
|
OmnifilterEngine::getInstance()->deleteNeedle(mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN));
|
||||||
|
|
||||||
|
mNeedleListCtrl->selectPrevItem();
|
||||||
|
mNeedleListCtrl->deleteSingleItem(index);
|
||||||
|
|
||||||
|
if (!mNeedleListCtrl->getNumSelected())
|
||||||
|
{
|
||||||
|
mNeedleListCtrl->selectFirstItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectNeedle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onNeedleNameChanged()
|
||||||
|
{
|
||||||
|
const std::string& old_name = mNeedleListCtrl->getSelectedItemLabel(NEEDLE_NAME_COLUMN);
|
||||||
|
const std::string& new_name = mNeedleNameCtrl->getValue().asString();
|
||||||
|
|
||||||
|
if (old_name == new_name)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LLScrollListItem* needle_item = mNeedleListCtrl->getFirstSelected();
|
||||||
|
|
||||||
|
const LLScrollListItem* name_check_item = mNeedleListCtrl->getItemByLabel(new_name, true, NEEDLE_NAME_COLUMN);
|
||||||
|
if (name_check_item && name_check_item != needle_item)
|
||||||
|
{
|
||||||
|
mNeedleNameCtrl->setValue(old_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::getInstance()->renameNeedle(old_name, new_name);
|
||||||
|
|
||||||
|
LLScrollListCell* needle_name_column = needle_item->getColumn(NEEDLE_NAME_COLUMN);
|
||||||
|
needle_name_column->setValue(new_name);
|
||||||
|
|
||||||
|
onSelectNeedle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onNeedleCheckboxChanged(LLUICtrl* ctrl)
|
||||||
|
{
|
||||||
|
const LLCheckBoxCtrl* check = static_cast<LLCheckBoxCtrl*>(ctrl);
|
||||||
|
OmnifilterEngine::Needle* needle = getSelectedNeedle();
|
||||||
|
if (!needle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
needle->mEnabled = check->getValue();
|
||||||
|
OmnifilterEngine::getInstance()->setDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Omnifilter::onLogLine(time_t time, const std::string& log_line)
|
||||||
|
{
|
||||||
|
LLDate date(time);
|
||||||
|
|
||||||
|
LLSD substitution;
|
||||||
|
substitution["datetime"] = date;
|
||||||
|
|
||||||
|
std::string time_str = "[hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt]";
|
||||||
|
LLStringUtil::format(time_str, substitution);
|
||||||
|
|
||||||
|
LLSD row;
|
||||||
|
row["columns"][LOG_DATE_COLUMN]["column"] = "timestamp";
|
||||||
|
row["columns"][LOG_DATE_COLUMN]["type"] = "text";
|
||||||
|
row["columns"][LOG_DATE_COLUMN]["value"] = time_str;
|
||||||
|
|
||||||
|
row["columns"][LOG_CONTENT_COLUMN]["column"] = "log_entry";
|
||||||
|
row["columns"][LOG_CONTENT_COLUMN]["type"] = "text";
|
||||||
|
row["columns"][LOG_CONTENT_COLUMN]["value"] = log_line;
|
||||||
|
|
||||||
|
bool scroll_to_end;
|
||||||
|
scroll_to_end = mFilterLogCtrl->getScrollbar()->isAtEnd();
|
||||||
|
|
||||||
|
LLScrollListItem* item = mFilterLogCtrl->addElement(row);
|
||||||
|
if (scroll_to_end)
|
||||||
|
{
|
||||||
|
mFilterLogCtrl->setScrollPos(INT32_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_str = "[year, datetime, slt]-[mthnum, datetime, slt]-[day, datetime, slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] SLT";
|
||||||
|
LLStringUtil::format(time_str, substitution);
|
||||||
|
item->getColumn(LOG_DATE_COLUMN)->setToolTip(time_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Omnifilter::postBuild()
|
||||||
|
{
|
||||||
|
mNeedleListCtrl = getChild<FSScrollListCtrl>("needle_list");
|
||||||
|
mAddNeedleBtn = getChild<LLButton>("add_needle");
|
||||||
|
mRemoveNeedleBtn = getChild<LLButton>("remove_needle");
|
||||||
|
mFilterLogCtrl = getChild<FSScrollListCtrl>("filter_log");
|
||||||
|
mPanelDetails = getChild<LLPanel>("panel_details");
|
||||||
|
mNeedleNameCtrl = getChild<LLLineEditor>("needle_name");
|
||||||
|
mSenderNameCtrl = getChild<LLLineEditor>("sender_name");
|
||||||
|
mSenderCaseSensitiveCheck = getChild<LLCheckBoxCtrl>("sender_case");
|
||||||
|
mSenderMatchTypeCombo = getChild<LLComboBox>("sender_match_type");
|
||||||
|
mContentCtrl = getChild<LLTextEditor>("content");
|
||||||
|
mContentCaseSensitiveCheck = getChild<LLCheckBoxCtrl>("content_case");
|
||||||
|
mContentMatchTypeCombo = getChild<LLComboBox>("content_match_type");
|
||||||
|
mRegionNameCtrl = getChild<LLLineEditor>("region_name");
|
||||||
|
mOwnerCtrl = getChild<LLLineEditor>("owner_uuid");
|
||||||
|
|
||||||
|
mTypeNearbyBtn = getChild<LLButton>("type_nearby");
|
||||||
|
mTypeIMBtn = getChild<LLButton>("type_im");
|
||||||
|
mTypeGroupIMBtn = getChild<LLButton>("type_group_im");
|
||||||
|
mTypeObjectChatBtn = getChild<LLButton>("type_object_chat");
|
||||||
|
mTypeObjectIMBtn = getChild<LLButton>("type_object_im");
|
||||||
|
mTypeScriptErrorBtn = getChild<LLButton>("type_script_error");
|
||||||
|
mTypeDialogBtn = getChild<LLButton>("type_dialog");
|
||||||
|
mTypeOfferBtn = getChild<LLButton>("type_inventory_offer");
|
||||||
|
mTypeInviteBtn = getChild<LLButton>("type_invite");
|
||||||
|
mTypeLureBtn = getChild<LLButton>("type_lure");
|
||||||
|
mTypeLoadURLBtn = getChild<LLButton>("type_load_url");
|
||||||
|
mTypeFriendshipOfferBtn = getChild<LLButton>("type_friendship");
|
||||||
|
mTypeTeleportRequestBtn = getChild<LLButton>("type_tp_request");
|
||||||
|
mTypeGroupNoticeBtn = getChild<LLButton>("type_group_notice");
|
||||||
|
|
||||||
|
mChatReplaceCtrl = getChild<LLLineEditor>("chat_replace");
|
||||||
|
mButtonReplyCtrl = getChild<LLLineEditor>("button_reply");
|
||||||
|
mTextBoxReplyCtrl = getChild<LLTextEditor>("text_box_reply");
|
||||||
|
|
||||||
|
mNeedleListCtrl->setSearchColumn(NEEDLE_NAME_COLUMN);
|
||||||
|
mNeedleListCtrl->deleteAllItems();
|
||||||
|
mNeedleListCtrl->setCommitOnSelectionChange(true);
|
||||||
|
|
||||||
|
mFilterLogCtrl->deleteAllItems();
|
||||||
|
|
||||||
|
for (auto const& needle_entry : OmnifilterEngine::getInstance()->getNeedleList())
|
||||||
|
{
|
||||||
|
const std::string& needle_name = needle_entry.first;
|
||||||
|
const OmnifilterEngine::Needle& needle = needle_entry.second;
|
||||||
|
addNeedle(needle_name, needle);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto logLine : OmnifilterEngine::getInstance()->mLog)
|
||||||
|
{
|
||||||
|
onLogLine(logLine.first, logLine.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::getInstance()->mLogSignal.connect(boost::bind(&Omnifilter::onLogLine, this, _1, _2));
|
||||||
|
|
||||||
|
if (mNeedleListCtrl->getItemCount())
|
||||||
|
{
|
||||||
|
mNeedleListCtrl->selectFirstItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
mContentCtrl->setCommitOnFocusLost(true);
|
||||||
|
mTextBoxReplyCtrl->setCommitOnFocusLost(true);
|
||||||
|
|
||||||
|
mNeedleListCtrl->setCommitCallback(boost::bind(&Omnifilter::onSelectNeedle, this));
|
||||||
|
mAddNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onAddNeedleClicked, this));
|
||||||
|
mRemoveNeedleBtn->setCommitCallback(boost::bind(&Omnifilter::onRemoveNeedleClicked, this));
|
||||||
|
mNeedleNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleNameChanged, this));
|
||||||
|
mSenderNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mContentCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mSenderCaseSensitiveCheck->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mSenderMatchTypeCombo->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mContentCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mContentCaseSensitiveCheck->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mContentMatchTypeCombo->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mRegionNameCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mChatReplaceCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mButtonReplyCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTextBoxReplyCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mOwnerCtrl->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeNearbyBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeGroupIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeObjectChatBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeObjectIMBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeScriptErrorBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeDialogBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeOfferBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeInviteBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeLureBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeLoadURLBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeFriendshipOfferBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeTeleportRequestBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
mTypeGroupNoticeBtn->setCommitCallback(boost::bind(&Omnifilter::onNeedleChanged, this));
|
||||||
|
|
||||||
|
onSelectNeedle();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* @file omnifilter.h
|
||||||
|
* @brief The Omnifilter editor
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2025, Zi Ree @ Second Life
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OMNIFILTER_H
|
||||||
|
#define OMNIFILTER_H
|
||||||
|
|
||||||
|
#include "omnifilterengine.h"
|
||||||
|
|
||||||
|
#include "llfloater.h"
|
||||||
|
|
||||||
|
class FSScrollListCtrl;
|
||||||
|
class LLButton;
|
||||||
|
class LLCheckBoxCtrl;
|
||||||
|
class LLComboBox;
|
||||||
|
class LLLineEditor;
|
||||||
|
class LLPanel;
|
||||||
|
class LLTextEditor;
|
||||||
|
|
||||||
|
class Omnifilter
|
||||||
|
: public LLFloater
|
||||||
|
{
|
||||||
|
friend class LLFloaterReg;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Omnifilter(const LLSD& key);
|
||||||
|
~Omnifilter();
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool postBuild() override final;
|
||||||
|
LLScrollListItem* addNeedle(const std::string& name, const OmnifilterEngine::Needle& needle);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OmnifilterEngine::Needle* getSelectedNeedle();
|
||||||
|
|
||||||
|
void onSelectNeedle();
|
||||||
|
void onNeedleChanged();
|
||||||
|
void onAddNeedleClicked();
|
||||||
|
void onRemoveNeedleClicked();
|
||||||
|
void onNeedleNameChanged();
|
||||||
|
void onNeedleCheckboxChanged(LLUICtrl* ctrl);
|
||||||
|
|
||||||
|
void onLogLine(time_t time, const std::string& logLine);
|
||||||
|
|
||||||
|
FSScrollListCtrl* mNeedleListCtrl;
|
||||||
|
LLButton* mAddNeedleBtn;
|
||||||
|
LLButton* mRemoveNeedleBtn;
|
||||||
|
FSScrollListCtrl* mFilterLogCtrl;
|
||||||
|
LLPanel* mPanelDetails;
|
||||||
|
LLLineEditor* mNeedleNameCtrl;
|
||||||
|
LLLineEditor* mSenderNameCtrl;
|
||||||
|
LLCheckBoxCtrl* mSenderCaseSensitiveCheck;
|
||||||
|
LLComboBox* mSenderMatchTypeCombo;
|
||||||
|
LLTextEditor* mContentCtrl;
|
||||||
|
LLCheckBoxCtrl* mContentCaseSensitiveCheck;
|
||||||
|
LLComboBox* mContentMatchTypeCombo;
|
||||||
|
LLLineEditor* mRegionNameCtrl;
|
||||||
|
LLLineEditor* mOwnerCtrl;
|
||||||
|
|
||||||
|
LLButton* mTypeNearbyBtn;
|
||||||
|
LLButton* mTypeIMBtn;
|
||||||
|
LLButton* mTypeGroupIMBtn;
|
||||||
|
LLButton* mTypeObjectChatBtn;
|
||||||
|
LLButton* mTypeObjectIMBtn;
|
||||||
|
LLButton* mTypeScriptErrorBtn;
|
||||||
|
LLButton* mTypeDialogBtn;
|
||||||
|
LLButton* mTypeOfferBtn;
|
||||||
|
LLButton* mTypeInviteBtn;
|
||||||
|
LLButton* mTypeLureBtn;
|
||||||
|
LLButton* mTypeLoadURLBtn;
|
||||||
|
LLButton* mTypeFriendshipOfferBtn;
|
||||||
|
LLButton* mTypeTeleportRequestBtn;
|
||||||
|
LLButton* mTypeGroupNoticeBtn;
|
||||||
|
|
||||||
|
LLLineEditor* mChatReplaceCtrl;
|
||||||
|
LLLineEditor* mButtonReplyCtrl;
|
||||||
|
LLTextEditor* mTextBoxReplyCtrl;
|
||||||
|
};
|
||||||
|
#endif // OMNIFILTER_H
|
||||||
|
|
@ -0,0 +1,400 @@
|
||||||
|
/**
|
||||||
|
* @file omnifilterengine.cpp
|
||||||
|
* @brief The core Omnifilter engine
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2025, Zi Ree @ Second Life
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "llviewerprecompiledheaders.h"
|
||||||
|
|
||||||
|
#include "omnifilterengine.h"
|
||||||
|
|
||||||
|
#include "llnotificationsutil.h"
|
||||||
|
#include "llsdserialize.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
OmnifilterEngine::OmnifilterEngine()
|
||||||
|
: LLSingleton<OmnifilterEngine>()
|
||||||
|
, LLEventTimer(5.0f)
|
||||||
|
, mDirty(false)
|
||||||
|
{
|
||||||
|
mEventTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::~OmnifilterEngine()
|
||||||
|
{
|
||||||
|
// delete Xxx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::init()
|
||||||
|
{
|
||||||
|
mNeedlesXMLPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "omnifilter.xml");
|
||||||
|
if (mNeedlesXMLPath.empty())
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Got empty file name for omnifilter XML storage." << LL_ENDL;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNeedles();
|
||||||
|
}
|
||||||
|
|
||||||
|
const OmnifilterEngine::Needle* OmnifilterEngine::logMatch(const std::string& needle_name, const Needle& needle)
|
||||||
|
{
|
||||||
|
time_t now = LLDate::now().secondsSinceEpoch();
|
||||||
|
mLog.push_back(std::make_pair(now, needle_name));
|
||||||
|
mLogSignal(now, needle_name);
|
||||||
|
|
||||||
|
return &needle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OmnifilterEngine::matchStrings(const std::string& needle_string, const std::string& haystack_string, eMatchType match_type, bool case_insensitive)
|
||||||
|
{
|
||||||
|
static LLCachedControl<bool> use_omnifilter(*LLControlGroup::getInstance("Global"), "OmnifilterEnabled");
|
||||||
|
if (!use_omnifilter)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view needle_content = needle_string;
|
||||||
|
std::string_view haystack_content = haystack_string;
|
||||||
|
|
||||||
|
std::string needle_lc_content;
|
||||||
|
std::string haystack_lc_content;
|
||||||
|
if (case_insensitive)
|
||||||
|
{
|
||||||
|
needle_lc_content = needle_string;
|
||||||
|
haystack_lc_content = haystack_string;
|
||||||
|
|
||||||
|
LLStringUtil::toLower(needle_lc_content);
|
||||||
|
LLStringUtil::toLower(haystack_lc_content);
|
||||||
|
needle_content = needle_lc_content;
|
||||||
|
haystack_content = haystack_lc_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (match_type)
|
||||||
|
{
|
||||||
|
case eMatchType::Regex:
|
||||||
|
{
|
||||||
|
boost::regbase::flag_type re_flags = boost::regex::normal;
|
||||||
|
if (case_insensitive)
|
||||||
|
{
|
||||||
|
re_flags |= boost::regex::icase;
|
||||||
|
}
|
||||||
|
boost::regex re(needle_string, re_flags);
|
||||||
|
if (boost::regex_match(haystack_string, re))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eMatchType::Substring:
|
||||||
|
{
|
||||||
|
if (haystack_content.find(needle_content) != haystack_content.npos)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case eMatchType::Exact:
|
||||||
|
{
|
||||||
|
if (haystack_content == needle_content)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "match type " << match_type << " unknown!" << LL_ENDL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OmnifilterEngine::Needle* OmnifilterEngine::match(const Haystack& haystack)
|
||||||
|
{
|
||||||
|
for (auto const& needle_entry : mNeedles)
|
||||||
|
{
|
||||||
|
const Needle& needle = needle_entry.second;
|
||||||
|
const std::string& needle_name = needle_entry.first;
|
||||||
|
|
||||||
|
if (!needle.mEnabled)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needle.mSenderName.empty())
|
||||||
|
{
|
||||||
|
if (!matchStrings(needle.mSenderName, haystack.mSenderName, needle.mSenderNameMatchType, needle.mSenderNameCaseInsensitive))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needle.mOwnerID.notNull())
|
||||||
|
{
|
||||||
|
if (needle.mOwnerID != haystack.mOwnerID)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needle.mRegionName.empty())
|
||||||
|
{
|
||||||
|
if (needle.mRegionName != haystack.mRegionName)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needle.mTypes.empty() && needle.mTypes.find(haystack.mType) == needle.mTypes.end())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchStrings(needle.mContent, haystack.mContent, needle.mContentMatchType, needle.mContentCaseInsensitive))
|
||||||
|
{
|
||||||
|
return logMatch(needle_name, needle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::Needle& OmnifilterEngine::newNeedle(const std::string& needle_name)
|
||||||
|
{
|
||||||
|
if (mNeedles.find(needle_name) != mNeedles.end())
|
||||||
|
{
|
||||||
|
Needle new_needle;
|
||||||
|
new_needle.mEnabled = false;
|
||||||
|
mNeedles[needle_name] = new_needle;
|
||||||
|
}
|
||||||
|
setDirty(true);
|
||||||
|
return mNeedles[needle_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::renameNeedle(const std::string& old_name, const std::string& new_name)
|
||||||
|
{
|
||||||
|
// https://stackoverflow.com/a/44883472
|
||||||
|
auto node_handler = mNeedles.extract(old_name);
|
||||||
|
node_handler.key() = new_name;
|
||||||
|
mNeedles.insert(std::move(node_handler));
|
||||||
|
|
||||||
|
setDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::deleteNeedle(const std::string& needle_name)
|
||||||
|
{
|
||||||
|
mNeedles.erase(needle_name);
|
||||||
|
setDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnifilterEngine::needle_list_t& OmnifilterEngine::getNeedleList()
|
||||||
|
{
|
||||||
|
return mNeedles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::setDirty(bool dirty)
|
||||||
|
{
|
||||||
|
mDirty = dirty;
|
||||||
|
|
||||||
|
if (mDirty)
|
||||||
|
{
|
||||||
|
mEventTimer.start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mEventTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::loadNeedles()
|
||||||
|
{
|
||||||
|
if (mNeedlesXMLPath.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(mNeedlesXMLPath))
|
||||||
|
{
|
||||||
|
// file does not exist (yet), just return empty
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::filesystem::is_regular_file(mNeedlesXMLPath))
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Omnifilter storage at '" << mNeedlesXMLPath << "' is not a regular file." << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("NotRegularFileError", args, LLSD());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::filesystem::file_size(mNeedlesXMLPath) == 0)
|
||||||
|
{
|
||||||
|
// file exists but is empty, this should not happen, so alert the user, they might have lost their needles
|
||||||
|
LL_DEBUGS("Omnifilter") << "Omnifilter storage file is empty." << mNeedlesXMLPath << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["ERROR_CODE"] = errno;
|
||||||
|
args["ERROR_MESSAGE"] = strerror(errno);
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("GenericFileEmptyError", args, LLSD());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LL_DEBUGS("Omnifilter") << "Loading needles" << mNeedlesXMLPath << LL_ENDL;
|
||||||
|
|
||||||
|
std::ifstream file;
|
||||||
|
file.open(mNeedlesXMLPath.c_str());
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Unable to open Omnifilter storage at '" << mNeedlesXMLPath << "' for reading." << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["ERROR_CODE"] = errno;
|
||||||
|
args["ERROR_MESSAGE"] = strerror(errno);
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("GenericFileOpenReadError", args, LLSD());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLSD needles_llsd;
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
LLSDSerialize::fromXML(needles_llsd, file);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Unable to read Omnifilter needles from '" << mNeedlesXMLPath << "'." << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["ERROR_CODE"] = errno;
|
||||||
|
args["ERROR_MESSAGE"] = strerror(errno);
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("GenericFileReadError", args, LLSD());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LLSD::map_iterator iter = needles_llsd.beginMap(); iter != needles_llsd.endMap(); ++iter)
|
||||||
|
{
|
||||||
|
const std::string& new_needle_name = (*iter).first;
|
||||||
|
LLSD needle_data = (*iter).second;
|
||||||
|
|
||||||
|
Needle new_needle;
|
||||||
|
new_needle.mSenderName = needle_data["sender_name"].asString();
|
||||||
|
new_needle.mContent = needle_data["content"].asString();
|
||||||
|
new_needle.mRegionName = needle_data["region_name"].asString();
|
||||||
|
new_needle.mChatReplace = needle_data["chat_replace"].asString();
|
||||||
|
new_needle.mButtonReply = needle_data["button_reply"].asString();
|
||||||
|
new_needle.mTextBoxReply = needle_data["textbox_reply"].asString();
|
||||||
|
new_needle.mSenderNameMatchType = static_cast<OmnifilterEngine::eMatchType>(needle_data["sender_name_match_type"].asInteger());
|
||||||
|
new_needle.mContentMatchType = static_cast<OmnifilterEngine::eMatchType>(needle_data["content_match_type"].asInteger());
|
||||||
|
|
||||||
|
LLSD types_llsd = needle_data["types"];
|
||||||
|
|
||||||
|
for (LLSD::array_iterator aiter = types_llsd.beginArray(); aiter != types_llsd.endArray(); ++aiter)
|
||||||
|
{
|
||||||
|
new_needle.mTypes.insert(static_cast<OmnifilterEngine::eType>((*aiter).asInteger()));
|
||||||
|
}
|
||||||
|
|
||||||
|
new_needle.mEnabled = needle_data["enabled"].asBoolean();
|
||||||
|
new_needle.mSenderNameCaseInsensitive = needle_data["sender_name_case_insensitive"].asBoolean();
|
||||||
|
new_needle.mContentCaseInsensitive = needle_data["content_case_insensitive"].asBoolean();
|
||||||
|
|
||||||
|
mNeedles[new_needle_name] = new_needle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OmnifilterEngine::saveNeedles()
|
||||||
|
{
|
||||||
|
if (mNeedlesXMLPath.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LL_DEBUGS("Omnifilter") << "Saving needles" << mNeedlesXMLPath << LL_ENDL;
|
||||||
|
|
||||||
|
std::ofstream file;
|
||||||
|
|
||||||
|
file.open(mNeedlesXMLPath.c_str());
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Unable to open Omnifilter storage at '" << mNeedlesXMLPath << "' for writing." << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["ERROR_CODE"] = errno;
|
||||||
|
args["ERROR_MESSAGE"] = strerror(errno);
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("GenericFileOpenWriteError", args, LLSD());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LLSD needles_llsd;
|
||||||
|
|
||||||
|
for (auto const& needle_entry : mNeedles)
|
||||||
|
{
|
||||||
|
const std::string& needle_name = needle_entry.first;
|
||||||
|
const Needle& needle = needle_entry.second;
|
||||||
|
|
||||||
|
needles_llsd[needle_name]["sender_name"] = needle.mSenderName;
|
||||||
|
needles_llsd[needle_name]["content"] = needle.mContent;
|
||||||
|
needles_llsd[needle_name]["region_name"] = needle.mRegionName;
|
||||||
|
needles_llsd[needle_name]["chat_replace"] = needle.mChatReplace;
|
||||||
|
needles_llsd[needle_name]["button_reply"] = needle.mButtonReply;
|
||||||
|
needles_llsd[needle_name]["textbox_reply"] = needle.mTextBoxReply;
|
||||||
|
needles_llsd[needle_name]["sender_name_match_type"] = needle.mSenderNameMatchType;
|
||||||
|
needles_llsd[needle_name]["content_match_type"] = needle.mContentMatchType;
|
||||||
|
needles_llsd[needle_name]["owner_id"] = needle.mOwnerID;
|
||||||
|
|
||||||
|
LLSD types_llsd;
|
||||||
|
for (auto type : needle.mTypes)
|
||||||
|
{
|
||||||
|
types_llsd.append(static_cast<S32>(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
needles_llsd[needle_name]["types"] = types_llsd;
|
||||||
|
needles_llsd[needle_name]["enabled"] = needle.mEnabled;
|
||||||
|
needles_llsd[needle_name]["sender_name_case_insensitive"] = needle.mSenderNameCaseInsensitive;
|
||||||
|
needles_llsd[needle_name]["content_case_insensitive"] = needle.mContentCaseInsensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (file.fail())
|
||||||
|
{
|
||||||
|
LL_DEBUGS("Omnifilter") << "Unable to save Omnifilter needles at '" << mNeedlesXMLPath << "'." << LL_ENDL;
|
||||||
|
LLSD args;
|
||||||
|
args["ERROR_CODE"] = errno;
|
||||||
|
args["ERROR_MESSAGE"] = strerror(errno);
|
||||||
|
args["FILE_NAME"] = mNeedlesXMLPath;
|
||||||
|
LLNotificationsUtil::add("GenericFileWriteError", args, LLSD());
|
||||||
|
}
|
||||||
|
|
||||||
|
setDirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtual
|
||||||
|
bool OmnifilterEngine::tick()
|
||||||
|
{
|
||||||
|
saveNeedles();
|
||||||
|
setDirty(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* @file omnifilterengine.h
|
||||||
|
* @brief The core Omnifilter engine
|
||||||
|
*
|
||||||
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
||||||
|
* Second Life Viewer Source Code
|
||||||
|
* Copyright (C) 2025, Zi Ree @ Second Life
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation;
|
||||||
|
* version 2.1 of the License only.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
* $/LicenseInfo$
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OMNIFILTERENGINE_H
|
||||||
|
#define OMNIFILTERENGINE_H
|
||||||
|
|
||||||
|
#include "lleventtimer.h"
|
||||||
|
#include "llsingleton.h"
|
||||||
|
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
#include <boost/signals2.hpp>
|
||||||
|
|
||||||
|
class OmnifilterEngine
|
||||||
|
: public LLSingleton<OmnifilterEngine>
|
||||||
|
, public LLEventTimer
|
||||||
|
{
|
||||||
|
LLSINGLETON(OmnifilterEngine);
|
||||||
|
~OmnifilterEngine();
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum eType
|
||||||
|
{
|
||||||
|
NearbyChat,
|
||||||
|
GroupChat,
|
||||||
|
GroupNotice,
|
||||||
|
GroupInvite,
|
||||||
|
InstantMessage,
|
||||||
|
ObjectChat,
|
||||||
|
ObjectInstantMessage,
|
||||||
|
ScriptError,
|
||||||
|
ScriptDialog,
|
||||||
|
FriendshipOffer,
|
||||||
|
InventoryOffer,
|
||||||
|
Lure,
|
||||||
|
TeleportRequest,
|
||||||
|
URLRequest,
|
||||||
|
TYPES_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
enum eMatchType
|
||||||
|
{
|
||||||
|
Exact,
|
||||||
|
Substring,
|
||||||
|
Regex,
|
||||||
|
MATCH_TYPES_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
class Haystack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string mSenderName;
|
||||||
|
std::string mContent;
|
||||||
|
std::string mRegionName;
|
||||||
|
|
||||||
|
LLUUID mOwnerID;
|
||||||
|
|
||||||
|
eType mType;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Needle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string mSenderName;
|
||||||
|
std::string mContent;
|
||||||
|
std::string mRegionName;
|
||||||
|
|
||||||
|
std::string mChatReplace;
|
||||||
|
std::string mButtonReply;
|
||||||
|
std::string mTextBoxReply;
|
||||||
|
|
||||||
|
eMatchType mSenderNameMatchType;
|
||||||
|
eMatchType mContentMatchType;
|
||||||
|
|
||||||
|
LLUUID mOwnerID;
|
||||||
|
|
||||||
|
std::set<eType> mTypes;
|
||||||
|
|
||||||
|
bool mEnabled;
|
||||||
|
bool mSenderNameCaseInsensitive = false;
|
||||||
|
bool mContentCaseInsensitive = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<std::string, OmnifilterEngine::OmnifilterEngine::Needle> needle_list_t;
|
||||||
|
needle_list_t& getNeedleList();
|
||||||
|
|
||||||
|
Needle& newNeedle(const std::string& needle_name);
|
||||||
|
void renameNeedle(const std::string& old_name, const std::string& new_name);
|
||||||
|
void deleteNeedle(const std::string& needle_name);
|
||||||
|
|
||||||
|
const Needle* match(const Haystack& haystack);
|
||||||
|
void setDirty(bool dirty);
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
typedef boost::signals2::signal<void(time_t, const std::string&)> log_signal_t;
|
||||||
|
log_signal_t mLogSignal;
|
||||||
|
|
||||||
|
std::list<std::pair<time_t, std::string>> mLog;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const Needle* logMatch(const std::string& needle_name, const Needle& needle);
|
||||||
|
bool matchStrings(const std::string& needle_string, const std::string& haystack_string, eMatchType match_type, bool case_insensitive);
|
||||||
|
|
||||||
|
void loadNeedles();
|
||||||
|
void saveNeedles();
|
||||||
|
|
||||||
|
virtual bool tick();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
needle_list_t mNeedles;
|
||||||
|
|
||||||
|
std::string mNeedlesXMLPath;
|
||||||
|
|
||||||
|
bool mDirty;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // OMNIFILTERENGINE_H
|
||||||
|
|
@ -165,6 +165,7 @@ with the same filename but different name
|
||||||
<texture name="Command_MiniMap_Icon" file_name="toolbar_icons/mini_map.png" preload="true" />
|
<texture name="Command_MiniMap_Icon" file_name="toolbar_icons/mini_map.png" preload="true" />
|
||||||
<texture name="Command_Move_Icon" file_name="toolbar_icons/move.png" preload="true" />
|
<texture name="Command_Move_Icon" file_name="toolbar_icons/move.png" preload="true" />
|
||||||
<texture name="Command_Environments_Icon" file_name="toolbar_icons/environments.png" preload="true" />
|
<texture name="Command_Environments_Icon" file_name="toolbar_icons/environments.png" preload="true" />
|
||||||
|
<texture name="Command_Omnifilter_Icon" file_name="toolbar_icons/omnifilter.png" preload="true" /> <!-- FS:Zi: Omnifilter -->
|
||||||
<texture name="Command_People_Icon" file_name="toolbar_icons/people.png" preload="true" />
|
<texture name="Command_People_Icon" file_name="toolbar_icons/people.png" preload="true" />
|
||||||
<texture name="Command_Performance_Icon" file_name="toolbar_icons/performance.png" preload="true" />
|
<texture name="Command_Performance_Icon" file_name="toolbar_icons/performance.png" preload="true" />
|
||||||
<texture name="Command_Picks_Icon" file_name="toolbar_icons/picks.png" preload="true" />
|
<texture name="Command_Picks_Icon" file_name="toolbar_icons/picks.png" preload="true" />
|
||||||
|
|
@ -886,6 +887,7 @@ with the same filename but different name
|
||||||
<texture name="Wearable_Favorites_Icon" file_name="toolbar_icons/wearable_favorites.png" preload="true" />
|
<texture name="Wearable_Favorites_Icon" file_name="toolbar_icons/wearable_favorites.png" preload="true" />
|
||||||
<texture name="beacons" file_name="toolbar_icons/beacons.png" preload="true" />
|
<texture name="beacons" file_name="toolbar_icons/beacons.png" preload="true" />
|
||||||
<texture name="Stop_Animations_Icon" file_name="toolbar_icons/stop_animations.png" preload="true" />
|
<texture name="Stop_Animations_Icon" file_name="toolbar_icons/stop_animations.png" preload="true" />
|
||||||
|
<texture name="omnifilter" file_name="toolbar_icons/omnifilter.png" preload="true" />
|
||||||
|
|
||||||
<texture name="skin ansastorm blood" file_name="skinspreview/ansa_blood.jpg" preload="true" />
|
<texture name="skin ansastorm blood" file_name="skinspreview/ansa_blood.jpg" preload="true" />
|
||||||
<texture name="skin ansastorm bright blue" file_name="skinspreview/ansa_blue.jpg" preload="true" />
|
<texture name="skin ansastorm bright blue" file_name="skinspreview/ansa_blue.jpg" preload="true" />
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
|
|
@ -0,0 +1,171 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
<floater
|
||||||
|
legacy_header_height="18"
|
||||||
|
can_resize="true"
|
||||||
|
can_minimize="true"
|
||||||
|
can_close="true"
|
||||||
|
height="400"
|
||||||
|
min_height="348"
|
||||||
|
min_width="668"
|
||||||
|
layout="topleft"
|
||||||
|
name="omnifilter"
|
||||||
|
help_topic="omnifilter"
|
||||||
|
save_rect="true"
|
||||||
|
save_visibility="true"
|
||||||
|
single_instance="true"
|
||||||
|
reuse_instance="true"
|
||||||
|
title="Omnifilter Rules Editor"
|
||||||
|
width="700">
|
||||||
|
|
||||||
|
<layout_stack name="needle_list_stack" layout="topleft" follows="left|top|bottom" height="374" top="20" left="8" width="190" orientation="vertical" show_drag_handle="true">
|
||||||
|
<layout_panel name="needle_list_layout" layout="topleft" height="150" min_height="94" user_resize="true">
|
||||||
|
|
||||||
|
<check_box name="enable_omnifilter" layout="topleft" follows="left|top" height="20" top="0" right="-1" label="Enable Omnifilter" control_name="OmnifilterEnabled" />
|
||||||
|
|
||||||
|
<fs_scroll_list name="needle_list" layout="topleft" follows="all" height="100" top_pad="4" draw_heading="true" draw_stripes="true" desired_line_height="18">
|
||||||
|
<fs_scroll_list.columns name="enabled" label="On" width="24" />
|
||||||
|
<fs_scroll_list.columns name="needle_name" label="Rule" width="175" />
|
||||||
|
|
||||||
|
<!-- example entries for UI editing -->
|
||||||
|
<row>
|
||||||
|
<column name="enabled" column="enabled" type="checkbox" value="true" />
|
||||||
|
<column name="needle_name" column="needle_name">Example Rule 1</column>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<column name="enabled" column="enabled" type="checkbox" />
|
||||||
|
<column name="needle_name" column="needle_name">Example Rule 2</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
</fs_scroll_list>
|
||||||
|
|
||||||
|
<button name="add_needle" layout="topleft" follows="left|bottom" height="20" top_pad="4" width="96" label="Add" />
|
||||||
|
<button name="remove_needle" layout="topleft" follows="right|bottom" height="20" left_pad="0" width="96" label="Remove" />
|
||||||
|
|
||||||
|
</layout_panel>
|
||||||
|
<layout_panel name="filter_log_layout" layout="topleft" height="130" min_height="50">
|
||||||
|
|
||||||
|
<fs_scroll_list name="filter_log" layout="topleft" follows="all" height="150" top="0" draw_heading="true" draw_stripes="true" can_sort="false">
|
||||||
|
<fs_scroll_list.columns name="timestamp" label="Time (SLT)" width="64" />
|
||||||
|
<fs_scroll_list.columns name="log_entry" label="Rule" />
|
||||||
|
|
||||||
|
<!-- example entries for UI editing -->
|
||||||
|
<row>
|
||||||
|
<column name="timestamp" column="timestamp" tool_tip="2015-11-24 00:01:02">00:01:02</column>
|
||||||
|
<column name="log_entry" column="log_entry">Example Rule 1</column>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<column name="timestamp" column="timestamp" tool_tip="2015-11-24 01:02:03">01:02:03</column>
|
||||||
|
<column name="log_entry" column="log_entry">Example Rule 2</column>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
</fs_scroll_list>
|
||||||
|
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
</layout_stack>
|
||||||
|
|
||||||
|
<panel name="panel_details" layout="topleft" follows="all" height="374" left_pad="4" right="-4">
|
||||||
|
|
||||||
|
<text name="needle_name_label" layout="topleft" follows="left|top" height="20" width="100" left="0" top_pad="0" valign="center" value="Rule Name:" />
|
||||||
|
<line_editor name="needle_name" layout="topleft" follows="left|right|top" height="20" left_pad="4" right="-4" />
|
||||||
|
<text name="sender_name_label" layout="topleft" follows="left|top" height="20" width="100" left="0" top_pad="2" valign="center" value="Sender Name:" />
|
||||||
|
<line_editor name="sender_name" layout="topleft" follows="left|right|top" height="20" left_pad="4" right="-110" />
|
||||||
|
<check_box name="sender_case" layout="topleft" follows="right|top" height="20" left_pad="4" width="30" label="Aa" />
|
||||||
|
|
||||||
|
<combo_box name="sender_match_type" layout="topleft" follows="right|top" height="20" left_pad="8" right="-4" >
|
||||||
|
|
||||||
|
<combo_item name="sender_exact" value="0">
|
||||||
|
<column width="40" name="label" label="|Abc|" />
|
||||||
|
<column name="longtext" label="Match full text" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
<combo_item name="sender_substring" value="1">
|
||||||
|
<column name="label" label="*Abc*" />
|
||||||
|
<column name="longtext" label="Match substring" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
<combo_item name="sender_regex" value="2">
|
||||||
|
<column name="label" label="/ *. /" />
|
||||||
|
<column name="longtext" label="Match regular expression" width.width="400" width.pixel_width="300" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
</combo_box>
|
||||||
|
|
||||||
|
<text name="content_label" layout="topleft" follows="left|top" height="20" left="0" width="100" top_pad="3" valign="center" value="Content:" />
|
||||||
|
<text_editor name="content" layout="topleft" follows="all" height="74" left_pad="4" right="-4" top_delta="0" />
|
||||||
|
<check_box name="content_case" layout="topleft" follows="right|bottom" height="20" width="30" right="-76" top_pad="4" label="Aa" />
|
||||||
|
|
||||||
|
<combo_box name="content_match_type" layout="topleft" follows="right|bottom" height="20" left_pad="8" right="-4" >
|
||||||
|
|
||||||
|
<combo_item name="content_exact" value="0">
|
||||||
|
<column width="40" name="label" label="|Abc|" />
|
||||||
|
<column name="longtext" label="Match full text" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
<combo_item name="content_substring" value="1">
|
||||||
|
<column name="label" label="*Abc*" />
|
||||||
|
<column name="longtext" label="Match substring" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
<combo_item name="content_regex" value="2">
|
||||||
|
<column name="label" label="/ *. /" />
|
||||||
|
<column name="longtext" label="Match regular expression" />
|
||||||
|
</combo_item>
|
||||||
|
|
||||||
|
</combo_box>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<text name="region_name_label" layout="topleft" follows="left|bottom" height="20" width="100" left="0" top_pad="2" valign="center" value="Region Name:" />
|
||||||
|
<line_editor name="region_name" layout="topleft" follows="left|right|bottom" height="20" left_pad="4" right="-4" />
|
||||||
|
<text name="owner_label" layout="topleft" follows="left|bottom" height="20" width="100" left="0" top_pad="2" valign="center" value="Owner:" />
|
||||||
|
<line_editor name="owner_uuid" layout="topleft" follows="left|right|bottom" height="20" left_pad="4" right="-4" />
|
||||||
|
|
||||||
|
<text name="match_source_label" layout="topleft" follows="left|bottom" width="100" height="20" top_pad="4" left="0" valign="center" value="Match Source:" />
|
||||||
|
|
||||||
|
<layout_stack orientation="horizontal" left="104" top_delta="0" right="-4" height="68" follows="left|right|bottom" layout="topleft">
|
||||||
|
|
||||||
|
<layout_panel width="72" layout="topleft" follows="all">
|
||||||
|
<button name="type_nearby" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="0" label="Nearby Chat" tool_tip="Regular public avatar chat nearby." />
|
||||||
|
<button name="type_im" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="IM" tool_tip="Instant messages from users." />
|
||||||
|
<button name="type_group_im" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Group Chat" tool_tip="Group chat from users." />
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
<layout_panel>
|
||||||
|
<button name="type_group_notice" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top="0" label="Notice" tool_tip="Group notices." />
|
||||||
|
<button name="type_invite" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Invite" tool_tip="Group invitations." />
|
||||||
|
<button name="type_object_chat" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Object Chat" tool_tip="Nearby chat from objects." />
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
<layout_panel>
|
||||||
|
<button name="type_object_im" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top="0" label="Object IM" tool_tip="Instant messages from objects." />
|
||||||
|
<button name="type_script_error" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Error" tool_tip="Debug chat coming from scripts." />
|
||||||
|
<button name="type_dialog" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Dialog" tool_tip="Matches fields in script dialogs." />
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
<layout_panel>
|
||||||
|
<button name="type_inventory_offer" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top="0" label="Offer" tool_tip="Matches fields in inventory offers." />
|
||||||
|
<button name="type_friendship" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Friendship" tool_tip="Friend requests from users." />
|
||||||
|
<button name="type_lure" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="TP Lure" tool_tip="Teleport offers from users." />
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
<layout_panel>
|
||||||
|
<button name="type_tp_request" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top="0" label="TP Request" tool_tip="Requests from users to send them a teleport offer." />
|
||||||
|
<button name="type_load_url" layout="topleft" follows="left|right|top" width= "72" height="20" is_toggle="true" top_pad="4" label="Load URL" tool_tip="Scripted requests to visit a website." />
|
||||||
|
</layout_panel>
|
||||||
|
|
||||||
|
</layout_stack>
|
||||||
|
|
||||||
|
<view_border layout="topleft" right="-4" follows="left|bottom|right" left="0" height="1" top_pad="4" />
|
||||||
|
|
||||||
|
<text name="chat_replace_label" layout="topleft" follows="left|bottom" left="0" height="20" width="100" top_pad="2" value="Chat Replace:" valign="center" />
|
||||||
|
<line_editor name="chat_replace" layout="topleft" follows="left|right|bottom" left_pad="4" height="20" right="-4" />
|
||||||
|
<text name="button_reply_label" layout="topleft" follows="left|bottom" left="0" height="20" width="100" top_pad="2" value="Button Reply:" valign="center" />
|
||||||
|
<line_editor name="button_reply" layout="topleft" follows="left|right|bottom" left_pad="4" height="20" right="-4" />
|
||||||
|
<text name="text_box_reply_label" layout="topleft" follows="left|bottom" left="0" height="20" width="100" top_pad="3" value="Text Box Reply:" valign="center" />
|
||||||
|
<text_editor name="text_box_reply" layout="topleft" follows="left|right|bottom" left_pad="4" height="60" right="-4" top_delta="0" />
|
||||||
|
|
||||||
|
</panel>
|
||||||
|
|
||||||
|
</floater>
|
||||||
|
|
@ -105,6 +105,17 @@
|
||||||
parameter="experiences"/>
|
parameter="experiences"/>
|
||||||
</menu_item_call>
|
</menu_item_call>
|
||||||
|
|
||||||
|
<menu_item_check
|
||||||
|
label="Omnifilter"
|
||||||
|
name="Omnifilter">
|
||||||
|
<menu_item_check.on_click
|
||||||
|
function="Floater.Toggle"
|
||||||
|
parameter="omnifilter" />
|
||||||
|
<menu_item_check.on_check
|
||||||
|
function="Floater.Visible"
|
||||||
|
parameter="omnifilter" />
|
||||||
|
</menu_item_check>
|
||||||
|
|
||||||
<menu_item_separator/>
|
<menu_item_separator/>
|
||||||
|
|
||||||
<menu_item_call
|
<menu_item_call
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@ Settings groups to be restored (backup will always save all):
|
||||||
<column name="restore_global_files_label">Controls</column>
|
<column name="restore_global_files_label">Controls</column>
|
||||||
<column name="value">key_bindings.xml</column>
|
<column name="value">key_bindings.xml</column>
|
||||||
</row>
|
</row>
|
||||||
|
<row name="restore_global_row_controls">
|
||||||
|
<column type="checkbox" name="restore_global_files_check" value="true" />
|
||||||
|
<column name="restore_global_files_label">Omnifilter</column>
|
||||||
|
<column name="value">omnifilter.xml</column>
|
||||||
|
</row>
|
||||||
</scroll_list>
|
</scroll_list>
|
||||||
|
|
||||||
</layout_panel>
|
</layout_panel>
|
||||||
|
|
|
||||||
|
|
@ -2782,6 +2782,7 @@ name="Command_360_Capture_Label">360° Snapshot</string>
|
||||||
<string name="Command_Poser_Tooltip">Pose your avatar and animated objects</string>
|
<string name="Command_Poser_Tooltip">Pose your avatar and animated objects</string>
|
||||||
<string name="Command_Primfeed_Label">Primfeed</string>
|
<string name="Command_Primfeed_Label">Primfeed</string>
|
||||||
<string name="Command_Primfeed_Tooltip">Post directly to your Primfeed account.</string>
|
<string name="Command_Primfeed_Tooltip">Post directly to your Primfeed account.</string>
|
||||||
|
<string name="Command_Omnifilter_Label">Omnifilter</string>
|
||||||
|
|
||||||
<string
|
<string
|
||||||
name="Command_360_Capture_Tooltip">Capture a 360° equirectangular image</string>
|
name="Command_360_Capture_Tooltip">Capture a 360° equirectangular image</string>
|
||||||
|
|
@ -2847,6 +2848,7 @@ name="Command_360_Capture_Tooltip">Capture a 360° equirectangular image</string
|
||||||
<string name="Command_RFO_Tooltip">Show only your friends in the viewer, all other avatars will be removed. Once enabled a TP is required to restore visibility of others.</string>
|
<string name="Command_RFO_Tooltip">Show only your friends in the viewer, all other avatars will be removed. Once enabled a TP is required to restore visibility of others.</string>
|
||||||
<string name="Command_DAO_Tooltip">Derender Animated Objects (aka Animesh) - Temporarily derenders all currently visible Animesh (attached and free roaming). Derendered animesh will reappear after a TP</string>
|
<string name="Command_DAO_Tooltip">Derender Animated Objects (aka Animesh) - Temporarily derenders all currently visible Animesh (attached and free roaming). Derendered animesh will reappear after a TP</string>
|
||||||
<string name="Command_Beacons_Tooltip">Show beacons</string>
|
<string name="Command_Beacons_Tooltip">Show beacons</string>
|
||||||
|
<string name="Command_Omnifilter_Tooltip">Edit the Omnifilter rules</string>
|
||||||
|
|
||||||
<string name="Toolbar_Bottom_Tooltip">currently in your bottom toolbar</string>
|
<string name="Toolbar_Bottom_Tooltip">currently in your bottom toolbar</string>
|
||||||
<string name="Toolbar_Left_Tooltip" >currently in your left toolbar</string>
|
<string name="Toolbar_Left_Tooltip" >currently in your left toolbar</string>
|
||||||
|
|
@ -3341,4 +3343,5 @@ Your current position: [AVATAR_POS]
|
||||||
<string name="Ultra">Ultra</string>
|
<string name="Ultra">Ultra</string>
|
||||||
<string name="Maximum">Maximum</string>
|
<string name="Maximum">Maximum</string>
|
||||||
|
|
||||||
|
<string name="OmnifilterNewNeedle">NEW RULE</string> <!-- <FS:Zi> Omnifilter -->
|
||||||
</strings>
|
</strings>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue