Skip to content

Commit

Permalink
Merge pull request #1198 from crowbartools/#1167-url-moderation
Browse files Browse the repository at this point in the history
Add a url moderation feature to the moderation tools
  • Loading branch information
ebiggz committed May 25, 2021
2 parents 4d2c773 + 8e2d0f7 commit 47f0e1d
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 51 deletions.
Expand Up @@ -14,7 +14,7 @@ module.exports = {
const timerManager = require("../../../timers/timer-manager");

//Send to chat moderation service
chatModerationManager.moderateMessage(data);
await chatModerationManager.moderateMessage(data);

// Send to command router to see if we need to act on a command.
commandHandler.handleChatEvent(data).catch(reason => {
Expand Down
2 changes: 1 addition & 1 deletion backend/chat/chat-listeners/twitch-chat-listeners.js
Expand Up @@ -28,7 +28,7 @@ exports.setupChatListeners = (streamerChatClient) => {
streamerChatClient.onPrivmsg(async (_channel, user, messageText, msg) => {
const firebotChatMessage = await chatHelpers.buildFirebotChatMessage(msg, messageText);

chatModerationManager.moderateMessage(firebotChatMessage);
await chatModerationManager.moderateMessage(firebotChatMessage);

// send to the frontend
if (firebotChatMessage.isHighlighted) {
Expand Down
71 changes: 67 additions & 4 deletions backend/chat/moderation/chat-moderation-manager.js
Expand Up @@ -4,6 +4,7 @@ const profileManager = require("../../common/profile-manager");
const { Worker } = require("worker_threads");
const frontendCommunicator = require("../../common/frontend-communicator");
const rolesManager = require("../../roles/custom-roles-manager");
const permitCommand = require("./url-permit-command");

let getChatModerationSettingsDb = () => profileManager.getJsonDbInProfile("/chat/moderation/chat-moderation-settings");
let getBannedWordsDb = () => profileManager.getJsonDbInProfile("/chat/moderation/banned-words", false);
Expand All @@ -17,6 +18,14 @@ let chatModerationSettings = {
enabled: false,
max: 10
},
urlModeration: {
enabled: false,
viewTime: {
enabled: false,
viewTimeInHours: 0
},
outputMessage: ""
},
exemptRoles: []
};

Expand Down Expand Up @@ -98,11 +107,14 @@ const countEmojis = (str) => {
*
* @param {import("../chat-helpers").FirebotChatMessage} chatMessage
*/
function moderateMessage(chatMessage) {
async function moderateMessage(chatMessage) {
if (chatMessage == null) return;

if (!chatModerationSettings.bannedWordList.enabled
&& !chatModerationSettings.emoteLimit.enabled) return;
if (
!chatModerationSettings.bannedWordList.enabled
&& !chatModerationSettings.emoteLimit.enabled
&& !chatModerationSettings.urlModeration.enabled
) return;

let moderateMessage = false;

Expand All @@ -114,19 +126,55 @@ function moderateMessage(chatMessage) {
}

if (moderateMessage) {
const chat = require("../twitch-chat");

if (chatModerationSettings.emoteLimit.enabled && !!chatModerationSettings.emoteLimit.max) {
const emoteCount = chatMessage.parts.filter(p => p.type === "emote").length;
const emojiCount = chatMessage.parts
.filter(p => p.type === "text")
.reduce((acc, part) => acc + countEmojis(part.text), 0);
if ((emoteCount + emojiCount) > chatModerationSettings.emoteLimit.max) {
const chat = require("../twitch-chat");
chat.deleteMessage(chatMessage.id);
return;
}
}

if (chatModerationSettings.urlModeration.enabled) {
if (permitCommand.hasTemporaryPermission(chatMessage.username)) return;

const message = chatMessage.rawText;
const regex = new RegExp(/[\w][.][a-zA-Z]/, "gi");

if (!regex.test(message)) return;

logger.debug("Url moderation: Found url in message...");

const settings = chatModerationSettings.urlModeration;
let outputMessage = settings.outputMessage || "";

if (settings.viewTime && settings.viewTime.enabled) {
const viewerDB = require('../../database/userDatabase');
const viewer = await viewerDB.getUserByUsername(chatMessage.username);

const viewerViewTime = viewer.minutesInChannel / 60;
const minimumViewTime = settings.viewTime.viewTimeInHours;

if (viewerViewTime > minimumViewTime) return;

outputMessage = outputMessage.replace("{viewTime}", minimumViewTime.toString());

logger.debug("Url moderation: Not enough view time.");
} else {
logger.debug("Url moderation: User does not have exempt role.");
}

chat.deleteMessage(chatMessage.id);

if (outputMessage) {
outputMessage = outputMessage.replace("{userName}", chatMessage.username);
chat.sendChatMessage(outputMessage);
}
}

const message = chatMessage.rawText;
const messageId = chatMessage.id;
Expand Down Expand Up @@ -204,6 +252,21 @@ function load() {
if (settings.emoteLimit == null) {
settings.emoteLimit = { enabled: false, max: 10 };
}

if (settings.urlModeration == null) {
settings.urlModeration = {
enabled: false,
viewTime: {
enabled: false,
viewTimeInHours: 0
},
outputMessage: ""
};
}

if (settings.urlModeration.enabled) {
permitCommand.registerPermitCommand();
}
}

let words = getBannedWordsDb().getData("/");
Expand Down
101 changes: 101 additions & 0 deletions backend/chat/moderation/url-permit-command.js
@@ -0,0 +1,101 @@
"use strict";

const logger = require("../../logwrapper");
const commandManager = require("../commands/CommandManager");
const frontendCommunicator = require("../../common/frontend-communicator");

const PERMIT_COMMAND_ID = "firebot:moderation:url:permit";
let tempPermittedUsers = [];

const permitCommand = {
definition: {
id: PERMIT_COMMAND_ID,
name: "Permit",
active: true,
trigger: "!permit",
usage: "[target]",
description: "Permits a viewer to post a url for a set duration (see Moderation -> Url Moderation).",
autoDeleteTrigger: false,
scanWholeMessage: false,
hideCooldowns: true,
restrictionData: {
restrictions: [
{
id: "sys-cmd-mods-only-perms",
type: "firebot:permissions",
mode: "roles",
roleIds: [
"broadcaster",
"mod"
]
}
]
},
options: {
permitDuration: {
type: "number",
title: "Duration in seconds",
default: 30,
description: "The amount of time the viewer has to post a link after the !permit command is used."
},
permitDisplayTemplate: {
type: "string",
title: "Output Template",
description: "The chat message shown when the permit command is used (leave empty for no message).",
tip: "Variables: {target}, {duration}",
default: `{target}, you have {duration} seconds to post your url in the chat.`,
useTextArea: true
}
}
},
onTriggerEvent: async event => {
const twitchChat = require("../twitch-chat");
const { commandOptions } = event;
const target = event.userCommand.args[0];

if (!target) {
twitchChat.sendChatMessage("Please specify a user to permit.");
return;
}

tempPermittedUsers.push(target);
logger.debug(`Url moderation: ${target} has been temporary permitted to post a url...`);

const message = commandOptions.permitDisplayTemplate.replace("{target}", target).replace("{duration}", commandOptions.permitDuration);

if (message) {
twitchChat.sendChatMessage(message);
}

setTimeout(() => {
tempPermittedUsers = tempPermittedUsers.filter(user => user !== target);
logger.debug(`Url moderation: Temporary url permission for ${target} expired.`);
}, commandOptions.permitDuration * 1000);
}
};

function hasTemporaryPermission(username) {
return tempPermittedUsers.includes(username);
}

function registerPermitCommand() {
if (!commandManager.hasSystemCommand(PERMIT_COMMAND_ID)) {
commandManager.registerSystemCommand(permitCommand);
}
}

function unregisterPermitCommand() {
commandManager.unregisterSystemCommand(PERMIT_COMMAND_ID);
}

frontendCommunicator.on("registerPermitCommand", () => {
registerPermitCommand();
});

frontendCommunicator.on("unregisterPermitCommand", () => {
unregisterPermitCommand();
});

exports.hasTemporaryPermission = hasTemporaryPermission;
exports.registerPermitCommand = registerPermitCommand;
exports.unregisterPermitCommand = unregisterPermitCommand;
18 changes: 13 additions & 5 deletions gui/app/controllers/moderation.controller.js
Expand Up @@ -29,7 +29,8 @@
$scope.getExemptRoles = () => {
return [
...viewerRolesService.getTwitchRoles(),
...viewerRolesService.getCustomRoles()
...viewerRolesService.getCustomRoles(),
...viewerRolesService.getTeamRoles()
].filter(r => chatModerationService.chatModerationData.settings.exemptRoles.includes(r.id));
};

Expand All @@ -38,7 +39,8 @@
const options =
[
...viewerRolesService.getTwitchRoles(),
...viewerRolesService.getCustomRoles()
...viewerRolesService.getCustomRoles(),
...viewerRolesService.getTeamRoles()
]
.filter(r =>
!chatModerationService.chatModerationData.settings.exemptRoles.includes(r.id))
Expand Down Expand Up @@ -69,9 +71,15 @@

$scope.cms = chatModerationService;

$scope.toggleBannedWordsFeature = () => {
chatModerationService.chatModerationData.settings.bannedWordList.enabled =
!chatModerationService.chatModerationData.settings.bannedWordList.enabled;
$scope.toggleUrlModerationFeature = () => {
if (!chatModerationService.chatModerationData.settings.urlModeration.enabled) {
chatModerationService.chatModerationData.settings.urlModeration.enabled = true;
chatModerationService.registerPermitCommand();
} else {
chatModerationService.chatModerationData.settings.urlModeration.enabled = false;
chatModerationService.unregisterPermitCommand();
}

chatModerationService.saveChatModerationSettings();
};

Expand Down
16 changes: 16 additions & 0 deletions gui/app/services/chat-moderation.service.js
Expand Up @@ -18,6 +18,14 @@
enabled: false,
max: 10
},
urlModeration: {
enabled: false,
viewTime: {
enabled: false,
viewTimeInHours: 0
},
outputMessage: ""
},
exemptRoles: []
},
bannedWords: []
Expand Down Expand Up @@ -81,6 +89,14 @@
backendCommunicator.fireEvent("removeAllBannedWords");
};

service.registerPermitCommand = () => {
backendCommunicator.fireEvent("registerPermitCommand");
};

service.unregisterPermitCommand = () => {
backendCommunicator.fireEvent("unregisterPermitCommand");
};

return service;
});
}());

0 comments on commit 47f0e1d

Please sign in to comment.