diff --git a/src/trubbel/manifest.json b/src/trubbel/manifest.json index 7b0da37..98bebe0 100644 --- a/src/trubbel/manifest.json +++ b/src/trubbel/manifest.json @@ -10,10 +10,10 @@ "description": "↓ Lots of features ↓\n\n**Chat:**\n\n– Clickable Steam Inspect Links\n\n– Custom Commands (accountage, followage, localmod/sub etc.)\n\n– BTTV-like Moderation\n\n– Channel Name in Popout Chat\n\n– Clickable Usernames in /mods, /vips and Raid Messages\n\n**Directory:**\n\n– Display Channel Follow Dates\n\n– Display Total Followed Channels\n\n– Live Thumbnail Previews\n\n**Inventory:**\n\n– Auto Claim Drops\n\n– Collapsible Drops\n\n**Livestream:**\n\n– Auto Mute/Pause Stream (in background tabs)\n\n**Mod View:**\n\n– Activity Feed Moderation\n\n– Auto Mod View\n\n**Overall:**\n\n– Live Sidebar Previews\n\n– Yet Another Prime Reminder\n\n**Player:**\n\n– Auto Reset Player\n\n**VODs:**\n\n– Auto Skip Muted Segments\n\n– Custom Seeking\n\n– Custom Progress Bar\n\n…among other things…", "author": "Trubbel", "maintainer": "Trubbel", - "version": "4.2.2", + "version": "4.2.3", "search_terms": "trubbel", "website": "https://twitch.tv/trubbel", "settings": "add_ons.trubbel_s_utilities", "created": "2025-01-06T23:29:54.496Z", - "updated": "2025-10-15T03:35:15.090Z" + "updated": "2025-10-19T17:30:48.508Z" } \ No newline at end of file diff --git a/src/trubbel/modules/appearance/declutter/index.js b/src/trubbel/modules/appearance/declutter/index.js index 36cce44..13f2e14 100644 --- a/src/trubbel/modules/appearance/declutter/index.js +++ b/src/trubbel/modules/appearance/declutter/index.js @@ -18,7 +18,7 @@ export default class Declutter { "hide-sidebar-treasure-train": ".side-nav-card div:has(> .hype-train-icon__train--treasure)", "hide-sidebar-hype-train": ".side-nav-card div:has(> .hype-train-icon__train--default)", "hide-sidebar-gift-discount": ".side-nav-card div:has(> [class*=\"giftGradient--\"])", - "hide-player-cc": "[data-a-target=\"player-settings-menu\"] div:has(> button.tw-interactable [d=\"M2 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5zm2 0h12v10H4V5z\"])", + "hide-player-cc": "[data-a-target=\"player-settings-menu\"] div:has(> button.tw-interactable [d=\"M2 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5zm2 0h12v10H4V5z\"]),[data-a-target=\"player-settings-menu\"] div:has(> button.tw-interactable [d=\"M4 3a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4zm1.744 5.962a1.93 1.93 0 0 1 3.598-.395l.105.21-.894.447-.105-.21a.93.93 0 0 0-1.734.19l-.007.028a3.166 3.166 0 0 0 0 1.536l.007.028a.93.93 0 0 0 1.734.19l.105-.21.894.448-.105.21a1.93 1.93 0 0 1-3.598-.396l-.007-.027a4.166 4.166 0 0 1 0-2.021l.007-.028zm5 0a1.93 1.93 0 0 1 3.598-.395l.105.21-.894.447-.105-.21a.93.93 0 0 0-1.734.19l-.007.028a3.165 3.165 0 0 0 0 1.536l.007.028a.93.93 0 0 0 1.734.19l.105-.21.894.448-.105.21a1.93 1.93 0 0 1-3.598-.396l-.007-.027a4.168 4.168 0 0 1 0-2.021l.007-.028z\"])", "hide-player-disclosure": ".disclosure-tool", "hide-player-mrv": ".video-player__overlay :is(.player-overlay-background--darkness-3):has(.offline-recommendations-video-card)", "hide-stories": "#side-nav [class*=\"storiesLeftNavSection--\"],#side-nav :is([style]) :has([class*=\"storiesLeftNavSectionCollapsedButton--\"]),div[class^=\"Layout-sc-\"]:has(> .scrollable-area[style] > div[style] > h2.sr-only)", diff --git a/src/trubbel/modules/appearance/tweaks/index.js b/src/trubbel/modules/appearance/tweaks/index.js index 84fb27e..f25e8ac 100644 --- a/src/trubbel/modules/appearance/tweaks/index.js +++ b/src/trubbel/modules/appearance/tweaks/index.js @@ -72,6 +72,17 @@ export default class Tweaks { } else { this.style.delete("viewer-list-padding"); } + // Appearance - Tweaks - Directory - Full-width in directory + if (this.settings.get("addon.trubbel.appearance.tweaks.directory.max_width")) { + this.style.set("directory-max-width", ` + main.twilight-main .common-centered-column, + main.twilight-main .directory-header-new__info { + max-width: unset !important; + } + `); + } else { + this.style.delete("directory-max-width"); + } // Appearance - Tweaks - Inventory - Display bigger images in the inventory page if (this.settings.get("addon.trubbel.appearance.tweaks.inventory.big_img")) { this.style.set("inv-big-img", ` diff --git a/src/trubbel/modules/channel/chat/message-highlight.js b/src/trubbel/modules/channel/chat/message-highlight.js new file mode 100644 index 0000000..74c0c40 --- /dev/null +++ b/src/trubbel/modules/channel/chat/message-highlight.js @@ -0,0 +1,195 @@ +import { BAD_USERS } from "../../../utilities/constants/types"; + +const { createElement, on, off } = FrankerFaceZ.utilities.dom; + +export default class MessageHighlight { + constructor(parent) { + this.parent = parent; + this.settings = parent.settings; + this.router = parent.router; + this.style = parent.style; + this.site = parent.site; + this.log = parent.log; + + this.isActive = false; + this.currentHoveredUser = null; + + this.handleNavigation = this.handleNavigation.bind(this); + this.enableMessageHighlight = this.enableMessageHighlight.bind(this); + this.disableMessageHighlight = this.disableMessageHighlight.bind(this); + this.handleSettingChange = this.handleSettingChange.bind(this); + this.handleMessageHover = this.handleMessageHover.bind(this); + this.handleMessageLeave = this.handleMessageLeave.bind(this); + } + + initialize() { + const enabled = this.settings.get("addon.trubbel.channel.chat.messages.highlight"); + if (enabled > 0) { + this.handleNavigation(); + } else { + this.disableMessageHighlight(); + } + } + + handleSettingChange(enabled) { + if (enabled > 0) { + this.log.info("[Message Highlight] Enabling message highlight"); + this.handleNavigation(); + } else { + this.log.info("[Message Highlight] Disabling message highlight"); + this.disableMessageHighlight(); + } + } + + handleNavigation() { + const chatRoutes = this.site.constructor.CHAT_ROUTES; + const currentRoute = this.router?.current?.name; + + let pathname; + + if (this.router?.match && this.router.match[1]) { + pathname = this.router.match[1]; + } else { + const location = this.router?.location; + const segment = location?.split("/").filter(segment => segment.length > 0); + pathname = segment?.[0]; + } + + if (chatRoutes.includes(currentRoute) && pathname && !BAD_USERS.includes(pathname)) { + const enabled = this.settings.get("addon.trubbel.channel.chat.messages.highlight"); + if (enabled > 0 && !this.isActive) { + this.log.info("[Message Highlight] Entering chat page, enabling message highlight"); + this.enableMessageHighlight(); + } + } else { + if (this.isActive) { + this.log.info("[Message Highlight] Leaving chat page, disabling message highlight"); + this.disableMessageHighlight(); + } + } + } + + enableMessageHighlight() { + if (this.isActive) return; + + this.log.info("[Message Highlight] Setting up message highlight"); + this.isActive = true; + + this.addEventListeners(); + } + + disableMessageHighlight() { + if (!this.isActive) return; + + this.log.info("[Message Highlight] Removing message highlight"); + this.isActive = false; + + if (this.currentHoveredUser) { + this.style.delete("trubbel-message-highlight"); + this.currentHoveredUser = null; + } + + this.removeEventListeners(); + } + + addEventListeners() { + const chatContainer = document.querySelector(".chat-scrollable-area__message-container"); + if (chatContainer) { + on(chatContainer, "mouseover", this.handleMessageHover); + on(chatContainer, "mouseout", this.handleMessageLeave); + } + + this.observer = new MutationObserver(() => { + const container = document.querySelector(".chat-scrollable-area__message-container"); + if (container && !container.__trubbel_highlight_attached) { + container.__trubbel_highlight_attached = true; + on(container, "mouseover", this.handleMessageHover); + on(container, "mouseout", this.handleMessageLeave); + } + }); + + this.observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + removeEventListeners() { + const containers = document.querySelectorAll(".chat-scrollable-area__message-container"); + containers.forEach(container => { + off(container, "mouseover", this.handleMessageHover); + off(container, "mouseout", this.handleMessageLeave); + delete container.__trubbel_highlight_attached; + }); + + if (this.observer) { + this.observer.disconnect(); + this.observer = null; + } + } + + handleMessageHover(event) { + if (!this.isActive) return; + + const highlightMode = this.settings.get("addon.trubbel.channel.chat.messages.highlight"); + + let target; + if (highlightMode === 1) { + // Usernames only + target = event.target.closest(".chat-line__username"); + } else if (highlightMode === 2) { + // Entire messages + target = event.target.closest(".chat-line__message"); + } + + if (!target) return; + + const messageLine = target.closest(".chat-line__message"); + if (!messageLine) return; + + const username = messageLine.dataset.user; + if (!username || username === this.currentHoveredUser) return; + + this.currentHoveredUser = username; + this.applyHighlight(username); + } + + handleMessageLeave(event) { + if (!this.isActive || !this.currentHoveredUser) return; + + const highlightMode = this.settings.get("addon.trubbel.channel.chat.messages.highlight"); + + let target; + if (highlightMode === 1) { + // Usernames only + target = event.target.closest(".chat-line__username"); + } else if (highlightMode === 2) { + // Entire messages + target = event.target.closest(".chat-line__message"); + } + + if (!target) return; + + const relatedTarget = event.relatedTarget; + if (relatedTarget) { + if (highlightMode === 1 && relatedTarget.closest(".chat-line__username")) return; + if (highlightMode === 2 && relatedTarget.closest(".chat-line__message")) return; + } + + this.currentHoveredUser = null; + this.style.delete("trubbel-message-highlight"); + } + + applyHighlight(username) { + const color = this.settings.get("addon.trubbel.channel.chat.messages.highlight.color"); + if (!color) return; + + this.style.set("trubbel-message-highlight", ` + body .chat-room .chat-scrollable-area__message-container > div:nth-child(1n+0) > .chat-line__message:not(.chat-line--inline)[data-user="${username}"], + body .chat-room .chat-scrollable-area__message-container > div:nth-child(1n+0) > div > .chat-line__message:not(.chat-line--inline)[data-user="${username}"], + body .chat-room .chat-line__message:not(.chat-line--inline):nth-child(1n+0)[data-user="${username}"] { + background-color: ${color} !important; + } + `); + } +} \ No newline at end of file diff --git a/src/trubbel/settings/appearance/tweaks.js b/src/trubbel/settings/appearance/tweaks.js index 73e376a..5532031 100644 --- a/src/trubbel/settings/appearance/tweaks.js +++ b/src/trubbel/settings/appearance/tweaks.js @@ -80,6 +80,21 @@ export class Appearance_Tweaks extends FrankerFaceZ.utilities.module.Module { + // Appearance - Tweaks - Directory - Full-width in directory + this.settings.add("addon.trubbel.appearance.tweaks.directory.max_width", { + default: false, + ui: { + sort: 0, + path: "Add-Ons > Trubbel\u2019s Utilities > Appearance > Tweaks >> Directory", + title: "Full-width in directory", + description: "Removes the `max-width` in the directory pages to reduce empty spaces.", + component: "setting-check-box" + }, + changed: () => this.tweaks.updateCSS() + }); + + + // Appearance - Tweaks - Inventory - Display bigger images in the inventory page this.settings.add("addon.trubbel.appearance.tweaks.inventory.big_img", { default: false, diff --git a/src/trubbel/settings/channel/chat.js b/src/trubbel/settings/channel/chat.js index 47a0123..8d8df37 100644 --- a/src/trubbel/settings/channel/chat.js +++ b/src/trubbel/settings/channel/chat.js @@ -3,6 +3,7 @@ import Commands from "../../modules/channel/chat/commands-handler"; import FirstTimeChatter from "../../modules/channel/chat/ftc"; import InfoMessage from "../../modules/channel/chat/info-message"; import ChatMarkdown from "../../modules/channel/chat/markdown"; +import MessageHighlight from "../../modules/channel/chat/message-highlight"; import OldClipFormat from "../../modules/channel/chat/old-clip-format"; import OldViewerList from "../../modules/channel/chat/old-viewer-list"; import PopoutChatName from "../../modules/channel/chat/popout-header-name"; @@ -35,6 +36,7 @@ export class Channel_Chat extends FrankerFaceZ.utilities.module.Module { this.firstTimeChatter = new FirstTimeChatter(this); this.infoMessage = new InfoMessage(this); this.chatMarkdown = new ChatMarkdown(this); + this.messageHighlight = new MessageHighlight(this); this.oldClipFormat = new OldClipFormat(this); this.oldViewerList = new OldViewerList(this); this.popoutChatName = new PopoutChatName(this); @@ -164,6 +166,47 @@ export class Channel_Chat extends FrankerFaceZ.utilities.module.Module { + // Channel - Chat - Messages - Highlight Messages on Hover + this.settings.add("addon.trubbel.channel.chat.messages.highlight", { + default: 0, + ui: { + path: "Add-Ons > Trubbel\u2019s Utilities > Channel > Chat >> Messages", + title: "Highlight Messages on Hover", + description: "Highlight all messages from a user.", + component: "setting-select-box", + data: [ + { value: 0, title: "Off" }, + { value: 1, title: "Username" }, + { value: 2, title: "Entire Message" } + ] + }, + changed: val => this.messageHighlight.handleSettingChange(val) + }); + + // Channel - Chat - Messages - Hover Highlight + this.settings.add("addon.trubbel.channel.chat.messages.highlight.color", { + default: "rgba(169, 112, 255, 0.5)", + requires: ["addon.trubbel.channel.chat.messages.highlight"], + process(ctx, val) { + if (!ctx.get("addon.trubbel.channel.chat.messages.highlight")) + return false; + return val; + }, + ui: { + path: "Add-Ons > Trubbel\u2019s Utilities > Channel > Chat >> Messages", + title: "Hover Highlight", + description: "Background color for highlighted messages.", + component: "setting-color-box" + }, + changed: () => { + if (this.messageHighlight?.currentHoveredUser) { + this.messageHighlight.applyHighlight(this.messageHighlight.currentHoveredUser); + } + } + }); + + + // Channel - Chat - Moderation - Enable right-click context menu this.settings.add("addon.trubbel.channel.chat.moderation.context", { default: false, @@ -351,6 +394,7 @@ export class Channel_Chat extends FrankerFaceZ.utilities.module.Module { this.firstTimeChatter.initialize(); this.infoMessage.initialize(); this.chatMarkdown.initialize(); + this.messageHighlight.initialize(); this.oldClipFormat.initialize(); this.oldViewerList.initialize(); this.popoutChatName.initialize(); @@ -368,6 +412,7 @@ export class Channel_Chat extends FrankerFaceZ.utilities.module.Module { this.firstTimeChatter.handleNavigation(); this.infoMessage.handleNavigation(); this.chatMarkdown.handleNavigation(); + this.messageHighlight.handleNavigation(); this.oldClipFormat.handleNavigation(); this.oldViewerList.handleNavigation(); this.popoutChatName.handleNavigation();