├── LICENSE.md ├── README.md ├── demo ├── 1.png ├── 2.png └── 3.png ├── index.js ├── manifest.json ├── ui └── Settings.jsx └── utils ├── addUser.js └── removeUser.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 RazerMoon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # User Notifs 2 | 3 | Shows a notification when a specified user sends a message 4 | 5 | ![1](demo/1.png) 6 | 7 | ![2](demo/2.png) 8 | 9 | ![3](demo/3.png) 10 | -------------------------------------------------------------------------------- /demo/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RazerMoon/userNotifs/b6b2123ceb2ddf343d0c2831a5ec6d44494703df/demo/1.png -------------------------------------------------------------------------------- /demo/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RazerMoon/userNotifs/b6b2123ceb2ddf343d0c2831a5ec6d44494703df/demo/2.png -------------------------------------------------------------------------------- /demo/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RazerMoon/userNotifs/b6b2123ceb2ddf343d0c2831a5ec6d44494703df/demo/3.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Plugin } = require('powercord/entities'); 2 | const { React, FluxDispatcher, getModule } = require('powercord/webpack'); 3 | const { inject, uninject } = require('powercord/injector'); 4 | const { findInReactTree } = require('powercord/util'); 5 | 6 | const Settings = require('./ui/Settings.jsx'); 7 | const addUser = require('./utils/addUser'); 8 | const removeUser = require('./utils/removeUser'); 9 | 10 | /** 11 | * Creates a notification when a specified user sends a message 12 | * @link https://github.com/RazerMoon/userNotifs 13 | * @license MIT 14 | * @extends Plugin 15 | */ 16 | module.exports = class UserNotifs extends Plugin { 17 | startPlugin () { 18 | powercord.api.settings.registerSettings(this.entityID, { 19 | category: this.entityID, 20 | label: 'User Notifs', 21 | render: Settings 22 | }); 23 | 24 | FluxDispatcher.subscribe('MESSAGE_CREATE', this.handleMessage.bind(this)); 25 | 26 | this.patchListener = this.patchListener.bind(this); 27 | 28 | document.addEventListener('mousedown', this.patchListener); 29 | } 30 | 31 | patchListener (e) { 32 | if (e.button === 2 && e.target?.tagName === 'IMG' && e.target.className.includes('avatar')) { 33 | setTimeout(() => { 34 | this.patchUserCM(); 35 | document.removeEventListener('mousedown', this.patchListener); 36 | }, 500); 37 | } 38 | } 39 | 40 | async patchUserCM () { 41 | const Menu = await getModule((m) => (m.__powercordOriginal_default || m.default)?.displayName === 'Menu'); 42 | const mod = await getModule((m) => (m.__powercordOriginal_default || m.default)?.displayName === 'GuildChannelUserContextMenu'); 43 | 44 | inject('usernotifs-usercm-patch', mod, 'default', ([ { user } ], res) => { 45 | if (!res) { 46 | return res; 47 | } 48 | 49 | const hasNotifyButton = findInReactTree(res.children, child => child.props && child.props.id === 'notify'); 50 | 51 | if (!hasNotifyButton) { 52 | const userOnList = this.settings.get('idlist', []).includes(user.id); 53 | 54 | const addUserButton = React.createElement(Menu.MenuItem, { 55 | id: 'notify', 56 | label: userOnList ? 'Stop Message Notifs' : 'Notify on Message', 57 | action: () => userOnList ? removeUser(user.id, this.settings) : addUser(user, this.settings) 58 | }); 59 | 60 | const devmodeItem = findInReactTree(res.props, child => child.props && child.props.id === 'devmode-copy-id'); 61 | const developerGroup = findInReactTree(res.props, child => child.props && child.props.children === devmodeItem); 62 | 63 | if (developerGroup) { 64 | if (!Array.isArray(developerGroup.props.children)) { 65 | developerGroup.props.children = [ developerGroup.props.children ]; 66 | } 67 | 68 | developerGroup.props.children.splice(developerGroup.props.children.length - 1, 0, addUserButton); 69 | } else { 70 | res.props.children.props.children.splice(res.props.children.props.children.length - 1, 0, [ React.createElement(Menu.MenuSeparator), React.createElement(Menu.MenuGroup, {}, addUserButton) ]); 71 | } 72 | } 73 | 74 | return res; 75 | }); 76 | } 77 | 78 | async handleMessage ({ message }) { 79 | // ! Small errors here cause discord to crash loop 80 | const idlist = this.settings.get('idlist', []); 81 | if (idlist.includes(message.author.id)) { 82 | const user = this.settings.get('details', []).find(item => item.id === message.author.id); 83 | 84 | const modules = await Promise.all([ getModule([ 'showNotification' ]), getModule([ 'getUserAvatarURL', 'getGuildIconURL' ]), getModule([ 'transitionTo' ]), getModule([ 'getChannel', 'getDMFromUserId' ]), getModule([ 'getGuild' ]) ]); 85 | 86 | if (!user) { 87 | console.log('[userNotifs] Something went wrong when fetching the user!'); 88 | return null; 89 | } 90 | 91 | const channel = modules[3].getChannel(message.channel_id); 92 | 93 | const getChannel = () => { 94 | if (channel.isDM()) { 95 | return 'DM'; 96 | } 97 | return `#${channel.name}, ${modules[4].getGuild(channel.getGuildId())}`; 98 | }; 99 | 100 | const getUsername = ({ username, discriminator }) => `${username}#${discriminator}`; 101 | 102 | if (!this.settings.get('dm', false) && channel.isDM()) { 103 | return null; 104 | } 105 | 106 | // ! Doesn't work with animated avatars 107 | modules[0].showNotification(modules[1].getUserAvatarURL(message.author, 'png'), `${getUsername(message.author)} (${getChannel()})`, message.content, { onClick: () => { 108 | // Yoinked from https://gist.github.com/jiangzhuo/793f6d120607bb71f30c45f4fa6ea00a 109 | modules[2].transitionTo(`/channels/${message.guild_id ? message.guild_id : '@me'}/${message.channel_id}/${message.id}`); 110 | } }, {}); 111 | } 112 | } 113 | 114 | pluginWillUnload () { 115 | powercord.api.settings.unregisterSettings(this.entityID); 116 | uninject('usernotifs-usercm-patch'); 117 | FluxDispatcher.unsubscribe('MESSAGE_CREATE', this.handleMessage); 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "User Notifs", 3 | "version": "0.9.6", 4 | "description": "Shows a notification when a specified user sends a message", 5 | "author": "RazerMoon", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /ui/Settings.jsx: -------------------------------------------------------------------------------- 1 | const { React } = require('powercord/webpack'); 2 | const { Button } = require('powercord/components'); 3 | const { TextInput, ButtonItem, Category, SwitchItem } = require('powercord/components/settings'); 4 | const addUser = require('../utils/addUser'); 5 | const removeUser = require('../utils/removeUser'); 6 | 7 | // eslint-disable-next-line no-warning-comments 8 | // TODO: Make the channel items more distinct/seperate from the actual settings 9 | // eslint-disable-next-line no-warning-comments 10 | // TODO: Add avatar preview 11 | module.exports = ({ getSetting, updateSetting, settings, toggleSetting }) => { 12 | const removeAll = () => { 13 | updateSetting('idlist', []); 14 | updateSetting('details', []); 15 | }; 16 | 17 | const [ userField, setUserField ] = React.useState(''); 18 | const [ idField, setIdField ] = React.useState(''); 19 | 20 | const [ opened, onChange ] = React.useState(true); 21 | 22 | return ( 23 |
24 | console.dir(settings)} 28 | > 29 | Log data 30 | 31 | removeAll()} 35 | > 36 | Reset 37 | 38 | toggleSetting('dm')} 42 | > 43 | Notify on DM 44 | 45 | 46 |
50 | 54 | Username 55 | 56 | 60 | ID 61 | 62 | 72 |
73 | {getSetting('details', []).map(({ id, username, discriminator }) => ( 74 | removeUser(id, { get: getSetting, 78 | set: updateSetting })} 79 | > 80 | {username}{discriminator ? `#${discriminator}` : ''} 81 | 82 | ))} 83 |
84 |
85 | ); 86 | }; 87 | -------------------------------------------------------------------------------- /utils/addUser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the value of a setting 3 | * @callback getSetting 4 | * @param {String} name Name of the setting 5 | * @param {*} defaultValue Default value for setting 6 | */ 7 | 8 | /** 9 | * Sets the value of a setting 10 | * @callback setSetting 11 | * @param {String} name Name of the setting 12 | * @param {*} newValue New value to set 13 | */ 14 | 15 | /** 16 | * Adds user to list. 17 | * @param {string} user Author object 18 | * @param {Object} settings Settings object for plugin with get and set methods 19 | * @param {getSetting} settings.get 20 | * @param {setSetting} settings.set 21 | * @returns 22 | */ 23 | module.exports = function addUser (user, settings = powercord.pluginManager.plugins.get('userNotifs').settings) { 24 | const list = settings.get('idlist', []); 25 | const details = settings.get('details', []); 26 | 27 | if (!list.includes(user.id)) { 28 | list.push(user.id); 29 | } 30 | 31 | if (!details.some(item => item.id === user.id)) { 32 | details.push(user); 33 | } 34 | 35 | // eslint-disable-next-line no-warning-comments 36 | // TODO: Don't update if nothing changed 37 | settings.set('idlist', list); 38 | settings.set('details', details); 39 | }; 40 | -------------------------------------------------------------------------------- /utils/removeUser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the value of a setting 3 | * @callback getSetting 4 | * @param {String} name Name of the setting 5 | * @param {*} defaultValue Default value for setting 6 | */ 7 | 8 | /** 9 | * Sets the value of a setting 10 | * @callback setSetting 11 | * @param {String} name Name of the setting 12 | * @param {*} newValue New value to set 13 | */ 14 | 15 | /** 16 | * Removes user from the list 17 | * @param {string} id ID of the user 18 | * @param {Object} settings Settings object for plugin with get and set methods 19 | * @param {getSetting} settings.get 20 | * @param {setSetting} settings.set 21 | * @returns 22 | */ 23 | module.exports = function removeUser (id, settings = powercord.pluginManager.plugins.get('userNotifs').settings) { 24 | const list = settings.get('idlist', []); 25 | const details = settings.get('details', []); 26 | 27 | if (!list || !details || list.length === 0 || details.length === 0) { 28 | return; 29 | } 30 | 31 | if (list.includes(id)) { 32 | settings.set( 33 | 'idlist', 34 | list.filter((item) => item !== id) 35 | ); 36 | } 37 | 38 | if (details.some((item) => item.id === id)) { 39 | settings.set( 40 | 'details', 41 | details.filter((item) => item.id !== id) 42 | ); 43 | } 44 | }; 45 | --------------------------------------------------------------------------------