({
24 | TWITCH: {
25 | onShowViewerCard: () => void 0,
26 | onShowViewerWarnPopover: () => void 0,
27 | },
28 | YOUTUBE: {},
29 | KICK: {},
30 | UNKNOWN: {},
31 | });
32 |
33 | m.set(ctx, data);
34 | }
35 |
36 | function update(platform: P, key: keyof ChatTools[P], value: ChatTools[P][keyof ChatTools[P]]) {
37 | if (!data) return;
38 |
39 | data[platform][key] = value;
40 | }
41 |
42 | function openViewerCard(e: MouseEvent, username: string, msgID: string) {
43 | if (!data || !e || !e.currentTarget || !username) return false;
44 |
45 | const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
46 | data[ctx.platform].onShowViewerCard(username, 0, msgID, rect.bottom);
47 | return true;
48 | }
49 |
50 | function openViewerWarnPopover(userId: string, userLogin: string, placement: Twitch.WarnUserPopoverPlacement) {
51 | if (!data || !userId || !userLogin) return false;
52 |
53 | data[ctx.platform].onShowViewerWarnPopover(userId, userLogin, placement);
54 | return true;
55 | }
56 |
57 | return {
58 | update,
59 | openViewerCard,
60 | openViewerWarnPopover,
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/src/composable/useApollo.ts:
--------------------------------------------------------------------------------
1 | import { ref } from "vue";
2 | import type { ApolloClient } from "@apollo/client";
3 |
4 | const client = ref | null>(null);
5 |
6 | export function useApollo() {
7 | return client;
8 | }
9 |
--------------------------------------------------------------------------------
/src/composable/useCookies.ts:
--------------------------------------------------------------------------------
1 | class CookieMap extends Map {
2 | refresh(): void {
3 | super.clear();
4 |
5 | const cookies = document.cookie.split(";").map((cookie) => cookie.trim());
6 |
7 | for (const cookie of cookies) {
8 | const [key, value] = cookie.split("=");
9 | super.set(key, decodeURIComponent(value));
10 | }
11 | }
12 |
13 | get(key: string): string | undefined {
14 | this.refresh();
15 |
16 | return super.get(key);
17 | }
18 | }
19 |
20 | const cookieMap = new CookieMap();
21 |
22 | export function useCookies() {
23 | cookieMap.refresh();
24 |
25 | return cookieMap;
26 | }
27 |
--------------------------------------------------------------------------------
/src/composable/useEmoji.ts:
--------------------------------------------------------------------------------
1 | import { inject, reactive } from "vue";
2 | import { SITE_ASSETS_URL } from "@/common/Constant";
3 |
4 | export interface Emoji {
5 | codes: string;
6 | char: string;
7 | name: string;
8 | category: string;
9 | group: string;
10 | subgroup: string;
11 | emote: SevenTV.ActiveEmote;
12 | }
13 |
14 | const emojiByName = new Map();
15 | const emojiByCode = new Map();
16 |
17 | const cached = [] as Emoji[];
18 | export async function loadEmojiList() {
19 | if (cached.length) return cached;
20 |
21 | const assetsBase = inject(SITE_ASSETS_URL, "");
22 | const data = (await (await fetch(assetsBase + "/emoji/emoji.json")).json().catch(() => void 0)) as Emoji[];
23 |
24 | for (const e of data) {
25 | const emoji = e as Emoji;
26 |
27 | emoji.emote = {
28 | id: emoji.codes,
29 | name: emoji.name,
30 | unicode: emoji.char,
31 | provider: "EMOJI",
32 | flags: 0,
33 | } as SevenTV.ActiveEmote;
34 |
35 | emojiByName.set(emoji.name, emoji);
36 | emojiByCode.set(emoji.char, emoji);
37 | }
38 |
39 | cached.push(...data);
40 | }
41 |
42 | export function useEmoji() {
43 | return reactive({
44 | emojiList: cached,
45 | emojiByCode,
46 | emojiByName,
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/src/composable/useLiveQuery.ts:
--------------------------------------------------------------------------------
1 | import { ref, watch } from "vue";
2 | import { MaybeRef, tryOnUnmounted } from "@vueuse/core";
3 | import { liveQuery } from "dexie";
4 |
5 | export function useLiveQuery(
6 | queryFn: () => T | Promise | undefined,
7 | onResult?: (result: T) => void,
8 | opt: LiveQueryOptions = {},
9 | ) {
10 | const value = ref();
11 |
12 | let queryStop = () => {};
13 | let watchStop = () => {};
14 | const stop = () => {
15 | queryStop();
16 | watchStop();
17 | };
18 |
19 | const handleResult = (result: T | undefined) => {
20 | if (!result) return;
21 | if (typeof opt.count === "number" && opt.count-- <= 0) {
22 | stop();
23 | }
24 |
25 | value.value = result;
26 | onResult?.(result);
27 | };
28 |
29 | queryStop = liveQuery(queryFn).subscribe(handleResult).unsubscribe;
30 |
31 | if (opt.reactives) {
32 | watchStop = watch(opt.reactives, () => {
33 | queryStop();
34 | queryStop = liveQuery(queryFn).subscribe(handleResult).unsubscribe;
35 | });
36 | }
37 |
38 | tryOnUnmounted(stop);
39 |
40 | opt.until?.then(stop);
41 |
42 | return value;
43 | }
44 |
45 | export interface LiveQueryOptions {
46 | count?: number;
47 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
48 | reactives?: MaybeRef[];
49 | until?: Promise;
50 | }
51 |
--------------------------------------------------------------------------------
/src/composable/useSound.ts:
--------------------------------------------------------------------------------
1 | import { tryOnUnmounted } from "@vueuse/core";
2 |
3 | const soundMap = new Map();
4 |
5 | /**
6 | * Play a sound
7 | *
8 | * @param path the path to the sound file, prepended by the assets URL set by the loader
9 | * @param reuse whether to reuse the audio element for the same path
10 | */
11 | export function useSound(path: string, reuse = true) {
12 | const existingAudioElement = soundMap.get(path);
13 | const audio = reuse && existingAudioElement ? existingAudioElement : new Audio();
14 |
15 | const play = (volume: number = 1) => {
16 | if (reuse && existingAudioElement) {
17 | existingAudioElement.volume = volume;
18 | existingAudioElement.play();
19 | return;
20 | }
21 |
22 | fetchAudio(path).then((blobUrl) => {
23 | audio.src = blobUrl;
24 | audio.volume = volume;
25 | audio.play();
26 | soundMap.set(path, audio);
27 | });
28 | };
29 |
30 | const stop = () => audio.pause();
31 |
32 | tryOnUnmounted(() => {
33 | audio.remove();
34 | });
35 |
36 | return { play, stop, audio };
37 | }
38 |
39 | async function fetchAudio(path: string): Promise {
40 | const response = await fetch(path);
41 | if (!response.ok) {
42 | throw new Error(`HTTP error: ${response.status} - ${response.statusText}`);
43 | }
44 | return URL.createObjectURL(await response.blob());
45 | }
46 |
47 | export interface Sound {
48 | play: (volume?: number) => void;
49 | stop: () => void;
50 | audio: HTMLAudioElement;
51 | }
52 |
--------------------------------------------------------------------------------
/src/composable/useTooltip.ts:
--------------------------------------------------------------------------------
1 | import { Component, markRaw, nextTick, reactive } from "vue";
2 | import { Placement, computePosition, shift } from "@floating-ui/dom";
3 |
4 | export const tooltip = reactive({
5 | x: 0,
6 | y: 0,
7 | content: null as Component | string | null,
8 | contentProps: {} as Record,
9 | container: null as HTMLElement | null,
10 | });
11 |
12 | /**
13 | * useTooltip() is a composable function to display tooltips efficiently
14 | *
15 | * @param content text, or a component, to display as the tooltip content
16 | * @param props if content is a component, these are the props to pass to it
17 | * @returns
18 | */
19 | export function useTooltip(content?: string | Component, props?: Record, opt?: TooltipOptions) {
20 | // this shows the tooltip
21 | function show(el: HTMLElement | undefined): void {
22 | if (!el) return;
23 |
24 | // Set the content, this is necessary to calculate the tooltip's positioning
25 | if (content !== undefined) {
26 | tooltip.content = typeof content !== "string" ? markRaw(content) : content === "" ? null : content;
27 | tooltip.contentProps = props ?? {};
28 | }
29 |
30 | // on the next tick we will update the position of the tooltip container
31 | nextTick(() => {
32 | computePosition(el, tooltip.container as HTMLElement, {
33 | placement: opt?.placement ?? "top",
34 | middleware: [shift({ padding: 8, crossAxis: true, mainAxis: true })],
35 | }).then(({ x: xVal, y: yVal }) => {
36 | tooltip.x = xVal;
37 | tooltip.y = yVal;
38 | });
39 | });
40 | }
41 |
42 | // this hides the tooltip
43 | function hide(): void {
44 | tooltip.content = null;
45 | tooltip.contentProps = {};
46 | }
47 |
48 | return { show, hide };
49 | }
50 |
51 | interface TooltipOptions {
52 | placement?: Placement;
53 | }
54 |
--------------------------------------------------------------------------------
/src/composable/useUserAgent.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { IBrowser, UAParser, UAParserInstance } from "ua-parser-js";
3 |
4 | interface UserAgentHelper {
5 | agent: UAParserInstance;
6 | browser: IBrowser;
7 | avif: boolean;
8 | preferredFormat: SevenTV.ImageFormat;
9 | }
10 |
11 | const agent = new UAParser();
12 | const browser = agent.getBrowser();
13 | const data = reactive({
14 | agent,
15 | browser,
16 | avif:
17 | (browser.name === "Chrome" && parseInt(browser.version as string, 10) >= 100) ||
18 | (browser.name === "Firefox" && parseInt(browser.version as string, 10) >= 113),
19 | preferredFormat: "WEBP",
20 | });
21 |
22 | export function useUserAgent() {
23 | return data;
24 | }
25 |
--------------------------------------------------------------------------------
/src/content/emoji.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Inserts the emoji vectors into the DOM.
3 | */
4 | export async function insertEmojiVectors(): Promise {
5 | const container = document.createElement("div");
6 | container.id = "seventv-emoji-container";
7 | container.style.display = "none";
8 | container.style.position = "fixed";
9 | container.style.top = "-1px";
10 | container.style.left = "-1px";
11 |
12 | // Get path to emoji blocks in assets
13 | const base = chrome.runtime.getURL("assets/emoji");
14 | const blocks = 11;
15 |
16 | for (let i = 0; i < blocks; i++) {
17 | const data = (await fetch(base + "/emojis" + i + ".svg")).text();
18 |
19 | const element = document.createElement("div");
20 | element.id = "emojis" + i;
21 | element.innerHTML = await data;
22 |
23 | container.appendChild(element);
24 | }
25 |
26 | document.head.appendChild(container);
27 | }
28 |
--------------------------------------------------------------------------------
/src/db/versions.idb.ts:
--------------------------------------------------------------------------------
1 | import type { Dexie7 } from "./idb";
2 |
3 | export function defineVersions(db: Dexie7) {
4 | db.version(2.4).stores({
5 | channels: "id,timestamp",
6 | emoteSets: "id,timestamp,priority,provider,scope",
7 | emotes: "id,timestamp,name,owner.id",
8 | cosmetics: "id,timestamp",
9 | entitlements: "id,scope,timestamp,user_id",
10 | settings: "key",
11 | });
12 |
13 | db.version(2.5).stores({
14 | channels: "id,timestamp",
15 | emoteSets: "id,timestamp,priority,provider,scope",
16 | emotes: "id,timestamp,name,owner.id",
17 | cosmetics: "id,timestamp,kind",
18 | entitlements: "id,scope,timestamp,user_id",
19 | settings: "key",
20 | });
21 | db.version(2.6).stores({
22 | channels: "id,timestamp",
23 | emoteSets: "id,timestamp,priority,provider,scope",
24 | emotes: "id,timestamp,name,owner.id",
25 | cosmetics: "id,timestamp,kind",
26 | entitlements: "id,scope,timestamp,user_id,platform_id",
27 | settings: "key",
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/directive/ElementLifecycleDirective.ts:
--------------------------------------------------------------------------------
1 | import type { Directive, DirectiveBinding } from "vue";
2 |
3 | export const ElementLifecycleDirective = {
4 | mounted(el: HTMLElement, binding: DirectiveBinding) {
5 | const { value: cb } = binding;
6 |
7 | cb("mounted", el);
8 | },
9 | updated(el: HTMLElement, binding: DirectiveBinding) {
10 | const { value: cb } = binding;
11 |
12 | cb("updated", el);
13 | },
14 | beforeUnmount(el: HTMLElement, binding: DirectiveBinding) {
15 | const { value: cb } = binding;
16 |
17 | cb("unmounted", el);
18 | },
19 | } as Directive;
20 |
21 | type Callback = (state: ElementLifecycle, el: HTMLElement) => void;
22 |
23 | export type ElementLifecycle = "mounted" | "updated" | "unmounted";
24 |
--------------------------------------------------------------------------------
/src/directive/TextPaintDirective.ts:
--------------------------------------------------------------------------------
1 | import type { Directive, DirectiveBinding } from "vue";
2 |
3 | const ATTR_SEVENTV_PAINT_ID = "data-seventv-paint-id";
4 | const ATTR_SEVENTV_TEXT = "data-seventv-painted-text";
5 |
6 | export const TextPaintDirective = {
7 | mounted(el: HTMLElement, binding: DirectiveBinding) {
8 | const { value: paint } = binding;
9 |
10 | updateElementStyles(el, paint);
11 | },
12 | updated(el: HTMLElement, binding: DirectiveBinding) {
13 | const { value: paint } = binding;
14 |
15 | updateElementStyles(el, paint);
16 | },
17 | } as Directive;
18 |
19 | type PaintedElement = HTMLElement & {
20 | __seventv_backup_style?: { backgroundImage: string; filter: string; color: string };
21 | };
22 | export function updateElementStyles(el: PaintedElement, paintID: string | null): void {
23 | const hasPaint = el.hasAttribute(ATTR_SEVENTV_PAINT_ID);
24 | const newPaint = hasPaint && paintID !== el.getAttribute(ATTR_SEVENTV_PAINT_ID);
25 |
26 | if (!hasPaint) {
27 | el.__seventv_backup_style = {
28 | backgroundImage: el.style.backgroundImage,
29 | filter: el.style.filter,
30 | color: el.style.color,
31 | };
32 | }
33 |
34 | if (hasPaint && newPaint) {
35 | const backup = el.__seventv_backup_style;
36 | el.style.backgroundImage = backup?.backgroundImage ?? "";
37 | el.style.filter = backup?.filter ?? "";
38 | el.style.color = backup?.color ?? "";
39 |
40 | el.classList.remove("seventv-painted-content");
41 | el.removeAttribute(ATTR_SEVENTV_TEXT);
42 | el.removeAttribute(ATTR_SEVENTV_PAINT_ID);
43 | }
44 | if (!paintID) return;
45 |
46 | el.classList.add("seventv-painted-content", "seventv-paint");
47 | el.setAttribute(ATTR_SEVENTV_TEXT, "true");
48 | el.setAttribute(ATTR_SEVENTV_PAINT_ID, paintID);
49 | }
50 |
--------------------------------------------------------------------------------
/src/directive/TooltipDirective.ts:
--------------------------------------------------------------------------------
1 | import type { Directive, DirectiveBinding } from "vue";
2 | import { useTooltip } from "@/composable/useTooltip";
3 | import { Placement } from "@floating-ui/dom";
4 |
5 | export const TooltipDirective = {
6 | mounted(el: HTMLElement, binding: DirectiveBinding) {
7 | handleTooltip(el, binding);
8 | },
9 | updated(el: HTMLElement, binding: DirectiveBinding) {
10 | handleTooltip(el, binding);
11 | },
12 | beforeUnmount() {
13 | useTooltip().hide();
14 | },
15 | } as Directive;
16 |
17 | function handleTooltip(el: HTMLElement, binding: DirectiveBinding) {
18 | const tooltipText = binding.value || "";
19 |
20 | switch (binding.arg) {
21 | case "position":
22 | el.setAttribute("data-tooltip-position", binding.value);
23 | break;
24 | default: {
25 | const { show, hide } = useTooltip(tooltipText, undefined, {
26 | placement: (el.getAttribute("data-tooltip-position") as Placement) || binding.arg,
27 | });
28 |
29 | el.addEventListener("mouseenter", () => show(el));
30 | el.addEventListener("mouseleave", hide);
31 | break;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from "vue-i18n";
2 |
3 | // Import locales
4 | const locale = {} as Record>;
5 | const importedLocales = import.meta.glob("@locale/*.yaml", { eager: true, import: "default" });
6 |
7 | for (const [path, mod] of Object.entries(importedLocales)) {
8 | const lang = path.replace("/locale/", "").replace(".yaml", "");
9 | locale[lang] = mod as Record;
10 | }
11 |
12 | // Create i18n instance
13 | export function setupI18n() {
14 | const inst = createI18n({
15 | locale: "en_US",
16 | legacy: false,
17 | globalInjection: true,
18 | messages: locale,
19 | });
20 |
21 | return inst;
22 | }
23 |
--------------------------------------------------------------------------------
/src/site/global/FloatContext.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
22 |
--------------------------------------------------------------------------------
/src/site/global/FloatScreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
33 |
--------------------------------------------------------------------------------
/src/site/global/Global.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
53 |
--------------------------------------------------------------------------------
/src/site/global/ModuleWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
24 |
--------------------------------------------------------------------------------
/src/site/global/Tooltip.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
32 |
33 |
63 |
--------------------------------------------------------------------------------
/src/site/global/components/FormCheckbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
25 |
--------------------------------------------------------------------------------
/src/site/global/components/FormInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
55 |
56 |
79 |
--------------------------------------------------------------------------------
/src/site/kick.com/composable/useUserdata.ts:
--------------------------------------------------------------------------------
1 | export async function useUserdata(
2 | auth: string,
3 | sessionToken: string | undefined,
4 | ): Promise<{
5 | id: number;
6 | username: string;
7 | bio: string;
8 | email: string;
9 | streamer_channel: {
10 | slug: string;
11 | };
12 | discord?: string;
13 | facebook?: string;
14 | twitter?: string;
15 | youtube?: string;
16 | tiktok?: string;
17 | instagram?: string;
18 | }> {
19 | const data = await fetch("https://kick.com/api/v1/user", {
20 | headers: {
21 | Authorization: "Bearer " + sessionToken,
22 | "X-XSRF-TOKEN": auth,
23 | },
24 | method: "GET",
25 | });
26 |
27 | return await data.json();
28 | }
29 |
--------------------------------------------------------------------------------
/src/site/kick.com/index.ts:
--------------------------------------------------------------------------------
1 | import { InjectionKey } from "vue";
2 |
3 | export interface ChatRoom {
4 | chatroom: ChatRoomData | null;
5 | currentChannelSlug: string;
6 | currentMessage: string;
7 | }
8 |
9 | export interface ChatRoomData {
10 | id: number;
11 | }
12 |
13 | export interface KickChannelInfo {
14 | active: boolean;
15 | slug: string;
16 | currentMessage: string;
17 | }
18 |
19 | export const KICK_CHANNEL_KEY = Symbol() as InjectionKey;
20 |
--------------------------------------------------------------------------------
/src/site/kick.com/modules/auth/Auth.ts:
--------------------------------------------------------------------------------
1 | import { useCookies } from "@/composable/useCookies";
2 |
3 | const tokenWrapRegexp = /\[7TV:[0-9a-fA-F]+\]/g;
4 |
5 | export async function setBioCode(identity: KickIdentity, code: string, cookies: ReturnType) {
6 | if (!identity) return;
7 |
8 | const tokenWrap = `[7TV:${code}]`;
9 |
10 | const auth = cookies.get("XSRF-TOKEN");
11 | if (!auth) return;
12 |
13 | const headers = new Headers();
14 | headers.set("x-xsrf-token", auth ?? "");
15 | headers.set("Content-Type", "application/json");
16 |
17 | const cleanBio = identity.bio?.replace(tokenWrapRegexp, "").trim() ?? "";
18 | const newBio = code ? (identity.bio ? `${cleanBio} ${tokenWrap}` : tokenWrap) : cleanBio;
19 |
20 | return fetch("https://kick.com/update_profile", {
21 | headers: {
22 | accept: "application/json, text/plain, */*",
23 | "accept-language": "en-US",
24 | "content-type": "application/json",
25 | "x-xsrf-token": auth,
26 | },
27 | referrer: "https://kick.com/dashboard/settings/profile",
28 | referrerPolicy: "strict-origin-when-cross-origin",
29 | body: JSON.stringify({
30 | id: identity.numID,
31 | email: identity.email,
32 | bio: newBio,
33 | discord: identity.discord,
34 | facebook: identity.facebook,
35 | twitter: identity.twitter,
36 | youtube: identity.youtube,
37 | tiktok: identity.tiktok,
38 | instagram: identity.instagram,
39 | }),
40 | method: "POST",
41 | mode: "cors",
42 | }).then((resp) => {
43 | if (!resp.ok) return;
44 |
45 | identity.bio = newBio;
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/src/site/kick.com/modules/chat/ChatModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
57 |
--------------------------------------------------------------------------------
/src/site/kick.com/modules/chat/ChatUserCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
54 |
55 |
60 |
--------------------------------------------------------------------------------
/src/site/kick.com/modules/emote-menu/EmoteMenuModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
55 |
--------------------------------------------------------------------------------
/src/site/kick.com/modules/settings/SettingsModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
34 |
--------------------------------------------------------------------------------
/src/site/site.normal.ts:
--------------------------------------------------------------------------------
1 | export function loadSite() {
2 | import("./site.app");
3 | }
4 |
--------------------------------------------------------------------------------
/src/site/site.ts:
--------------------------------------------------------------------------------
1 | import { log } from "@/common/Logger";
2 | import { semanticVersionToNumber } from "@/common/Transform";
3 | import { loadSite } from "./site.normal";
4 |
5 | (async () => {
6 | const host: string = import.meta.env.VITE_APP_HOST;
7 | const versionBranch: string = import.meta.env.VITE_APP_VERSION_BRANCH;
8 |
9 | const manifestURL = `${host}/manifest${versionBranch ? "." + versionBranch.toLowerCase() : ""}.json`;
10 |
11 | const manifest = await fetch(manifestURL)
12 | .then((res) => res.json())
13 | .catch((err) => log.error("", "Failed to fetch host manifest", err.message));
14 |
15 | const localVersion = semanticVersionToNumber(import.meta.env.VITE_APP_VERSION);
16 | const hostedVersion = manifest ? semanticVersionToNumber(manifest.version) : 0;
17 |
18 | (window as Window & { seventv?: SeventvGlobalScope }).seventv = {
19 | host_manifest: manifest ?? null,
20 | };
21 |
22 | if (!manifest || hostedVersion <= localVersion) {
23 | log.info("", "Using Local Mode,", "v" + import.meta.env.VITE_APP_VERSION);
24 | loadSite();
25 | } else {
26 | seventv.hosted = true;
27 |
28 | const v1 = document.createElement("script");
29 | v1.id = "seventv-site-hosted";
30 | v1.src = manifest.index_file;
31 | v1.type = "module";
32 |
33 | const v2 = document.createElement("link");
34 | v2.rel = "stylesheet";
35 | v2.type = "text/css";
36 | v2.href = manifest.stylesheet_file;
37 | v2.setAttribute("charset", "utf-8");
38 | v2.setAttribute("content", "text/html");
39 | v2.setAttribute("http-equiv", "content-type");
40 | v2.id = "seventv-stylesheet";
41 |
42 | document.head.appendChild(v2);
43 | document.head.appendChild(v1);
44 |
45 | log.info("", "Using Hosted Mode,", "v" + manifest.version);
46 | }
47 | })();
48 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/chat/ChatMessageUnhandled.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/chat/components/tray/ChatTray.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/chat/components/tray/Tray.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
34 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/custom-commands/CommandModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
58 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/custom-commands/Commands/Refresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
41 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/emote-menu/EmoteMenuButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
35 |
36 |
57 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/mod-logs/ModLogsAuthorityMessages.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
20 |
28 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/mod-logs/ModLogsButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Mod Logs
7 |
8 |
9 |
10 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/mod-logs/ModLogsRecentActions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
35 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/mod-logs/ModLogsStore.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import type { ChatMessage } from "@/common/chat/ChatMessage";
3 |
4 | const data = reactive({
5 | modMessages: [] as ChatMessage[],
6 | });
7 |
8 | export function useModLogsStore() {
9 | return data;
10 | }
11 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/player/PlayerContentWarning.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/player/PlayerStatsTooltip.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
28 |
29 |
46 |
--------------------------------------------------------------------------------
/src/site/twitch.tv/modules/settings/SettingsModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/src/site/youtube.com/YouTubeSite.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
41 |
--------------------------------------------------------------------------------
/src/site/youtube.com/modules/chat/ChatData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
77 |
--------------------------------------------------------------------------------
/src/types/kick.module.d.ts:
--------------------------------------------------------------------------------
1 | // import AuthModuleVue from "@/site/kick.com/modules/auth/AuthModule_disabled.vue";
2 | import ChatInputModuleVue from "@/site/kick.com/modules/chat-input/ChatInputModule.vue";
3 | import ChatModuleVue from "@/site/kick.com/modules/chat/ChatModule.vue";
4 | import EmoteMenuModuleVue from "@/site/kick.com/modules/emote-menu/EmoteMenuModule.vue";
5 | import SettingsModuleVue from "@/site/kick.com/modules/settings/SettingsModule.vue";
6 |
7 | declare type KickModuleID = keyof KickModuleComponentMap;
8 |
9 | declare type KickModuleComponentMap = {
10 | auth: typeof AuthModuleVue;
11 | chat: typeof ChatModuleVue;
12 | settings: typeof SettingsModuleVue;
13 | "chat-input": typeof ChatInputModuleVue;
14 | "emote-menu": typeof EmoteMenuModuleVue;
15 | };
16 |
--------------------------------------------------------------------------------
/src/types/react-extended.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | /* eslint-disable @typescript-eslint/prefer-namespace-keyword */
4 |
5 | type MaybeElement = Element | null;
6 |
7 | declare namespace ReactExtended {
8 | type AnyReactComponent = WritableComponent & { [x: string]: any };
9 |
10 | type WritableComponent = React.Component
& {
11 | props: Writeable["props"]>;
12 | state: Writeable["state"]>;
13 | };
14 |
15 | type Writeable = { -readonly [P in keyof T]: Writeable };
16 |
17 | interface ReactVNode {
18 | alternate: ReactVNode | null;
19 | child: ReactVNode | null;
20 | childExpirationTime: number | null;
21 | effectTag: number | null;
22 | elementType: React.ElementType
| null;
23 | expirationTime: number | null;
24 | index: number | null;
25 | key: Key | null;
26 | mode: number | null;
27 | return: ReactVNode | null;
28 | sibling: ReactVNode | null;
29 | stateNode: React.ReactInstance | null;
30 | tag: number | null;
31 | type: React.ElementType
| null;
32 | pendingProps: P;
33 | memoizedProps: P;
34 | }
35 |
36 | interface ReactFunctionalFiber
extends ReactVNode
{
37 | elementType: { render: (props: P) => ReactExtended.ReactVNode };
38 | pendingProps: P;
39 | }
40 |
41 | interface ReactRuntimeElement extends React.ReactElement {
42 | $$typeof: symbol;
43 | ref: { current: MaybeElement } | ((e: MaybeElement) => void) | null;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/types/tw.module.d.ts:
--------------------------------------------------------------------------------
1 | import type AutoclaimModuleVue from "@/site/twitch.tv/modules/avatars/AutoclaimModuleVue.vue";
2 | import type AvatarsModuleVue from "@/site/twitch.tv/modules/avatars/AvatarsModule.vue";
3 | import type ChatInputControllerComponent from "@/site/twitch.tv/modules/chat-input-controller/ChatInputControllerModule.vue";
4 | import type ChatInputModuleVue from "@/site/twitch.tv/modules/chat-input/ChatInputModule.vue";
5 | import type ChatVodModuleVue from "@/site/twitch.tv/modules/chat-vod/ChatVodModule.vue";
6 | import type ChatModuleVue from "@/site/twitch.tv/modules/chat/ChatModule.vue";
7 | import type EmoteMenuModuleVue from "@/site/twitch.tv/modules/emote-menu/EmoteMenuModule.vue";
8 | import type HiddenElementsModuleVue from "@/site/twitch.tv/modules/hidden-elements/HiddenElementsModule.vue";
9 | import type ModLogsModule from "@/site/twitch.tv/modules/mod-logs/ModLogsModule.vue";
10 | import type PlayerModule from "@/site/twitch.tv/modules/player/PlayerModule.vue";
11 | import type SettingsModuleVue from "@/site/twitch.tv/modules/settings/SettingsModule.vue";
12 | import type SidebarPreviewsModuleVue from "@/site/twitch.tv/modules/sidebar-previews/SidebarPreviewsModule.vue";
13 |
14 | declare type TwModuleID = keyof TwModuleComponentMap;
15 |
16 | declare type TwModuleComponentMap = {
17 | "chat-input-controller": typeof ChatInputControllerComponent;
18 | "chat-input": typeof ChatInputModuleVue;
19 | "chat-vod": typeof ChatVodModuleVue;
20 | "emote-menu": typeof EmoteMenuModuleVue;
21 | "hidden-elements": typeof HiddenElementsModuleVue;
22 | "mod-logs": typeof ModLogsModule;
23 | "sidebar-previews": typeof SidebarPreviewsModuleVue;
24 | autoclaim: typeof AutoclaimModuleVue;
25 | avatars: typeof AvatarsModuleVue;
26 | chat: typeof ChatModuleVue;
27 | player: PlayerModule;
28 | settings: typeof SettingsModuleVue;
29 | };
30 |
--------------------------------------------------------------------------------
/src/types/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // eslint-disable-next-line prettier/prettier
3 | import type { DefineComponent } from "vue";
4 |
5 | declare module "*.vue" {
6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
7 | const component: DefineComponent, Record, any>;
8 | export default component;
9 | }
10 |
11 | declare module "*?sharedworker&inline" {
12 | const WorkerFactory: new () => SharedWorker;
13 | export default WorkerFactory;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/yt.module.d.ts:
--------------------------------------------------------------------------------
1 | import type ChatModuleVue from "@/site/youtube.com/modules/chat/ChatModule.vue";
2 |
3 | declare type YtModuleID = keyof YtModuleComponentMap;
4 |
5 | declare type YtModuleComponentMap = {
6 | chat: typeof ChatModuleVue;
7 | };
8 |
--------------------------------------------------------------------------------
/src/ui/UiButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
14 |
15 |
80 |
--------------------------------------------------------------------------------
/src/ui/UiCopiedMessageToast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ message }}
5 |
6 |
7 |
8 |
9 |
10 |
28 |
29 |
50 |
--------------------------------------------------------------------------------
/src/ui/UiLazy.ts:
--------------------------------------------------------------------------------
1 | import { onUnmounted, reactive } from "vue";
2 |
3 | const instances = reactive