Skip to content

Commit

Permalink
Added:
Browse files Browse the repository at this point in the history
- Ability to click and open user cards when using /mods and /vips
- Show Full Messages /w Expanded Replies
- BTTV-like mod tools with options
- /shrug command
  • Loading branch information
Trubbel committed Feb 19, 2025
1 parent 5008a39 commit adde905
Show file tree
Hide file tree
Showing 9 changed files with 732 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/trubbel/features/commands/shrug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default async function shrug(context, inst, event) {
const message = event?.message?.replace(/^\/shrug\s/, "");
event.sendMessage(message + " ¯\\_(ツ)_/¯");
}
4 changes: 4 additions & 0 deletions src/trubbel/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ChatCommands } from "./settings/chat-commands";
import { ChatModeration } from "./settings/chat-moderation";
import { ChatViewerCard } from "./settings/chat-viewer-card";
import { ClipsVideos } from "./settings/clips-videos";
import { Directory } from "./settings/directory";
import { DropsRewards } from "./settings/drops-rewards";
Expand All @@ -12,6 +14,8 @@ class Trubbel extends Addon {
super(...args);

this.inject(ChatCommands);
this.inject(ChatModeration);
this.inject(ChatViewerCard);
this.inject(ClipsVideos);
this.inject(Directory);
this.inject(DropsRewards);
Expand Down
5 changes: 2 additions & 3 deletions src/trubbel/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
"description": "Just some random things.",
"author": "Trubbel",
"maintainer": "Trubbel",
"version": "2.0.1",
"version": "2.4.1",
"search_terms": "trubbel",
"website": "https://twitch.tv/trubbel",
"created": "2025-01-06T23:29:54.496Z",
"updated": "2025-01-06T23:29:54.496Z"
"created": "2025-01-06T23:29:54.496Z"
}
5 changes: 5 additions & 0 deletions src/trubbel/settings/chat-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class ChatCommands extends FrankerFaceZ.utilities.module.Module {
// Store command handler functions for cleanup
this.commandHandlers = new Set();

// Chat - Custom Commands - Enable Custom Chat Commands
this.settings.add("addon.trubbel.chat.custom-commands", {
default: false,
ui: {
Expand All @@ -34,6 +35,7 @@ export class ChatCommands extends FrankerFaceZ.utilities.module.Module {
accountage: "Show how long ago you created your account.",
chatters: "Show the current channels amount of chatters.",
followage: "Show your followage in the current channel.",
shrug: "Appends `¯\\_(ツ)_/¯` to your message.",
uptime: "Show the channels current uptime."
};

Expand Down Expand Up @@ -114,6 +116,9 @@ export class ChatCommands extends FrankerFaceZ.utilities.module.Module {
case "followage":
getFollowAge(this, inst);
break;
case "shrug":
shrug(this, inst, e);
break;
case "uptime":
getStreamUptime(this, inst);
break;
Expand Down
477 changes: 477 additions & 0 deletions src/trubbel/settings/chat-moderation.js

Large diffs are not rendered by default.

212 changes: 212 additions & 0 deletions src/trubbel/settings/chat-viewer-card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
const { createElement } = FrankerFaceZ.utilities.dom;

export class ChatViewerCard extends FrankerFaceZ.utilities.module.Module {
constructor(...args) {
super(...args);

this.inject("settings");
this.inject("site.router");
this.inject("site.fine");

this.STYLE_ID = "trubbel-mod-vip-usernames";
this.isEnabled = false;

// Chat - Viewer Cards - Enable Viewer Cards for /mods and /vips
this.settings.add("addon.trubbel.chat.viewer-card", {
default: false,
ui: {
sort: 0,
path: "Add-Ons > Trubbel\u2019s Utilities > Chat >> Viewer Cards",
title: "Enable Viewer Cards for /mods and /vips",
description: "This gives you the ability to click on any usernames when using /mods and /vips which then opens up their viewer cards.\n\n**Note:** Might not work for every language.",
component: "setting-check-box"
},
changed: () => this.handleViewerCards()
});

this.handleModListClick = (event) => this.onHandleModListClick(event);
}

onEnable() {
this.settings.getChanges("addon.trubbel.chat.viewer-card", () => this.handleViewerCards());
this.router.on(":route", this.checkNavigation, this);
this.checkNavigation();
}

init() {
if (this.isEnabled) return;
document.addEventListener("click", this.handleModListClick);
this.on("chat:buffer-message", this.onBufferMessage, this);
this.injectStyles();
this.isEnabled = true;
}

onBufferMessage(event) {
if (!this.settings.get("addon.trubbel.chat.viewer-card")) return;

const messageType = event?.message?.type;
if (messageType !== 29) return;

const message = event?.message?.message;
const regex = /:\s*(.+?)\./;
const match = message.match(regex);
if (!match) return;

setTimeout(() => this.processModList(message, match), 50);
}

cleanup() {
if (!this.isEnabled) return;
document.removeEventListener("click", this.handleModListClick);
this.off("chat:buffer-message", this.onBufferMessage, this);
this.removeStyles();
this.isEnabled = false;
}

processModList(message, match) {
const statusElement = this.findStatusMessage(message);
if (!statusElement || statusElement.querySelector("ffz-username")) return;

const usernames = match[1].split(",")
.map(username => username.trim())
.filter(username => username.length > 0);

let newText = message.substring(0, match.index + 1);
usernames.forEach((username, index) => {
if (index > 0) newText += ",";
newText += ` <ffz-username>${username}</ffz-username>`;
});
newText += ".";

// try to injectStyles() making usernames bold within /mods and /vips
statusElement.innerHTML = newText;
}

findStatusMessage(messageText) {
const statusMessages = Array.from(document.querySelectorAll(".chat-line__status"));
for (const element of statusMessages.reverse()) {
if (element.textContent.trim() === messageText.trim()) {
return element;
}
}
return null;
}

async onHandleModListClick(event) {
const target = event.target;
const statusMsg = target.closest(".chat-line__status");
if (!statusMsg) return;

const content = statusMsg.textContent;
const match = content.match(/:\s*(.+?)\./);
if (!match) return;

const usernames = match[1].split(",")
.map(username => username.trim())
.filter(username => username.length > 0);

const username = this.getClickedUsername(event, usernames);
if (!username) {
this.log.info("No exact username match found for clicked text");
return;
}

const chatContainer = this.fine.wrap("chat-container");
if (chatContainer?.first) {
const container = chatContainer.first;
if (container.onUsernameClick) {
const rect = statusMsg.getBoundingClientRect();
container.onUsernameClick(username, null, null, rect.top);
return;
}
}
}

getClickedUsername(event, usernames) {
let clickedRange;
if (document.caretRangeFromPoint) {
clickedRange = document.caretRangeFromPoint(event.clientX, event.clientY);
} else if (document.caretPositionFromPoint) {
const caretPosition = document.caretPositionFromPoint(event.clientX, event.clientY);
if (caretPosition) {
clickedRange = document.createRange();
clickedRange.setStart(caretPosition.offsetNode, caretPosition.offset);
clickedRange.setEnd(caretPosition.offsetNode, caretPosition.offset);
}
}

if (!clickedRange) return null;

const container = clickedRange.startContainer;
const offset = clickedRange.startOffset;
const fullText = container.textContent || "";

let currentPosition = 0;
for (const username of usernames) {
const usernameStart = fullText.indexOf(username, currentPosition);
if (usernameStart !== -1) {
const usernameEnd = usernameStart + username.length;
if (offset >= usernameStart && offset <= usernameEnd) {
return username;
}
currentPosition = usernameEnd;
}
}

return null;
}

checkNavigation() {
if (!this.settings.get("addon.trubbel.chat.viewer-card")) return;

const chatRoutes = [
"dash-popout-chat",
"dash-stream-manager",
"embed-chat",
"mod-popout-chat",
"mod-view",
"popout",
"user"
];

if (chatRoutes.includes(this.router?.current?.name)) {
this.init();
} else {
this.cleanup();
}
}

injectStyles() {
if (document.getElementById(this.STYLE_ID)) return;
const style = createElement("style", {
id: this.STYLE_ID,
textContent: `
ffz-username {
cursor: pointer;
display: inline;
font-weight: bold;
}
ffz-username:hover {
text-decoration: underline;
}
`
});
document.head.appendChild(style);
}

removeStyles() {
const style = document.getElementById(this.STYLE_ID);
if (style) {
style.remove();
}
}

handleViewerCards() {
const enabled = this.settings.get("addon.trubbel.chat.viewer-card");
if (enabled) {
this.checkNavigation();
} else {
this.cleanup();
}
}
}
14 changes: 7 additions & 7 deletions src/trubbel/settings/clips-videos.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 0,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> Timestamps",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> Timestamps",
title: "Enable Custom Timestamps for Clips",
description: "Show the full timestamp when a clip was created.",
component: "setting-check-box"
Expand All @@ -31,7 +31,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 1,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> Timestamps",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> Timestamps",
title: "Enable Custom Timestamps for Videos",
description: "Show the full timestamp when a video was created.",
component: "setting-check-box"
Expand All @@ -44,7 +44,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 2,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> Timestamps",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> Timestamps",
title: "Enable Timestamps for Most Recent Videos",
description: "Show timestamps on most recent videos when a stream is offline.",
component: "setting-check-box"
Expand All @@ -57,7 +57,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: "medium",
ui: {
sort: 3,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> Timestamps",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> Timestamps",
title: "Timestamp Format",
description: "The default combined timestamp format. Custom time formats are formatted using the [Day.js](https://day.js.org/docs/en/display/format) library.",
component: "setting-combo-box",
Expand Down Expand Up @@ -87,7 +87,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 4,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> Timestamps",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> Timestamps",
title: "Enable Relative Timestamp",
description: "Include relative timestamp, such as `(2 days ago)`, `(2 months ago)`, `(2 years ago)` at the end.",
component: "setting-check-box"
Expand All @@ -100,7 +100,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 0,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> VODs",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> VODs",
title: "Enable Auto-Skip Muted Segments",
description: "Automatically detects and skips muted segments.\n\nOnce you reach the start of a muted segment, this will automatically skip to the end of that muted segment.",
component: "setting-check-box"
Expand All @@ -112,7 +112,7 @@ export class ClipsVideos extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 1,
path: "Add-Ons > Trubbel's Utilities > Clips and Videos >> VODs",
path: "Add-Ons > Trubbel\u2019s Utilities > Clips and Videos >> VODs",
title: "Enable Auto-Skip Notifications",
description: "Show a notification bottom left when a muted segment is skipped.",
component: "setting-check-box"
Expand Down
20 changes: 20 additions & 0 deletions src/trubbel/settings/ui-tweaks.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ export class UITweaks extends FrankerFaceZ.utilities.module.Module {
},
changed: () => this.updateCSS()
});
// UI Tweaks - Chat - Show Full Messages /w Expanded Replies
this.settings.add("addon.trubbel.ui-tweaks.chat-show-full-message", {
default: false,
ui: {
sort: 1,
path: "Add-Ons > Trubbel\u2019s Utilities > UI Tweaks >> Chat",
title: "Show Full Messages /w Expanded Replies",
description: "Allows you to see the entire message someone is replying to in chat, instead of it being cut off.\n\n**Note:** Twitch settings needs to be \"**Expanded**\" in \`Chat Settings > Chat Appearance > Replies in Chat > Expanded\`,\n\n& FFZ settings needs to be \"**Twitch (Default)**\" in [Chat > Appearance > Replies](~chat.appearance.replies).",
component: "setting-check-box"
},
changed: () => this.updateCSS()
});
// UI Tweaks - System Theme - Enable System Theme
this.settings.add("addon.trubbel.ui-tweaks.system-theme", {
default: false,
Expand Down Expand Up @@ -165,6 +177,14 @@ export class UITweaks extends FrankerFaceZ.utilities.module.Module {
this.style.delete("viewer-list-padding1");
this.style.delete("viewer-list-padding2");
}
// UI Tweaks - Chat - Show Full Messages /w Expanded Replies
if (this.settings.get("addon.trubbel.ui-tweaks.chat-show-full-message")) {
this.style.set("show-full-message", ".chat-line__message-container p[title*=\"@\"] {white-space: break-spaces !important;}");
this.style.set("show-full-message-mentioned", ".chat-line__message-container p:has(.reply-line--mentioned) {white-space: break-spaces !important;}");
} else {
this.style.delete("show-full-message");
this.style.delete("show-full-message-mentioned");
}
// UI Tweaks - Titles - Show full titles for Stream Tooltips
if (this.settings.get("addon.trubbel.ui-tweaks.full-side-nav-tooltip")) {
this.style.set("show-full-side-nav-tooltip1", ".tw-balloon :has(.online-side-nav-channel-tooltip__body) { max-width: none !important; }");
Expand Down
2 changes: 1 addition & 1 deletion src/trubbel/settings/whispers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Whispers extends FrankerFaceZ.utilities.module.Module {
default: false,
ui: {
sort: 0,
path: "Add-Ons > Trubbel's Utilities > Whispers >> Resizable",
path: "Add-Ons > Trubbel\u2019s Utilities > Whispers >> Resizable",
title: "Enable Resizable Drop Down",
description: "Gives the ability to adjust the height of the whisper window drop down.",
component: "setting-check-box"
Expand Down

0 comments on commit adde905

Please sign in to comment.