Skip to content

Commit

Permalink
Trubbel’s Utilities 2.0.1
Browse files Browse the repository at this point in the history
Initial release of a new add-on. Just some random things.
  • Loading branch information
Trubbel authored Feb 14, 2025
1 parent 06a7c2d commit 5008a39
Show file tree
Hide file tree
Showing 31 changed files with 2,654 additions and 0 deletions.
88 changes: 88 additions & 0 deletions src/trubbel/features/clips-videos/most-recent-video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { MOST_RECENT_CARD_SELECTOR, MOST_RECENT_SELECTOR } from "../../utils/constants/selectors";
import GET_VIDEO from "../../utils/graphql/video_info.gql";
const { createElement } = FrankerFaceZ.utilities.dom;

let lastProcessedVideoId = null; // Tracks the last processed video ID
let processing = false; // Prevents re-entrant calls

export default async function applyMostRecentVideoTimestamp(ctx) {
if (ctx.router.current?.name === "user" || ctx.router.current?.name === "mod-view") {
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-most-recent")) return;

const element = await ctx.site.awaitElement(MOST_RECENT_SELECTOR, document.documentElement, 15000);
if (element) {
const container = await ctx.site.awaitElement(MOST_RECENT_CARD_SELECTOR, document.documentElement, 15000);
if (container) {
if (container && !document.querySelector(".most_recent_video-overlay")) {
lastProcessedVideoId = null;
processing = false;
}

const videoId = element?.getAttribute("href").split("/videos/")[1];
if (!videoId) return;

if (processing) {
ctx.log.info("[Most Recent Video Timestamp] Skipping as processing is already in progress.");
return;
}

if (videoId === lastProcessedVideoId) {
ctx.log.info("[Most Recent Video Timestamp] Skipping as this video ID is already processed.");
return;
}

processing = true;
lastProcessedVideoId = videoId;

try {
const apollo = ctx.resolve("site.apollo");
if (!apollo) {
ctx.log.error("[Most Recent Video Timestamp] Apollo client not resolved.");
return null;
}

const result = await apollo.client.query({
query: GET_VIDEO,
variables: {
id: videoId,
}
});

const createdAt = result?.data?.video?.createdAt;
if (createdAt) {
const overlay = createElement("div", {
class: "most_recent_video-overlay",
style: "display: block;"
});

const notice = createElement("div", {
class: "most_recent_video-timestamp",
style: "padding: 0px 0.4rem; background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 1.3rem; border-radius: 0.2rem; position: absolute; bottom: 0px; right: 0px; margin: 2px;"
});

let relative = "";
if (ctx.settings.get("addon.trubbel.clip-video.timestamps-relative")) {
relative = `(${ctx.i18n.toRelativeTime(createdAt)})`;
}
const timestamp = ctx.i18n.formatDateTime(
createdAt,
ctx.settings.get("addon.trubbel.clip-video.timestamps-format")
);
notice.textContent = `${timestamp} ${relative}`;

overlay.appendChild(notice);
container.appendChild(overlay);
}
} catch (error) {
ctx.log.error("[Most Recent Video Timestamp] Error applying video timestamp:", error);
} finally {
processing = false;
}
} else {
ctx.log.warn("[Most Recent Video Timestamp] container not found.");
}
} else {
ctx.log.warn("[Most Recent Video Timestamp] element not found.");
}
}
}
71 changes: 71 additions & 0 deletions src/trubbel/features/clips-videos/set-clip-timestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { CLIPS_TIMESTAMP, VODS_TIMESTAMP } from "../../utils/graphql/clip_info.gql";
import GET_CLIP from "../../utils/graphql/clip_info.gql";

let lastProcessedClipId = null; // Tracks the last processed clip ID
let processing = false; // Prevents re-entrant calls

