├── .gitignore
├── README.md
├── manifest.json
├── style.scss
├── components
├── Reactors.jsx
└── Settings.jsx
├── LICENSE
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## who-reacted
2 | A plugin for [Powercord](https://powercord.dev/) that shows the avatars of the users who reacted to a message.
3 |
4 | 
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Who Reacted",
3 | "version": "1.1.0",
4 | "description": "Shows the avatars of the users who reacted to a message.",
5 | "author": "Jaime Filho (Marmota)",
6 | "license": "MIT"
7 | }
--------------------------------------------------------------------------------
/style.scss:
--------------------------------------------------------------------------------
1 | .powercord-who-reacted-reactors {
2 | margin-left: 6px;
3 |
4 | .more-reactors {
5 | background-color: var(--background-tertiary);
6 | color: var(--text-normal);
7 | font-weight: 500;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/components/Reactors.jsx:
--------------------------------------------------------------------------------
1 | const { React, Flux, getModule, getModuleByDisplayName } = require('powercord/webpack');
2 |
3 | const ReactionStore = getModule([ 'getReactions', '_dispatcher' ], false);
4 | const VoiceUserSummaryItem = getModuleByDisplayName('VoiceUserSummaryItem', false);
5 |
6 | const Reactors = ({ count, max, users }) => {
7 | function renderMoreUsers (text, className) {
8 | return (
9 |
10 | +{1 + count - max}
11 |
12 | );
13 | }
14 |
15 | return (
16 |
22 | );
23 | }
24 |
25 | module.exports = Flux.connectStores([ ReactionStore ], ({ message, emoji }) => ({
26 | users: Object.values(ReactionStore.getReactions(message.getChannelId(), message.id, emoji) ?? {})
27 | }))(Reactors);
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Jaime Filho
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/components/Settings.jsx:
--------------------------------------------------------------------------------
1 | const { React } = require('powercord/webpack');
2 | const { TextInput, SliderInput, SwitchItem } = require('powercord/components/settings');
3 |
4 | const Settings = ({ getSetting, updateSetting }) => {
5 | function getThresholdMarkerLabel (value) {
6 | if (value === 0) {
7 | return 'Off';
8 | }
9 |
10 | if (value % 1000 === 0) {
11 | return `${value / 1000}k`;
12 | }
13 |
14 | return value;
15 | }
16 |
17 | return (
18 |
19 | {
24 | if (isNaN(value) || value < 0 || value > 99) {
25 | return;
26 | }
27 |
28 | updateSetting('maxUsersShown', value);
29 | }}
30 | >
31 | Max users shown
32 |
33 | updateSetting('reactionThreshold', value)}
44 | >
45 | Reaction threshold
46 |
47 | updateSetting('userThreshold', value)}
59 | >
60 | User threshold
61 |
62 | updateSetting('useHighestUserCount', value)}
66 | >
67 | Use highest user count
68 |
69 |
70 | );
71 | };
72 |
73 | module.exports = Settings;
74 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { Plugin } = require('powercord/entities');
2 | const { React, getModule } = require('powercord/webpack');
3 | const { inject, uninject } = require('powercord/injector');
4 | const { findInTree, getReactInstance, waitFor } = require('powercord/util');
5 |
6 | const Settings = require('./components/Settings');
7 | const Reactors = require('./components/Reactors');
8 |
9 | module.exports = class WhoReacted extends Plugin {
10 | constructor () {
11 | super();
12 | this.selectors = {
13 | reaction: `.${getModule([ 'reactions', 'reaction' ], false).reaction}`
14 | };
15 | }
16 |
17 | async startPlugin () {
18 | await this.loadStylesheet('style.scss');
19 |
20 | powercord.api.settings.registerSettings('who-reacted', {
21 | category: this.entityID,
22 | label: 'Who Reacted',
23 | render: Settings
24 | });
25 |
26 | await this._patchReaction();
27 | }
28 |
29 | pluginWillUnload () {
30 | powercord.api.settings.unregisterSettings('who-reacted');
31 |
32 | uninject('who-reacted-reactors');
33 | this._forceUpdateAllReactions();
34 | }
35 |
36 | async _patchReaction () {
37 | const Reaction = await this._findReaction();
38 |
39 | const { settings } = this;
40 |
41 | function canShowReactors ({ reactions }) {
42 | const reactionThreshold = settings.get('reactionThreshold', 10);
43 | if (reactionThreshold !== 0 && reactions.length > reactionThreshold) {
44 | return false;
45 | }
46 |
47 | const userThreshold = settings.get('userThreshold', 100);
48 | if (userThreshold !== 0) {
49 | const userCount = settings.get('useHighestUserCount', true) ?
50 | Math.max(...reactions.map(reaction => reaction.count)) :
51 | reactions.reduce((total, reaction) => total + reaction.count, 0);
52 |
53 | if (userCount > userThreshold) {
54 | return false;
55 | }
56 | }
57 |
58 | return true;
59 | }
60 |
61 | inject('who-reacted-reactors', Reaction.prototype, 'render', function (args, result) {
62 | const { message, emoji, count } = this.props;
63 |
64 | if (canShowReactors(message)) {
65 | const renderTooltip = result.props.children;
66 | result.props.children = props => {
67 | const tooltip = renderTooltip(props);
68 | const popout = tooltip.props.children.props.children;
69 |
70 | const renderReactionInner = popout.props.children;
71 | popout.props.children = props => {
72 | const reactionInner = renderReactionInner(props);
73 |
74 | reactionInner.props.children.push(React.createElement(Reactors, {
75 | message,
76 | emoji,
77 | count,
78 | max: settings.get('maxUsersShown', 6)
79 | }));
80 |
81 | return reactionInner;
82 | };
83 |
84 | return tooltip;
85 | };
86 | }
87 |
88 | return result;
89 | });
90 |
91 | this._forceUpdateAllReactions();
92 | }
93 |
94 | async _findReaction () {
95 | return this._findReactionReactElement(await waitFor(this.selectors.reaction)).type;
96 | }
97 |
98 | // Thanks @Juby210
99 | _forceUpdateAllReactions () {
100 | for (const element of document.querySelectorAll(this.selectors.reaction)) {
101 | this._findReactionReactElement(element).stateNode.forceUpdate();
102 | }
103 | }
104 |
105 | _findReactionReactElement (node) {
106 | return findInTree(getReactInstance(node), r => r?.type?.displayName === 'Reaction', {
107 | walkable: [ 'return' ]
108 | });
109 | }
110 | };
111 |
--------------------------------------------------------------------------------