diff --git a/src/plugins/keywordNotify/README.md b/src/plugins/keywordNotify/README.md new file mode 100644 index 000000000..0bc61d611 --- /dev/null +++ b/src/plugins/keywordNotify/README.md @@ -0,0 +1,3 @@ +# KeywordNotify + +Allows for custom regex-defined keywords to notify the user exactly how a ping would. Adds a custom inbox for viewing keywords next to the mentions. \ No newline at end of file diff --git a/src/plugins/keywordNotify/index.tsx b/src/plugins/keywordNotify/index.tsx new file mode 100644 index 000000000..411930f37 --- /dev/null +++ b/src/plugins/keywordNotify/index.tsx @@ -0,0 +1,456 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated, camila314, and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import definePlugin, { OptionType } from "@utils/types"; +import { Button, ChannelStore, Forms, Select, Switch, SelectedChannelStore, TabBar, TextInput, UserStore, UserUtils, useState } from "@webpack/common"; +import { classNameFactory } from "@api/Styles"; +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { DeleteIcon } from "@components/Icons"; +import { Devs } from "@utils/constants"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { Flex } from "@components/Flex"; +import { Margins } from "@utils/margins"; +import { Message, User } from "discord-types/general/index.js"; +import { useForceUpdater } from "@utils/react"; + +type KeywordEntry = { regex: string, listIds: Array, listType: ListType, ignoreCase: boolean }; + +let keywordEntries: Array = []; +let currentUser: User; +let keywordLog: Array = []; + +const MenuHeader = findByCodeLazy(".sv)()?(0,"); +const Popout = findByCodeLazy(".loadingMore&&null=="); +const recentMentionsPopoutClass = findByPropsLazy("recentMentionsPopout"); +const createMessageRecord = findByCodeLazy("THREAD_CREATED?[]:(0,"); +const KEYWORD_ENTRIES_KEY = "KeywordNotify_keywordEntries"; +const KEYWORD_LOG_KEY = "KeywordNotify_log"; + +const cl = classNameFactory("vc-keywordnotify-"); + +async function addKeywordEntry(forceUpdate: () => void) { + keywordEntries.push({ regex: "", listIds: [], listType: ListType.BlackList, ignoreCase: false }); + await DataStore.set(KEYWORD_ENTRIES_KEY, keywordEntries); + forceUpdate(); +} + +async function removeKeywordEntry(idx: number, forceUpdate: () => void) { + keywordEntries.splice(idx, 1); + await DataStore.set(KEYWORD_ENTRIES_KEY, keywordEntries); + forceUpdate(); +} + +function safeMatchesRegex(str: string, regex: string, flags: string) { + try { + return str.match(new RegExp(regex, flags)); + } catch { + return false; + } +} + +enum ListType { + BlackList = "BlackList", + Whitelist = "Whitelist" +} + +function highlightKeywords(str: string, entries: Array) { + let regexes: Array; + try { + regexes = entries.map(e => new RegExp(e.regex, "g" + (e.ignoreCase ? "i" : ""))); + } catch (err) { + return [str]; + } + + const matches = regexes.map(r => str.match(r)).flat().filter(e => e != null); + if (matches.length == 0) { + return [str]; + } + + const idx = str.indexOf(matches[0]); + + return [ + {str.substring(0, idx)}, + {matches[0]}, + {str.substring(idx + matches[0].length)} + ]; +} + +function Collapsible({ title, children }) { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + {isOpen && children} +
+ ); +} + +function ListedIds({ listIds, setListIds }) { + const update = useForceUpdater(); + const [values] = useState(listIds); + + async function onChange(e: string, index: number) { + values[index] = e; + setListIds(values); + update(); + } + + const elements = values.map((currentValue: string, index: number) => { + return ( + +
+ onChange(e, index)} + /> +
+ +
+ ); + }); + + return ( + <> + {elements} + + ); +} + +function ListTypeSelector({ listType, setListType }) { + return ( +