export default async function setClipTimestamp(ctx) {
if (ctx.router.current?.name === "clip-page" || ctx.router.current?.name === "user-clip") {
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-clip")) return;

const clipId = location.hostname === "clips.twitch.tv"
? location.pathname.slice(1)
: location.pathname.split("/clip/")[1];

if (!clipId) {
ctx.log.error("[Clip Timestamp] Unable to get the clip ID.");
return;
}

if (processing) {
ctx.log.info("[Clip Timestamp] Skipping as processing is already in progress.");
return;
}

// Skip if the clip ID has already been processed
if (clipId === lastProcessedClipId) {
ctx.log.info("[Clip Timestamp] Skipping as this clip ID is already processed.");
return;
}

processing = true;
lastProcessedClipId = clipId;

try {
const element = await ctx.site.awaitElement(`${CLIPS_TIMESTAMP}, ${VODS_TIMESTAMP}`);
if (element) {
const apollo = ctx.resolve("site.apollo");
if (!apollo) {
ctx.log.error("[Clip Timestamp] Apollo client not resolved.");
return null;
}

const result = await apollo.client.query({
query: GET_CLIP,
variables: { slug: clipId },
});

const createdAt = result?.data?.clip?.createdAt;
if (createdAt) {
let relative = "";
if (ctx.settings.get("addon.trubbel.clip-video.timestamps-relative")) {
relative = `(${ctx.i18n.toRelativeTime(createdAt)})`;
}
const timestamp = ctx.i18n.formatDateTime(
createdAt,
ctx.settings.get("addon.trubbel.clip-video.timestamps-format")
);
element.textContent = `${timestamp} ${relative}`;
} else {
ctx.log.warn("[Clip Timestamp] No createdAt data found for clip.");
}
} else {
ctx.log.warn("[Clip Timestamp] Clip timestamp element not found.");
}
} catch (error) {
ctx.log.error("[Clip Timestamp] Error applying new clip timestamp:", error);
} finally {
processing = false; // Ensure processing is reset even on error
}
}
}
89 changes: 89 additions & 0 deletions src/trubbel/features/clips-videos/set-timestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { TIMESTAMP_SELECTOR } from "../../utils/constants/selectors";
import GET_CLIP from "../../utils/graphql/clip_info.gql";
import GET_VIDEO from "../../utils/graphql/video_info.gql";

async function getIdFromURL(ctx) {
if (location.hostname === "clips.twitch.tv" || location.pathname.includes("/clip/")) {
const clipId = location.hostname === "clips.twitch.tv"
? location.pathname.slice(1)
: location.pathname.split("/clip/")[1];

if (clipId) {
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-clip")) return;
return { type: "clip", id: clipId };
}
}

const videoMatch = location.pathname.match(/\/videos\/(\d+)/);
if (videoMatch) {
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-video")) return;
return { type: "video", id: videoMatch[1] };
}

return null;
}

async function fetchCreationDate(ctx, type, id) {
try {
const apollo = ctx.resolve("site.apollo");
if (!apollo) {
ctx.log.error("[Set Timestamp] Apollo client not resolved.");
return null;
}

const query = type === "clip" ? GET_CLIP : GET_VIDEO;
const variables = type === "clip" ? { slug: id } : { id };

const result = await apollo.client.query({
query,
variables,
});

return type === "clip"
? result?.data?.clip?.createdAt
: result?.data?.video?.createdAt;
} catch (error) {
ctx.log.error(`[Set Timestamp] Error fetching ${type} data:`, error);
return null;
}
}

async function updateTimestamp(createdAt, ctx) {
const element = await ctx.site.awaitElement(TIMESTAMP_SELECTOR, document.documentElement, 15000);
if (!element) {
ctx.log.error("[Set Timestamp] Timestamp element not found.");
return;
}

let relative = "";
if (ctx.settings.get("addon.trubbel.clip-video.timestamps-relative")) {
relative = `(${ctx.i18n.toRelativeTime(createdAt)})`;
}

const timestamp = ctx.i18n.formatDateTime(
createdAt,
ctx.settings.get("addon.trubbel.clip-video.timestamps-format")
);

element.textContent = `${timestamp} ${relative}`;
}

