Skip to content

Commit

Permalink
shared chat
Browse files Browse the repository at this point in the history
  • Loading branch information
FrantaBOT committed Sep 25, 2024
1 parent c8b13ea commit 5e3dbd2
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 21 deletions.
13 changes: 10 additions & 3 deletions src/app/chat/Badge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:alt="alt"
:style="{
backgroundColor,
borderRadius,
}"
@mouseenter="show(imgRef)"
@mouseleave="hide()"
Expand All @@ -20,16 +21,18 @@ import BadgeTooltip from "./BadgeTooltip.vue";
const props = defineProps<{
alt: string;
type: "twitch" | "app";
badge: Twitch.ChatBadge | SevenTV.Cosmetic<"BADGE">;
type: "twitch" | "picture" | "app";
badge: Twitch.ChatBadge | string | SevenTV.Cosmetic<"BADGE">;
}>();
const backgroundColor = ref("");
const borderRadius = ref("");
const srcset = {
twitch: (badge: Twitch.ChatBadge) => `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
picture: (badge: string) => `${badge.slice(0, -9)}28x28.png 1.6x, ${badge.slice(0, -9)}70x70.png 3.8x`,
app: (badge: SevenTV.Cosmetic<"BADGE">) =>
badge.data.host.files.map((fi, i) => `https:${badge.data.host.url}/${fi.name} ${i + 1}x`).join(", "),
}[props.type](props.badge as SevenTV.Cosmetic<"BADGE"> & Twitch.ChatBadge);
}[props.type](props.badge as SevenTV.Cosmetic<"BADGE"> & string & Twitch.ChatBadge);
const imgRef = ref<HTMLElement>();
Expand All @@ -44,6 +47,10 @@ function isApp(badge: typeof props.badge): badge is SevenTV.Cosmetic<"BADGE"> {
if (isApp(props.badge)) {
backgroundColor.value = props.badge.data.backgroundColor ?? "";
}
if (typeof props.badge == "string") {
borderRadius.value = "25%";
}
</script>

<style scoped lang="scss">
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/UserMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<UserTag
v-if="msg.author && !hideAuthor"
:user="msg.author"
:source-data="msg.sourceData"
:badges="msg.badges"
:msg-id="msg.sym"
@open-native-card="openViewerCard($event, msg.author.username, msg.id)"
Expand Down
17 changes: 15 additions & 2 deletions src/app/chat/UserTag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
<div v-if="user && user.displayName" ref="tagRef" class="seventv-chat-user" :style="{ color: user.color }">
<!--Badge List -->
<span
v-if="!hideBadges && ((twitchBadges.length && twitchBadgeSets?.count) || cosmetics.badges.size)"
v-if="
!hideBadges && ((twitchBadges.length && twitchBadgeSets?.count) || cosmetics.badges.size || sourceData)
"
class="seventv-chat-user-badge-list"
>
<Badge
v-if="sourceData"
:key="sourceData.login"
:badge="sourceData.profileImageURL"
:alt="sourceData.displayName"
type="picture"
/>
<Badge
v-for="badge of twitchBadges"
:key="badge.id"
Expand Down Expand Up @@ -60,6 +69,7 @@ import { autoPlacement, shift } from "@floating-ui/dom";
const props = withDefaults(
defineProps<{
user: ChatUser;
sourceData?: Twitch.SharedChat;
msgId?: symbol;
asMention?: boolean;
hideBadges?: boolean;
Expand Down Expand Up @@ -106,7 +116,10 @@ watchEffect(() => {
const setID = key;
const badgeID = value;
for (const setGroup of [twitchBadgeSets.value.channelsBySet, twitchBadgeSets.value.globalsBySet]) {
for (const setGroup of [
props.sourceData?.badges.channelsBySet ?? twitchBadgeSets.value.channelsBySet,
twitchBadgeSets.value.globalsBySet,
]) {
if (!setGroup) continue;
const set = setGroup.get(setID);
Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/14.SubscriptionMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
</span>
<span class="bold">Subscribed</span>
with
{{ plan }}.
{{ plan }}
<template v-if="msgData.sourceData"> to {{ msgData.sourceData.displayName }} </template>
<span>.</span>
</div>
</div>

Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/15.Resubscription.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
</span>
<span class="bold">Subscribed</span>
with
{{ plan }}. They've subscribed for
{{ plan }}
<template v-if="msgData.sourceData"> to {{ msgData.sourceData.displayName }} </template>
<span>. They've subscribed for </span>
<span class="bold"> {{ msgData.cumulativeMonths }} months</span>
<template v-if="msgData.shouldShareStreakTenure">
, {{ msgData.streakMonths }} month{{ (msgData.streakMonths ?? 0) > 1 ? "s" : "" }} in a row.
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/msg/21.SubGift.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<span class="bold"> Tier {{ msgData.methods?.plan.charAt(0) }} </span>
Sub to
<span class="bold"> {{ msgData.recipientDisplayName }} </span>
<template v-if="msgData.sourceData"> in {{ msgData.sourceData.displayName }}'s channel! </template>
</div>
</div>
</span>
Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/msg/26.Raid.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<template>
<span class="seventv-raid-message-container seventv-highlight">
<span class="bold">{{ msgData.params.displayName }}</span>
raided with a viewer count of
raided
<template v-if="msgData.sourceData"> {{ msgData.sourceData.displayName }}'s channel </template>
with a viewer count of
<span class="bold"> {{ msgData.params.viewerCount }}</span>
<span>.</span>
</span>
Expand Down
5 changes: 4 additions & 1 deletion src/app/chat/msg/35.SubMysteryGift.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
is gifting {{ msgData.massGiftCount }} Tier {{ msgData.plan.charAt(0) }} Sub{{
msgData.massGiftCount > 1 ? "s" : ""
}}
to {{ msgData.channel }}'s community.
<template v-if="msgData.sourceData">
<span>to {{ msgData.sourceData.displayName }}'s community</span>
</template>
<span>.</span>
<template v-if="msgData.senderCount == msgData.massGiftCount">
It's their first Gift Sub in the channel!
</template>
Expand Down
6 changes: 5 additions & 1 deletion src/app/chat/msg/41.BitsBadgeTier.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
{{ msgData.user.displayName }}
</span>
just earned a new
<span class="bold">{{ msgData.threshold }} Bits badge!</span>
<span class="bold">{{ msgData.threshold }} Bits badge</span>
<template v-if="msgData.sourceData">
<span> in {{ msgData.sourceData.displayName }}'s channel</span>
</template>
<span>!</span>
</div>
</div>
<div v-if="msg.body" class="bits-badge-message">
Expand Down
1 change: 1 addition & 0 deletions src/app/chat/msg/43.PointsReward.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<span class="reward-name bold">
{{ msgData.reward.name }}
</span>
<template v-if="msgData.sourceData"> in {{ msgData.sourceData.displayName }}'s channel </template>
</div>
<span class="reward-cost bold">
<TwChannelPoints />
Expand Down
5 changes: 4 additions & 1 deletion src/app/chat/msg/49.AnnouncementMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
<div class="announce-icon">
<TwAnnounce />
</div>
<div class="announce-title">Announcement</div>
<div class="announce-title">
<template v-if="msgData.sourceData"> {{ msgData.sourceData.displayName }}'s </template>
Announcement
</div>
</div>
<div class="announce-message">
<slot />
Expand Down
6 changes: 5 additions & 1 deletion src/app/chat/msg/52.ViewerMilestone.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
</div>
<span class="bold">Watch Streak Reached!: </span>
<span v-if="msg.author">{{ msg.author.displayName }}</span>
is currently on a {{ msgData.watchStreak }}-stream streak!
is currently on a {{ msgData.watchStreak }}-stream streak
<template v-if="msgData.sourceData">
<span>in {{ msgData.sourceData.displayName }}'s channel</span>
</template>
<span>!</span>
</div>
</div>

Expand Down
5 changes: 5 additions & 0 deletions src/common/chat/ChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class ChatMessage<C extends ComponentFactory = ComponentFactory> {
public body = "";
public author: ChatUser | null = null;
public channelID = "";
public sourceData: Twitch.SharedChat | undefined;
private component?: C | null = null;
public componentProps?: InstanceType<C>["$props"] | null = null;
public highlight: Highlight | null = null;
Expand Down Expand Up @@ -96,6 +97,10 @@ export class ChatMessage<C extends ComponentFactory = ComponentFactory> {
this.id = id;
}

public setSourceData(sourceData: Twitch.SharedChat | undefined): void {
this.sourceData = sourceData;
}

public setHighlight(color: string, label: string): void {
this.highlight = {
color,
Expand Down
57 changes: 51 additions & 6 deletions src/site/twitch.tv/modules/chat/ChatController.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
@mouseleave="properties.hovering = false"
>
<div id="seventv-message-container" class="seventv-message-container">
<ChatList ref="chatList" :list="list" :message-handler="messageHandler" />
<ChatList
ref="chatList"
:list="list"
:shared-chat-data="sharedChatDataByChannelID"
:message-handler="messageHandler"
/>
</div>

<!-- New Messages during Scrolling Pause -->
Expand Down Expand Up @@ -40,7 +45,7 @@ import { log } from "@/common/Logger";
import { HookedInstance, awaitComponents } from "@/common/ReactHooks";
import { defineFunctionHook, definePropertyHook, unsetPropertyHook } from "@/common/Reflection";
import { ChatMessage } from "@/common/chat/ChatMessage";
import { ChannelRole, useChannelContext } from "@/composable/channel/useChannelContext";
import { ChannelContext, ChannelRole, useChannelContext } from "@/composable/channel/useChannelContext";
import { useChatEmotes } from "@/composable/chat/useChatEmotes";
import { useChatMessages } from "@/composable/chat/useChatMessages";
import { useChatProperties } from "@/composable/chat/useChatProperties";
Expand Down Expand Up @@ -70,7 +75,7 @@ const props = defineProps<{
const mod = getModule<"TWITCH", "chat">("chat")!;
const { sendMessage: sendWorkerMessage } = useWorker();
const { list, controller, room } = toRefs(props);
const { list, controller, room, presentation } = toRefs(props);
const el = document.createElement("seventv-container");
el.id = "seventv-chat-controller";
Expand Down Expand Up @@ -102,6 +107,7 @@ const ignoreClearChat = useConfig<boolean>("chat.ignore_clear_chat");
// Defines the current channel for hooking
const currentChannel = ref<CurrentChannel | null>(null);
const sharedChannels = new Map<string, ChannelContext>();
// Capture the chat root node
watchEffect(() => {
Expand Down Expand Up @@ -225,9 +231,22 @@ definePropertyHook(controller.value.component, "props", {
// Send presence upon message sent
messages.sendMessage = v.chatConnectionAPI.sendMessage;
defineFunctionHook(v.chatConnectionAPI, "sendMessage", function (old, ...args) {
worker.sendMessage("CHANNEL_ACTIVE_CHATTER", {
channel: toRaw(ctx.base),
});
if (sharedChatDataByChannelID.value?.size != 0) {
for (const [key, value] of sharedChatDataByChannelID.value?.entries() ?? []) {
worker.sendMessage("CHANNEL_ACTIVE_CHATTER", {
channel: {
id: key,
username: value.login,
displayName: value.displayName,
active: true,
},
});
}
} else {
worker.sendMessage("CHANNEL_ACTIVE_CHATTER", {
channel: toRaw(ctx.base),
});
}
// Run message content patching middleware
for (const fn of mod.instance?.messageSendMiddleware.values() ?? []) {
Expand All @@ -245,6 +264,32 @@ definePropertyHook(controller.value.component, "props", {
},
});
const sharedChatDataByChannelID = ref<Map<string, Twitch.SharedChat> | null>(null);
watch(
presentation,
(inst, old) => {
if (!inst || !inst.component) return;
if (old && old.component && inst !== old) {
unsetPropertyHook(old.component, "props");
return;
}
definePropertyHook(inst.component, "props", {
value(v) {
sharedChatDataByChannelID.value = v.sharedChatDataByChannelID;
for (const channelID of sharedChatDataByChannelID.value.keys()) {
if (!sharedChannels.has(channelID) && channelID != ctx.id) {
sharedChannels.set(channelID, useChannelContext(channelID, true));
}
}
},
});
},
{ immediate: true },
);
const a = awaitComponents<Twitch.MessageCardOpeners>({
parentSelector: ".stream-chat",
predicate: (n) => {
Expand Down
13 changes: 13 additions & 0 deletions src/site/twitch.tv/modules/chat/ChatList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import BasicSystemMessage from "@/app/chat/msg/BasicSystemMessage.vue";
const props = defineProps<{
list: HookedInstance<Twitch.ChatListComponent>;
messageHandler: Twitch.MessageHandlerAPI | null;
sharedChatData: Map<string, Twitch.SharedChat> | null;
}>();
const ctx = useChannelContext();
Expand Down Expand Up @@ -78,6 +79,7 @@ const showMonitoredLowTrustUser = useConfig<boolean>("highlights.basic.monitored
const messageHandler = toRef(props, "messageHandler");
const list = toRef(props, "list");
const sharedChatData = toRef(props, "sharedChatData");
// Unrender messages out of view
const chatListEl = ref<HTMLElement>();
Expand Down Expand Up @@ -162,6 +164,17 @@ function onChatMessage(msg: ChatMessage, msgData: Twitch.AnyMessage, shouldRende
msg.setHighlight("#ff7d00", "Restricted Suspicious User");
}
let sourceRoomID =
msgData.sourceRoomID ?? msgData.sharedChat?.sourceRoomID ?? msgData.message?.sourceRoomID ?? null;
if (!sourceRoomID && msgData.nonce) {
sourceRoomID = msg.channelID;
}
if (sourceRoomID) {
msgData.sourceData = sharedChatData.value?.get(sourceRoomID);
msg.setSourceData(msgData.sourceData);
}
// define message author
const authorData = msgData.user ?? msgData.message?.user ?? null;
if (authorData) {
Expand Down
9 changes: 9 additions & 0 deletions src/types/twitch.messages.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ declare namespace Twitch {
export interface AnyMessage {
id: string;
type: number;
sourceRoomID?: string;
sourceData?: SharedChat;
sharedChat?: SharedChatMessage;
user?: ChatUser | null;
message?: ChatMessage;
badges?: Record<string, string>;
Expand All @@ -16,6 +19,12 @@ declare namespace Twitch {
notifySent?: () => void;
}

export interface SharedChatMessage extends AnyMessage {
sourceBadges: Record<string, string>;
sourceID: string;
sourceRoomID: string;
}

export interface DisplayableMessage extends AnyMessage {
messageParts?: ChatMessage.Part[];
messageBody?: string;
Expand Down
8 changes: 5 additions & 3 deletions src/worker/worker.http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ enum ProviderPriority {
}

export class WorkerHttp {
private lastPresenceAt = 0;
private lastPresenceAt: Map<string, number> = new Map();
static imageFormat: SevenTV.ImageFormat = "WEBP";

constructor(private driver: WorkerDriver) {
Expand Down Expand Up @@ -53,11 +53,13 @@ export class WorkerHttp {
});
driver.addEventListener("set_channel_presence", (ev) => {
if (!ev.port || !ev.port.platform || !ev.port.user || !ev.detail) return;
if (this.lastPresenceAt && this.lastPresenceAt > Date.now() - 1e4) {

const lastPresenceAt = this.lastPresenceAt?.get(ev.detail.id);
if (lastPresenceAt && lastPresenceAt > Date.now() - 1e4) {
return;
}

this.lastPresenceAt = Date.now();
this.lastPresenceAt.set(ev.detail.id, Date.now());
this.writePresence(ev.port.platform, ev.port.user.id, ev.detail.id);
});
driver.addEventListener("imageformat_updated", async (ev) => {
Expand Down

0 comments on commit 5e3dbd2

Please sign in to comment.