From 0509db3b57d807196cd3a2c98868dcb75c7cdb02 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 31 May 2021 04:54:52 +0530 Subject: [PATCH 01/26] add basic settings --- BadWordsApp.ts | 20 +++++++++++--- app.json | 5 ++-- config/Settings.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 config/Settings.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index a01acc9..96eb74e 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -1,12 +1,26 @@ import { IAppAccessors, + IConfigurationExtend, + IEnvironmentRead, ILogger, -} from '@rocket.chat/apps-engine/definition/accessors'; -import { App } from '@rocket.chat/apps-engine/definition/App'; -import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; +} from "@rocket.chat/apps-engine/definition/accessors"; +import { App } from "@rocket.chat/apps-engine/definition/App"; +import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; +import { Settings } from "./config/Settings"; export class BadWordsApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } + + protected async extendConfiguration( + configuration: IConfigurationExtend, + environmentRead: IEnvironmentRead + ): Promise { + await Promise.all( + Settings.map((setting) => + configuration.settings.provideSetting(setting) + ) + ); + } } diff --git a/app.json b/app.json index 9528939..e3b39d0 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "id": "925a2e1e-d239-4ab8-a583-57203a8c3754", "version": "0.0.1", - "requiredApiVersion": "^1.4.0", + "requiredApiVersion": "^1.19.0", "iconFile": "icon.png", "author": { "name": "Rocket.Chat", @@ -11,5 +11,6 @@ "name": "BadWords", "nameSlug": "badwords", "classFile": "BadWordsApp.ts", - "description": "A simple app to block badwords and apply moderation policies in channels" + "description": "A simple app to block badwords and apply moderation policies in channels", + "implements": [] } \ No newline at end of file diff --git a/config/Settings.ts b/config/Settings.ts new file mode 100644 index 0000000..001f7ca --- /dev/null +++ b/config/Settings.ts @@ -0,0 +1,65 @@ +import { + ISetting, + SettingType, +} from "@rocket.chat/apps-engine/definition/settings"; + +export enum AppSetting { + LinkToExtractBadWords = "link_to_extract_bad_words", + ApplyFilterToAllChannels = "apply_filter_to_all_channels", + ListOfBlockedWords = "list_of_blocked_words", + ListOfAllowededWords = "list_of_Allowed_words", + IncludeChannels = "include_channels", + ExcludeChannels = "exclude_channels", +} + +export const Settings: Array = [ + { + id: AppSetting.LinkToExtractBadWords, + public: true, + type: SettingType.STRING, + packageValue: + "https://raw.githubusercontent.com/web-mech/badwords/master/lib/lang.json", + i18nLabel: "link_to_extract_bad_words", + required: false, + }, + { + id: AppSetting.ListOfBlockedWords, + public: true, + type: SettingType.STRING, + packageValue: "", + i18nLabel: "list_of_blocked_words", + required: false, + }, + { + id: AppSetting.ListOfAllowededWords, + public: true, + type: SettingType.STRING, + packageValue: "", + i18nLabel: "list_of_Allowed_words", + required: false, + }, + { + id: AppSetting.ApplyFilterToAllChannels, + public: true, + type: SettingType.BOOLEAN, + packageValue: true, + i18nLabel: "apply_filter_to_all_channels", + required: false, + }, + { + id: AppSetting.IncludeChannels, + public: true, + type: SettingType.STRING, + packageValue: "", + i18nLabel: "Include_the_Channels", + required: false, + }, + { + id: AppSetting.ExcludeChannels, + public: true, + type: SettingType.STRING, + packageValue: "", + i18nLabel: "Exclude_the_Channels", + required: false, + }, +]; From 6ab7149cbf40152650c15661878bfccad8c32d17 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 31 May 2021 05:00:33 +0530 Subject: [PATCH 02/26] add i18n --- i18n/en.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 i18n/en.json diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..c1f9517 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,9 @@ +{ + "link_to_extract_bad_words" : "URL to extract Bad Words", + "apply_filter_to_all_channels" : "Apply Bad Words filtration to all Channels", + "list_of_blocked_words" : "List of Blocked Words", + "list_of_Allowed_words" : "List of Allowed Words", + "include_channels" : "Include these channels", + "exclude_channels" : "Exclude these channels" + +} \ No newline at end of file From f497d9fa169eec16e83b3c01ba3c8d327efe3381 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 31 May 2021 05:46:30 +0530 Subject: [PATCH 03/26] add settings update handler and fetch from url --- BadWordsApp.ts | 17 ++++++++++++++++- handlers/OnSettingsUpdatedHandler.ts | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 handlers/OnSettingsUpdatedHandler.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index 96eb74e..e09c24c 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -1,17 +1,32 @@ import { IAppAccessors, IConfigurationExtend, + IConfigurationModify, IEnvironmentRead, + IHttp, ILogger, + IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; +import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; import { Settings } from "./config/Settings"; - +import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; export class BadWordsApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } + public blockedWords: any; + + public async onSettingUpdated( + setting: ISetting, + configurationModify: IConfigurationModify, + read: IRead, + http: IHttp + ): Promise { + const settingsUpdated = new OnSettingsUpdatedHandler(this, read, http); + this.blockedWords = await settingsUpdated.run(); + } protected async extendConfiguration( configuration: IConfigurationExtend, diff --git a/handlers/OnSettingsUpdatedHandler.ts b/handlers/OnSettingsUpdatedHandler.ts new file mode 100644 index 0000000..2155d32 --- /dev/null +++ b/handlers/OnSettingsUpdatedHandler.ts @@ -0,0 +1,23 @@ +import { IHttp, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; +import { AppSetting } from "../config/Settings"; + +export class OnSettingsUpdatedHandler { + constructor( + private readonly app: IApp, + private readonly read: IRead, + private readonly http: IHttp + ) {} + + public async run(): Promise> { + const badWordsURL: string = await this.read + .getEnvironmentReader() + .getSettings() + .getValueById(AppSetting.LinkToExtractBadWords); + const fetchBlockedWordsFromURL = await this.http.get(badWordsURL); + const blockedWordsFromURL = JSON.parse( + fetchBlockedWordsFromURL.content || "" + ); + return blockedWordsFromURL.words; + } +} From a61ee9e63413ed0c3e0268995fa8dfe72aa04f93 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 1 Jun 2021 00:47:08 +0530 Subject: [PATCH 04/26] refactoring getting blocked words --- BadWordsApp.ts | 12 +++++++++-- handlers/OnSettingsUpdatedHandler.ts | 24 ++++++++++++---------- lib/Settings.ts | 30 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 lib/Settings.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index e09c24c..1d1a07a 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -12,11 +12,12 @@ import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; import { Settings } from "./config/Settings"; import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; +import { getBlockedWords } from "./lib/Settings"; export class BadWordsApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } - public blockedWords: any; + public blockedWords: Array; public async onSettingUpdated( setting: ISetting, @@ -24,7 +25,13 @@ export class BadWordsApp extends App { read: IRead, http: IHttp ): Promise { - const settingsUpdated = new OnSettingsUpdatedHandler(this, read, http); + const settingsUpdated = new OnSettingsUpdatedHandler( + this, + setting, + configurationModify, + read, + http + ); this.blockedWords = await settingsUpdated.run(); } @@ -37,5 +44,6 @@ export class BadWordsApp extends App { configuration.settings.provideSetting(setting) ) ); + this.blockedWords = await getBlockedWords(environmentRead, this.getAccessors().http); } } diff --git a/handlers/OnSettingsUpdatedHandler.ts b/handlers/OnSettingsUpdatedHandler.ts index 2155d32..fcb30ff 100644 --- a/handlers/OnSettingsUpdatedHandler.ts +++ b/handlers/OnSettingsUpdatedHandler.ts @@ -1,23 +1,27 @@ -import { IHttp, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { + IConfigurationModify, + IHttp, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; -import { AppSetting } from "../config/Settings"; +import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; +import { getBlockedWords } from "../lib/Settings"; export class OnSettingsUpdatedHandler { constructor( private readonly app: IApp, + private readonly settings: ISetting, + private readonly configurationModify: IConfigurationModify, private readonly read: IRead, private readonly http: IHttp ) {} public async run(): Promise> { - const badWordsURL: string = await this.read - .getEnvironmentReader() - .getSettings() - .getValueById(AppSetting.LinkToExtractBadWords); - const fetchBlockedWordsFromURL = await this.http.get(badWordsURL); - const blockedWordsFromURL = JSON.parse( - fetchBlockedWordsFromURL.content || "" + const blockedWords = getBlockedWords( + this.read.getEnvironmentReader(), + this.http ); - return blockedWordsFromURL.words; + + return blockedWords; } } diff --git a/lib/Settings.ts b/lib/Settings.ts new file mode 100644 index 0000000..ad6ef23 --- /dev/null +++ b/lib/Settings.ts @@ -0,0 +1,30 @@ +import { + IEnvironmentRead, + IHttp, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { AppSetting } from "../config/Settings"; + +export const getBlockedWords = async ( + environmentRead: IEnvironmentRead, + http: IHttp +) => { + const badWordsURL: string = await environmentRead + .getSettings() + .getValueById(AppSetting.LinkToExtractBadWords); + + const listOfBlockedWordsSetting: string = await environmentRead + .getSettings() + .getValueById(AppSetting.ListOfBlockedWords); + + const fetchBlockedWordsFromURL = await http.get(badWordsURL); + const blockedWordsFromURL = JSON.parse( + fetchBlockedWordsFromURL.content || "{}" + ); + const blockedWords: Array = blockedWordsFromURL.words; + + const blockedWordsFromSettings: Array = listOfBlockedWordsSetting + .split(",") + .map((word) => word.trim()); + blockedWords.push(...blockedWordsFromSettings); + return blockedWords; +}; From b719c0e920da6be476d08348a891de0abb5c6d5b Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Thu, 10 Jun 2021 00:43:10 +0530 Subject: [PATCH 05/26] removing allowed words from blocked list --- lib/Settings.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/Settings.ts b/lib/Settings.ts index ad6ef23..2180cf8 100644 --- a/lib/Settings.ts +++ b/lib/Settings.ts @@ -16,15 +16,31 @@ export const getBlockedWords = async ( .getSettings() .getValueById(AppSetting.ListOfBlockedWords); + const listOfAllowedWordsSetting: string = await environmentRead + .getSettings() + .getValueById(AppSetting.ListOfAllowededWords); + + const listOfAllowedWords: Array = listOfAllowedWordsSetting + .split(",") + .map((word) => word.trim()); + + const blockedWordsFromSettings: Array = listOfBlockedWordsSetting + .split(",") + .map((word) => word.trim()); + const fetchBlockedWordsFromURL = await http.get(badWordsURL); const blockedWordsFromURL = JSON.parse( fetchBlockedWordsFromURL.content || "{}" ); const blockedWords: Array = blockedWordsFromURL.words; - - const blockedWordsFromSettings: Array = listOfBlockedWordsSetting - .split(",") - .map((word) => word.trim()); blockedWords.push(...blockedWordsFromSettings); + + listOfAllowedWords.map((word) => { + const idx = blockedWords.indexOf(word); + if (idx > -1) { + blockedWords.splice(idx, 1); + } + }); + return blockedWords; }; From 6c991dd661581a001f1445c2984666735eec3977 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Thu, 10 Jun 2021 03:50:31 +0530 Subject: [PATCH 06/26] reading message and basic filtering --- BadWordsApp.ts | 33 ++++++++++++++++++++++++++-- app.json | 4 +++- handlers/PreMessageSentHandler.ts | 32 +++++++++++++++++++++++++++ lib/Messages.ts | 36 +++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 handlers/PreMessageSentHandler.ts create mode 100644 lib/Messages.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index 1d1a07a..059b2e9 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -5,20 +5,46 @@ import { IEnvironmentRead, IHttp, ILogger, + IMessageBuilder, + IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; +import { + IMessage, + IPreMessageSentModify, +} from "@rocket.chat/apps-engine/definition/messages"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; import { Settings } from "./config/Settings"; import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; +import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; -export class BadWordsApp extends App { +export class BadWordsApp extends App implements IPreMessageSentModify { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } public blockedWords: Array; + async executePreMessageSentModify( + message: IMessage, + builder: IMessageBuilder, + read: IRead, + http: IHttp, + persist: IPersistence + ): Promise { + const preMessageHandler = new PreMessageSentHandler( + this, + message, + builder, + read, + http, + persist, + this.blockedWords + ); + return preMessageHandler.run(); + } + public async onSettingUpdated( setting: ISetting, configurationModify: IConfigurationModify, @@ -44,6 +70,9 @@ export class BadWordsApp extends App { configuration.settings.provideSetting(setting) ) ); - this.blockedWords = await getBlockedWords(environmentRead, this.getAccessors().http); + this.blockedWords = await getBlockedWords( + environmentRead, + this.getAccessors().http + ); } } diff --git a/app.json b/app.json index e3b39d0..f006d03 100644 --- a/app.json +++ b/app.json @@ -12,5 +12,7 @@ "nameSlug": "badwords", "classFile": "BadWordsApp.ts", "description": "A simple app to block badwords and apply moderation policies in channels", - "implements": [] + "implements": [ + "IPreMessageSentModify" + ] } \ No newline at end of file diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts new file mode 100644 index 0000000..15d77d1 --- /dev/null +++ b/handlers/PreMessageSentHandler.ts @@ -0,0 +1,32 @@ +import { + IHttp, + IMessageBuilder, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; +import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; +import { clean } from "../lib/Messages"; +export class PreMessageSentHandler { + constructor( + private app: IApp, + private message: IMessage, + private builder: IMessageBuilder, + private read: IRead, + private http: IHttp, + private persist: IPersistence, + private blockedWords: Array + ) {} + + public async run() { + const { text = "", room, sender } = this.message; + + const cleanText = clean(this.blockedWords, text); + const cleanMessage = this.builder + .setText(cleanText) + .setRoom(room) + .setSender(sender); + + return cleanMessage.getMessage(); + } +} diff --git a/lib/Messages.ts b/lib/Messages.ts new file mode 100644 index 0000000..458638f --- /dev/null +++ b/lib/Messages.ts @@ -0,0 +1,36 @@ +let regex = /[^a-zA-Z0-9|\$|\@]|\^/g; +let splitRegex = /\b/; +let placeHolder = "*"; +let replaceRegex = /\w/g; +let exclude: Array = []; + +const isProfane = (blockedWords: Array, string: string): boolean => { + return ( + blockedWords.filter((word) => { + const wordExp = new RegExp( + `\\b${word.replace(/(\W)/g, "\\$1")}\\b`, + "gi" + ); + return ( + !exclude.includes(word.toLowerCase()) && wordExp.test(string) + ); + }).length > 0 || false + ); +}; + +const replaceWord = (string: string): string => { + return string.replace(regex, "").replace(replaceRegex, placeHolder); +}; + +export const clean = (blockedWords: Array, string: string) => { + let cleanText: string = string + .split(splitRegex) + .map((word) => { + if (isProfane(blockedWords, word)) { + word = replaceWord(word); + } + return word; + }) + .join(splitRegex.exec(string)![0]); + return cleanText; +}; From 7fa7806a72bb31d59b54c3cbc957b5612c4de4ef Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 18 Jun 2021 02:54:02 +0530 Subject: [PATCH 07/26] refactoring PreMessageSentHandler --- handlers/PreMessageSentHandler.ts | 10 +++++++++- lib/Messages.ts | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index 15d77d1..ef7b0f6 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -19,9 +19,17 @@ export class PreMessageSentHandler { ) {} public async run() { + if (this.blockedWords.length == 0) { + return this.message; + } + const { text = "", room, sender } = this.message; + const { cleanText, isAnyWordProfane } = clean(this.blockedWords, text); + + if (!isAnyWordProfane) { + return this.message; + } - const cleanText = clean(this.blockedWords, text); const cleanMessage = this.builder .setText(cleanText) .setRoom(room) diff --git a/lib/Messages.ts b/lib/Messages.ts index 458638f..28e38d3 100644 --- a/lib/Messages.ts +++ b/lib/Messages.ts @@ -23,14 +23,16 @@ const replaceWord = (string: string): string => { }; export const clean = (blockedWords: Array, string: string) => { + let isAnyWordProfane = false; let cleanText: string = string .split(splitRegex) .map((word) => { if (isProfane(blockedWords, word)) { + isAnyWordProfane = true; word = replaceWord(word); } return word; }) .join(splitRegex.exec(string)![0]); - return cleanText; + return { isAnyWordProfane, cleanText }; }; From 2648f35ac79470a1af8c7809bc919acd598a29e9 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 5 Jul 2021 18:20:12 +0530 Subject: [PATCH 08/26] adding CheckPreMessageSent and channels check --- BadWordsApp.ts | 16 +++++++ handlers/CheckPreMessageSentHandler.ts | 60 ++++++++++++++++++++++++++ lib/Settings.ts | 9 ++++ 3 files changed, 85 insertions(+) create mode 100644 handlers/CheckPreMessageSentHandler.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index 059b2e9..cc0db39 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -17,6 +17,7 @@ import { import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; import { Settings } from "./config/Settings"; +import { CheckPreMessageSentHandler } from "./handlers/CheckPreMessageSentHandler"; import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; @@ -26,6 +27,21 @@ export class BadWordsApp extends App implements IPreMessageSentModify { } public blockedWords: Array; + async checkPreMessageSentModify( + message: IMessage, + read: IRead, + http: IHttp + ): Promise { + const checkPreMessageSentHandler = new CheckPreMessageSentHandler( + this, + message, + read, + http, + this.blockedWords + ); + return checkPreMessageSentHandler.check(); + } + async executePreMessageSentModify( message: IMessage, builder: IMessageBuilder, diff --git a/handlers/CheckPreMessageSentHandler.ts b/handlers/CheckPreMessageSentHandler.ts new file mode 100644 index 0000000..518d432 --- /dev/null +++ b/handlers/CheckPreMessageSentHandler.ts @@ -0,0 +1,60 @@ +import { IHttp, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; +import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { AppSetting } from "../config/Settings"; +import { getSettingValue } from "../lib/Settings"; + +export class CheckPreMessageSentHandler { + constructor( + private app: IApp, + private message: IMessage, + private read: IRead, + private http: IHttp, + private blockedWords: Array + ) {} + + private async checkForChannels(room: IRoom): Promise { + const { displayName: roomName } = room; + + const applyToAllChannels = await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.ApplyFilterToAllChannels + ); + + if (applyToAllChannels) { + const excludedChannels = ( + await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.ExcludeChannels + ) + ) + .split(",") + .map((e: string) => e.trim()); + return !excludedChannels.includes(roomName); + } else { + const includedChannels = ( + await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.IncludeChannels + ) + ) + .split(",") + .map((e) => e.trim()); + return includedChannels.includes(roomName); + } + } + + public async check(): Promise { + if (this.blockedWords.length == 0) return false; + + const { room, sender } = this.message; + const { type } = room; + + if (type !== "l" && type !== "d") { + return this.checkForChannels(room); + } + + return true; + } +} diff --git a/lib/Settings.ts b/lib/Settings.ts index 2180cf8..7055ef4 100644 --- a/lib/Settings.ts +++ b/lib/Settings.ts @@ -44,3 +44,12 @@ export const getBlockedWords = async ( return blockedWords; }; + +export const getSettingValue = async ( + environmentRead: IEnvironmentRead, + id: string +) => { + return await ( + await environmentRead.getSettings().getById(id) + ).value; +}; From 991fc107ba01569c2157a91366587c97fa7b6dc4 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 5 Jul 2021 18:27:59 +0530 Subject: [PATCH 09/26] add settings for Direct and LiveChat Messages --- config/Settings.ts | 18 ++++++++++++++++++ handlers/CheckPreMessageSentHandler.ts | 17 +++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/config/Settings.ts b/config/Settings.ts index 001f7ca..1ec8471 100644 --- a/config/Settings.ts +++ b/config/Settings.ts @@ -6,6 +6,8 @@ import { export enum AppSetting { LinkToExtractBadWords = "link_to_extract_bad_words", ApplyFilterToAllChannels = "apply_filter_to_all_channels", + ApplyFilterToDirectMessages = "apply_filter_to_direct_messages", + ApplyFilterToLivechatMessages = "apply_filter_to_livechat_messages", ListOfBlockedWords = "list_of_blocked_words", ListOfAllowededWords = "list_of_Allowed_words", IncludeChannels = "include_channels", @@ -38,6 +40,22 @@ export const Settings: Array = [ i18nLabel: "list_of_Allowed_words", required: false, }, + { + id: AppSetting.ApplyFilterToDirectMessages, + public: true, + type: SettingType.BOOLEAN, + packageValue: true, + i18nLabel: "apply_filter_to_direct_messages", + required: false, + }, + { + id: AppSetting.ApplyFilterToLivechatMessages, + public: true, + type: SettingType.BOOLEAN, + packageValue: true, + i18nLabel: "apply_filter_to_livechat_messages", + required: false, + }, { id: AppSetting.ApplyFilterToAllChannels, public: true, diff --git a/handlers/CheckPreMessageSentHandler.ts b/handlers/CheckPreMessageSentHandler.ts index 518d432..53c6b49 100644 --- a/handlers/CheckPreMessageSentHandler.ts +++ b/handlers/CheckPreMessageSentHandler.ts @@ -51,10 +51,19 @@ export class CheckPreMessageSentHandler { const { room, sender } = this.message; const { type } = room; - if (type !== "l" && type !== "d") { - return this.checkForChannels(room); + switch (type) { + case "d": + return await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.ApplyFilterToDirectMessages + ); + case "l": + return await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.ApplyFilterToLivechatMessages + ); + default: + return this.checkForChannels(room); } - - return true; } } From a7b4faafd48af9e4ff11e4465878ad707ffa8006 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 5 Jul 2021 18:30:55 +0530 Subject: [PATCH 10/26] Check badwords for Edited Messages --- BadWordsApp.ts | 44 +++++++++++++++++++++++++++++++++++++++++--- app.json | 3 ++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/BadWordsApp.ts b/BadWordsApp.ts index cc0db39..96dc2e3 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -13,6 +13,7 @@ import { App } from "@rocket.chat/apps-engine/definition/App"; import { IMessage, IPreMessageSentModify, + IPreMessageUpdatedModify, } from "@rocket.chat/apps-engine/definition/messages"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; @@ -21,7 +22,10 @@ import { CheckPreMessageSentHandler } from "./handlers/CheckPreMessageSentHandle import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; -export class BadWordsApp extends App implements IPreMessageSentModify { +export class BadWordsApp + extends App + implements IPreMessageSentModify, IPreMessageUpdatedModify +{ constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } @@ -49,7 +53,7 @@ export class BadWordsApp extends App implements IPreMessageSentModify { http: IHttp, persist: IPersistence ): Promise { - const preMessageHandler = new PreMessageSentHandler( + const preMessageSentHandler = new PreMessageSentHandler( this, message, builder, @@ -58,7 +62,41 @@ export class BadWordsApp extends App implements IPreMessageSentModify { persist, this.blockedWords ); - return preMessageHandler.run(); + return preMessageSentHandler.run(); + } + + async checkPreMessageUpdatedModify( + message: IMessage, + read: IRead, + http: IHttp + ): Promise { + const checkPreMessageUpdatedHandler = new CheckPreMessageSentHandler( + this, + message, + read, + http, + this.blockedWords + ); + return checkPreMessageUpdatedHandler.check(); + } + + async executePreMessageUpdatedModify( + message: IMessage, + builder: IMessageBuilder, + read: IRead, + http: IHttp, + persist: IPersistence + ): Promise { + const preMessageUpdatedHandler = new PreMessageSentHandler( + this, + message, + builder, + read, + http, + persist, + this.blockedWords + ); + return preMessageUpdatedHandler.run(); } public async onSettingUpdated( diff --git a/app.json b/app.json index f006d03..c7409d1 100644 --- a/app.json +++ b/app.json @@ -13,6 +13,7 @@ "classFile": "BadWordsApp.ts", "description": "A simple app to block badwords and apply moderation policies in channels", "implements": [ - "IPreMessageSentModify" + "IPreMessageSentModify", + "IPreMessageUpdatedModify" ] } \ No newline at end of file From e4acd892cce62fb6550f8501284e2fa0e2f34a83 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 5 Jul 2021 18:39:37 +0530 Subject: [PATCH 11/26] improving internationalisation --- config/Settings.ts | 4 ++-- i18n/en.json | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/config/Settings.ts b/config/Settings.ts index 1ec8471..6117803 100644 --- a/config/Settings.ts +++ b/config/Settings.ts @@ -69,7 +69,7 @@ export const Settings: Array = [ public: true, type: SettingType.STRING, packageValue: "", - i18nLabel: "Include_the_Channels", + i18nLabel: "include_channels", required: false, }, { @@ -77,7 +77,7 @@ export const Settings: Array = [ public: true, type: SettingType.STRING, packageValue: "", - i18nLabel: "Exclude_the_Channels", + i18nLabel: "exclude_channels", required: false, }, ]; diff --git a/i18n/en.json b/i18n/en.json index c1f9517..db683c5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,9 +1,10 @@ { - "link_to_extract_bad_words" : "URL to extract Bad Words", - "apply_filter_to_all_channels" : "Apply Bad Words filtration to all Channels", - "list_of_blocked_words" : "List of Blocked Words", - "list_of_Allowed_words" : "List of Allowed Words", - "include_channels" : "Include these channels", - "exclude_channels" : "Exclude these channels" - -} \ No newline at end of file + "link_to_extract_bad_words": "URL to extract Blocked Words", + "apply_filter_to_all_channels": "Apply Bad Words filtration Channels, Teams, Groups and discussions", + "list_of_blocked_words": "List of Blocked Words", + "list_of_Allowed_words": "List of Allowed Words", + "include_channels": "Include these channels", + "exclude_channels": "Exclude these channels", + "apply_filter_to_direct_messages": "Apply Bad Words filtration to Direct Messages", + "apply_filter_to_livechat_messages": "Apply Bad Words filtration to LiveChat Messages" +} From 6a544db433d59d2f6d43d733fc85d320400d2061 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 16 Jul 2021 00:24:19 +0530 Subject: [PATCH 12/26] Attachment description filtering --- handlers/PreMessageSentHandler.ts | 36 ++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index ef7b0f6..10371d0 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -5,7 +5,10 @@ import { IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; -import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; +import { + IMessage, + IMessageAttachment, +} from "@rocket.chat/apps-engine/definition/messages"; import { clean } from "../lib/Messages"; export class PreMessageSentHandler { constructor( @@ -23,18 +26,41 @@ export class PreMessageSentHandler { return this.message; } - const { text = "", room, sender } = this.message; - const { cleanText, isAnyWordProfane } = clean(this.blockedWords, text); + const { text = "", room, sender, attachments = [] } = this.message; + // const attachments = this.message.attachments as IMessageAttachment[]; + const isAttachment = attachments.length > 0; + const messageText = text ? text : attachments[0].description; + + const { cleanText, isAnyWordProfane } = clean( + this.blockedWords, + messageText ? messageText : "" + ); if (!isAnyWordProfane) { return this.message; } - const cleanMessage = this.builder + if (isAttachment) { + const filteredAttachment = [ + { + ...attachments[0], + description: cleanText, + }, + ]; + + const cleanAttachmentMessage = this.builder + .setAttachments(filteredAttachment) + .setRoom(room) + .setSender(sender); + + return cleanAttachmentMessage.getMessage(); + } + + const cleanTextMessage = this.builder .setText(cleanText) .setRoom(room) .setSender(sender); - return cleanMessage.getMessage(); + return cleanTextMessage.getMessage(); } } From c7dd1ba26137c9de2e4d7ef98e014e6cfb1da8db Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Fri, 16 Jul 2021 00:32:37 +0530 Subject: [PATCH 13/26] Refactoring Attachment desc filtering --- handlers/PreMessageSentHandler.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index 10371d0..8661e1f 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -27,7 +27,6 @@ export class PreMessageSentHandler { } const { text = "", room, sender, attachments = [] } = this.message; - // const attachments = this.message.attachments as IMessageAttachment[]; const isAttachment = attachments.length > 0; const messageText = text ? text : attachments[0].description; @@ -40,6 +39,8 @@ export class PreMessageSentHandler { return this.message; } + const filteredMessage = this.builder.setRoom(room).setSender(sender); + if (isAttachment) { const filteredAttachment = [ { @@ -48,19 +49,11 @@ export class PreMessageSentHandler { }, ]; - const cleanAttachmentMessage = this.builder - .setAttachments(filteredAttachment) - .setRoom(room) - .setSender(sender); - - return cleanAttachmentMessage.getMessage(); + filteredMessage.setAttachments(filteredAttachment); + } else { + filteredMessage.setText(cleanText); } - const cleanTextMessage = this.builder - .setText(cleanText) - .setRoom(room) - .setSender(sender); - - return cleanTextMessage.getMessage(); + return filteredMessage.getMessage(); } } From 370e1bc6bc96a8aef57e5c5db446b312ae71f46a Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 9 Aug 2021 23:58:07 +0530 Subject: [PATCH 14/26] persist offending users --- handlers/PreMessageSentHandler.ts | 4 +++ lib/getStats.ts | 26 ++++++++++++++++ lib/storeStats.ts | 50 +++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 lib/getStats.ts create mode 100644 lib/storeStats.ts diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index 8661e1f..bb2f4cd 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -10,6 +10,8 @@ import { IMessageAttachment, } from "@rocket.chat/apps-engine/definition/messages"; import { clean } from "../lib/Messages"; +import { storeStatsForOffendingUsers } from "../lib/storeStats"; + export class PreMessageSentHandler { constructor( private app: IApp, @@ -54,6 +56,8 @@ export class PreMessageSentHandler { filteredMessage.setText(cleanText); } + storeStatsForOffendingUsers(room, sender, this.persist, this.read); + return filteredMessage.getMessage(); } } diff --git a/lib/getStats.ts b/lib/getStats.ts new file mode 100644 index 0000000..c76beb8 --- /dev/null +++ b/lib/getStats.ts @@ -0,0 +1,26 @@ +import { IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; + +export async function getStatsForOffendingUser( + rid: string, + uid: string, + read: IRead +) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + const userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + uid + ); + + const [record] = await read + .getPersistenceReader() + .readByAssociations([roomAssociation, userAssociation]); + + return record; +} diff --git a/lib/storeStats.ts b/lib/storeStats.ts new file mode 100644 index 0000000..064fbdd --- /dev/null +++ b/lib/storeStats.ts @@ -0,0 +1,50 @@ +import { + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { getStatsForOffendingUser } from "./getStats"; + +export async function storeStatsForOffendingUsers( + { id: rid, displayName }: IRoom, + { id: uid, username }: IUser, + persist: IPersistence, + read: IRead +) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + const userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + uid + ); + const record: any = await getStatsForOffendingUser(rid, uid, read); + + if (!record) { + const newRecord = { + badWordsCount: 1, + roomName: displayName || "", + userName: username, + }; + await persist.createWithAssociations(newRecord, [ + roomAssociation, + userAssociation, + ]); + } else { + const updatedRecord = { + badWordsCount: record.badWordsCount + 1, + roomName: displayName || "", + userName: username, + }; + await persist.updateByAssociations( + [roomAssociation, userAssociation], + updatedRecord + ); + } +} From 8252b415ab18117aa105681e58b057e2f57bd927 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Mon, 9 Aug 2021 23:59:06 +0530 Subject: [PATCH 15/26] get stats of a room --- lib/getStats.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/getStats.ts b/lib/getStats.ts index c76beb8..e622eaf 100644 --- a/lib/getStats.ts +++ b/lib/getStats.ts @@ -24,3 +24,16 @@ export async function getStatsForOffendingUser( return record; } + +export async function getStatsForRoom(rid: string, read: IRead) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + + const [record] = await read + .getPersistenceReader() + .readByAssociation(roomAssociation); + + return record; +} From 68ab1026cd760872df5c9cf4f2c16645b0e06699 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 00:01:50 +0530 Subject: [PATCH 16/26] notifying users of bad lang --- handlers/PreMessageSentHandler.ts | 7 +++++++ lib/sendNotifyMessage.ts | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 lib/sendNotifyMessage.ts diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index bb2f4cd..f8f1205 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -11,6 +11,7 @@ import { } from "@rocket.chat/apps-engine/definition/messages"; import { clean } from "../lib/Messages"; import { storeStatsForOffendingUsers } from "../lib/storeStats"; +import { sendNotifyMessage } from "../lib/sendNotifyMessage"; export class PreMessageSentHandler { constructor( @@ -56,6 +57,12 @@ export class PreMessageSentHandler { filteredMessage.setText(cleanText); } + sendNotifyMessage( + room, + sender, + this.message.threadId, + this.read.getNotifier() + ); storeStatsForOffendingUsers(room, sender, this.persist, this.read); return filteredMessage.getMessage(); diff --git a/lib/sendNotifyMessage.ts b/lib/sendNotifyMessage.ts new file mode 100644 index 0000000..48dff78 --- /dev/null +++ b/lib/sendNotifyMessage.ts @@ -0,0 +1,23 @@ +import { INotifier } from "@rocket.chat/apps-engine/definition/accessors"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; + +export async function sendNotifyMessage( + room: IRoom, + user: IUser, + threadId: string | undefined, + notify: INotifier +) { + const builder = notify.getMessageBuilder(); + + builder + .setRoom(room) + .setUsernameAlias("Bad Words Moderator") + .setText(`*${user.username}*, Please watch your Language!`); + + if (threadId) { + builder.setThreadId(threadId); + } + + await notify.notifyUser(user, builder.getMessage()); +} From 59dabdb9558e3750b464e6b336adc524f5377a74 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 00:09:55 +0530 Subject: [PATCH 17/26] adding slashCommand --- commands/testCommand.ts | 40 ++++++++++++++++++++++++++++++++++ lib/showModal.ts | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 commands/testCommand.ts create mode 100644 lib/showModal.ts diff --git a/commands/testCommand.ts b/commands/testCommand.ts new file mode 100644 index 0000000..dbef959 --- /dev/null +++ b/commands/testCommand.ts @@ -0,0 +1,40 @@ +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + ISlashCommand, + SlashCommandContext, +} from "@rocket.chat/apps-engine/definition/slashcommands"; +import { showModal } from "../lib/showModal"; + +export class TestCommand implements ISlashCommand { + public command = "testing"; + public i18nParamsExample = "example params"; + public i18nDescription = "command description"; + public providesPreview = false; + + public async executor( + context: SlashCommandContext, + read: IRead, + modify: IModify, + http: IHttp, + persis: IPersistence + ): Promise { + const triggerId = context.getTriggerId(); + + const args = context.getArguments(); + console.log("Arsga re = ", args); + + const room = context.getRoom(); + + if (triggerId) { + const modal = await showModal(room, read, modify); + await modify + .getUiController() + .openModalView(modal, { triggerId }, context.getSender()); + } + } +} diff --git a/lib/showModal.ts b/lib/showModal.ts new file mode 100644 index 0000000..6229862 --- /dev/null +++ b/lib/showModal.ts @@ -0,0 +1,48 @@ +import { + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; + +export async function showModal( + room: IRoom, + read: IRead, + modify: IModify +): Promise { + const block = modify.getCreator().getBlockBuilder(); + + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + room.id + ); + const records = await read + .getPersistenceReader() + .readByAssociation(roomAssociation); + + if (records.length == 0) { + block.addContextBlock({ + elements: [ + block.newPlainTextObject( + `No data found for Room - ${room.displayName}` + ), + ], + }); + } else { + console.log("The data is = ", records); + } + + return { + id: room.id, + title: block.newPlainTextObject("This is test"), + close: block.newButtonElement({ + text: block.newPlainTextObject("Cancel"), + }), + blocks: block.getBlocks(), + }; +} From d34367f2c38dc6f625c3eb14a80dae71502cf145 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 00:13:14 +0530 Subject: [PATCH 18/26] adding slash command to config --- BadWordsApp.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BadWordsApp.ts b/BadWordsApp.ts index 96dc2e3..dbce872 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -17,11 +17,13 @@ import { } from "@rocket.chat/apps-engine/definition/messages"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; +import { TestCommand } from "./commands/testCommand"; import { Settings } from "./config/Settings"; import { CheckPreMessageSentHandler } from "./handlers/CheckPreMessageSentHandler"; import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; + export class BadWordsApp extends App implements IPreMessageSentModify, IPreMessageUpdatedModify @@ -124,6 +126,9 @@ export class BadWordsApp configuration.settings.provideSetting(setting) ) ); + await configuration.slashCommands.provideSlashCommand( + new TestCommand() + ); this.blockedWords = await getBlockedWords( environmentRead, this.getAccessors().http From d735fa4617bf5f7b8850d81c24bf5a5c1536a93e Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 17:11:04 +0530 Subject: [PATCH 19/26] refactoring send notify message --- handlers/PreMessageSentHandler.ts | 3 ++- lib/sendNotifyMessage.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index f8f1205..239b92e 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -61,7 +61,8 @@ export class PreMessageSentHandler { room, sender, this.message.threadId, - this.read.getNotifier() + this.read.getNotifier(), + `*${sender.username}*, Please watch your Language!` ); storeStatsForOffendingUsers(room, sender, this.persist, this.read); diff --git a/lib/sendNotifyMessage.ts b/lib/sendNotifyMessage.ts index 48dff78..c6f4797 100644 --- a/lib/sendNotifyMessage.ts +++ b/lib/sendNotifyMessage.ts @@ -6,14 +6,15 @@ export async function sendNotifyMessage( room: IRoom, user: IUser, threadId: string | undefined, - notify: INotifier + notify: INotifier, + message: string ) { const builder = notify.getMessageBuilder(); builder .setRoom(room) .setUsernameAlias("Bad Words Moderator") - .setText(`*${user.username}*, Please watch your Language!`); + .setText(message); if (threadId) { builder.setThreadId(threadId); From 27146706d2f1195ee0d7a859889eefcc9c263571 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 17:45:30 +0530 Subject: [PATCH 20/26] adding functionality to slash command --- commands/testCommand.ts | 36 +++++++++++++++++++++++++++--------- lib/showModal.ts | 30 ++++++++++++++---------------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/commands/testCommand.ts b/commands/testCommand.ts index dbef959..53e333e 100644 --- a/commands/testCommand.ts +++ b/commands/testCommand.ts @@ -8,6 +8,7 @@ import { ISlashCommand, SlashCommandContext, } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { sendNotifyMessage } from "../lib/sendNotifyMessage"; import { showModal } from "../lib/showModal"; export class TestCommand implements ISlashCommand { @@ -24,17 +25,34 @@ export class TestCommand implements ISlashCommand { persis: IPersistence ): Promise { const triggerId = context.getTriggerId(); - - const args = context.getArguments(); - console.log("Arsga re = ", args); - const room = context.getRoom(); + const sender = context.getSender(); + const threadId = context.getThreadId(); - if (triggerId) { - const modal = await showModal(room, read, modify); - await modify - .getUiController() - .openModalView(modal, { triggerId }, context.getSender()); + if (sender.roles.includes("admin") || sender.roles.includes("owner")) { + if (triggerId) { + const modal = await showModal(room, read, modify); + await modify + .getUiController() + .openModalView(modal, { triggerId }, sender); + console.log("After UI MODAL"); + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "Something went wrong!!" + ); + } + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "This slash command can be only used by admins and owners" + ); } } } diff --git a/lib/showModal.ts b/lib/showModal.ts index 6229862..297f3e9 100644 --- a/lib/showModal.ts +++ b/lib/showModal.ts @@ -1,14 +1,10 @@ import { IModify, - IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; -import { - RocketChatAssociationModel, - RocketChatAssociationRecord, -} from "@rocket.chat/apps-engine/definition/metadata"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { getStatsForRoom } from "./getStats"; export async function showModal( room: IRoom, @@ -17,14 +13,8 @@ export async function showModal( ): Promise { const block = modify.getCreator().getBlockBuilder(); - const roomAssociation = new RocketChatAssociationRecord( - RocketChatAssociationModel.ROOM, - room.id - ); - const records = await read - .getPersistenceReader() - .readByAssociation(roomAssociation); - + const records: any = await getStatsForRoom(room.id, read); + console.log("Records = ", records); if (records.length == 0) { block.addContextBlock({ elements: [ @@ -34,12 +24,20 @@ export async function showModal( ], }); } else { - console.log("The data is = ", records); + records.forEach((record) => { + block.addContextBlock({ + elements: [ + block.newMarkdownTextObject( + `*${record.userName}* - *${record.badWordsCount}*` + ), + ], + }); + }); } return { - id: room.id, - title: block.newPlainTextObject("This is test"), + id: "122121", + title: block.newPlainTextObject(`Offending Users in ${room.displayName}`), close: block.newButtonElement({ text: block.newPlainTextObject("Cancel"), }), From 3caf89fe28b2f1949a74455faf5e5720081bd8f9 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 10 Aug 2021 18:32:25 +0530 Subject: [PATCH 21/26] slashCommand minor fix --- commands/testCommand.ts | 2 +- lib/getStats.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands/testCommand.ts b/commands/testCommand.ts index 53e333e..6745a38 100644 --- a/commands/testCommand.ts +++ b/commands/testCommand.ts @@ -12,7 +12,7 @@ import { sendNotifyMessage } from "../lib/sendNotifyMessage"; import { showModal } from "../lib/showModal"; export class TestCommand implements ISlashCommand { - public command = "testing"; + public command = "bad-words"; public i18nParamsExample = "example params"; public i18nDescription = "command description"; public providesPreview = false; diff --git a/lib/getStats.ts b/lib/getStats.ts index e622eaf..5ee4756 100644 --- a/lib/getStats.ts +++ b/lib/getStats.ts @@ -31,9 +31,9 @@ export async function getStatsForRoom(rid: string, read: IRead) { rid ); - const [record] = await read + const records = await read .getPersistenceReader() .readByAssociation(roomAssociation); - return record; + return records; } From 41ebe73f507ab9bdc635f283b22162a291201ebe Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 24 Aug 2021 19:00:17 +0530 Subject: [PATCH 22/26] add app icon --- icon.png | Bin 2664 -> 33170 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icon.png b/icon.png index a7c0cc49a64bd06d47e4e0b507d26338e8d92582..32e8db6a2174f806a0af41e35863629437789b30 100644 GIT binary patch literal 33170 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 SfQ*0mt~t3o)?WV4 Date: Tue, 24 Aug 2021 19:03:18 +0530 Subject: [PATCH 23/26] add more settings for ban and notify message --- config/Settings.ts | 18 ++++++++++++++++++ i18n/en.json | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/config/Settings.ts b/config/Settings.ts index 6117803..d9510f8 100644 --- a/config/Settings.ts +++ b/config/Settings.ts @@ -12,6 +12,8 @@ export enum AppSetting { ListOfAllowededWords = "list_of_Allowed_words", IncludeChannels = "include_channels", ExcludeChannels = "exclude_channels", + SendWarningMessage = "send_warning_message", + BanUserAfterThisLimit = "ban_user_after_this_limit", } export const Settings: Array = [ @@ -40,6 +42,14 @@ export const Settings: Array = [ i18nLabel: "list_of_Allowed_words", required: false, }, + { + id: AppSetting.SendWarningMessage, + public: true, + type: SettingType.BOOLEAN, + packageValue: true, + i18nLabel: "send_warning_message", + required: false, + }, { id: AppSetting.ApplyFilterToDirectMessages, public: true, @@ -80,4 +90,12 @@ export const Settings: Array = [ i18nLabel: "exclude_channels", required: false, }, + { + id: AppSetting.BanUserAfterThisLimit, + public: true, + type: SettingType.NUMBER, + packageValue: 0, + i18nLabel: "ban_user_after_this_limit", + required: false, + }, ]; diff --git a/i18n/en.json b/i18n/en.json index db683c5..37dad58 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6,5 +6,7 @@ "include_channels": "Include these channels", "exclude_channels": "Exclude these channels", "apply_filter_to_direct_messages": "Apply Bad Words filtration to Direct Messages", - "apply_filter_to_livechat_messages": "Apply Bad Words filtration to LiveChat Messages" + "apply_filter_to_livechat_messages": "Apply Bad Words filtration to LiveChat Messages", + "send_warning_message": "Send Warning Message to users upon usage of Bad Words", + "ban_user_after_this_limit": "Ban user from sending messages after this Limit of Bad Words" } From 7161186701df1a6d0c193c7a1be14b49cbcbfd1b Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 24 Aug 2021 19:12:05 +0530 Subject: [PATCH 24/26] adding prevent message, banning users --- BadWordsApp.ts | 18 ++++++++++- app.json | 3 +- handlers/PreMessageSentHandler.ts | 22 +++++++++---- handlers/PreMessageSentPreventHandler.ts | 40 ++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 handlers/PreMessageSentPreventHandler.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index dbce872..3482250 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -13,6 +13,7 @@ import { App } from "@rocket.chat/apps-engine/definition/App"; import { IMessage, IPreMessageSentModify, + IPreMessageSentPrevent, IPreMessageUpdatedModify, } from "@rocket.chat/apps-engine/definition/messages"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; @@ -23,16 +24,31 @@ import { CheckPreMessageSentHandler } from "./handlers/CheckPreMessageSentHandle import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; +import { PreMessageSentPreventHandler } from "./handlers/PreMessageSentPreventHandler"; export class BadWordsApp extends App - implements IPreMessageSentModify, IPreMessageUpdatedModify + implements + IPreMessageSentModify, + IPreMessageUpdatedModify, + IPreMessageSentPrevent { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); } public blockedWords: Array; + async executePreMessageSentPrevent( + message: IMessage, + read: IRead + ): Promise { + const preMessageSentPreventHandler = new PreMessageSentPreventHandler( + message, + read + ); + return preMessageSentPreventHandler.run(); + } + async checkPreMessageSentModify( message: IMessage, read: IRead, diff --git a/app.json b/app.json index c7409d1..7d36ffa 100644 --- a/app.json +++ b/app.json @@ -14,6 +14,7 @@ "description": "A simple app to block badwords and apply moderation policies in channels", "implements": [ "IPreMessageSentModify", - "IPreMessageUpdatedModify" + "IPreMessageUpdatedModify", + "IPreMessageSentPrevent" ] } \ No newline at end of file diff --git a/handlers/PreMessageSentHandler.ts b/handlers/PreMessageSentHandler.ts index 239b92e..0e7da03 100644 --- a/handlers/PreMessageSentHandler.ts +++ b/handlers/PreMessageSentHandler.ts @@ -12,6 +12,8 @@ import { import { clean } from "../lib/Messages"; import { storeStatsForOffendingUsers } from "../lib/storeStats"; import { sendNotifyMessage } from "../lib/sendNotifyMessage"; +import { AppSetting } from "../config/Settings"; +import { getSettingValue } from "../lib/Settings"; export class PreMessageSentHandler { constructor( @@ -57,13 +59,21 @@ export class PreMessageSentHandler { filteredMessage.setText(cleanText); } - sendNotifyMessage( - room, - sender, - this.message.threadId, - this.read.getNotifier(), - `*${sender.username}*, Please watch your Language!` + const SendWarningMessage = await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.SendWarningMessage ); + + if (SendWarningMessage) { + sendNotifyMessage( + room, + sender, + this.message.threadId, + this.read.getNotifier(), + `*${sender.username}*, Please watch your Language!` + ); + } + storeStatsForOffendingUsers(room, sender, this.persist, this.read); return filteredMessage.getMessage(); diff --git a/handlers/PreMessageSentPreventHandler.ts b/handlers/PreMessageSentPreventHandler.ts new file mode 100644 index 0000000..ba657d4 --- /dev/null +++ b/handlers/PreMessageSentPreventHandler.ts @@ -0,0 +1,40 @@ +import { IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; +import { AppSetting } from "../config/Settings"; +import { getStatsForOffendingUser } from "../lib/getStats"; +import { sendNotifyMessage } from "../lib/sendNotifyMessage"; +import { getSettingValue } from "../lib/Settings"; + +export class PreMessageSentPreventHandler { + constructor(private message: IMessage, private read: IRead) {} + + public async run() { + const { room, sender } = this.message; + + const record: any = await getStatsForOffendingUser( + room.id, + sender.id, + this.read + ); + const badWordsCount = record.badWordsCount; + const badWordsLimit = await getSettingValue( + this.read.getEnvironmentReader(), + AppSetting.BanUserAfterThisLimit + ); + const banned = record.banned; + + if (badWordsLimit === 0 || badWordsLimit > badWordsCount) { + return false; + } + + sendNotifyMessage( + room, + sender, + this.message.threadId, + this.read.getNotifier(), + `*${sender.username}*, You are banned from this channel for using harsh language! You cannot send anymore messages in this channel until and unless you are unbanned by the admin or the owner.` + ); + + return true; + } +} From 60b7c0bffacdc0550c05bec266a5439185c49d84 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 24 Aug 2021 19:25:24 +0530 Subject: [PATCH 25/26] adding ban user and clear records --- BadWordsApp.ts | 25 ++++++- app.json | 3 +- handlers/BlockActionHandler.ts | 50 +++++++++++++ handlers/PreMessageSentPreventHandler.ts | 2 +- lib/showModal.ts | 34 ++++++--- lib/storeStats.ts | 93 ++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 handlers/BlockActionHandler.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index 3482250..c361648 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -6,6 +6,7 @@ import { IHttp, ILogger, IMessageBuilder, + IModify, IPersistence, IRead, } from "@rocket.chat/apps-engine/definition/accessors"; @@ -25,13 +26,20 @@ import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; import { PreMessageSentHandler } from "./handlers/PreMessageSentHandler"; import { getBlockedWords } from "./lib/Settings"; import { PreMessageSentPreventHandler } from "./handlers/PreMessageSentPreventHandler"; +import { + IUIKitInteractionHandler, + IUIKitResponse, + UIKitBlockInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { BlockActionHandler } from "./handlers/BlockActionHandler"; export class BadWordsApp extends App implements IPreMessageSentModify, IPreMessageUpdatedModify, - IPreMessageSentPrevent + IPreMessageSentPrevent, + IUIKitInteractionHandler { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { super(info, logger, accessors); @@ -117,6 +125,21 @@ export class BadWordsApp return preMessageUpdatedHandler.run(); } + async executeBlockActionHandler( + context: UIKitBlockInteractionContext, + read: IRead, + http: IHttp, + persist: IPersistence, + modify: IModify + ): Promise { + const blockActionHandler = new BlockActionHandler( + context, + read, + persist + ); + return blockActionHandler.run(); + } + public async onSettingUpdated( setting: ISetting, configurationModify: IConfigurationModify, diff --git a/app.json b/app.json index 7d36ffa..f6091d7 100644 --- a/app.json +++ b/app.json @@ -15,6 +15,7 @@ "implements": [ "IPreMessageSentModify", "IPreMessageUpdatedModify", - "IPreMessageSentPrevent" + "IPreMessageSentPrevent", + "IUIKitInteractionHandler" ] } \ No newline at end of file diff --git a/handlers/BlockActionHandler.ts b/handlers/BlockActionHandler.ts new file mode 100644 index 0000000..5bab04f --- /dev/null +++ b/handlers/BlockActionHandler.ts @@ -0,0 +1,50 @@ +import { + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { UIKitBlockInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { banUser, clearStats, unBanUser } from "../lib/storeStats"; + +export class BlockActionHandler { + constructor( + private context: UIKitBlockInteractionContext, + private read: IRead, + private persist: IPersistence + ) {} + + public async run() { + const data = this.context.getInteractionData(); + const { actionId, value } = data; + + const args = value?.split("."); + const rid = args && args[0]; + const uid = args && args[1]; + + switch (actionId) { + case "clear": { + clearStats(rid || "", uid || "", this.persist, this.read); + return { + success: true, + }; + } + + case "ban": { + banUser(rid || "", uid || "", this.persist, this.read); + return { + success: true, + }; + } + + case "unban": { + unBanUser(rid || "", uid || "", this.persist, this.read); + return { + success: true, + }; + } + } + + return { + success: false, + }; + } +} diff --git a/handlers/PreMessageSentPreventHandler.ts b/handlers/PreMessageSentPreventHandler.ts index ba657d4..5975355 100644 --- a/handlers/PreMessageSentPreventHandler.ts +++ b/handlers/PreMessageSentPreventHandler.ts @@ -23,7 +23,7 @@ export class PreMessageSentPreventHandler { ); const banned = record.banned; - if (badWordsLimit === 0 || badWordsLimit > badWordsCount) { + if (badWordsLimit === 0 || (badWordsLimit > badWordsCount && banned === false)) { return false; } diff --git a/lib/showModal.ts b/lib/showModal.ts index 297f3e9..465ffdb 100644 --- a/lib/showModal.ts +++ b/lib/showModal.ts @@ -1,8 +1,6 @@ -import { - IModify, - IRead, -} from "@rocket.chat/apps-engine/definition/accessors"; +import { IModify, IRead } from "@rocket.chat/apps-engine/definition/accessors"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { ButtonStyle } from "@rocket.chat/apps-engine/definition/uikit"; import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; import { getStatsForRoom } from "./getStats"; @@ -25,11 +23,27 @@ export async function showModal( }); } else { records.forEach((record) => { - block.addContextBlock({ + block.addActionsBlock({ elements: [ - block.newMarkdownTextObject( - `*${record.userName}* - *${record.badWordsCount}*` - ), + block.newButtonElement({ + text: block.newPlainTextObject( + `${record.userName} - ${record.badWordsCount}` + ), + }), + block.newButtonElement({ + actionId: "clear", + text: block.newPlainTextObject("Clear"), + value: `${record.rid}.${record.uid}`, + style: ButtonStyle.PRIMARY, + }), + block.newButtonElement({ + actionId: record.banned ? "unban" : "ban", + text: record.banned + ? block.newPlainTextObject("Un-Ban") + : block.newPlainTextObject("Ban"), + value: `${record.rid}.${record.uid}`, + style: ButtonStyle.DANGER, + }), ], }); }); @@ -37,7 +51,9 @@ export async function showModal( return { id: "122121", - title: block.newPlainTextObject(`Offending Users in ${room.displayName}`), + title: block.newPlainTextObject( + `Offending Users in ${room.displayName}` + ), close: block.newButtonElement({ text: block.newPlainTextObject("Cancel"), }), diff --git a/lib/storeStats.ts b/lib/storeStats.ts index 064fbdd..529e1b4 100644 --- a/lib/storeStats.ts +++ b/lib/storeStats.ts @@ -29,8 +29,11 @@ export async function storeStatsForOffendingUsers( if (!record) { const newRecord = { badWordsCount: 1, + banned: false, roomName: displayName || "", userName: username, + rid, + uid, }; await persist.createWithAssociations(newRecord, [ roomAssociation, @@ -41,6 +44,9 @@ export async function storeStatsForOffendingUsers( badWordsCount: record.badWordsCount + 1, roomName: displayName || "", userName: username, + rid, + uid, + banned: record.banned, }; await persist.updateByAssociations( [roomAssociation, userAssociation], @@ -48,3 +54,90 @@ export async function storeStatsForOffendingUsers( ); } } + +export async function clearStats( + rid: string, + uid: string, + persist: IPersistence, + read: IRead +) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + const userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + uid + ); + + const record: any = await getStatsForOffendingUser(rid, uid, read); + + if (record) { + const clearRecord = { + ...record, + badWordsCount: 0, + }; + await persist.updateByAssociations( + [roomAssociation, userAssociation], + clearRecord + ); + } +} + +export async function banUser( + rid: string, + uid: string, + persist: IPersistence, + read: IRead +) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + const userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + uid + ); + + const record: any = await getStatsForOffendingUser(rid, uid, read); + + if (record) { + const banRecord = { + ...record, + banned: true, + }; + await persist.updateByAssociations( + [roomAssociation, userAssociation], + banRecord + ); + } +} + +export async function unBanUser( + rid: string, + uid: string, + persist: IPersistence, + read: IRead +) { + const roomAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.ROOM, + rid + ); + const userAssociation = new RocketChatAssociationRecord( + RocketChatAssociationModel.USER, + uid + ); + + const record: any = await getStatsForOffendingUser(rid, uid, read); + + if (record) { + const banRecord = { + ...record, + banned: false, + }; + await persist.updateByAssociations( + [roomAssociation, userAssociation], + banRecord + ); + } +} From af8ab321557fcc7dce3c73c0eb701ae4fa80b0f3 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 24 Aug 2021 19:33:37 +0530 Subject: [PATCH 26/26] adding more functionalities to slash command --- BadWordsApp.ts | 4 +- commands/BadWordsCommand.ts | 173 ++++++++++++++++++++++++++++++++++++ commands/testCommand.ts | 58 ------------ 3 files changed, 175 insertions(+), 60 deletions(-) create mode 100644 commands/BadWordsCommand.ts delete mode 100644 commands/testCommand.ts diff --git a/BadWordsApp.ts b/BadWordsApp.ts index c361648..64f821d 100644 --- a/BadWordsApp.ts +++ b/BadWordsApp.ts @@ -19,7 +19,7 @@ import { } from "@rocket.chat/apps-engine/definition/messages"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; import { ISetting } from "@rocket.chat/apps-engine/definition/settings"; -import { TestCommand } from "./commands/testCommand"; +import { BadWordsCommand } from "./commands/BadWordsCommand"; import { Settings } from "./config/Settings"; import { CheckPreMessageSentHandler } from "./handlers/CheckPreMessageSentHandler"; import { OnSettingsUpdatedHandler } from "./handlers/OnSettingsUpdatedHandler"; @@ -166,7 +166,7 @@ export class BadWordsApp ) ); await configuration.slashCommands.provideSlashCommand( - new TestCommand() + new BadWordsCommand() ); this.blockedWords = await getBlockedWords( environmentRead, diff --git a/commands/BadWordsCommand.ts b/commands/BadWordsCommand.ts new file mode 100644 index 0000000..880d821 --- /dev/null +++ b/commands/BadWordsCommand.ts @@ -0,0 +1,173 @@ +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + ISlashCommand, + SlashCommandContext, +} from "@rocket.chat/apps-engine/definition/slashcommands"; +import { sendNotifyMessage } from "../lib/sendNotifyMessage"; +import { showModal } from "../lib/showModal"; +import { banUser, clearStats, unBanUser } from "../lib/storeStats"; + +export class BadWordsCommand implements ISlashCommand { + public command = "bad-words"; + public i18nParamsExample = "example params"; + public i18nDescription = "command description"; + public providesPreview = false; + + public async executor( + context: SlashCommandContext, + read: IRead, + modify: IModify, + http: IHttp, + persis: IPersistence + ): Promise { + const triggerId = context.getTriggerId(); + const room = context.getRoom(); + const sender = context.getSender(); + const threadId = context.getThreadId(); + + if (sender.roles.includes("admin") || sender.roles.includes("owner")) { + const args = context.getArguments(); + console.log("the args are = ", args); + switch (args[0]) { + case "stats": { + if (triggerId) { + const modal = await showModal(room, read, modify); + await modify + .getUiController() + .openModalView(modal, { triggerId }, sender); + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "Something went wrong!!" + ); + } + break; + } + + case "clear": { + if (args[1]) { + const username = args[1]; + const user = await read + .getUserReader() + .getByUsername(username); + if (user) { + clearStats(room.id, user.id, persis, read); + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + `*${user.username}'s* record has been cleared` + ); + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "User not found! Please enter a valid username." + ); + } + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "Please provide a username after clear keyword!" + ); + } + break; + } + + case "ban": { + if (args[1]) { + const username = args[1]; + const user = await read + .getUserReader() + .getByUsername(username); + if (user) { + banUser(room.id, user.id, persis, read); + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + `*${user.username}* has been banned from this room!` + ); + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "User not found! Please enter a valid username." + ); + } + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "Please provide a username after ban keyword!" + ); + } + break; + } + + case "unban": { + if (args[1]) { + const username = args[1]; + const user = await read + .getUserReader() + .getByUsername(username); + if (user) { + unBanUser(room.id, user.id, persis, read); + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + `*${user.username}* has been un-banned from this room!` + ); + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "User not found! Please enter a valid username." + ); + } + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "Please provide a username after unban keyword!" + ); + } + break; + } + } + } else { + sendNotifyMessage( + room, + sender, + threadId, + modify.getNotifier(), + "This slash command can be only used by admins and owners" + ); + } + } +} diff --git a/commands/testCommand.ts b/commands/testCommand.ts deleted file mode 100644 index 6745a38..0000000 --- a/commands/testCommand.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - IHttp, - IModify, - IPersistence, - IRead, -} from "@rocket.chat/apps-engine/definition/accessors"; -import { - ISlashCommand, - SlashCommandContext, -} from "@rocket.chat/apps-engine/definition/slashcommands"; -import { sendNotifyMessage } from "../lib/sendNotifyMessage"; -import { showModal } from "../lib/showModal"; - -export class TestCommand implements ISlashCommand { - public command = "bad-words"; - public i18nParamsExample = "example params"; - public i18nDescription = "command description"; - public providesPreview = false; - - public async executor( - context: SlashCommandContext, - read: IRead, - modify: IModify, - http: IHttp, - persis: IPersistence - ): Promise { - const triggerId = context.getTriggerId(); - const room = context.getRoom(); - const sender = context.getSender(); - const threadId = context.getThreadId(); - - if (sender.roles.includes("admin") || sender.roles.includes("owner")) { - if (triggerId) { - const modal = await showModal(room, read, modify); - await modify - .getUiController() - .openModalView(modal, { triggerId }, sender); - console.log("After UI MODAL"); - } else { - sendNotifyMessage( - room, - sender, - threadId, - modify.getNotifier(), - "Something went wrong!!" - ); - } - } else { - sendNotifyMessage( - room, - sender, - threadId, - modify.getNotifier(), - "This slash command can be only used by admins and owners" - ); - } - } -}