Skip to content
Draft
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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
showdown/*
*.d.ts
5 changes: 1 addition & 4 deletions chat.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
/**
* @warning Use `formatText` from Tools instead!
*/
export default function formatText(input: string): string;
export function formatText(str: string, isTrusted?: boolean | undefined, replaceLinebreaks?: boolean | undefined): string;
9 changes: 8 additions & 1 deletion chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* @license MIT
*/

const toID = text => text.toLowerCase().replace(/[^a-z0-9]/g, '');

const linkRegex =
/(?:(?:https?:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*|www\.[a-z0-9-]+(?:\.[a-z0-9-]+)+|\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:(?:com?|org|net|edu|info|us|jp)\b|[a-z]{2,3}(?=:[0-9]|\/)))(?::[0-9]+)?(?:\/(?:(?:[^\s()&<>]|&amp;|&quot;|\((?:[^\\s()<>&]|&amp;)*\))*(?:[^\s()[\]{}".,!?;:&<>*`^~\\]|\((?:[^\s()<>&]|&amp;)*\)))?)?|[a-z0-9.]+@[a-z0-9-]+(?:\.[a-z0-9-]+)*\.[a-z]{2,})(?![^ ]*&gt;)/gi;

Expand Down Expand Up @@ -420,7 +422,12 @@ class TextFormatter {

/**
* Takes a string and converts it to HTML by replacing standard chat formatting with the appropriate HTML tags.
* @warning Use `formatText` from Tools instead!
* @param str {string}
* @param isTrusted {boolean=}
* @param replaceLinebreaks {boolean=}
* @returns string
*/
module.exports = function formatText(str, isTrusted = false, replaceLinebreaks = false) {
exports.formatText = function formatText(str, isTrusted = false, replaceLinebreaks = false) {
return new TextFormatter(str, isTrusted, replaceLinebreaks).get();
};
10 changes: 0 additions & 10 deletions classes/common.d.ts

This file was deleted.

154 changes: 115 additions & 39 deletions classes/message.d.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,135 @@
import type { HTMLopts } from './common.d.ts';
import type { Client } from '../client.d.ts';
import type Room from './room.d.ts';
import type User from './user.d.ts';

type MessageOpts = {
by: string;
text: string;
type: 'chat' | 'pm';
target: string;
raw: string;
isIntro: boolean;
parent: Client;
time: void | number;
};

export default class Message {
/**
* @import { Ranks, HTML, HTMLOpts } from '../types/common.d.ts';
* @import { Client } from '../client.js';
*/
export class Message {
/**
* @constructor
* @param input {{
* by: string;
* text: string;
* type: 'chat' | 'pm';
* target: string;
* raw: string;
* isIntro: boolean;
* parent: Client;
* isHidden?: boolean;
* time?: number;
* }}
*/
constructor(input: {
by: string;
text: string;
type: 'chat' | 'pm';
target: string;
raw: string;
isIntro: boolean;
parent: Client;
isHidden?: boolean;
time?: number;
});
/**
* Author of the message.
* @type User
* @example User(PartMan)
*/
author: User;
/**
* Full message content.
* @type string
* @example 'Hi!'
*/
content: string;
/**
* Message string, as-received from the server.
* @type string
* @example '|c|+PartMan|Hi!'
*/
raw: string;
/**
* Client that received the message.
* @type Client
*/
parent: Client;
msgRank: HTMLopts['rank'] | ' ';
/**
* The rank of the author that sent the message.
* @type {Ranks | ' '}
* @example '+'
*/
msgRank: Ranks | ' ';
/**
* The command of the message. '/botmsg' will only be set as the command if no other command was used.
* @type {string | null}
* @example '!dt'
*/
command: string | null;
/**
* Whether the message was received before joining (eg: via history). These messages
* will not be emitted if scrollback is not explicitly enabled.
* @type boolean
*/
isIntro: boolean;
/**
* Whether this message fulfilled a waiting condition. See User/Room's `waitFor` for more info.
* @see {User.waitFor}
* @see {Room.waitFor}
* @type boolean
*/
awaited: boolean;
/**
* UNIX timestamp that the message was received at.
* @type number
*/
time: number;

/**
* Chatrooms have 'chat', while PMs have 'pm'. Some methods/properties change accordingly.
* @type { 'chat' | 'pm' }
*/
type: 'chat' | 'pm';
target: 'chat' extends this['type'] ? Room : 'pm' extends this['type'] ? User : never;
isHidden: 'pm' extends this['type'] ? boolean : never;

constructor(input: MessageOpts);

/**
* Responds to the message
* @param text The text to respond (to the message) with
* @returns A promise that resolves when the message is sent successfully
* The room / DM in which the message was sent. For PMs, this is always the non-client
* User, not necessarily the author of the message!
* @type { Room | User | never }
*/
target: Room | User | never;
/**
* Whether the message is hidden (eg: `/botmsg`).
* @type {boolean}
*/
isHidden: boolean;
/**
* Responds to the message.
* @param text {string} The text to respond (to the message) with.
* @returns {Promise<Message>} A promise that resolves when the message is sent successfully.
*/
reply(text: string): Promise<Message>;

/**
* Privately responds to the message
* @param text The text to privately respond (to the message) with
* Privately responds to the message.
* @param text {string} The text to privately respond (to the message) with.
* @returns void
*/
privateReply(text: string): void;

/**
* Sends HTML in the message context (chatroom for 'chat', PMs for 'pm')
* @param html The HTML to send
* @param opts HTML options. If a string is passed, it is used as HTMLopts.name.
* Sends HTML in the message context (chatroom for 'chat', PMs for 'pm').
* @param html {HTML} The HTML to send.
* @param opts {HTMLOpts} HTML options. If a string is passed, it is used as HTMLOpts.name.
* @returns {HTML | string} Returns HTML only if `opts.notransform` is true.
*/
sendHTML(html: any, opts?: HTMLopts | string): boolean;

sendHTML(html: HTML, opts: HTMLOpts): HTML | string;
/**
* Privately sends HTML in the message context (chatroom for 'chat', PMs for 'pm')
* @param html The HTML to send
* @param opts HTML options. If a string is passed, it is used as HTMLopts.name.
* @param html {HTML} The HTML to send
* @param opts {HTMLOpts} HTML options. If a string is passed, it is used as HTMLOpts.name.
* @returns {HTML | string} Returns HTML only if `opts.notransform` is true.
*/
replyHTML(html: any, opts?: HTMLopts | string): boolean;
replyHTML(html: HTML, opts: HTMLOpts): HTML | string;
[customInspectSymbol](depth: any, options: any, inspect: any): any;
}
import { User } from './user.js';
import type { Client } from '../client.js';
import type { Ranks } from '../types/common.d.ts';
import { Room } from './room.js';
import type { HTML } from '../types/common.d.ts';
import type { HTMLOpts } from '../types/common.d.ts';
declare const customInspectSymbol: unique symbol;
export {};
132 changes: 124 additions & 8 deletions classes/message.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,106 @@
// @ts-check
'use strict';

const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');

const { toID } = require('../tools.js');
const { User } = require('./user.js');
const { Room } = require('./room.js');

/**
* @import { Ranks, HTML, HTMLOpts } from '../types/common.d.ts';
* @import { Client } from '../client.js';
*/

class Message {
/**
* Author of the message.
* @type User
* @example User(PartMan)
*/
author;
/**
* Full message content.
* @type string
* @example 'Hi!'
*/
content;
/**
* Message string, as-received from the server.
* @type string
* @example '|c|+PartMan|Hi!'
*/
raw;
/**
* Client that received the message.
* @type Client
*/
parent;
/**
* The rank of the author that sent the message.
* @type {Ranks | ' '}
* @example '+'
*/
msgRank;
/**
* The command of the message. '/botmsg' will only be set as the command if no other command was used.
* @type {string | null}
* @example '!dt'
*/
command;
/**
* Whether the message was received before joining (eg: via history). These messages
* will not be emitted if scrollback is not explicitly enabled.
* @type boolean
*/
isIntro;
/**
* Whether this message fulfilled a waiting condition. See User/Room's `waitFor` for more info.
* @see {User.waitFor}
* @see {Room.waitFor}
* @type boolean
*/
awaited;
/**
* UNIX timestamp that the message was received at.
* @type number
*/
time;
/**
* Chatrooms have 'chat', while PMs have 'pm'. Some methods/properties change accordingly.
* @type { 'chat' | 'pm' }
*/
type;
/**
* The room / DM in which the message was sent. For PMs, this is always the non-client
* User, not necessarily the author of the message!
* @type { Room | User | never }
*/
target;
/**
* Whether the message is hidden (eg: `/botmsg`).
* @type {boolean}
*/
isHidden;

/**
* @constructor
* @param input {{
* by: string;
* text: string;
* type: 'chat' | 'pm';
* target: string;
* raw: string;
* isIntro: boolean;
* parent: Client;
* isHidden?: boolean;
* time?: number;
* }}
*/
constructor(input) {
const { by, text, type, target, raw, isIntro, parent, time, isHidden } = input;
const msgRank = by[0];

const msgRank = /** @type {Ranks} */ (by[0]);
const byId = toID(by);
if (byId && !parent.users.get(byId)) {
parent.addUser(by);
Expand All @@ -15,7 +109,7 @@ class Message {
this.content = text;
const match = text.match(/^[/!][^ ]+/);
if (match) this.command = match[0];
else this.command = false;
else this.command = null;
this.msgRank = msgRank;
this.raw = raw;
this.parent = parent;
Expand All @@ -36,31 +130,53 @@ class Message {
this.parent.handle(new Error(`Message: Expected type chat/pm; got ${this.type}`));
}
}
/**
* Responds to the message.
* @param text {string} The text to respond (to the message) with.
* @returns {Promise<Message>} A promise that resolves when the message is sent successfully.
*/
reply(text) {
return this.target.send(text);
}
/**
* Privately responds to the message.
* @param text {string} The text to privately respond (to the message) with.
* @returns void
*/
privateReply(text) {
if (this.type !== 'chat') this.reply(text);
else {
if (this.target instanceof User) this.reply(text);
else if (this.target instanceof Room) {
const privateSend = this.target.privateSend(this.author.userid, text);
if (privateSend === false) this.author.send(text);
}
}
/**
* Sends HTML in the message context (chatroom for 'chat', PMs for 'pm').
* @param html {HTML} The HTML to send.
* @param opts {HTMLOpts} HTML options. If a string is passed, it is used as HTMLOpts.name.
* @returns {HTML | string} Returns HTML only if `opts.notransform` is true.
*/
sendHTML(html, opts) {
return this.target.sendHTML(html, opts);
}
/**
* Privately sends HTML in the message context (chatroom for 'chat', PMs for 'pm')
* @param html {HTML} The HTML to send
* @param opts {HTMLOpts} HTML options. If a string is passed, it is used as HTMLOpts.name.
* @returns {HTML | string} Returns HTML only if `opts.notransform` is true.
*/
replyHTML(html, opts) {
if (this.type === 'pm') return this.target.sendHTML(html, opts);
if (this.type === 'chat') return this.target.privateHTML(this.author.userid, html, opts);
if (this.target instanceof User) return this.target.sendHTML(html, opts);
if (this.target instanceof Room) return this.target.privateHTML(this.author.userid, html, opts);
return '';
}
[customInspectSymbol](depth, options, inspect) {
if (depth < 1) return options.stylize(`${this.title} [PS-Message]`, 'special');
if (depth < 1) return options.stylize(`${this.content} [PS-Message]`, 'special');
const logObj = {};
const keys = ['content', 'type', 'raw', 'time', 'author', 'target', 'command', 'parent', 'isIntro', 'awaited'];
keys.forEach(key => (logObj[key] = this[key]));
return `${options.stylize('PS-Message', 'special')} ${inspect(logObj, { ...options, depth: options.depth - 1 })}`;
}
}

module.exports = Message;
exports.Message = Message;
Loading
Loading