Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: Trubbel’s Utilities 2.4.1 #267

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to use the chat_types enum available from the site.chat module for this, rather than a hard-coded number. The message type IDs are not always stable, but the chat_types enum is loaded from Twitch's bundle so it stays up to date. In this case I believe you'd want chat_types.Info


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 = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as before, there's an existing list of chat routes you can use.

"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