mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-27 13:08:30 +00:00
Move chat-line into its own module. Update the chat buffer code to always remove visible lines in pairs to avoid messing up rendering. Grab chat line types from webpack.
This commit is contained in:
parent
2c28afc6a6
commit
10c6c1cb87
3 changed files with 269 additions and 162 deletions
|
@ -86,7 +86,8 @@ export default class Twilight extends BaseSite {
|
||||||
Twilight.KNOWN_MODULES = {
|
Twilight.KNOWN_MODULES = {
|
||||||
simplebar: n => n.globalObserver && n.initDOMLoadedElements,
|
simplebar: n => n.globalObserver && n.initDOMLoadedElements,
|
||||||
react: n => n.Component && n.createElement,
|
react: n => n.Component && n.createElement,
|
||||||
'extension-service': n => n.extensionService
|
'extension-service': n => n.extensionService,
|
||||||
|
'chat-types': n => n.a && n.a.PostWithMention
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,47 @@ import {has} from 'utilities/object';
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
import Scroller from './scroller';
|
import Scroller from './scroller';
|
||||||
|
import ChatLine from './line';
|
||||||
|
|
||||||
|
|
||||||
|
const ChatTypes = (e => {
|
||||||
|
e[e.Post = 0] = "Post",
|
||||||
|
e[e.Action = 1] = "Action",
|
||||||
|
e[e.PostWithMention = 2] = "PostWithMention",
|
||||||
|
e[e.Ban = 3] = "Ban",
|
||||||
|
e[e.Timeout = 4] = "Timeout",
|
||||||
|
e[e.AutoModRejectedPrompt = 5] = "AutoModRejectedPrompt",
|
||||||
|
e[e.AutoModMessageRejected = 6] = "AutoModMessageRejected",
|
||||||
|
e[e.AutoModMessageAllowed = 7] = "AutoModMessageAllowed",
|
||||||
|
e[e.AutoModMessageDenied = 8] = "AutoModMessageDenied",
|
||||||
|
e[e.Connected = 9] = "Connected",
|
||||||
|
e[e.Disconnected = 10] = "Disconnected",
|
||||||
|
e[e.Reconnect = 11] = "Reconnect",
|
||||||
|
e[e.Hosting = 12] = "Hosting",
|
||||||
|
e[e.Unhost = 13] = "Unhost",
|
||||||
|
e[e.Subscription = 14] = "Subscription",
|
||||||
|
e[e.Resubscription = 15] = "Resubscription",
|
||||||
|
e[e.SubGift = 16] = "SubGift",
|
||||||
|
e[e.Clear = 17] = "Clear",
|
||||||
|
e[e.SubscriberOnlyMode = 18] = "SubscriberOnlyMode",
|
||||||
|
e[e.FollowerOnlyMode = 19] = "FollowerOnlyMode",
|
||||||
|
e[e.SlowMode = 20] = "SlowMode",
|
||||||
|
e[e.RoomMods = 21] = "RoomMods",
|
||||||
|
e[e.RoomState = 22] = "RoomState",
|
||||||
|
e[e.Raid = 23] = "Raid",
|
||||||
|
e[e.Unraid = 24] = "Unraid",
|
||||||
|
e[e.Notice = 25] = "Notice",
|
||||||
|
e[e.Info = 26] = "Info",
|
||||||
|
e[e.BadgesUpdated = 27] = "BadgesUpdated",
|
||||||
|
e[e.Purchase = 28] = "Purchase"
|
||||||
|
})({});
|
||||||
|
|
||||||
|
|
||||||
|
const NULL_TYPES = [
|
||||||
|
'Reconnect',
|
||||||
|
'RoomState',
|
||||||
|
'BadgesUpdated'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
const EVENTS = [
|
const EVENTS = [
|
||||||
|
@ -57,6 +98,7 @@ export default class ChatHook extends Module {
|
||||||
this.inject('chat');
|
this.inject('chat');
|
||||||
|
|
||||||
this.inject(Scroller);
|
this.inject(Scroller);
|
||||||
|
this.inject(ChatLine);
|
||||||
|
|
||||||
|
|
||||||
this.ChatController = this.fine.define(
|
this.ChatController = this.fine.define(
|
||||||
|
@ -69,11 +111,6 @@ export default class ChatHook extends Module {
|
||||||
n => n.showViewersList && n.onChatInputFocus
|
n => n.showViewersList && n.onChatInputFocus
|
||||||
);
|
);
|
||||||
|
|
||||||
this.ChatLine = this.fine.define(
|
|
||||||
'chat-line',
|
|
||||||
n => n.renderMessageBody
|
|
||||||
);
|
|
||||||
|
|
||||||
this.PinnedCheer = this.fine.define(
|
this.PinnedCheer = this.fine.define(
|
||||||
'pinned-cheer',
|
'pinned-cheer',
|
||||||
n => n.collapseCheer && n.saveRenderedMessageRef
|
n => n.collapseCheer && n.saveRenderedMessageRef
|
||||||
|
@ -233,10 +270,12 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
|
const ct = this.web_munch.getModule('chat-types');
|
||||||
|
this.chatTypes = ct ? ct.a : ChatTypes;
|
||||||
|
|
||||||
this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
|
this.chat.context.on('changed:chat.font-family', this.updateChatCSS, this);
|
||||||
this.chat.context.on('changed:chat.bits.stack', this.updateChatLines, this);
|
|
||||||
this.chat.context.on('changed:chat.adjustment-mode', this.updateColors, this);
|
this.chat.context.on('changed:chat.adjustment-mode', this.updateColors, this);
|
||||||
this.chat.context.on('changed:chat.adjustment-contrast', this.updateColors, this);
|
this.chat.context.on('changed:chat.adjustment-contrast', this.updateColors, this);
|
||||||
this.chat.context.on('changed:theme.is-dark', this.updateColors, this);
|
this.chat.context.on('changed:theme.is-dark', this.updateColors, this);
|
||||||
|
@ -272,6 +311,10 @@ export default class ChatHook extends Module {
|
||||||
if ( ! service._ffz_was_here )
|
if ( ! service._ffz_was_here )
|
||||||
this.wrapChatService(service.constructor);
|
this.wrapChatService(service.constructor);
|
||||||
|
|
||||||
|
const buffer = inst.chatBuffer;
|
||||||
|
if ( ! buffer._ffz_was_here )
|
||||||
|
this.wrapChatBuffer(buffer.constructor);
|
||||||
|
|
||||||
service.client.events.removeAll();
|
service.client.events.removeAll();
|
||||||
service.connectHandlers();
|
service.connectHandlers();
|
||||||
|
|
||||||
|
@ -297,107 +340,34 @@ export default class ChatHook extends Module {
|
||||||
for(const inst of instances)
|
for(const inst of instances)
|
||||||
this.fixPinnedCheer(inst);
|
this.fixPinnedCheer(inst);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const React = this.web_munch.getModule('react');
|
wrapChatBuffer(cls) {
|
||||||
|
const t = this;
|
||||||
|
|
||||||
if ( React ) {
|
cls.prototype._ffz_was_here = true;
|
||||||
const t = this,
|
|
||||||
e = React.createElement;
|
|
||||||
|
|
||||||
this.ChatLine.ready((cls, instances) => {
|
cls.prototype.toArray = function() {
|
||||||
cls.prototype.shouldComponentUpdate = function(props, state) {
|
const buf = this.buffer,
|
||||||
const show = state.alwaysShowMessage || ! props.message.deleted,
|
ct = t.chatTypes,
|
||||||
old_show = this._ffz_show;
|
target = buf.length - this.maxSize;
|
||||||
|
|
||||||
// We can't just compare props.message.deleted to this.props.message.deleted
|
if ( target > 0 ) {
|
||||||
// because the message object is the same object. So, store the old show
|
let removed = 0, last;
|
||||||
// state for later reference.
|
for(let i=0; i < target; i++)
|
||||||
this._ffz_show = show;
|
if ( buf[i] && ! NULL_TYPES.includes(ct[buf[i].type]) ) {
|
||||||
|
removed++;
|
||||||
|
last = i;
|
||||||
|
}
|
||||||
|
|
||||||
return show !== old_show ||
|
this.buffer = buf.slice(removed % 2 === 0 ? target : last);
|
||||||
//state.renderDebug !== this.state.renderDebug ||
|
} else
|
||||||
props.message !== this.props.message ||
|
// Make a shallow copy of the array because other code expects it to change.
|
||||||
props.isCurrentUserModerator !== this.props.isCurrentUserModerator ||
|
this.buffer = Array.from(buf);
|
||||||
props.showModerationIcons !== this.props.showModerationIcons ||
|
|
||||||
props.showTimestamps !== this.props.showTimestamps;
|
|
||||||
}
|
|
||||||
|
|
||||||
//const old_render = cls.prototype.render;
|
this._isDirty = false;
|
||||||
|
return this.buffer;
|
||||||
cls.prototype.render = function() {
|
|
||||||
const msg = this.props.message,
|
|
||||||
is_action = msg.type === 1,
|
|
||||||
user = msg.user,
|
|
||||||
color = t.colors.process(user.color),
|
|
||||||
room = msg.channel ? msg.channel.slice(1) : undefined,
|
|
||||||
|
|
||||||
show = this.state.alwaysShowMessage || ! this.props.message.deleted;
|
|
||||||
|
|
||||||
if ( ! msg.message && msg.messageParts )
|
|
||||||
detokenizeMessage(msg);
|
|
||||||
|
|
||||||
const tokens = t.chat.tokenizeMessage(msg),
|
|
||||||
fragment = t.chat.renderTokens(tokens, e);
|
|
||||||
|
|
||||||
return e('div', {
|
|
||||||
className: 'chat-line__message',
|
|
||||||
'data-room-id': this.props.channelID,
|
|
||||||
'data-room': room,
|
|
||||||
'data-user-id': user.userID,
|
|
||||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
|
||||||
|
|
||||||
//onClick: () => this.setState({renderDebug: ((this.state.renderDebug||0) + 1) % 3})
|
|
||||||
}, [
|
|
||||||
this.props.showTimestamps && e('span', {
|
|
||||||
className: 'chat-line__timestamp'
|
|
||||||
}, t.chat.formatTime(msg.timestamp)),
|
|
||||||
this.renderModerationIcons(),
|
|
||||||
e('span', {
|
|
||||||
className: 'chat-line__message--badges'
|
|
||||||
}, t.chat.renderBadges(msg, e)),
|
|
||||||
e('a', {
|
|
||||||
className: 'chat-author__display-name',
|
|
||||||
style: { color },
|
|
||||||
onClick: this.usernameClickHandler
|
|
||||||
}, user.userDisplayName),
|
|
||||||
user.isIntl && e('span', {
|
|
||||||
className: 'chat-author__intl-login',
|
|
||||||
style: { color },
|
|
||||||
onClick: this.usernameClickHandler
|
|
||||||
}, ` (${user.userLogin})`),
|
|
||||||
e('span', null, is_action ? ' ' : ': '),
|
|
||||||
show ?
|
|
||||||
e('span', {
|
|
||||||
className:'message',
|
|
||||||
style: is_action ? { color } : null
|
|
||||||
}, fragment)
|
|
||||||
:
|
|
||||||
e('span', {
|
|
||||||
className: 'chat-line__message--deleted',
|
|
||||||
}, e('a', {
|
|
||||||
href: '',
|
|
||||||
onClick: this.alwaysShowMessage
|
|
||||||
}, `<message deleted>`)),
|
|
||||||
|
|
||||||
/*this.state.renderDebug === 2 && e('div', {
|
|
||||||
className: 'border mg-t-05'
|
|
||||||
}, old_render.call(this)),
|
|
||||||
|
|
||||||
this.state.renderDebug === 1 && e('div', {
|
|
||||||
className: 'message--debug',
|
|
||||||
style: {
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
lineHeight: '1.1em'
|
|
||||||
}
|
|
||||||
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const inst of instances)
|
|
||||||
inst.forceUpdate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,8 +434,7 @@ export default class ChatHook extends Module {
|
||||||
for(const inst of this.PinnedCheer.instances)
|
for(const inst of this.PinnedCheer.instances)
|
||||||
inst.forceUpdate();
|
inst.forceUpdate();
|
||||||
|
|
||||||
for(const inst of this.ChatLine.instances)
|
this.chat_line.updateLines();
|
||||||
inst.forceUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -697,63 +666,4 @@ function extractCheerPrefix(parts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function detokenizeMessage(msg) {
|
|
||||||
const out = [],
|
|
||||||
parts = msg.messageParts,
|
|
||||||
l = parts.length,
|
|
||||||
emotes = {};
|
|
||||||
|
|
||||||
let idx = 0, ret, last_type = null;
|
|
||||||
|
|
||||||
for(let i=0; i < l; i++) {
|
|
||||||
const part = parts[i],
|
|
||||||
type = part.type,
|
|
||||||
content = part.content;
|
|
||||||
|
|
||||||
if ( type === 0 )
|
|
||||||
ret = content;
|
|
||||||
|
|
||||||
else if ( type === 1 )
|
|
||||||
ret = `@${content.recipient}`;
|
|
||||||
|
|
||||||
else if ( type === 2 )
|
|
||||||
ret = content.displayText;
|
|
||||||
|
|
||||||
else if ( type === 3 ) {
|
|
||||||
if ( content.cheerAmount ) {
|
|
||||||
ret = `${content.alt}${content.cheerAmount}`;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const url = (content.images.themed ? content.images.dark : content.images.sources)['1x'],
|
|
||||||
match = /\/emoticons\/v1\/(\d+)\/[\d.]+$/.exec(url),
|
|
||||||
id = match && match[1];
|
|
||||||
|
|
||||||
ret = content.alt;
|
|
||||||
|
|
||||||
if ( id ) {
|
|
||||||
const em = emotes[id] = emotes[id] || [],
|
|
||||||
offset = last_type > 0 ? 1 : 0;
|
|
||||||
em.push({startIndex: idx + offset, endIndex: idx + ret.length - 1});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( last_type > 0 )
|
|
||||||
ret = ` ${ret}`;
|
|
||||||
|
|
||||||
} else if ( type === 4 )
|
|
||||||
ret = `https://clips.twitch.tv/${content.slug}`;
|
|
||||||
|
|
||||||
if ( ret ) {
|
|
||||||
idx += ret.length;
|
|
||||||
last_type = type;
|
|
||||||
out.push(ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.message = out.join('');
|
|
||||||
msg.emotes = emotes;
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
196
src/sites/twitch-twilight/modules/chat/line.js
Normal file
196
src/sites/twitch-twilight/modules/chat/line.js
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Chat Line
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import {createElement as e} from 'utilities/dom';
|
||||||
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
|
export default class ChatLine extends Module {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
this.inject('settings');
|
||||||
|
this.inject('i18n');
|
||||||
|
this.inject('chat');
|
||||||
|
this.inject('site.fine');
|
||||||
|
this.inject('site.web_munch');
|
||||||
|
|
||||||
|
this.ChatLine = this.fine.define(
|
||||||
|
'chat-line',
|
||||||
|
n => n.renderMessageBody
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnable() {
|
||||||
|
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
||||||
|
|
||||||
|
const t = this,
|
||||||
|
React = this.web_munch.getModule('react');
|
||||||
|
if ( ! React )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const e = React.createElement;
|
||||||
|
|
||||||
|
this.ChatLine.ready((cls, instances) => {
|
||||||
|
cls.prototype.shouldComponentUpdate = function(props, state) {
|
||||||
|
const show = state.alwaysShowMessage || ! props.message.deleted,
|
||||||
|
old_show = this._ffz_show;
|
||||||
|
|
||||||
|
// We can't just compare props.message.deleted to this.props.message.deleted
|
||||||
|
// because the message object is the same object. So, store the old show
|
||||||
|
// state for later reference.
|
||||||
|
this._ffz_show = show;
|
||||||
|
|
||||||
|
return show !== old_show ||
|
||||||
|
//state.renderDebug !== this.state.renderDebug ||
|
||||||
|
props.message !== this.props.message ||
|
||||||
|
props.isCurrentUserModerator !== this.props.isCurrentUserModerator ||
|
||||||
|
props.showModerationIcons !== this.props.showModerationIcons ||
|
||||||
|
props.showTimestamps !== this.props.showTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cls.prototype.render = function() {
|
||||||
|
const msg = this.props.message,
|
||||||
|
is_action = msg.type === 1,
|
||||||
|
user = msg.user,
|
||||||
|
color = t.parent.colors.process(user.color),
|
||||||
|
room = msg.channel ? msg.channel.slice(1) : undefined,
|
||||||
|
|
||||||
|
show = this.state.alwaysShowMessage || ! this.props.message.deleted;
|
||||||
|
|
||||||
|
if ( ! msg.message && msg.messageParts )
|
||||||
|
detokenizeMessage(msg);
|
||||||
|
|
||||||
|
const tokens = t.chat.tokenizeMessage(msg),
|
||||||
|
fragment = t.chat.renderTokens(tokens, e);
|
||||||
|
|
||||||
|
const out = e('div', {
|
||||||
|
className: 'chat-line__message',
|
||||||
|
'data-room-id': this.props.channelID,
|
||||||
|
'data-room': room,
|
||||||
|
'data-user-id': user.userID,
|
||||||
|
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||||
|
|
||||||
|
//onClick: () => this.setState({renderDebug: ((this.state.renderDebug||0) + 1) % 3})
|
||||||
|
}, [
|
||||||
|
this.props.showTimestamps && e('span', {
|
||||||
|
className: 'chat-line__timestamp'
|
||||||
|
}, t.chat.formatTime(msg.timestamp)),
|
||||||
|
this.renderModerationIcons(),
|
||||||
|
e('span', {
|
||||||
|
className: 'chat-line__message--badges'
|
||||||
|
}, t.chat.renderBadges(msg, e)),
|
||||||
|
e('a', {
|
||||||
|
className: 'chat-author__display-name',
|
||||||
|
style: { color },
|
||||||
|
onClick: this.usernameClickHandler
|
||||||
|
}, user.userDisplayName),
|
||||||
|
user.isIntl && e('span', {
|
||||||
|
className: 'chat-author__intl-login',
|
||||||
|
style: { color },
|
||||||
|
onClick: this.usernameClickHandler
|
||||||
|
}, ` (${user.userLogin})`),
|
||||||
|
e('span', null, is_action ? ' ' : ': '),
|
||||||
|
show ?
|
||||||
|
e('span', {
|
||||||
|
className:'message',
|
||||||
|
style: is_action ? { color } : null
|
||||||
|
}, fragment)
|
||||||
|
:
|
||||||
|
e('span', {
|
||||||
|
className: 'chat-line__message--deleted',
|
||||||
|
}, e('a', {
|
||||||
|
href: '',
|
||||||
|
onClick: this.alwaysShowMessage
|
||||||
|
}, `<message deleted>`)),
|
||||||
|
|
||||||
|
/*this.state.renderDebug === 2 && e('div', {
|
||||||
|
className: 'border mg-t-05'
|
||||||
|
}, old_render.call(this)),
|
||||||
|
|
||||||
|
this.state.renderDebug === 1 && e('div', {
|
||||||
|
className: 'message--debug',
|
||||||
|
style: {
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
lineHeight: '1.1em'
|
||||||
|
}
|
||||||
|
}, JSON.stringify([tokens, msg.emotes], null, 2))*/
|
||||||
|
]);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const inst of instances)
|
||||||
|
inst.forceUpdate();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateLines() {
|
||||||
|
for(const inst of this.ChatLine.instances)
|
||||||
|
inst.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function detokenizeMessage(msg) {
|
||||||
|
const out = [],
|
||||||
|
parts = msg.messageParts,
|
||||||
|
l = parts.length,
|
||||||
|
emotes = {};
|
||||||
|
|
||||||
|
let idx = 0, ret, last_type = null;
|
||||||
|
|
||||||
|
for(let i=0; i < l; i++) {
|
||||||
|
const part = parts[i],
|
||||||
|
type = part.type,
|
||||||
|
content = part.content;
|
||||||
|
|
||||||
|
if ( type === 0 )
|
||||||
|
ret = content;
|
||||||
|
|
||||||
|
else if ( type === 1 )
|
||||||
|
ret = `@${content.recipient}`;
|
||||||
|
|
||||||
|
else if ( type === 2 )
|
||||||
|
ret = content.displayText;
|
||||||
|
|
||||||
|
else if ( type === 3 ) {
|
||||||
|
if ( content.cheerAmount ) {
|
||||||
|
ret = `${content.alt}${content.cheerAmount}`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const url = (content.images.themed ? content.images.dark : content.images.sources)['1x'],
|
||||||
|
match = /\/emoticons\/v1\/(\d+)\/[\d.]+$/.exec(url),
|
||||||
|
id = match && match[1];
|
||||||
|
|
||||||
|
ret = content.alt;
|
||||||
|
|
||||||
|
if ( id ) {
|
||||||
|
const em = emotes[id] = emotes[id] || [],
|
||||||
|
offset = last_type > 0 ? 1 : 0;
|
||||||
|
em.push({startIndex: idx + offset, endIndex: idx + ret.length - 1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( last_type > 0 )
|
||||||
|
ret = ` ${ret}`;
|
||||||
|
|
||||||
|
} else if ( type === 4 )
|
||||||
|
ret = `https://clips.twitch.tv/${content.slug}`;
|
||||||
|
|
||||||
|
if ( ret ) {
|
||||||
|
idx += ret.length;
|
||||||
|
last_type = type;
|
||||||
|
out.push(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.message = out.join('');
|
||||||
|
msg.emotes = emotes;
|
||||||
|
return msg;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue