%name%Will replace the name
\n\n
%msg%Will replace the message
\n
%codeblock%Will build a CodeBlock
\n
%bold%Makes the message Bold
\n
%italic%Makes the message Italic
\n
%underline%Underlines the message
\n
%code%Makes the message look like code
\n
%quote%Makes the message look like QuoteBlock
\n
%bold%Makes the message Bold
\n
%italic%Makes the message Italic
\n
%underline%Underlines the message
\n
%code%Makes the message look like code
\n
%timestamp%Adds a timestamp from message to Quotation.
\n
%msg-link%Adds the MessageLink from the message that you Quoted.
`)[0])
129 | return form;
130 | };
131 | parseMessage(name, msg, time, link) {
132 | this.loadSettings()
133 | var b = this.settings.replacem;
134 | var a = b.replace(/%codeblock%/g, `${'```'}`)
135 | .replace(/%msg%/g, msg)
136 | .replace(/%name%/g, name)
137 | .replace(/%timestamp%/g, time)
138 | .replace(/%bold%/g, `${'**'}`)
139 | .replace(/%italic%/g, `${'*'}`)
140 | .replace(/%code%/g, `${'`'}`)
141 | .replace(/%underline%/g, `${'__'}`)
142 | .replace(/%quote%/g, `${'> '}`)
143 | .replace(/%msg-link%/g, link);
144 | return a;
145 | }
146 | patchMessageToolBar() {
147 | Patcher.after(WebpackModules.getByIndex(WebpackModules.getIndex(e => e.default && e.default.displayName === 'MiniPopover')), "default", (_, args, react) => {
148 | const _this = args[0]['children'][args[0].children.length-1]['props'];
149 | const timeStamp = _this.message.timestamp._d.toString().split(" ");
150 | const time = `${timeStamp[2]} ${timeStamp[1]} ${timeStamp[3]}, ${timeStamp[4]}`;
151 | const msgLink = location.href + "/" + _this.message.id;
152 | react.props.children.unshift(
153 | React.createElement(Tooltip, {
154 | text: document.documentElement.lang == "de" ? "Zitieren" : "Quote",
155 | position: "top",
156 | color: "black",
157 | }, e => React.createElement("div", {
158 | onMouseEnter: e.onMouseEnter,
159 | onMouseLeave: e.onMouseLeave,
160 | className: "button-1ZiXG9",
161 | onClick: () => {
162 | Dispatcher.dispatchToLastSubscribed("INSERT_TEXT", {content: this.parseMessage(_this.message.author.username, _this.message.content, time, msgLink)+"\n"})
163 | },
164 | children: React.createElement("svg", {
165 | className: "icon-3Gkjwa",
166 | width: "26",
167 | height: "26",
168 | children: React.createElement("path", {
169 | fill: "currentColor",
170 | d: "M19.8401 5.39392C20.1229 4.73405 19.6389 4 18.921 4H17.1231C16.7417 4 16.3935 4.21695 16.2254 4.55933L13.3297 10.4581C13.195 10.7324 13.125 11.0339 13.125 11.3394V19C13.125 19.5523 13.5727 20 14.125 20H20C20.5523 20 21 19.5523 21 19V12.875C21 12.3227 20.5523 11.875 20 11.875H17.8208C17.4618 11.875 17.2198 11.508 17.3612 11.178L19.8401 5.39392ZM9.71511 5.39392C9.99791 4.73405 9.51388 4 8.79596 4H6.99809C6.61669 4 6.2685 4.21695 6.10042 4.55933L3.20466 10.4581C3.07001 10.7324 3 11.0339 3 11.3394V19C3 19.5523 3.44772 20 4 20H9.875C10.4273 20 10.875 19.5523 10.875 19V12.875C10.875 12.3227 10.4273 11.875 9.875 11.875H7.69577C7.33681 11.875 7.0948 11.508 7.2362 11.178L9.71511 5.39392Z"
171 | })
172 | })
173 | })
174 | )
175 | )
176 | })
177 | }
178 | patchQuoteFunction() {
179 | Patcher.instead(WebpackModules.getByProps("createQuotedText"), "createQuotedText", (_, props) => {
180 | const message = props[0];
181 | const timeStamp = message.timestamp._d.toString().split(" ");
182 | const time = `${timeStamp[2]} ${timeStamp[1]} ${timeStamp[3]}, ${timeStamp[4]}`;
183 | const msgLink = location.href + "/" + message.id;
184 | return this.parseMessage(message.author.username, message.content, time, msgLink)+"\n";
185 | })
186 | }
187 | onStart() {
188 | this.patchMessageToolBar();
189 | this.patchQuoteFunction();
190 | }
191 | onStop() {
192 | Patcher.unpatchAll();
193 | }
194 |
195 | }
196 |
197 | };
198 | return plugin(Plugin, Api);
199 | })(global.ZeresPluginLibrary.buildPlugin(config));
200 | })();
201 |
--------------------------------------------------------------------------------
/ DISCONTINUED/MessageLinkEmbed/MessageLinkEmbed.plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name MessageLinkEmbed
3 | * @displayName MessageLinkEmbed
4 | * @authorId 415849376598982656
5 | * @invite gvA2ree
6 | */
7 | /*@cc_on
8 | @if (@_jscript)
9 |
10 | // Offer to self-install for clueless users that try to run this directly.
11 | var shell = WScript.CreateObject("WScript.Shell");
12 | var fs = new ActiveXObject("Scripting.FileSystemObject");
13 | var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins");
14 | var pathSelf = WScript.ScriptFullName;
15 | // Put the user at ease by addressing them in the first person
16 | shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
17 | if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
18 | shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
19 | } else if (!fs.FolderExists(pathPlugins)) {
20 | shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
21 | } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
22 | fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
23 | // Show the user where to put plugins in the future
24 | shell.Exec("explorer " + pathPlugins);
25 | shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
26 | }
27 | WScript.Quit();
28 |
29 | @else@*/
30 |
31 | const MessageLinkEmbed = (() => {
32 | const config = {
33 | info: {
34 | name: "MessageLinkEmbed",
35 | authors: [
36 | {
37 | name: "Strencher",
38 | discord_id: "415849376598982656",
39 | github_username: "Strencher",
40 | twitter_username: "Strencher3"
41 | },
42 | {
43 | name: "Juby210",
44 | discord_id: "324622488644616195",
45 | github_username: "Juby210"
46 | }
47 | ],
48 | version: "0.0.2",
49 | description: "Make messagelinks look like any other link but with the message. Converted from a PC plugin -> BD plugin. full credits go to @Juby210",
50 | github: "https://github.com/Strencher/BetterDiscordStuff/blob/master/MessageLinkEmbed/MessageLinkEmbed.plugin.js",
51 | github_raw: "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/MessageLinkEmbed/MessageLinkEmbed.plugin.js"
52 | },
53 | changelog: [
54 | {
55 | title: "fixed",
56 | type: "fixed",
57 | items: ["Fixed clicking on the name reloads discord."]
58 | }
59 | ]
60 | };
61 |
62 | return !global.ZeresPluginLibrary ? class {
63 | constructor() { this._config = config; }
64 | getName() { return config.info.name; }
65 | getAuthor() { return config.info.authors.map(a => a.name).join(", "); }
66 | getDescription() { return config.info.description; }
67 | getVersion() { return config.info.version; }
68 | load() {
69 | BdApi.showConfirmationModal("Library plugin is needed",
70 | [`The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`], {
71 | confirmText: "Download",
72 | cancelText: "Cancel",
73 | onConfirm: () => {
74 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
75 | if (error) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
76 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
77 | });
78 | }
79 | });
80 | }
81 | start() { }
82 | stop() { }
83 | } : (([Plugin, Api]) => {
84 | const plugin = (Plugin, Api) => {
85 |
86 | const { WebpackModules, PluginUtilities, DiscordModules, ReactComponents, Patcher } = Api;
87 | const { React } = DiscordModules;
88 | const Message = WebpackModules.find(m=>m.default && m.default.displayName == "Message");
89 | const { embedMedia, embedAuthor, embedLink, grid, embedFull, embed, embedFooter, embedFooterText, embedImage, embedAuthorNameLink, embedFooterSeparator, embedMargin, embedAuthorIcon, embedAuthorName, embedDescription } = WebpackModules.getByProps("embedAuthor");
90 | const { embedWrapper } = WebpackModules.getByProps("embedWrapper");
91 | const { markup } = WebpackModules.getByProps("markup");
92 | const { anchor } = WebpackModules.getByProps("anchorUnderlineOnHover");
93 | const { parse } = WebpackModules.getByProps("parse", "parseTopic");
94 | const { MessageTimestamp } = WebpackModules.getByProps("MessageTimestamp");
95 | const { getMessage } = WebpackModules.getByProps("getMessage");
96 | const isVideo = attachment => !!attachment.url.match(/\.(?:mp4|mov|webm)$/)
97 | const Video = WebpackModules.getByDisplayName("LazyVideo");
98 | const Image = WebpackModules.getByDisplayName("LazyImageZoomable");
99 | const { stringify } = WebpackModules.getByProps('stringify', 'parse', 'encode');
100 | const User = WebpackModules.find(m => m.prototype && m.prototype.tag);
101 | const Timestamp = WebpackModules.find(m => m.prototype && m.prototype.toDate && m.prototype.month)
102 | const joinClass = (...e) => e.join(" ");
103 | const { jumpToMessage } = WebpackModules.getByProps("jumpToMessage")
104 | let lastFetch = 0
105 | let cache = {}
106 | async function getMsg(channelId, messageId) {
107 | let message = getMessage(channelId, messageId) || cache[messageId]
108 | if (!message) {
109 | if (lastFetch > Date.now() - 2500) await new Promise(r => setTimeout(r, 2500))
110 | const data = await DiscordModules.APIModule.get({
111 | url: DiscordModules.DiscordConstants.Endpoints.MESSAGES(channelId),
112 | query: stringify({
113 | limit: 1,
114 | around: messageId
115 | }),
116 | retries: 2
117 | })
118 | lastFetch = Date.now()
119 | message = data.body[0]
120 | if (!message) return
121 | message.author = new User(message.author)
122 | message.timestamp = new Timestamp(message.timestamp)
123 | }
124 | cache[messageId] = message
125 | return message;
126 | }
127 | const getMsgWithQueue = (() => {
128 | let pending = Promise.resolve()
129 |
130 | const run = async (channelId, messageId) => {
131 | try {
132 | await pending
133 | } finally {
134 | return getMsg(channelId, messageId)
135 | }
136 | }
137 |
138 | return (channelId, messageId) => (pending = run(channelId, messageId))
139 | })()
140 | class LinkEmbed extends React.Component {
141 | constructor(props) {
142 | super(props)
143 | this.state = null;
144 | }
145 |
146 | async componentDidMount() {
147 | if (!this.state) {
148 | const linkArray = this.props.msgLink.split('/')
149 | this.setState(await getMsgWithQueue(linkArray[5], linkArray[6]))
150 | }
151 | }
152 | render() {
153 | if(!this.state) return null;
154 | let attachment = null
155 | if (this.state.attachments[0] && this.state.attachments[0].width)
156 | attachment = this.state.attachments[0]
157 | if (this.state.embeds[0] && this.state.embeds[0].type == '')
158 | attachment = this.state.embeds[0].image || this.state.embeds[0].thumbnail
159 | if (attachment && !attachment.proxy_url) attachment.proxy_url = attachment.proxyURL
160 | if (attachment) {
161 | if (!attachment.proxy_url) attachment.proxy_url = attachment.proxyURL
162 | attachment = isVideo(attachment) ? React.createElement(Video, {
163 | className: embedWrapper,
164 | fileName: attachment.filename,
165 | fileSize: attachment.size,
166 | naturalHeight: attachment.height,
167 | naturalWidth: attachment.width,
168 | poster: attachment.proxy_url + '?format=jpeg',
169 | src: attachment.url,
170 | width: attachment.width > 370 ? 370 : attachment.width,
171 | playable: true
172 | }) : React.createElement(Image, {
173 | width: attachment.width,
174 | height: attachment.height,
175 | original: attachment.url,
176 | src: attachment.proxy_url,
177 | className: joinClass(embedMedia, embedImage, embedWrapper)
178 | })
179 | }
180 | return React.createElement("div", {
181 | className: joinClass(embedWrapper, embedFull, embed, markup, grid),
182 | style: {
183 | borderLeft: "4px solid "+this.state.colorString || "white"
184 | }
185 | }, React.createElement("div", {
186 | className: grid,
187 | children: [
188 | React.createElement("div", {
189 | className: joinClass(embedAuthor, embedMargin),
190 | children: [
191 | React.createElement("img", {
192 | className: embedAuthorIcon,
193 | src: this.state.author.avatarURL
194 | }),
195 | React.createElement("a", {
196 | className: joinClass(anchor, embedAuthorName, embedAuthorNameLink, embedLink),
197 | href: this.props.msgLink,
198 | onClick: e=> {
199 | e.preventDefault();
200 | jumpToMessage(this.state.channel_id, this.state.id)
201 | },
202 | children: this.state.author.tag
203 | })
204 | ]
205 | }),
206 | React.createElement("div", {
207 | className: joinClass(embedDescription, embedMargin),
208 | children: [parse(this.state.content), attachment]
209 | }),
210 | React.createElement("div", {
211 | className: joinClass(embedFooter, embedFooterText, embedMargin),
212 | children: [
213 | parse(`<#${this.state.channel_id}>`),
214 | React.createElement("span", {
215 | className: embedFooterSeparator,
216 | children: "•",
217 | style: {
218 | color: "var(--header-secondary)"
219 | }
220 | }),
221 | React.createElement(MessageTimestamp, {
222 | timestamp: this.state.timestamp
223 | })
224 | ]
225 | })
226 | ]
227 | }))
228 | }
229 | }
230 | return class MessageLinkEmbed extends Plugin {
231 | constructor() {
232 | super();
233 | }
234 |
235 | onStart() {
236 | Patcher.after(Message, "default", (_, e) => {
237 | const [{childrenMessageContent: { props: { message, content, onUpdate } }}] = e;
238 | if(!message || !content || !message.content) return;
239 | const match = message.content.match(/https?:\/\/((canary|ptb)\.)?discordapp\.com\/channels\/(\d{17,19}|@me)\/\d{17,19}\/\d{17,19}/g);
240 | if(!match || content.find(f=>f.props && f.props.name == "LinkEmbed")) return;
241 | match.forEach(lnk => {
242 | content.push("\n", React.createElement(LinkEmbed, { msgLink: lnk, name: "LinkEmbed" }));
243 | })
244 | onUpdate();
245 | })
246 | }
247 | onStop() {
248 | Patcher.unpatchAll();
249 | }
250 |
251 | }
252 |
253 | };
254 | return plugin(Plugin, Api);
255 | })(global.ZeresPluginLibrary.buildPlugin(config));
256 | })();
--------------------------------------------------------------------------------
/ DISCONTINUED/TwitchChatV2/TwitchChatV2.plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name TwitchChatV2
3 | * @displayName TwitchChatV2
4 | * @invite gvA2ree
5 | * @authorId 415849376598982656
6 | */
7 | /*@cc_on
8 | @if (@_jscript)
9 |
10 | // Offer to self-install for clueless users that try to run this directly.
11 | var shell = WScript.CreateObject("WScript.Shell");
12 | var fs = new ActiveXObject("Scripting.FileSystemObject");
13 | var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins");
14 | var pathSelf = WScript.ScriptFullName;
15 | // Put the user at ease by addressing them in the first person
16 | shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
17 | if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
18 | shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
19 | } else if (!fs.FolderExists(pathPlugins)) {
20 | shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
21 | } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
22 | fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
23 | // Show the user where to put plugins in the future
24 | shell.Exec("explorer " + pathPlugins);
25 | shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
26 | }
27 | WScript.Quit();
28 |
29 | @else@*/
30 |
31 | module.exports = (() => {
32 | const config = {
33 | info: {
34 | name: "TwitchChatV2",
35 | authors: [
36 | {
37 | name: "Strencher",
38 | discord_id: "415849376598982656",
39 | github_username: "Strencher",
40 | twitter_username: "Strencher3"
41 | }
42 | ],
43 | version: "0.0.6",
44 | description: "Adds an Twitch chat to discord.",
45 | github: "https://github.com/Strencher/BetterDiscordStuff/TwitchChatV2/TwitchChatV2.plugin.js",
46 | github_raw: "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/TwitchChatV2/TwitchChatV2.plugin.js"
47 | },
48 | changelog: [
49 | {
50 | title: "Yeah",
51 | type: "added",
52 | items: [
53 | "**Added an External Window for the chat. Activate it in the settings**",
54 | "**Added DarkMode**"
55 | ]
56 | }
57 | ],
58 | defaultConfig: [
59 | {
60 | type: "textbox",
61 | id: "channel",
62 | name: "Default Channel",
63 | note: "The Channel theyre auto filled in the Open Modal"
64 | },
65 | {
66 | type: "switch",
67 | id: "window",
68 | name: "External window",
69 | note: "Open the chat in an External window.",
70 | value: false
71 | },
72 | {
73 | type: "switch",
74 | id: "dark",
75 | name: "Dark Chat",
76 | note: "Enables DarkMode on both Chats.",
77 | value: true
78 | }
79 | ]
80 | };
81 |
82 | return !global.ZeresPluginLibrary ? class {
83 | constructor() { this._config = config; }
84 | getName() { return config.info.name; }
85 | getAuthor() { return config.info.authors.map(a => a.name).join(", "); }
86 | getDescription() { return config.info.description; }
87 | getVersion() { return config.info.version; }
88 | load() {
89 | BdApi.showConfirmationModal("Library plugin is needed",
90 | ["The library plugin needed for TwitchChatV2 is missing. Please click Download Now to install it."], {
91 | confirmText: "Download",
92 | cancelText: "Cancel",
93 | onConfirm: () => {
94 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
95 | if (error) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
96 | await new Promise(r => require("fs").writeFile(require("path").join(ContentManager.pluginsFolder, "0PluginLibrary.plugin.js"), body, r));
97 | });
98 | }
99 | });
100 | }
101 | start() { }
102 | stop() { }
103 | } : (([Plugin, Api]) => {
104 | const plugin = (Plugin, Api) => {
105 | const { WebpackModules, PluginUtilities, Utilities, DiscordModules, ReactComponents, Patcher } = Api;
106 | const { React, Textbox, DiscordConstants: { KeyboardKeys } } = DiscordModules;
107 | const Tooltip = WebpackModules.getByDisplayName("Tooltip");
108 | const { ModalRoot: Modal } = WebpackModules.getByProps("ModalRoot");
109 | class TwitchChatButton extends React.Component {
110 | render() {
111 | return React.createElement(Tooltip, {
112 | position: "bottom",
113 | color: "black",
114 | text: "Open Twitch Chat"
115 | }, e => React.createElement("div", {
116 | onClick: this.props.onClick,
117 | className: "iconWrapper-2OrFZ1 clickable-3rdHwn",
118 | onMouseEnter: e.onMouseEnter,
119 | onMouseLeave: e.onMouseLeave,
120 | style: {
121 | backgroundColor: "transparent",
122 | borderColor: "transparent"
123 | },
124 | children: React.createElement("img", {
125 | className: "icon-22AiRD",
126 | src: "https://image.flaticon.com/icons/svg/733/733577.svg",
127 | width: "18",
128 | height: "18"
129 |
130 | })
131 | }))
132 | }
133 | }
134 | return class TwitchChatV2 extends Plugin {
135 | constructor() {
136 | super();
137 | }
138 | saveSettings() {
139 | PluginUtilities.saveSettings("TwitchChatV2", this.settings);
140 | }
141 | getSettingsPanel() {
142 | return this.buildSettingsPanel().getElement()
143 | }
144 | openChat(val) {
145 | WebpackModules.getByProps("openModal").openModal(prop => {
146 | return React.createElement(Modal, {
147 | onKeyDown: (e) => {
148 | if (e.keyCode == KeyboardKeys.ESCAPE) prop.onClose();
149 | },
150 | transitionState: prop.transitionState,
151 | children: [React.createElement("iframe", {
152 | src: `https://www.twitch.tv/embed/${val ? val : this.settings.channel}/chat?parent=twitch.tv${this.settings.dark ? "&darkpopout": ""}`,
153 | width: "400",
154 | height: "420",
155 | style: {
156 | margin: "20px 20px 60px",
157 | },
158 | }), React.createElement("button", {
159 | className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMedium-1AC_Sl grow-q77ONN",
160 | onClick: () => prop.onClose(),
161 | children: React.createElement("div", {
162 | className: "contents-18-Yxp"
163 | }, "Close"),
164 | style: {
165 | margin: "10px",
166 | width: "10px",
167 | position: 'absolute',
168 | right: "0",
169 | bottom: '0'
170 | }
171 | })]
172 | })
173 | });
174 | }
175 | async onStart() {
176 | const TitleBar = await ReactComponents.getComponentByName('HeaderBarContainer', `.title-3qD0b-`);
177 | Patcher.after(TitleBar.component.prototype, "render", (e, _, ret) => {
178 | const title = Utilities.getNestedProp(ret, 'props.toolbar.props.children');
179 | if (!title || !Array.isArray(title)) return;
180 | title.unshift(
181 | React.createElement(TwitchChatButton, {
182 | onClick: () => {
183 | WebpackModules.getByProps("openModal").openModal(props => {
184 | let tmpVal = this.settings.channel, ref;
185 | return React.createElement(Modal, {
186 | transitionState: props.transitionState,
187 | children: [
188 | React.createElement("h1", {
189 | className: "h2-2gWE-o title-3sZWYQ da-h2 da-title defaultColor-1_ajX0 da-defaultColor title-18-Ds0 marginBottom20-32qID7 marginTop8-1DLZ1n da-title da-marginBottom20 da-marginTop8",
190 |
191 | }, "Select Channel"),
192 | React.createElement(Textbox, {
193 | value: tmpVal,
194 | ref: e => (ref = e),
195 | style: {
196 | margin: "10px",
197 | maxWidth: "420px"
198 | },
199 | onChange: e => {
200 | ref.props.value = tmpVal = e;
201 | ref.forceUpdate();
202 | },
203 | onKeyDown: e => {
204 | if (e.keyCode == KeyboardKeys.ENTER && tmpVal) {
205 | this.settings.channel = tmpVal;
206 | this.saveSettings()
207 | this.openChat(tmpVal);
208 | props.onClose();
209 | }
210 | },
211 | placeholder: "Type the ChannelName, confirm with Enter or Click Open",
212 | autoFocus: true
213 | }),
214 | React.createElement("div", {
215 | children: [
216 | React.createElement("button", {
217 | className: "button-38aScr lookLink-9FtZy- colorPrimary-3b3xI6 sizeMedium-1AC_Sl grow-q77ONN",
218 | onClick: () => props.onClose(),
219 | children: React.createElement("div", {
220 | className: "contents-18-Yxp"
221 | }, "Close"),
222 | style: {
223 | margin: "10px",
224 | width: "10px",
225 | position: 'absolute',
226 | right: "105px",
227 | bottom: '0'
228 | }
229 | }),
230 | React.createElement("button", {
231 | className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMedium-1AC_Sl grow-q77ONN",
232 | onClick: () => {
233 | this.settings.channel = tmpVal;
234 | this.saveSettings()
235 | this.openChat(tmpVal);
236 | props.onClose()
237 | },
238 | children: React.createElement("div", {
239 | className: "contents-18-Yxp"
240 | }, "Open"),
241 | style: {
242 | margin: "10px",
243 | width: "10px",
244 | position: 'absolute',
245 | right: "0",
246 | bottom: '0'
247 | }
248 | })
249 | ],
250 | style: {
251 | height: '50px'
252 | }
253 | })
254 | ]
255 | })
256 | });
257 | }
258 | })
259 | )
260 | })
261 | }
262 | onStop() {
263 | Patcher.unpatchAll();
264 | }
265 | }
266 |
267 | };
268 | return plugin(Plugin, Api);
269 | })(global.ZeresPluginLibrary.buildPlugin(config));
270 | })();
271 |
--------------------------------------------------------------------------------
/InvisibleTyping/InvisibleTyping.plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name InvisibleTyping
3 | * @version 1.4.4
4 | * @author Strencher
5 | * @authorId 415849376598982656
6 | * @description Enhanced version of silent typing.
7 | * @source https://github.com/Strencher/BetterDiscordStuff/blob/master/InvisibleTyping/InvisibleTyping.plugin.js
8 | * @invite gvA2ree
9 | * @changelogDate 2025-08-02
10 | */
11 |
12 | 'use strict';
13 |
14 | /* react */
15 | const React = BdApi.React;
16 |
17 | /* @manifest */
18 | var manifest = {
19 | "name": "InvisibleTyping",
20 | "version": "1.4.4",
21 | "author": "Strencher",
22 | "authorId": "415849376598982656",
23 | "description": "Enhanced version of silent typing.",
24 | "source": "https://github.com/Strencher/BetterDiscordStuff/blob/master/InvisibleTyping/InvisibleTyping.plugin.js",
25 | "invite": "gvA2ree",
26 | "changelog": [{
27 | "title": "It works again!",
28 | "type": "fixed",
29 | "items": [
30 | "Fixed for the latest Discord update"
31 | ]
32 | }],
33 | "changelogDate": "2025-08-02"
34 | };
35 |
36 | /* @api */
37 | const {
38 | Components,
39 | ContextMenu,
40 | Data,
41 | DOM,
42 | Logger,
43 | Net,
44 | Patcher,
45 | Plugins,
46 | ReactUtils,
47 | Themes,
48 | UI,
49 | Utils,
50 | Webpack
51 | } = new BdApi(manifest.name);
52 |
53 | /* @styles */
54 |
55 | var Styles = {
56 | sheets: [],
57 | _element: null,
58 | load() {
59 | DOM.addStyle(this.sheets.join("\n"));
60 | },
61 | unload() {
62 | DOM.removeStyle();
63 | }
64 | };
65 |
66 | /* ../common/Changelog/style.scss */
67 | Styles.sheets.push("/* ../common/Changelog/style.scss */", `.Changelog-Title-Wrapper {
68 | font-size: 20px;
69 | font-weight: 600;
70 | font-family: var(--font-display);
71 | color: var(--header-primary);
72 | line-height: 1.2;
73 | }
74 | .Changelog-Title-Wrapper div {
75 | font-size: 12px;
76 | font-weight: 400;
77 | font-family: var(--font-primary);
78 | color: var(--primary-300);
79 | line-height: 1.3333333333;
80 | }
81 |
82 | .Changelog-Banner {
83 | width: 405px;
84 | border-radius: 8px;
85 | margin-bottom: 20px;
86 | }
87 |
88 | .Changelog-Item {
89 | color: #c4c9ce;
90 | }
91 | .Changelog-Item .Changelog-Header {
92 | display: flex;
93 | text-transform: uppercase;
94 | font-weight: 700;
95 | align-items: center;
96 | margin-bottom: 10px;
97 | }
98 | .Changelog-Item .Changelog-Header.added {
99 | color: #45BA6A;
100 | }
101 | .Changelog-Item .Changelog-Header.changed {
102 | color: #F0B232;
103 | }
104 | .Changelog-Item .Changelog-Header.fixed {
105 | color: #EC4245;
106 | }
107 | .Changelog-Item .Changelog-Header.improved {
108 | color: #5865F2;
109 | }
110 | .Changelog-Item .Changelog-Header::after {
111 | content: "";
112 | flex-grow: 1;
113 | height: 1px;
114 | margin-left: 7px;
115 | background: currentColor;
116 | }
117 | .Changelog-Item span {
118 | display: list-item;
119 | list-style: inside;
120 | margin-left: 5px;
121 | }
122 | .Changelog-Item span::marker {
123 | color: var(--background-accent);
124 | }`);
125 |
126 | /* ../common/Changelog/index.tsx */
127 | function showChangelog(manifest) {
128 | if (Data.load("lastVersion") === manifest.version) return;
129 | const i18n = Webpack.getByKeys("getLocale");
130 | const formatter = new Intl.DateTimeFormat(i18n.getLocale(), {
131 | month: "long",
132 | day: "numeric",
133 | year: "numeric"
134 | });
135 | const title = React.createElement("div", {
136 | className: "Changelog-Title-Wrapper"
137 | }, React.createElement("h1", null, "What's New - ", manifest.name), React.createElement("div", null, formatter.format(new Date(manifest.changelogDate)), " - v", manifest.version));
138 | const items = manifest.changelog.map((item) => React.createElement("div", {
139 | className: "Changelog-Item"
140 | }, React.createElement("h4", {
141 | className: `Changelog-Header ${item.type}`
142 | }, item.title), item.items.map((item2) => React.createElement("span", null, item2))));
143 | "changelogImage" in manifest && items.unshift(
144 | React.createElement("img", {
145 | className: "Changelog-Banner",
146 | src: manifest.changelogImage
147 | })
148 | );
149 | UI.alert(title, items);
150 | Data.save("lastVersion", manifest.version);
151 | }
152 |
153 | /* components/typingButton.scss */
154 | Styles.sheets.push("/* components/typingButton.scss */", `.invisibleTypingButton svg {
155 | color: var(--interactive-normal);
156 | overflow: visible;
157 | }
158 |
159 | .invisibleTypingButton .disabledStrokeThrough {
160 | position: absolute;
161 | transform: translateX(-15px) translateY(530px) rotate(-45deg);
162 | }
163 |
164 | .invisibleTypingButton {
165 | background: transparent;
166 | }
167 | .invisibleTypingButton:hover:not(.disabled) svg {
168 | color: var(--interactive-hover);
169 | }
170 |
171 | .invisibleTypingTooltip {
172 | display: inline-flex;
173 | }`);
174 | var styles = {
175 | "invisibleTypingButton": "invisibleTypingButton",
176 | "disabledStrokeThrough": "disabledStrokeThrough"
177 | };
178 |
179 | /* components/icons/keyboard.tsx */
180 | function Keyboard({
181 | disabled,
182 | ...props
183 | }) {
184 | return React.createElement("svg", {
185 | ...props,
186 | width: "25",
187 | height: "25",
188 | viewBox: "0 0 576 512"
189 | }, React.createElement("path", {
190 | fill: "currentColor",
191 | d: "M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z"
192 | }), disabled ? React.createElement("rect", {
193 | className: styles.disabledStrokeThrough,
194 | x: "10",
195 | y: "10",
196 | width: "600pt",
197 | height: "70px",
198 | fill: "#f04747"
199 | }) : null);
200 | }
201 |
202 | /* modules/shared.js */
203 | const Dispatcher = Webpack.getByKeys("_dispatch");
204 | const Flux = Webpack.getByKeys("Store");
205 | const TypingModule = Webpack.getByKeys("startTyping");
206 | const useStateFromStores = Webpack.getByStrings("useStateFromStores", {
207 | searchExports: true
208 | });
209 | const buildClassName = (...args) => {
210 | return args.reduce((classNames, arg) => {
211 | if (!arg) return classNames;
212 | if (typeof arg === "string" || typeof arg === "number") {
213 | classNames.push(arg);
214 | } else if (Array.isArray(arg)) {
215 | const nestedClassNames = buildClassName(...arg);
216 | if (nestedClassNames) classNames.push(nestedClassNames);
217 | } else if (typeof arg === "object") {
218 | Object.keys(arg).forEach((key) => {
219 | if (arg[key]) classNames.push(key);
220 | });
221 | }
222 | return classNames;
223 | }, []).join(" ");
224 | };
225 |
226 | /* modules/settings.js */
227 | const Settings = new class Settings2 extends Flux.Store {
228 | constructor() {
229 | super(Dispatcher, {});
230 | }
231 | _settings = Data.load("settings") ?? {};
232 | get(key, def) {
233 | return this._settings[key] ?? def;
234 | }
235 | set(key, value) {
236 | this._settings[key] = value;
237 | Data.save("settings", this._settings);
238 | this.emitChange();
239 | }
240 | }();
241 |
242 | /* components/typingButton.tsx */
243 | const ChatButton = Webpack.getBySource("CHAT_INPUT_BUTTON_NOTIFICATION")?.Z;
244 | const removeItem = function(array, item) {
245 | while (array.includes(item)) {
246 | array.splice(array.indexOf(item), 1);
247 | }
248 | return array;
249 | };
250 |
251 | function InvisibleTypingContextMenu() {
252 | const enabled = useStateFromStores([Settings], () => Settings.get("autoEnable", true));
253 | return React.createElement(
254 | ContextMenu.Menu, {
255 | navId: "invisible-typing-context-menu",
256 | onClose: ContextMenu.close
257 | },
258 | React.createElement(
259 | ContextMenu.Item, {
260 | id: "globally-disable-or-enable-typing",
261 | label: enabled ? "Disable Globally" : "Enable Globally",
262 | action: () => {
263 | Settings.set("autoEnable", !enabled);
264 | }
265 | }
266 | ),
267 | React.createElement(
268 | ContextMenu.Item, {
269 | color: "danger",
270 | label: "Reset Config",
271 | disabled: !Settings.get("exclude", []).length,
272 | id: "reset-config",
273 | action: () => {
274 | Settings.set("exclude", []);
275 | UI.showToast("Successfully reset config for all channels.", {
276 | type: "success"
277 | });
278 | }
279 | }
280 | )
281 | );
282 | }
283 |
284 | function InvisibleTypingButton({
285 | channel,
286 | isEmpty
287 | }) {
288 | const enabled = useStateFromStores([Settings], InvisibleTypingButton.getState.bind(this, channel.id));
289 | const handleClick = React.useCallback(() => {
290 | const excludeList = [...Settings.get("exclude", [])];
291 | if (excludeList.includes(channel.id)) {
292 | removeItem(excludeList, channel.id);
293 | TypingModule.stopTyping(channel.id);
294 | } else {
295 | excludeList.push(channel.id);
296 | if (!isEmpty) TypingModule.startTyping(channel.id);
297 | }
298 | Settings.set("exclude", excludeList);
299 | }, [enabled]);
300 | const handleContextMenu = React.useCallback((event) => {
301 | ContextMenu.open(event, () => {
302 | return React.createElement(InvisibleTypingContextMenu, null);
303 | });
304 | }, [enabled]);
305 | return React.createElement(Components.Tooltip, {
306 | text: enabled ? "Typing Enabled" : "Typing Disabled"
307 | }, (props) => React.createElement(
308 | "div", {
309 | ...props,
310 | onClick: handleClick,
311 | onContextMenu: handleContextMenu,
312 | style: {
313 | padding: "5px"
314 | }
315 | },
316 | React.createElement(
317 | ChatButton, {
318 | className: buildClassName(
319 | styles.invisibleTypingButton, {
320 | enabled,
321 | disabled: !enabled
322 | }
323 | )
324 | },
325 | React.createElement(Keyboard, {
326 | disabled: !enabled
327 | })
328 | )
329 | ));
330 | }
331 | InvisibleTypingButton.getState = function(channelId) {
332 | const isGlobal = Settings.get("autoEnable", true);
333 | const isExcluded = Settings.get("exclude", []).includes(channelId);
334 | if (isGlobal && isExcluded) return false;
335 | if (isExcluded && !isGlobal) return true;
336 | return isGlobal;
337 | };
338 |
339 | /* components/settings.json */
340 | var SettingsItems = [{
341 | type: "switch",
342 | name: "Automatically enable",
343 | note: "Automatically enables the typing indicator for each channel that isn't manually disabled",
344 | id: "autoEnable",
345 | value: true
346 | }];
347 |
348 | /* components/settings.jsx */
349 | const {
350 | SettingItem,
351 | SwitchInput
352 | } = Components;
353 |
354 | function SwitchItem(props) {
355 | const value = useStateFromStores([Settings], () => Settings.get(props.id, props.value));
356 | return React.createElement(
357 | SettingItem, {
358 | ...props,
359 | inline: true
360 | },
361 | React.createElement(
362 | SwitchInput, {
363 | value,
364 | onChange: (v) => {
365 | Settings.set(props.id, v);
366 | }
367 | }
368 | )
369 | );
370 | }
371 |
372 | function renderItems(items) {
373 | return items.map((item) => {
374 | switch (item.type) {
375 | case "switch":
376 | return React.createElement(SwitchItem, {
377 | ...item
378 | });
379 | default:
380 | return null;
381 | }
382 | });
383 | }
384 |
385 | function SettingsPanel() {
386 | return React.createElement("div", null, renderItems(SettingsItems));
387 | }
388 |
389 | /* index.tsx */
390 | class InvisibleTyping {
391 | start() {
392 | Styles.load();
393 | showChangelog(manifest);
394 | this.patchTyping();
395 | this.patchChannelTextArea();
396 | }
397 | stop() {
398 | Styles.unload();
399 | Patcher.unpatchAll();
400 | }
401 | getState(channelId) {
402 | return InvisibleTypingButton.getState(channelId);
403 | }
404 | setState(channelId, value) {
405 | const excludeList = [...Settings.get("exclude", [])];
406 | if (value) {
407 | if (!excludeList.includes(channelId))
408 | excludeList.push(channelId);
409 | } else {
410 | excludeList.splice(excludeList.indexOf(channelId), 1);
411 | TypingModule.stopTyping(channelId);
412 | }
413 | Settings.set("exclude", excludeList);
414 | }
415 | patchTyping() {
416 | Patcher.instead(TypingModule, "startTyping", (_, [channelId], originalMethod) => {
417 | const globalTypingEnabled = Settings.get("autoEnable", true);
418 | const excludeList = Settings.get("exclude", []);
419 | const shouldType = globalTypingEnabled ? !excludeList.includes(channelId) : excludeList.includes(channelId);
420 | if (!shouldType) return;
421 | originalMethod(channelId);
422 | });
423 | }
424 | patchChannelTextArea() {
425 | const ChatButtonsGroup = Webpack.getBySource("type", "showAllButtons", "paymentsBlocked")?.Z;
426 | Patcher.after(ChatButtonsGroup, "type", (_, args, res) => {
427 | if (args.length == 2 && !args[0].disabled && args[0].type.analyticsName == "normal" && res.props.children && Array.isArray(res.props.children)) {
428 | res.props.children.unshift(React.createElement(InvisibleTypingButton, {
429 | channel: args[0].channel,
430 | isEmpty: !Boolean(args[0].textValue)
431 | }));
432 | }
433 | });
434 | }
435 | getSettingsPanel() {
436 | return React.createElement(SettingsPanel, null);
437 | }
438 | }
439 |
440 | module.exports = InvisibleTyping;
--------------------------------------------------------------------------------
/ShowAllActivities/ShowAllActivities.plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @name ShowAllActivities
3 | * @version 1.1.2
4 | * @description See every status a user has enabled. Original made by Juby210#0577.
5 | * @github https://github.com/Strencher/BetterDiscordStuff/tree/master/ShowAllActivities
6 | * @github_raw https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/ShowAllActivities/ShowAllActivities.plugin.js
7 | * @invite gvA2ree
8 | * @changelogImage https://cdn.discordapp.com/attachments/672786846018961418/1053059354552696932/20th-century-fox-intro.png
9 | * @changelogDate 2022-12-15T23:00:00.000Z
10 | * @author Strencher, Juby210
11 | */
12 |
13 | 'use strict';
14 |
15 | /* @module @manifest */
16 | const config = {
17 | "name": "ShowAllActivities",
18 | "version": "1.1.2",
19 | "authors": [{
20 | "name": "Strencher",
21 | "discord_id": "415849376598982656",
22 | "github_username": "Strencher",
23 | "twitter_username": "Strencher3"
24 | },
25 | {
26 | "name": "Juby210",
27 | "discord_id": "324622488644616195",
28 | "github_username": "Juby210"
29 | }
30 |
31 | ],
32 | "description": "See every status a user has enabled. Original made by Juby210#0577.",
33 | "github": "https://github.com/Strencher/BetterDiscordStuff/tree/master/ShowAllActivities",
34 | "github_raw": "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/ShowAllActivities/ShowAllActivities.plugin.js",
35 | "invite": "gvA2ree",
36 | "changelogImage": "https://cdn.discordapp.com/attachments/672786846018961418/1053059354552696932/20th-century-fox-intro.png",
37 | "changelogDate": "2022-12-15T23:00:00.000Z",
38 | "changelog": [{
39 | "title": "Fixes",
40 | "type": "fixed",
41 | "items": [
42 | "After a very, very long time, this plugin has returned. If you notice any bugs, please file an issue on my github or create a forum post in my server about it!"
43 | ]
44 | }]
45 | };
46 |
47 | /*@end */
48 |
49 | /* @module @api */
50 | const {
51 | Net,
52 | Data,
53 | Patcher,
54 | ReactUtils,
55 | Utils,
56 | Webpack: Webpack$2,
57 | UI,
58 | ContextMenu,
59 | DOM
60 | } = new BdApi(config.name);
61 | /*@end */
62 |
63 | var Api = /*#__PURE__*/ Object.freeze({
64 | __proto__: null,
65 | ContextMenu: ContextMenu,
66 | DOM: DOM,
67 | Data: Data,
68 | Net: Net,
69 | Patcher: Patcher,
70 | ReactUtils: ReactUtils,
71 | UI: UI,
72 | Utils: Utils,
73 | Webpack: Webpack$2
74 | });
75 |
76 | /* @module webpack.js */
77 | const {
78 | Webpack,
79 | Webpack: {
80 | Filters
81 | }
82 | } = Api;
83 | const getByProps = (...props) => {
84 | return Webpack.getModule(Filters.byProps(...props));
85 | };
86 | const getBulk = (...queries) => {
87 | return Webpack.getBulk.apply(null, queries.map((q) => typeof q === "function" ? {
88 | filter: q
89 | } : q));
90 | };
91 | const getByPrototypeFields = (...fields) => {
92 | return Webpack.getModule(Filters.byPrototypeFields(...fields));
93 | };
94 | const getStore = (name) => {
95 | return Webpack.getModule((m) => m?._dispatchToken && m.getName?.() === name);
96 | };
97 | const getMangled = function*(filter, target = null) {
98 | yield target = getModule((m) => Object.values(m).some(filter), {
99 | searchExports: false
100 | });
101 | yield target && Object.keys(target).find((k) => filter(target[k]));
102 | };
103 | const getModule = Webpack.getModule;
104 | var Webpack$1 = {
105 | ...Webpack,
106 | getByPrototypeFields,
107 | getMangled,
108 | getByProps,
109 | getStore,
110 | getBulk
111 | };
112 |
113 | /*@end */
114 |
115 | /* @module @styles */
116 |
117 | var Styles = {
118 | sheets: [],
119 | _element: null,
120 | load() {
121 | DOM.addStyle(this.sheets.join("\n"));
122 | },
123 | unload() {
124 | DOM.removeStyle();
125 | }
126 | };
127 | /*@end */
128 |
129 | /* @module react */
130 | var React = BdApi.React;
131 | /*@end */
132 |
133 | /* @module settings.js */
134 | const Settings = {
135 | _listeners: new Set(),
136 | _settings: Data.load("settings") ?? {},
137 | addReactChangeListener(listener) {
138 | Settings._listeners.add(listener);
139 | },
140 | removeReactChangeListener(listener) {
141 | Settings._listeners.delete(listener);
142 | },
143 | get(key, def) {
144 | return Settings._settings[key] ?? def;
145 | },
146 | set(key, value) {
147 | Settings._settings[key] = value;
148 | Data.save("settings", Settings._settings);
149 | }
150 | };
151 |
152 | /*@end */
153 |
154 | /* @module settings.jsx */
155 | const SwitchItemComponent = Webpack$1.getModule((m) => typeof m === "function" && m.toString().includes("tooltipNote"), {
156 | searchExports: true
157 | });
158 | const SwitchItem = (props) => {
159 | const [value, setValue] = React.useState(props.value);
160 | return React.createElement(
161 | SwitchItemComponent, {
162 | ...props,
163 | value,
164 | onChange: (val) => (setValue(val), props.onChange(val))
165 | }
166 | );
167 | };
168 | const Items = [{
169 | id: "showAlways",
170 | name: "Show Always",
171 | note: "Shows the controls bar even if only one activity is present.",
172 | value: false
173 | }];
174 |
175 | function SettingsPanel() {
176 | return React.createElement(React.Fragment, null, Items.map((item) => React.createElement(
177 | SwitchItem, {
178 | ...item,
179 | value: Settings.get(item.id, item.value),
180 | onChange: (value) => Settings.set(item.id, value)
181 | },
182 | item.name
183 | )));
184 | }
185 |
186 | /*@end */
187 |
188 | /* @module wrapper.scss */
189 | Styles.sheets.push("/* wrapper.scss */", `.wrapper {
190 | display: flex;
191 | flex-direction: column;
192 | }
193 | .wrapper .controls {
194 | margin-bottom: 10px;
195 | display: flex;
196 | align-items: center;
197 | justify-content: space-between;
198 | padding: 5px;
199 | background: var(--background-secondary-alt);
200 | border-radius: 3px;
201 | flex: 1 0;
202 | margin-top: 10px;
203 | }
204 | .wrapper .controls .caret {
205 | display: inline-flex;
206 | align-items: center;
207 | justify-content: center;
208 | cursor: pointer;
209 | border-radius: 3px;
210 | background-color: rgba(255, 255, 255, 0.3);
211 | }
212 | .wrapper .controls .caret.disabled {
213 | cursor: not-allowed;
214 | opacity: 0.3;
215 | }
216 | .wrapper .controls .caret:hover:not(.disabled) {
217 | background: var(--background-modifier-accent);
218 | }
219 | .wrapper .controls .carosell {
220 | display: flex;
221 | align-items: center;
222 | }
223 | .wrapper .controls .carosell .dot {
224 | margin: 0 4px;
225 | width: 10px;
226 | cursor: pointer;
227 | height: 10px;
228 | border-radius: 100px;
229 | background: var(--interactive-muted);
230 | transition: background 0.3s;
231 | opacity: 0.6;
232 | }
233 | .wrapper .controls .carosell .dot:hover:not(.selected) {
234 | opacity: 1;
235 | }
236 | .wrapper .controls .carosell .dot.selected {
237 | opacity: 1;
238 | background: var(--dot-color, var(--brand-experiment));
239 | }
240 |
241 | .tooltip {
242 | --background-floating: var(--background-secondary);
243 | }`);
244 | var styles$1 = {
245 | "wrapper": "wrapper",
246 | "controls": "controls",
247 | "caret": "caret",
248 | "disabled": "disabled",
249 | "carosell": "carosell",
250 | "dot": "dot",
251 | "selected": "selected",
252 | "tooltip": "tooltip"
253 | };
254 | /*@end */
255 |
256 | /* @module caret.scss */
257 | Styles.sheets.push("/* caret.scss */", `.SAA-caret {
258 | color: #ddd;
259 | }
260 | .SAA-caret.right {
261 | transform: rotate(-90deg);
262 | }
263 | .SAA-caret.left {
264 | transform: rotate(90deg);
265 | }`);
266 | var styles = {
267 | "SAACaret": "SAA-caret",
268 | "right": "right",
269 | "left": "left"
270 | };
271 | /*@end */
272 |
273 | /* @module caret.jsx */
274 | function Caret({
275 | direction,
276 | ...props
277 | }) {
278 | return React.createElement("svg", {
279 | className: "SAA-caret " + styles[direction.toLowerCase()],
280 | width: "24",
281 | height: "24",
282 | viewBox: "0 0 24 24",
283 | ...props
284 | }, React.createElement("path", {
285 | fill: "currentColor",
286 | fillRule: "evenodd",
287 | clipRule: "evenodd",
288 | d: "M16.59 8.59004L12 13.17L7.41 8.59004L6 10L12 16L18 10L16.59 8.59004Z"
289 | }));
290 | }
291 |
292 | /*@end */
293 |
294 | /* @module colors.json */
295 | var spotify = "#1db954";
296 | var STREAMING = "#593695";
297 | var ActivityColors = {
298 | spotify: spotify,
299 | "363445589247131668": "#ff0000",
300 | "463097721130188830": "#d9252a",
301 | "802958789555781663": "#593695",
302 | STREAMING: STREAMING,
303 | "562286213059444737": "#3a004b",
304 | "83226320970055681": "#889afb",
305 | "782685898163617802": "#889afb",
306 | "356869127241072640": "#112120",
307 | "367827983903490050": "#e5649d"
308 | };
309 |
310 | /*@end */
311 |
312 | /* @module wrapper.jsx */
313 | const ActivityTypes = {
314 | 0: "PLAYING",
315 | 1: "STREAMING",
316 | 2: "LISTENING",
317 | 3: "WATCHING",
318 | 4: "CUSTOM_STATUS",
319 | 5: "COMPETING",
320 | COMPETING: 5,
321 | CUSTOM_STATUS: 4,
322 | LISTENING: 2,
323 | PLAYING: 0,
324 | STREAMING: 1,
325 | WATCHING: 3
326 | };
327 | const {
328 | useCallback,
329 | useMemo,
330 | useState
331 | } = React;
332 | const useStateFromStores = Webpack$1.getModule((m) => m.useStateFromStores).useStateFromStores;
333 | const useStateFromStoresArray = Webpack$1.getModule((m) => m.useStateFromStores).useStateFromStoresArray;
334 | const {
335 | Messages
336 | } = Webpack$1.getModule((m) => m?.Messages?.MEMBER_LIST_SHOWN);
337 | const PresenceStore = Webpack$1.getStore("PresenceStore");
338 | const Tooltip = BdApi.Components.Tooltip;
339 | const [UserActivity, UserActivityTypes] = (() => {
340 | const module = Webpack$1.getModule((m) => Object.values(m).some((e) => e?.USER_POPOUT_V2));
341 | return [
342 | module.Z,
343 | module[Object.keys(module).find((e) => module[e]?.USER_POPOUT_V2)]
344 | ];
345 | })();
346 | const classes = Webpack$1.getByProps("activity", "buttonColor") ?? {};
347 |
348 | function ActivityWrapper({
349 | user,
350 | activityType: ActivityType = UserActivity,
351 | whatever: WhateverWrapper,
352 | ...props
353 | }) {
354 | const activities = useStateFromStoresArray([PresenceStore], () => {
355 | return PresenceStore.getActivities(user.id).filter((ac) => ac.type !== 4);
356 | });
357 | const [activityIndex, setActivityIndex] = useState(0);
358 | const currentActivity = useMemo(() => activities[activityIndex], [activityIndex, activities]);
359 | const shouldShowControls = useStateFromStores([Settings], () => {
360 | return activities.length > 1 || Settings.get("showAlways", false);
361 | }, [activities]);
362 | const canGo = (type) => {
363 | if (activityIndex === -1 || activities.length === 0 || activityIndex > activities.length - 1)
364 | return false;
365 | switch (type) {
366 | case "backward": {
367 | return activityIndex > 0;
368 | }
369 | case "forward": {
370 | return activityIndex !== activities.length - 1 && activityIndex < activities.length - 1;
371 | }
372 | }
373 | };
374 | const handleSelectNext = (type) => useCallback(() => {
375 | if (!canGo(type))
376 | return;
377 | let index;
378 | switch (type) {
379 | case "backward":
380 | index = activityIndex - 1;
381 | break;
382 | case "forward":
383 | index = activityIndex + 1;
384 | break;
385 | }
386 | if (index < 0 || index > activities.length)
387 | return;
388 | setActivityIndex(index);
389 | }, [activities, activityIndex, user]);
390 | const goForward = handleSelectNext("forward");
391 | const goBackward = handleSelectNext("backward");
392 | if (!activities.length)
393 | return null;
394 | if (!currentActivity) {
395 | setActivityIndex(0);
396 | return null;
397 | }
398 | const style = {
399 | "--dot-color": ActivityColors[Object.keys(ActivityColors).find((e) => currentActivity.id?.includes(e) || currentActivity.application_id === e || currentActivity.type === ActivityTypes[e])]
400 | };
401 | return React.createElement(WhateverWrapper, null, React.createElement("div", {
402 | className: Utils.className(styles$1.wrapper, {
403 | [styles$1.spotify]: currentActivity.id?.startsWith("spotify")
404 | }),
405 | style
406 | }, React.createElement(
407 | ActivityType, {
408 | __SAA: true,
409 | ...props,
410 | user,
411 | activity: currentActivity,
412 | type: UserActivityTypes.USER_POPOUT_V2,
413 | key: currentActivity.application_id,
414 | className: Utils.className(classes.activity),
415 | source: "Profile Popout",
416 | actionColor: classes.buttonColor,
417 | openAction: props.onClose,
418 | onOpenGameProfile: props.onClose
419 | }
420 | ), shouldShowControls && React.createElement("div", {
421 | className: styles$1.controls
422 | }, React.createElement(
423 | Tooltip, {
424 | key: "LEFT",
425 | text: Messages.PAGINATION_PREVIOUS,
426 | tooltipClassName: styles$1.tooltip,
427 | spacing: 14
428 | },
429 | (props2) => React.createElement(
430 | "div", {
431 | ...props2,
432 | className: Utils.className(styles$1.caret, {
433 | [styles$1.disabled]: !canGo("backward")
434 | }),
435 | onClick: goBackward
436 | },
437 | React.createElement(Caret, {
438 | direction: "left"
439 | })
440 | )
441 | ), React.createElement("div", {
442 | className: styles$1.carosell
443 | }, activities.map((_, i) => React.createElement(
444 | "div", {
445 | key: "dot--" + i,
446 | onClick: () => setActivityIndex(i),
447 | className: Utils.className(styles$1.dot, {
448 | [styles$1.selected]: i === activityIndex
449 | })
450 | }
451 | ))), React.createElement(
452 | Tooltip, {
453 | key: "RIGHT",
454 | text: Messages.PAGINATION_NEXT,
455 | tooltipClassName: styles$1.tooltip,
456 | spacing: 14
457 | },
458 | (props2) => React.createElement(
459 | "div", {
460 | ...props2,
461 | className: Utils.className(styles$1.caret, {
462 | [styles$1.disabled]: !canGo("forward")
463 | }),
464 | onClick: goForward
465 | },
466 | React.createElement(Caret, {
467 | direction: "right"
468 | })
469 | )
470 | ))));
471 | }
472 |
473 | /*@end */
474 |
475 | /* @module changelog.css */
476 | Styles.sheets.push("/* changelog.css */", `.SAA-changelog-item {
477 | color: #ddd;
478 | }
479 |
480 | .SAA-changelog-header {
481 | text-transform: uppercase;
482 | font-weight: 700;
483 | display: flex;
484 | align-items: center;
485 | margin-bottom: 10px;
486 | }
487 |
488 | .item-changelog-added .SAA-changelog-header {
489 | color: #45BA6A;
490 | }
491 | .item-changelog-fixed .SAA-changelog-header {
492 | color: #EC4245;
493 | }
494 | .item-changelog-improved .SAA-changelog-header {
495 | color: #5865F2;
496 | }
497 |
498 | .SAA-changelog-header::after {
499 | content: "";
500 | flex-grow: 1;
501 | height: 1px;
502 | background: currentColor;
503 | margin-left: 7px;
504 | }
505 |
506 | .SAA-changelog-item span {
507 | display: list-item;
508 | margin-left: 5px;
509 | list-style: inside;
510 | }
511 |
512 | .SAA-changelog-item span::marker {
513 | color: var(--background-accent);
514 | }
515 | .SAA-changelog-banner {
516 | width: 405px;
517 | border-radius: 8px;
518 | margin-bottom: 20px;
519 | }
520 |
521 | .SAA-title-wrap {
522 | font-size: 18px;
523 | }
524 |
525 | .SAA-title-wrap span {
526 | font-size: 12px;
527 | color: var(--text-muted);
528 | font-family: var(--font-primary);
529 | }
530 | `); /*@end */
531 |
532 | /* @module index.jsx */
533 | class ShowAllActivities {
534 | maybeShowChangelog() {
535 | if (config.version === Settings.get("latestUsedVersion"))
536 | return;
537 | const items = config.changelog.map((item) => React.createElement("div", {
538 | className: "SAA-changelog-item item-changelog-" + item.type
539 | }, React.createElement("h4", {
540 | className: "SAA-changelog-header"
541 | }, item.type), React.createElement("span", null, item.items)));
542 | "changelogImage" in config && items.unshift(
543 | React.createElement("img", {
544 | className: "SAA-changelog-banner",
545 | src: config.changelogImage
546 | })
547 | );
548 | Settings.set("latestUsedVersion", config.version);
549 | const formatter = new Intl.DateTimeFormat(document.documentElement.lang, {
550 | month: "long",
551 | day: "numeric",
552 | year: "numeric"
553 | });
554 | UI.alert(React.createElement("div", {
555 | className: "SAA-title-wrap"
556 | }, React.createElement("h1", null, "What's New - ", config.name), React.createElement("span", null, formatter.format(new Date(config.changelogDate)))), items);
557 | }
558 | getSettingsPanel() {
559 | return React.createElement(SettingsPanel, null);
560 | }
561 | start() {
562 | Styles.load();
563 | this.patchUserActivityContainer();
564 | this.maybeShowChangelog();
565 | }
566 | patchUserActivityContainer() {
567 | const [UserActivityModule, method] = Webpack$1.getMangled((m) => m?.toString?.().includes("onOpenGameProfile:"));
568 | Patcher.after(UserActivityModule, method, (_, [props], res) => {
569 | if (props.__SAA)
570 | return;
571 | const youTellMe = res.type;
572 | const ActivityType = res?.props?.children?.type;
573 | if (typeof youTellMe !== "function" || typeof ActivityType !== "function")
574 | return;
575 | return React.createElement(ActivityWrapper, {
576 | activityType: ActivityType,
577 | whatever: youTellMe,
578 | user: props.user,
579 | ...props
580 | });
581 | });
582 | }
583 | stop() {
584 | Styles.unload();
585 | Patcher.unpatchAll();
586 | }
587 | }
588 |
589 | /*@end */
590 |
591 | module.exports = ShowAllActivities;
--------------------------------------------------------------------------------