export default async function setTimestamp(ctx) {
const validRoutes = ["clip-page", "user-clip", "video"];
if (!validRoutes.includes(ctx.router.current_name)) return;

const idInfo = await getIdFromURL(ctx);
if (!idInfo) return;

if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-clip") && idInfo.type === "clip") {
return;
}
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-video") && idInfo.type === "video") {
return;
}

const createdAt = await fetchCreationDate(ctx, idInfo.type, idInfo.id);
if (createdAt) {
await updateTimestamp(createdAt, ctx);
}
}
72 changes: 72 additions & 0 deletions src/trubbel/features/clips-videos/set-video-timestamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { VODS_TIMESTAMP } from "../../utils/constants/selectors";
import GET_VIDEO from "../../utils/graphql/video_info.gql";

let lastProcessedVideoId = null; // Tracks the last processed video ID
let processing = false; // Prevents re-entrant calls

export default async function setVideoTimestamp(ctx) {
if (ctx.router.current?.name === "video") {
if (!ctx.settings.get("addon.trubbel.clip-video.timestamps-video")) return;

const videoId = location.pathname.split("/videos/")[1];
if (!videoId) {
ctx.log.error("[Video Timestamp] Unable to get the video ID.");
return;
}

if (processing) {
ctx.log.info("[Video Timestamp] Skipping as processing is already in progress.");
return;
}

// Skip if the video ID has already been processed
if (videoId === lastProcessedVideoId) {
ctx.log.info("[Video Timestamp] Skipping as this video ID is already processed.");
return;
}

processing = true;
lastProcessedVideoId = videoId;

try {
const metadataElement = await ctx.site.awaitElement(VODS_TIMESTAMP);
if (metadataElement) {
const apollo = ctx.resolve("site.apollo");
if (!apollo) {
ctx.log.error("[Video Timestamp] Apollo client not resolved.");
return null;
}

const result = await apollo.client.query({
query: GET_VIDEO,
variables: {
id: videoId,
},
});

const createdAt = result?.data?.video?.createdAt;
if (createdAt) {
let relative = "";
if (ctx.settings.get("addon.trubbel.clip-video.timestamps-relative")) {
relative = `(${ctx.i18n.toRelativeTime(createdAt)})`;
}

const timestamp = ctx.i18n.formatDateTime(
createdAt,
ctx.settings.get("addon.trubbel.clip-video.timestamps-format")
);

metadataElement.textContent = `${timestamp} ${relative}`;
} else {
ctx.log.warn("[Video Timestamp] No data found for video.");
}
} else {
ctx.log.warn("[Video Timestamp] Video timestamp element not found.");
}
} catch (error) {
ctx.log.error("[Video Timestamp] Error applying new video timestamp:", error);
} finally {
processing = false; // Ensure processing is reset even on error
}
}
}
37 changes: 37 additions & 0 deletions src/trubbel/features/commands/accountage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import GET_ACCOUNTAGE from "../../utils/graphql/accountage.gql";
import { formatAccountAge } from "../../utils/format";

export default async function getAccountAge(context, inst) {
const userId = inst?.props.currentUserID;
const username = inst?.props.currentUserLogin;

const twitch_data = await context.twitch_data.getUser(userId);
const displayName = twitch_data.displayName;
const login = twitch_data.login;
const user = displayName.toLowerCase() === login ? displayName : `${displayName} (${login})`;

const apollo = context.resolve("site.apollo");
if (!apollo) {
return null;
}

const result = await apollo.client.query({
query: GET_ACCOUNTAGE,
variables: {
login: username
}
});

const accountAge = result?.data?.user?.createdAt;
if (accountAge) {
inst.addMessage({
type: context.site.children.chat.chat_types.Notice,
message: `${user} created their account ${formatAccountAge(accountAge)}.`
});
} else {
inst.addMessage({
type: context.site.children.chat.chat_types.Notice,
message: `Unable to get accountage for ${user}.`
});
}
}
Loading

0 comments on commit 5008a39

Please sign in to comment.