Skip to content

Commit

Permalink
Merge branch 'master' into feat/death-message
Browse files Browse the repository at this point in the history
  • Loading branch information
vyneer committed Dec 28, 2023
2 parents b072dc3 + 57369b9 commit 63b32bd
Show file tree
Hide file tree
Showing 17 changed files with 402 additions and 219 deletions.
28 changes: 14 additions & 14 deletions assets/chat/css/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -535,23 +535,11 @@ hr {
}
}

/* Broadcasts */
.msg-broadcast {
background-color: $color-chat-emphasize;
color: $color-text-broadcast !important;
font-size: 1.1em;
font-weight: 400;
padding-top: $gutter-md;
padding-bottom: $gutter-md;
time {
margin-right: $gutter-md;
}
}

.msg-donation,
.msg-subscription,
.msg-giftsub,
.msg-massgift {
.msg-massgift,
.msg-broadcast {
text-shadow: 1px 1px 3px rgba(0, 0, 0, 1);
font-size: 1.1em;
font-weight: 400;
Expand Down Expand Up @@ -614,6 +602,18 @@ hr {
}
}

/* Broadcasts */
.msg-broadcast {
border-color: $color-text-broadcast;

.broadcast-icon {
background: transparent url('../img/icon-broadcast.png') no-repeat center
center;
background-size: contain;
filter: invert(100%);
}
}

/* Donations */
.msg-donation {
.donation-icon {
Expand Down
Binary file added assets/chat/img/icon-broadcast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 39 additions & 30 deletions assets/chat/js/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ class Chat {
MessageTypes.GIFTSUB,
MessageTypes.MASSGIFT,
MessageTypes.DONATION,
MessageTypes.BROADCAST,
MessageTypes.DEATH,
].includes(message.type)
) {
Expand All @@ -748,25 +749,21 @@ class Chat {
message.message.substring(0, 4).toLowerCase() === '/me ' ||
message.type === MessageTypes.DEATH;
// check if this is the current users message
message.isown = message.user.username === this.user.username;
message.isown = message.user?.username === this.user.username;
// get mentions from message
message.mentioned = Chat.extractNicks(message.message).filter((a) =>
this.users.has(a.toLowerCase()),
);
// set tagged state
message.tag = this.taggednicks.get(message.user.username);
message.tag = this.taggednicks.get(message.user?.username);
// set tagged note
message.title = this.taggednotes.get(message.user.username) || '';
message.title = this.taggednotes.get(message.user?.username) || '';
}

// Populate highlight for this $message
if (message.type === MessageTypes.USER) {
// check if the last message was from the same user
message.continued =
win.lastmessage &&
!win.lastmessage.target &&
win.lastmessage.user &&
win.lastmessage.user.username === message.user.username;
message.continued = win.isContinued(message);
// set highlighted state
message.highlighted = this.shouldHighlightMessage(message);
}
Expand Down Expand Up @@ -1080,30 +1077,32 @@ class Chat {
const textonly = Chat.removeSlashCmdFromText(data.data);
const usr = this.users.get(data.nick.toLowerCase());
const win = this.mainwindow;
if (
win.lastmessage !== null &&
const isCombo =
this.emoteService.canUserUseEmote(usr, textonly) &&
Chat.removeSlashCmdFromText(win.lastmessage.message) === textonly
) {
if (win.lastmessage.type === MessageTypes.EMOTE) {
win.lastmessage.incEmoteCount();
Chat.removeSlashCmdFromText(win.lastmessage?.message) === textonly;

if (this.user.equalWatching(usr.watching)) {
win.lastmessage.ui.classList.toggle('watching-same', true);
}
if (isCombo && win.lastmessage?.type === MessageTypes.EMOTE) {
win.lastmessage.incEmoteCount();

this.mainwindow.update();
} else {
win.removeLastMessage();
const msg = MessageBuilder.emote(textonly, data.timestamp, 2).into(
this,
);
if (this.user.equalWatching(usr.watching)) {
win.lastmessage.ui.classList.toggle('watching-same', true);
}

if (this.user.equalWatching(usr.watching)) {
msg.ui.classList.add('watching-same');
}
this.mainwindow.update();
return;
}

if (isCombo && win.lastmessage?.type === MessageTypes.USER) {
win.removeLastMessage();
const msg = MessageBuilder.emote(textonly, data.timestamp, 2).into(this);

if (this.user.equalWatching(usr.watching)) {
msg.ui.classList.add('watching-same');
}
} else if (!this.resolveMessage(data.nick, data.data)) {
return;
}

if (!this.resolveMessage(data.nick, data.data)) {
MessageBuilder.message(data.data, usr, data.timestamp).into(this);
}
}
Expand Down Expand Up @@ -1263,12 +1262,22 @@ class Chat {
if (!this.backlogloading) {
const retryMilli = Math.floor(Math.random() * 30000) + 4000;
setTimeout(() => window.location.reload(true), retryMilli);

MessageBuilder.broadcast(
`Restart incoming in ${Math.round(retryMilli / 1000)} seconds ...`,
`Restart incoming in ${Math.round(retryMilli / 1000)} seconds...`,
new ChatUser({
nick: 'System',
id: -1,
}),
data.timestamp,
).into(this);
}
} else {
MessageBuilder.broadcast(data.data, data.timestamp).into(this);
MessageBuilder.broadcast(
data.data,
new ChatUser(data.user),
data.timestamp,
).into(this);
}
}

Expand Down Expand Up @@ -2360,7 +2369,7 @@ class Chat {
}

static removeSlashCmdFromText(msg) {
return msg.replace(regexslashcmd, '').trim();
return msg?.replace(regexslashcmd, '').trim();
}

static extractNicks(text) {
Expand Down
2 changes: 2 additions & 0 deletions assets/chat/js/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class ChatUserFocus {
} else if (this.chat.settings.get('focusmentioned')) {
rule = `
.msg-death[data-username="${value}"], .msg-death[data-mentioned~="${value}"],
.msg-broadcast[data-username="${value}"], .msg-broadcast[data-mentioned~="${value}"],
.msg-subscription[data-username="${value}"], .msg-subscription[data-mentioned~="${value}"],
.msg-giftsub[data-username="${value}"], .msg-giftsub[data-mentioned~="${value}"], .msg-giftsub[data-giftee="${value}"],
.msg-massgift[data-username="${value}"], .msg-massgift[data-mentioned~="${value}"],
Expand All @@ -60,6 +61,7 @@ class ChatUserFocus {
} else {
rule = `
.msg-death[data-username="${value}"],
.msg-broadcast[data-username="${value}"],
.msg-subscription[data-username="${value}"],
.msg-giftsub[data-username="${value}"], .msg-giftsub[data-giftee="${value}"],
.msg-massgift[data-username="${value}"],
Expand Down
11 changes: 9 additions & 2 deletions assets/chat/js/formatters/UrlFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ export default class UrlFormatter {
const decodedUrl = self.elem.html(url).text();
const m = decodedUrl.match(self.linkregex);
if (m) {
const encodedUrl = self.encodeUrl(m[0]);
const normalizedUrl = this.normalizeUrl(encodedUrl);
const normalizedUrl = self.encodeUrl(this.normalizeUrl(m[0]));

let embedHashLink = '';
try {
Expand Down Expand Up @@ -114,6 +113,14 @@ export default class UrlFormatter {
return url.split('?')[0];
}

if (/youtu(?:be\.com|\.be)/i.test(url)) {
// Same as with xeets, remove the nasty share tracking query param
// from YouTube links.
const ytLink = new URL(url);
ytLink.searchParams.delete('si');
return ytLink.href;
}

return url;
}
}
32 changes: 31 additions & 1 deletion assets/chat/js/formatters/UrlFormatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,37 @@ describe('Normalizing URLs', () => {
);
});

test("Don't modify a URL that isn't Twitter or X", () => {
test('Remove the share tracking query param from a youtube.com link', () => {
expect(
urlFormatter.normalizeUrl(
'https://www.youtube.com/live/2NjXXQYtUNY?si=5ALpT28ptRec6T7u&t=70',
),
).toBe('https://www.youtube.com/live/2NjXXQYtUNY?t=70');
});

test('Remove the share tracking query param from a youtu.be link', () => {
expect(
urlFormatter.normalizeUrl(
'https://youtu.be/SbPP1i6INPk?si=K0qpdHBGOIJ-gBMK&t=60',
),
).toBe('https://youtu.be/SbPP1i6INPk?t=60');
});

test("Don't modify a youtube.com link that doesn't contain the share tracking query param", () => {
expect(
urlFormatter.normalizeUrl(
'https://www.youtube.com/live/2NjXXQYtUNY?t=70',
),
).toBe('https://www.youtube.com/live/2NjXXQYtUNY?t=70');
});

test("Don't modify a youtu.be link that doesn't contain the share tracking query param", () => {
expect(urlFormatter.normalizeUrl('https://youtu.be/SbPP1i6INPk?t=60')).toBe(
'https://youtu.be/SbPP1i6INPk?t=60',
);
});

test("Don't modify a URL that isn't Twitter, X or YouTube", () => {
expect(
urlFormatter.normalizeUrl('https://www.twitch.tv/search?term=vtuber'),
).toBe('https://www.twitch.tv/search?term=vtuber');
Expand Down
21 changes: 21 additions & 0 deletions assets/chat/js/menus/ChatUserInfoMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export default class ChatUserInfoMenu extends ChatMenuFloating {

this.header = this.ui.find('.toolbar span');

this.watchingSubheader = this.ui.find(
'.user-info h5.watching-subheader',
)[0];

this.createdDateSubheader = this.ui.find('.user-info h5.date-subheader')[0];

this.tagSubheader = this.ui.find('.user-info h5.tag-subheader')[0];
Expand Down Expand Up @@ -237,6 +241,15 @@ export default class ChatUserInfoMenu extends ChatMenuFloating {
const tagNote = this.chat.taggednotes.get(this.clickedNick);
const usernameFeatures = selectedUser.classList.value;

const watchingEmbed = this.buildWatchingEmbed(this.clickedNick);
if (watchingEmbed !== '') {
this.watchingSubheader.style.display = '';
this.watchingSubheader.replaceChildren('Watching: ', watchingEmbed);
} else {
this.watchingSubheader.style.display = 'none';
this.watchingSubheader.replaceChildren();
}

const formattedDate = this.buildCreatedDate(this.clickedNick);
if (formattedDate) {
this.createdDateSubheader.style.display = '';
Expand Down Expand Up @@ -292,6 +305,14 @@ export default class ChatUserInfoMenu extends ChatMenuFloating {
this.redraw();
}

buildWatchingEmbed(nick) {
const user = this.chat.users.get(nick);
if (!user?.watching) {
return '';
}
return `${user.watching.id} on ${user.watching.platform}`;
}

buildCreatedDate(nick) {
const user = this.chat.users.get(nick.toLowerCase());
if (!user?.createdDate) {
Expand Down
64 changes: 64 additions & 0 deletions assets/chat/js/messages/ChatBroadcastMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { usernameColorFlair } from './ChatUserMessage';
import ChatEventMessage from './ChatEventMessage';
import MessageTypes from './MessageTypes';

export default class ChatBroadcastMessage extends ChatEventMessage {
constructor(message, user, timestamp = null) {
super(message, timestamp);
this.type = MessageTypes.BROADCAST;
this.user = user;
}

buildUserTemplate(chat = null) {
if (this.user.isSystem()) {
return [];
}

const colorFlair = usernameColorFlair(chat.flairs, this.user);

/** @type HTMLAnchorElement */
const user = document
.querySelector('#user-template')
?.content.cloneNode(true).firstElementChild;
user.title = this.title;
user.classList.add(colorFlair?.name);
user.innerText = this.user.displayName;

const ctrl = document.createElement('span');
ctrl.classList.toggle('ctrl');

if (this.slashme) {
return [user, ctrl, ' '];
}

ctrl.innerText = ': ';

return [user, ctrl];
}

html(chat = null) {
const eventTemplate = super.html(chat);

const text = eventTemplate.querySelector('.event-bottom')?.innerHTML;
eventTemplate.querySelector('.event-bottom').remove();
eventTemplate.querySelector('.event-info').innerHTML = text;

const user = this.buildUserTemplate(chat);

eventTemplate.querySelector('.event-icon').classList.add('broadcast-icon');
eventTemplate.querySelector('.event-info').prepend(...user);

const classes = Array.from(eventTemplate.classList);
const attributes = eventTemplate
.getAttributeNames()
.reduce((object, attributeName) => {
if (attributeName === 'class') return object;
return {
...object,
[attributeName]: eventTemplate.getAttribute(attributeName),
};
}, {});

return this.wrap(eventTemplate.innerHTML, classes, attributes);
}
}
2 changes: 1 addition & 1 deletion assets/chat/js/messages/ChatEventMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default class ChatEventMessage extends ChatMessage {
.querySelector('#event-template')
?.content.cloneNode(true).firstElementChild;

if (this.user && this.user.username)
if (this.user && this.user.username && !this.user.isSystem())
eventTemplate.dataset.username = this.user.username;
if (this.mentioned && this.mentioned.length > 0)
eventTemplate.dataset.mentioned = this.mentioned.join(' ').toLowerCase();
Expand Down
11 changes: 11 additions & 0 deletions assets/chat/js/messages/ChatMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,15 @@ export default class ChatMessage extends ChatUIMessage {
user.equalWatching(this.watching),
);
}

/**
* @param {boolean} isContinued
*/
setContinued(isContinued) {
this.ui.classList.toggle('msg-continue', isContinued);
const ctrl = this.ui.querySelector('.ctrl');
if (ctrl) ctrl.textContent = isContinued ? '' : ': ';

this.continued = isContinued;
}
}
5 changes: 3 additions & 2 deletions assets/chat/js/messages/MessageBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ChatDonationMessage from './ChatDonationMessage';
import ChatRegularSubscriptionMessage from './subscriptions/ChatRegularSubscriptionMessage';
import ChatGiftedSubscriptionMessage from './subscriptions/ChatGiftedSubscriptionMessage';
import ChatMassSubscriptionMessage from './subscriptions/ChatMassSubscriptionMessage';
import ChatBroadcastMessage from './ChatBroadcastMessage';
import ChatDeathMessage from './ChatDeathMessage';

export default class MessageBuilder {
Expand All @@ -28,8 +29,8 @@ export default class MessageBuilder {
return new ChatMessage(message, timestamp, MessageTypes.INFO);
}

static broadcast(message, timestamp = null) {
return new ChatMessage(message, timestamp, MessageTypes.BROADCAST);
static broadcast(message, user, timestamp = null) {
return new ChatBroadcastMessage(message, user, timestamp);
}

static command(message, timestamp = null) {
Expand Down
Loading

0 comments on commit 63b32bd

Please sign in to comment.