-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial release of a new add-on. Just some random things.
- Loading branch information
Showing
31 changed files
with
2,654 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}.` | ||
}); | ||
} | ||
} |
Oops, something went wrong.