Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/handlers/twitch/helix/fetchCheers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const saveCheers = async (cheers: Cheermote[]) => {
cheer.tiers.forEach((tier) => {
const name = `${cheer.prefix}${tier.min_bits}`;
chatCheers[name] = {
name,
prefix: cheer.prefix,
color: tier.color,
url: tier.images.dark.animated['4'],
minBits: tier.min_bits,
Expand Down
2 changes: 1 addition & 1 deletion src/types/twitchEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface Emote {
id: string; // An ID that uniquely identifies this emote.
emote_set_id: string; // An ID that identifies the emote set that the emote belongs to.
owner_id: string; // The ID of the broadcaster who owns the emote.
format: 'animated' | 'static'; // The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only static. But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. The possible formats are: animated - An animated GIF is available for this emote. static - A static PNG file is available for this emote.
format: 'animated' | 'static'[]; // The formats that the emote is available in. For example, if the emote is available only as a static PNG, the array contains only static. But if the emote is available as a static PNG and an animated GIF, the array contains static and animated. The possible formats are: animated - An animated GIF is available for this emote. static - A static PNG file is available for this emote.
}

interface Mention {
Expand Down
6 changes: 5 additions & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ export type ChatBadge = {
};

export type ChatCheer = {
name: string;
prefix: string;
url: string;
color: string;
minBits: number;
};

export type ChatCheerWithBits = Exclude<ChatCheer, 'minBits'> & {
bits: number;
};
92 changes: 39 additions & 53 deletions src/views/Chat/ChatImageRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import twemoji from '@twemoji/api';
import classNames from 'classnames';

import { JSX } from 'react';
import { Fragment, JSX } from 'react';
import { store } from '../../store/store';
import { ChannelChatMessageEvent } from '../../types/twitchEvents';
import { ChatCheer, ChatEmote } from '../../types/types';
import { ChatCheer, ChatCheerWithBits, ChatEmote } from '../../types/types';
import { bttvModifierMap, bttvModifiers } from './bttvModifierFlags';
import { parseFrankerFaceZModifierFlags } from './parseFrankerFaceZModifierFlags';
import { parseSevenTVModifierFlags } from './parseSevenTVModifierFlags';
Expand All @@ -28,27 +27,53 @@ function getTwitchEmote(emoteId: string): ChatEmote {
};
}

export const ChatImageRenderer = ({
fragments,
bits,
}: {
fragments: ChannelChatMessageEvent['message']['fragments'];
bits?: number;
}): JSX.Element => {
export const ChatImageRenderer = ({ fragments }: { fragments: ChannelChatMessageEvent['message']['fragments'] }): JSX.Element => {
const chatEmotes = store((s) => s.chatEmotes);
const chatCheers = store((s) => s.chatCheers);

function findChatCheer(prefix: string, bits: number): ChatCheer | undefined {
let foundCheer: ChatCheer | undefined = undefined;
for (const cheer of Object.values(chatCheers)) {
if (cheer.prefix.toLowerCase() === prefix.toLowerCase() && cheer.minBits <= bits) {
foundCheer = cheer;
}
}

return foundCheer;
}

const messageParts: {
match: string;
emote: ChatEmote | undefined;
cheer: ChatCheer | undefined;
cheer: ChatCheerWithBits | undefined;
skip: boolean;
modifierFlags?: string[];
}[] = [];

const nextMessageModifierFlags: string[] = [];

fragments.forEach((fragment) => {
if (fragment.type === 'cheermote') {
if (fragment.cheermote) {
const foundCheer = findChatCheer(fragment.cheermote.prefix, fragment.cheermote.bits);
if (foundCheer) {
const cheer: ChatCheerWithBits = {
...foundCheer,
bits: fragment.cheermote.bits,
};

messageParts.push({
match: fragment.text,
emote: undefined,
cheer,
skip: false,
});
nextMessageModifierFlags.length = 0;
return;
}
}
}

if (fragment.emote) {
messageParts.push({
match: fragment.text,
Expand All @@ -61,37 +86,6 @@ export const ChatImageRenderer = ({
}

fragment.text.split(wordRegex).forEach((match) => {
if (bits) {
let closestCheer: ChatCheer | undefined = undefined;
// A match might look like VoHiYo199, but the cheer name is VoHiYo, so we need to remove the bits
const cheerName = match.replace(/\d+$/, '');
for (const cheer of Object.values(chatCheers)) {
// Check if the cheer name matches the message part
if (!cheer.name.startsWith(cheerName)) {
continue;
}

if (cheer.minBits <= bits) {
if (!closestCheer || cheer.minBits > closestCheer.minBits) {
closestCheer = cheer;
continue;
}

closestCheer = cheer;
}
}

if (closestCheer) {
messageParts.push({
match,
emote: undefined,
cheer: closestCheer,
skip: false,
});
return;
}
}

if (bttvModifiers.includes(match)) {
messageParts.push({
match,
Expand Down Expand Up @@ -183,20 +177,12 @@ export const ChatImageRenderer = ({
const cheerAmount = Number(match.replace(/\D/g, ''));

return (
<>
<img
className={classNames('chat-cheer')}
key={`${match}.${index}`}
src={cheer.url}
// srcSet={emote.srcSet}
alt={match}
title={match}
width={28}
/>
<Fragment key={`${match}.${index}`}>
<img className={classNames('chat-cheer')} src={cheer.url} alt={match} title={match} width={28} />
<span className={classNames('chat-cheer-amount')} style={{ color: cheer.color }}>
{cheerAmount}
</span>
</>
</Fragment>
);
}

Expand Down
88 changes: 76 additions & 12 deletions src/views/Main/ChatPreview/ChatPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,80 @@ import { ChatEntry } from '../../Chat/ChatEntry';

import './ChatPreview.less';

const fakesTwitchMessages = [
'Wow so awesome!',
'This is so cool peepoWow',
'Did you see that? Unbeleafable! haHAA',
'Loving the stream, keep it up!',
'!hype Can we get some hype in the chat? catJAM',
const fakeTwitchMessages: ChannelChatMessageEvent['message'][] = [
{
text: 'Kappa5000 keep being awesome!',
fragments: [
{
type: 'cheermote',
text: 'Kappa5000',
cheermote: {
prefix: 'kappa',
bits: 5000,
tier: 1,
},
},
{
type: 'text',
text: ' keep being awesome!',
},
],
},
{
text: 'This is so cool peepoWow',
fragments: [
{
type: 'text',
text: 'This is so cool peepoWow',
},
],
},
{
text: 'Did you see that? Unbeleafable! haHAA LUL',
fragments: [
{
type: 'text',
text: 'Did you see that? Unbeleafable! haHAA ',
},
{
type: 'emote',
text: 'LUL',
emote: {
id: '425618',
emote_set_id: '0',
owner_id: '0',
format: ['static'],
},
},
],
},
{
text: 'Loving the stream, keep it up! Cheer169',
fragments: [
{
type: 'text',
text: 'Loving the stream, keep it up!',
},
{
type: 'cheermote',
text: 'Cheer169',
cheermote: {
prefix: 'cheer',
bits: 169,
tier: 1,
},
},
],
},
{
text: '!hype Can we get some hype in the chat? catJAM',
fragments: [
{
type: 'text',
text: '!hype Can we get some hype in the chat? catJAM',
},
],
},
];

const fakeUsers: {
Expand Down Expand Up @@ -85,11 +153,7 @@ export const ChatPreview = ({ overlayParameters }: { overlayParameters: typeof D
const fakeChatMessageEvents = Array.from({ length: 5 }, (_, idx: number) => {
{
const user = fakeUsers[idx];
const message = fakesTwitchMessages[idx];
const chatMessage: ChannelChatMessageEvent['message'] = {
text: message,
fragments: [{ text: message, type: 'text' }],
};

return {
broadcaster_user_id: '0',
broadcaster_user_login: 'athano',
Expand All @@ -98,7 +162,7 @@ export const ChatPreview = ({ overlayParameters }: { overlayParameters: typeof D
chatter_user_login: user.login,
chatter_user_name: user.name,
message_id: String(idx),
message: chatMessage,
message: fakeTwitchMessages[idx],
message_type: 'text',
badges: fakeUsers[idx].badges,
color: user.color,
Expand Down
Loading