mirror of
https://github.com/hack-chat/main.git
synced 2024-03-22 13:20:33 +08:00
syncing dev changes
This commit is contained in:
parent
85b9ad08bb
commit
7b7a511b8d
|
@ -353,7 +353,9 @@ function join(channel) {
|
|||
var args = JSON.parse(message.data);
|
||||
var cmd = args.cmd;
|
||||
var command = COMMANDS[cmd];
|
||||
command.call(null, args);
|
||||
if (command) {
|
||||
command.call(null, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,9 +370,9 @@ var COMMANDS = {
|
|||
info: function (args) {
|
||||
args.nick = '*';
|
||||
pushMessage(args);
|
||||
},
|
||||
},
|
||||
|
||||
emote: function (args) {
|
||||
emote: function (args) {
|
||||
args.nick = '*';
|
||||
pushMessage(args);
|
||||
},
|
||||
|
@ -410,6 +412,30 @@ var COMMANDS = {
|
|||
if ($('#joined-left').checked) {
|
||||
pushMessage({ nick: '*', text: nick + " left" });
|
||||
}
|
||||
},
|
||||
|
||||
captcha: function (args) {
|
||||
var messageEl = document.createElement('div');
|
||||
messageEl.classList.add('info');
|
||||
|
||||
|
||||
var nickSpanEl = document.createElement('span');
|
||||
nickSpanEl.classList.add('nick');
|
||||
messageEl.appendChild(nickSpanEl);
|
||||
|
||||
var nickLinkEl = document.createElement('a');
|
||||
nickLinkEl.textContent = '#';
|
||||
nickSpanEl.appendChild(nickLinkEl);
|
||||
|
||||
var textEl = document.createElement('pre');
|
||||
textEl.style.fontSize = '4px';
|
||||
textEl.classList.add('text');
|
||||
textEl.innerHTML = args.text;
|
||||
|
||||
messageEl.appendChild(textEl);
|
||||
$('#messages').appendChild(messageEl);
|
||||
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,7 +473,13 @@ function pushMessage(args) {
|
|||
|
||||
if (args.trip) {
|
||||
var tripEl = document.createElement('span');
|
||||
tripEl.textContent = args.trip + " ";
|
||||
|
||||
if (args.mod) {
|
||||
tripEl.textContent = String.fromCodePoint(11088) + " " + args.trip + " ";
|
||||
} else {
|
||||
tripEl.textContent = args.trip + " ";
|
||||
}
|
||||
|
||||
tripEl.classList.add('trip');
|
||||
nickSpanEl.appendChild(tripEl);
|
||||
}
|
||||
|
@ -456,6 +488,10 @@ function pushMessage(args) {
|
|||
var nickLinkEl = document.createElement('a');
|
||||
nickLinkEl.textContent = args.nick;
|
||||
|
||||
if (args.color && /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(args.color)) {
|
||||
nickLinkEl.setAttribute('style', 'color:#' + args.color + ' !important');
|
||||
}
|
||||
|
||||
nickLinkEl.onclick = function () {
|
||||
insertAtCursor("@" + args.nick + " ");
|
||||
$('#chatinput').focus();
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
isAdmin,
|
||||
isModerator,
|
||||
levels,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC';
|
||||
|
||||
// module main
|
||||
|
@ -24,6 +25,16 @@ export async function run({
|
|||
// find targets current connections
|
||||
const targetMod = server.findSockets({ trip: payload.trip });
|
||||
if (targetMod.length !== 0) {
|
||||
// build update notice with new privileges
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetMod[0]),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
uType: 'user', // @todo use legacyLevelToLabel from _LegacyFunctions.js
|
||||
level: levels.default,
|
||||
},
|
||||
};
|
||||
|
||||
for (let i = 0, l = targetMod.length; i < l; i += 1) {
|
||||
// downgrade privilages
|
||||
targetMod[i].uType = 'user';
|
||||
|
@ -35,6 +46,14 @@ export async function run({
|
|||
text: 'You are now a user.',
|
||||
channel: targetMod[i].channel, // @todo Multichannel
|
||||
}, targetMod[i]);
|
||||
|
||||
// notify channel
|
||||
server.broadcast({
|
||||
...updateNotice,
|
||||
...{
|
||||
channel: targetMod[i].channel,
|
||||
},
|
||||
}, { channel: targetMod[i].channel });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
Description: Allows calling client to change their nickname color
|
||||
*/
|
||||
|
||||
import {
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC';
|
||||
|
||||
// module support functions
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
// module main
|
||||
export async function run({
|
||||
core, server, socket, payload,
|
||||
server, socket, payload,
|
||||
}) {
|
||||
const channel = socket.channel;
|
||||
|
||||
|
@ -35,9 +39,9 @@ export async function run({
|
|||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
socket.color = false;
|
||||
socket.color = false; // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
socket.color = newColor;
|
||||
socket.color = newColor; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
|
@ -46,7 +50,7 @@ export async function run({
|
|||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
|
|
|
@ -85,7 +85,7 @@ export async function run({
|
|||
cmd: 'updateUser',
|
||||
nick: newNick,
|
||||
channel, // @todo Multichannel
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// build join and leave notices for legacy clients
|
||||
|
@ -114,7 +114,7 @@ export async function run({
|
|||
server.send(joinNotice, peerList[i]);
|
||||
} else {
|
||||
// send update info
|
||||
// @todo this should be sent to every channel the client is in
|
||||
// @todo this should be sent to every channel the client is in (multichannel)
|
||||
server.send(updateNotice, peerList[i]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ export async function run({
|
|||
|
||||
// get trip and level
|
||||
const { trip, level } = getUserPerms(pass, core.config, channel);
|
||||
|
||||
|
||||
// store the user values
|
||||
const userInfo = {
|
||||
nick,
|
||||
|
@ -122,7 +122,7 @@ export async function run({
|
|||
// send join announcement and prep online set reply
|
||||
for (let i = 0, l = newPeerList.length; i < l; i += 1) {
|
||||
server.reply(joinAnnouncement, newPeerList[i]);
|
||||
|
||||
|
||||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
@deprecated This module will be removed or replaced
|
||||
*/
|
||||
import {
|
||||
verifyNickname,
|
||||
getUserPerms,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC';
|
||||
|
||||
|
@ -104,7 +102,7 @@ export async function run({ server, socket, payload }) {
|
|||
channel: payload.channel,
|
||||
isme: true,
|
||||
},
|
||||
...getUserDetails(socket)
|
||||
...getUserDetails(socket),
|
||||
});
|
||||
|
||||
// reply with new user list
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
} from '../utility/_LegacyFunctions';
|
||||
|
||||
// module support functions
|
||||
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
|
|
|
@ -9,12 +9,45 @@
|
|||
import {
|
||||
isModerator,
|
||||
} from '../utility/_UAC';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
legacyInviteReply,
|
||||
legacyWhisperReply,
|
||||
} from '../utility/_LegacyFunctions';
|
||||
|
||||
// module support functions
|
||||
/**
|
||||
* Returns the channel that should be invited to.
|
||||
* @param {any} channel
|
||||
* @return {string}
|
||||
*/
|
||||
export function getChannel(channel = undefined) {
|
||||
if (typeof channel === 'string') {
|
||||
return channel;
|
||||
}
|
||||
return Math.random().toString(36).substr(2, 8);
|
||||
}
|
||||
|
||||
const parseText = (text) => {
|
||||
// verifies user input is text
|
||||
if (typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sanitizedText = text;
|
||||
|
||||
// strip newlines from beginning and end
|
||||
sanitizedText = sanitizedText.replace(/^\s*\n|^\s+$|\n\s*$/g, '');
|
||||
// replace 3+ newlines with just 2 newlines
|
||||
sanitizedText = sanitizedText.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return sanitizedText;
|
||||
};
|
||||
|
||||
// module constructor
|
||||
export function init(core) {
|
||||
|
@ -101,24 +134,31 @@ export function chatCheck({
|
|||
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// build fake chat payload
|
||||
const mutedPayload = {
|
||||
const outgoingPayload = {
|
||||
cmd: 'chat',
|
||||
nick: socket.nick,
|
||||
nick: socket.nick, /* @legacy */
|
||||
uType: socket.uType, /* @legacy */
|
||||
userid: socket.userid,
|
||||
channel: socket.channel,
|
||||
text: payload.text,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
level: socket.level,
|
||||
};
|
||||
|
||||
if (socket.trip) {
|
||||
mutedPayload.trip = socket.trip;
|
||||
outgoingPayload.trip = socket.trip;
|
||||
}
|
||||
|
||||
if (socket.color) {
|
||||
outgoingPayload.color = socket.color;
|
||||
}
|
||||
|
||||
// broadcast to any duplicate connections in channel
|
||||
server.broadcast(mutedPayload, { channel: socket.channel, hash: socket.hash });
|
||||
server.broadcast(outgoingPayload, { channel: socket.channel, hash: socket.hash });
|
||||
|
||||
// broadcast to allies, if any
|
||||
if (core.muzzledHashes[socket.hash].allies) {
|
||||
server.broadcast(
|
||||
mutedPayload,
|
||||
outgoingPayload,
|
||||
{
|
||||
channel: socket.channel,
|
||||
nick: core.muzzledHashes[socket.hash].allies,
|
||||
|
@ -140,24 +180,62 @@ export function chatCheck({
|
|||
}
|
||||
|
||||
// shadow-prevent all invites from muzzled users
|
||||
export function inviteCheck({ core, socket, payload }) {
|
||||
export function inviteCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
// @todo convert to protocol 2
|
||||
/* const nickValid = Invite.checkNickname(payload.nick);
|
||||
if (nickValid !== null) {
|
||||
server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: nickValid,
|
||||
// check for spam
|
||||
if (server.police.frisk(socket.address, 2)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'You are sending invites too fast. Wait a moment before trying again.',
|
||||
id: Errors.Invite.RATELIMIT,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// verify user input
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
if (typeof socket.channel === 'undefined' || typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
} else if (typeof payload.userid !== 'number' || typeof payload.channel !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Verify this socket is part of payload.channel - multichannel patch
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate common channel
|
||||
const channel = Invite.getChannel();
|
||||
const channel = getChannel(payload.to);
|
||||
|
||||
// send fake reply
|
||||
server.reply(Invite.createSuccessPayload(payload.nick, channel), socket); */
|
||||
// build invite
|
||||
const outgoingPayload = {
|
||||
cmd: 'invite',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
inviteChannel: channel,
|
||||
};
|
||||
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyInviteReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -169,27 +247,56 @@ export function inviteCheck({ core, socket, payload }) {
|
|||
export function whisperCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (core.muzzledHashes[socket.hash]) {
|
||||
const targetNick = payload.nick;
|
||||
// if this is a legacy client add missing params to payload
|
||||
if (socket.hcProtocol === 1) {
|
||||
payload.channel = socket.channel; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
server.reply({
|
||||
cmd: 'info',
|
||||
type: 'whisper',
|
||||
text: `You whispered to @${targetNick}: ${payload.text}`,
|
||||
// verify user input
|
||||
const text = parseText(payload.text);
|
||||
|
||||
if (!text) {
|
||||
// lets not send objects or empty text, yea?
|
||||
return server.police.frisk(socket.address, 13);
|
||||
}
|
||||
|
||||
// check for spam
|
||||
const score = text.length / 83 / 4;
|
||||
if (server.police.frisk(socket.address, score)) {
|
||||
return server.reply({
|
||||
cmd: 'warn', // @todo Add numeric error code as `id`
|
||||
text: 'You are sending too much text. Wait a moment and try again.\nPress the up arrow key to restore your last message.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
const outgoingPayload = {
|
||||
cmd: 'whisper',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
from: socket.userid,
|
||||
to: targetUser.userid,
|
||||
text,
|
||||
};
|
||||
|
||||
// blanket "spam" protection, may expose the ratelimiting lines from
|
||||
// `chat` and use that, @todo one day #lazydev
|
||||
server.police.frisk(socket.address, 9);
|
||||
// send invite notice to this client
|
||||
if (socket.hcProtocol === 1) {
|
||||
server.reply(legacyWhisperReply(outgoingPayload, targetUser.nick), socket);
|
||||
} else {
|
||||
server.reply(outgoingPayload, socket);
|
||||
}
|
||||
|
||||
targetUser.whisperReply = socket.nick;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
162
server/src/commands/mod/forcecolor.js
Normal file
162
server/src/commands/mod/forcecolor.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
Description: Forces a change on the target socket's nick color
|
||||
*/
|
||||
|
||||
import {
|
||||
isModerator,
|
||||
getUserDetails,
|
||||
} from '../utility/_UAC';
|
||||
import {
|
||||
Errors,
|
||||
} from '../utility/_Constants';
|
||||
import {
|
||||
findUser,
|
||||
} from '../utility/_Channels';
|
||||
|
||||
const verifyColor = (color) => /(^[0-9A-F]{6}$)|(^[0-9A-F]{3}$)/i.test(color);
|
||||
|
||||
// module main
|
||||
export async function run({
|
||||
server, socket, payload,
|
||||
}) {
|
||||
// increase rate limit chance and ignore if not admin or mod
|
||||
if (!isModerator(socket.level)) {
|
||||
return server.police.frisk(socket.address, 10);
|
||||
}
|
||||
|
||||
const channel = socket.channel;
|
||||
if (typeof payload.channel === 'undefined') {
|
||||
payload.channel = channel;
|
||||
}
|
||||
|
||||
// check user input
|
||||
if (typeof payload.nick !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof payload.color !== 'string') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure requested nickname meets standards
|
||||
const newColor = payload.color.trim().toUpperCase().replace(/#/g, '');
|
||||
if (newColor !== 'RESET' && !verifyColor(newColor)) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Invalid color! Color must be in hex value',
|
||||
channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// find target user
|
||||
const targetUser = findUser(server, payload);
|
||||
if (!targetUser) {
|
||||
return server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Could not find user in that channel',
|
||||
id: Errors.Global.UNKNOWN_USER,
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
}
|
||||
|
||||
// TODO: Change this uType to use level / uac
|
||||
// i guess coloring mods or admins isn't the best idea?
|
||||
if (targetUser.uType !== 'user') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newColor === 'RESET') {
|
||||
targetUser.color = false;
|
||||
} else {
|
||||
targetUser.color = newColor;
|
||||
}
|
||||
|
||||
// build update notice with new color
|
||||
const updateNotice = {
|
||||
...getUserDetails(targetUser),
|
||||
...{
|
||||
cmd: 'updateUser',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
},
|
||||
};
|
||||
|
||||
// notify channel that the user has changed their name
|
||||
// @todo this should be sent to every channel the user is in (multichannel)
|
||||
server.broadcast(updateNotice, { channel: socket.channel });
|
||||
|
||||
// mod perks
|
||||
// TODO: Change this uType to use level / uac
|
||||
if (socket.uType !== 'user') {
|
||||
if (typeof server.police.records[socket.address] !== 'undefined') {
|
||||
server.police.records[socket.address].score = -50;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// module hook functions
|
||||
export function initHooks(server) {
|
||||
server.registerHook('in', 'chat', this.colorCheck.bind(this), 20);
|
||||
}
|
||||
|
||||
// hooks chat commands checking for /whisper
|
||||
export function colorCheck({
|
||||
core, server, socket, payload,
|
||||
}) {
|
||||
if (typeof payload.text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload.text.startsWith('/forcecolor ')) {
|
||||
const input = payload.text.split(' ');
|
||||
|
||||
// If there is no nickname target parameter
|
||||
if (input[1] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input[2] === undefined) {
|
||||
server.reply({
|
||||
cmd: 'warn',
|
||||
text: 'Refer to `/help forcecolor` for instructions on how to use this command.',
|
||||
channel: socket.channel, // @todo Multichannel
|
||||
}, socket);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = input[1].replace(/@/g, '');
|
||||
|
||||
this.run({
|
||||
core,
|
||||
server,
|
||||
socket,
|
||||
payload: {
|
||||
cmd: 'forcecolor',
|
||||
nick: target,
|
||||
color: input[2],
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
// module meta
|
||||
export const requiredData = ['nick', 'color'];
|
||||
export const info = {
|
||||
name: 'forcecolor',
|
||||
description: 'Forces a user nick to become a certain color',
|
||||
usage: `
|
||||
API: { cmd: 'forcecolor', nick: '<target nick>', color: '<color as hex>' }
|
||||
Text: /forcecolor <target nick> <color as hex>`,
|
||||
};
|
|
@ -78,7 +78,6 @@ export async function run({ server, socket, payload }) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const newPeerList = server.findSockets({ channel: payload.channel });
|
||||
const moveAnnouncement = {
|
||||
...getUserDetails(badClient),
|
||||
|
@ -95,7 +94,7 @@ export async function run({ server, socket, payload }) {
|
|||
nicks.push(newPeerList[i].nick); /* @legacy */
|
||||
users.push({
|
||||
...{
|
||||
channel,
|
||||
channel: payload.channel,
|
||||
isme: false,
|
||||
},
|
||||
...getUserDetails(newPeerList[i]),
|
||||
|
@ -114,7 +113,7 @@ export async function run({ server, socket, payload }) {
|
|||
cmd: 'onlineSet',
|
||||
nicks, /* @legacy */
|
||||
users,
|
||||
channel, // @todo Multichannel (?)
|
||||
channel: payload.channel, // @todo Multichannel (?)
|
||||
}, badClient);
|
||||
|
||||
badClient.channel = payload.channel;
|
||||
|
|
|
@ -61,7 +61,7 @@ export function legacyLevelToLabel(level) {
|
|||
* @param {string} nick Sender nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyInviteOut(payload, nick) {
|
||||
export function legacyInviteOut(payload, nick) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
|
@ -99,7 +99,7 @@ export function legacyInviteReply(payload, nick) {
|
|||
* @param {string} nick Sender nick
|
||||
* @return {object}
|
||||
*/
|
||||
export function legacyWhisperOut(payload, from) {
|
||||
export function legacyWhisperOut(payload, from) {
|
||||
return {
|
||||
...payload,
|
||||
...{
|
||||
|
|
Loading…
Reference in New Issue
Block a user