diff --git a/package.json b/package.json index f2bbf53ee..a06f18619 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.2", "chokidar": "^3.5.3", + "color2k": "^2.0.0", "dexie": "^3.2.2", "eslint": "^8.30.0", "eslint-plugin-prettier": "^4.2.1", diff --git a/src/app.d.ts b/src/app.d.ts index c065ea148..d4d569875 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -61,6 +61,7 @@ declare namespace SevenTV { data?: Emote; provider?: Provider; + overlaid?: ActiveEmote[]; } interface User { diff --git a/src/common/Transform.ts b/src/common/Transform.ts index e122e448f..832d0fb02 100644 --- a/src/common/Transform.ts +++ b/src/common/Transform.ts @@ -62,7 +62,7 @@ export function convertBttvEmoteSet(data: BTTV.UserResponse, channelID: string): emotes: [...data.channelEmotes, ...data.sharedEmotes].map((e) => ({ id: e.id, name: e.code, - flags: BTTV_ZeroWidth.indexOf(e.code) > -1 ? 0 : 256, + flags: 0, provider: "BTTV", data: convertBttvEmote(e), })), @@ -73,7 +73,7 @@ export function convertBttvEmote(data: BTTV.Emote): SevenTV.Emote { return { id: data.id, name: data.code, - flags: BTTV_ZeroWidth.indexOf(data.code) > -1 ? 0 : 256, + flags: BTTV_ZeroWidth.indexOf(data.code) > -1 ? 256 : 0, tags: [], lifecycle: 3, listed: true, diff --git a/src/site/twitch.tv/index.ts b/src/site/twitch.tv/index.ts index 98b4f96b4..be1032f6a 100644 --- a/src/site/twitch.tv/index.ts +++ b/src/site/twitch.tv/index.ts @@ -89,6 +89,11 @@ export function getChatLines(container: HTMLElement, idList?: string[]) { return lines; } +export namespace Regex { + export const MessageDelimiter = new RegExp("( )", "g"); + export const SevenTVLink = new RegExp("^https?:\\/\\/(?:www\\.)?7tv.app\\/emotes\\/(?[0-9a-f]{24})", "gi"); +} + export namespace Selectors { export const ROOT = "#root div"; export const NAV = "[data-a-target='top-nav-container']"; @@ -109,3 +114,83 @@ export namespace Selectors { export const ChatMessageUsername = ".chat-line__usernames"; export const ChatMessageTimestamp = ".chat-line__timestamp"; } + +export const enum MessageType { + MESSAGE = 0, + EXTENSIONMESSAGE, + MODERATION, + MODERATIONACTION, + TARGETEDMODERATIONACTION, + AUTOMOD, + SUBSCRIBERONLYMODE, + FOLLOWERONLYMODE, + SLOWMODE, + EMOTEONLYMODE, + R9KMODE, + CONNECTED, + DISCONNECTED, + RECONNECT, + HOSTING, + UNHOST, + HOSTED, + SUBSCRIPTION, + RESUBSCRIPTION, + GIFTPAIDUPGRADE, + ANONGIFTPAIDUPGRADE, + PRIMEPAIDUPGRADE, + PRIMECOMMUNITYGIFTRECEIVEDEVENT, + EXTENDSUBSCRIPTION, + SUBGIFT, + ANONSUBGIFT, + CLEAR, + ROOMMODS, + ROOMSTATE, + RAID, + UNRAID, + NOTICE, + INFO, + BADGESUPDATED, + PURCHASE, + BITSCHARITY, + CRATEGIFT, + REWARDGIFT, + SUBMYSTERYGIFT, + ANONSUBMYSTERYGIFT, + STANDARDPAYFORWARD, + COMMUNITYPAYFORWARD, + FIRSTCHEERMESSAGE, + FIRSTMESSAGEHIGHLIGHT, + BITSBADGETIERMESSAGE, + INLINEPRIVATECALLOUT, + CHANNELPOINTSREWARD, + COMMUNITYCHALLENGECONTRIBUTION, + LIVEMESSAGESEPARATOR, + RESTRICTEDLOWTRUSTUSERMESSAGE, + COMMUNITYINTRODUCTION, + SHOUTOUT, + ANNOUNCEMENTMESSAGE, + MIDNIGHTSQUID, + CHARITYDONATION, + MESSAGEIDUPDATE, + PINNEDCHAT, +} + +export const enum ModerationType { + BAN = 0, + TIMEOUT, + DELETE, +} + +export const enum MessagePartType { + TEXT = 0, + MODERATEDTEXT, + FLAGGEDSEGMENT, + CURRENTUSERHIGHLIGHT, + MENTION, + LINK, + EMOTE, + CLIPLINK, + VIDEOLINK, + SEVENTVEMOTE = 700, + SEVENTVLINK, +} diff --git a/src/site/twitch.tv/modules/chat/ChatBackend.ts b/src/site/twitch.tv/modules/chat/ChatBackend.ts index d4dd30219..001a8262d 100644 --- a/src/site/twitch.tv/modules/chat/ChatBackend.ts +++ b/src/site/twitch.tv/modules/chat/ChatBackend.ts @@ -1,4 +1,5 @@ -import { getEmoteButton } from "@/site/twitch.tv"; +import { getEmoteButton, getChatController } from "@/site/twitch.tv"; +import { hasBadContrast, darken, parseToRgba } from "color2k"; export const tools = { emoteClick: (() => null) as Twitch.EmoteButton["props"]["onEmoteClick"], @@ -56,3 +57,40 @@ export const sendDummyMessage = (controller: Twitch.ChatControllerComponent) => id: "seventv-hook-message", }); }; + +// Temporary solution +const darkTheme = getChatController()?.props.theme === 1; + +const calculated = new Map>(); +calculated.set(true, new Map()); +calculated.set(false, new Map()); + +export function normalizeUsername(colour: string, readableColors: boolean): string { + let temp = colour.toLowerCase(); + const shouldShiftUp = readableColors === darkTheme; + const backgroundColor = shouldShiftUp ? "#0f0e11" : "#faf9fa"; + + if (!hasBadContrast(temp, "readable", backgroundColor)) return temp; + + // See if we have calculated the value + const stored = calculated.get(shouldShiftUp)?.get(colour); + if (stored) return stored; + + const rgb = parseToRgba(temp).slice(0, 3); + + if (shouldShiftUp && rgb.every((e) => e < 36)) { + calculated.get(shouldShiftUp)?.set(colour, "#7a7a7a"); + return "#7a7a7a"; + } + + let i = 0; + + while (hasBadContrast(temp, "readable", backgroundColor) && i < 50) { + temp = darken(temp, 0.1 * (shouldShiftUp ? -1 : 1)); + i++; + } + + calculated.get(shouldShiftUp)?.set(colour, temp); + + return temp; +} diff --git a/src/site/twitch.tv/modules/chat/ChatController.vue b/src/site/twitch.tv/modules/chat/ChatController.vue index b668ab90f..fca55bedf 100644 --- a/src/site/twitch.tv/modules/chat/ChatController.vue +++ b/src/site/twitch.tv/modules/chat/ChatController.vue @@ -34,7 +34,7 @@ + + diff --git a/src/site/twitch.tv/modules/chat/components/BanSliderBackend.ts b/src/site/twitch.tv/modules/chat/components/BanSliderBackend.ts new file mode 100644 index 000000000..1a998939e --- /dev/null +++ b/src/site/twitch.tv/modules/chat/components/BanSliderBackend.ts @@ -0,0 +1,73 @@ +import { getChatController } from "@/site/twitch.tv"; + +export const maxVal = 300.0; +const minVal = 40.0; +const delVal = 80.0; +const maxSeconds = 1209600.0; + +const controller = getChatController(); + +export function shouldModerate(msg: Twitch.ChatMessage) { + return ( + (controller?.props.isCurrentUserModerator && + msg.badges && + !("moderator" in msg.badges) && + !("broadcaster" in msg.badges) && + !("staff" in msg.badges)) ?? + false + ); +} + +export function executeModAction(message: string, name: string, id: string) { + controller?.sendMessage(message.replace("{user}", name).replace("{id}", id)); +} + +export function numberToTime(val: number): string { + if (val < 60) { + return `${Math.round(val)} seconds`; + } else if (val < 60 * 60) { + return `${Math.round(val / 60)} minutes`; + } else if (val < 60 * 60 * 24) { + return `${Math.round(val / (60 * 60))} hours`; + } else { + return `${Math.round(val / (60 * 60 * 24))} days`; + } +} + +export class sliderData { + command = ""; + text = ""; + time = 0; + banVis = 0; + unbanVis = 0; + color = ""; + pos = "0px"; + + constructor(pos: number) { + this.pos = `${pos}px`; + + if (pos < -40) { + this.command = "/unban {user}"; + this.unbanVis = 1; + } else if (pos < minVal) { + return; + } else if (pos < delVal) { + this.command = "/delete {id}"; + this.text = "Delete"; + this.color = "#FFFF00"; + this.banVis = 1; + } else if (pos < maxVal) { + const time = Math.pow((pos + 0.25 * maxVal - delVal) / (1.25 * maxVal - delVal), 10) * maxSeconds; + + this.command = `/timeout {user} ${Math.round(time)}`; + this.text = String(numberToTime(time)); + this.color = "#FFA500"; + this.banVis = 1; + } else { + this.command = "/ban {user}"; + this.text = "Ban"; + this.color = "#C40000"; + this.banVis = 1; + } + } +} diff --git a/src/site/twitch.tv/modules/chat/components/ChatBadge.vue b/src/site/twitch.tv/modules/chat/components/ChatBadge.vue index e9307b477..df731b036 100644 --- a/src/site/twitch.tv/modules/chat/components/ChatBadge.vue +++ b/src/site/twitch.tv/modules/chat/components/ChatBadge.vue @@ -32,6 +32,8 @@ const { show, hide } = useTooltip(ChatBadgeTooltip, { diff --git a/src/site/twitch.tv/modules/chat/components/ChatEmote.vue b/src/site/twitch.tv/modules/chat/components/ChatEmote.vue index 908159606..3f5fd7b26 100644 --- a/src/site/twitch.tv/modules/chat/components/ChatEmote.vue +++ b/src/site/twitch.tv/modules/chat/components/ChatEmote.vue @@ -2,12 +2,15 @@ + + + diff --git a/src/site/twitch.tv/modules/chat/components/ChatUserTag.vue b/src/site/twitch.tv/modules/chat/components/ChatUserTag.vue index dd65b785f..1feef739e 100644 --- a/src/site/twitch.tv/modules/chat/components/ChatUserTag.vue +++ b/src/site/twitch.tv/modules/chat/components/ChatUserTag.vue @@ -1,7 +1,7 @@