diff --git a/.gitignore b/.gitignore index 4330556..5857946 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ package-lock.json node_modules -docs \ No newline at end of file +docs +storage \ No newline at end of file diff --git a/jsdoc.json b/jsdoc.json deleted file mode 100644 index 0eb82a4..0000000 --- a/jsdoc.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "tags": { - "allowUnknownTags": true - }, - "source": { - "include": ["./src"], - "includePattern": ".js$", - "excludePattern": "(node_modules/|docs)" - }, - "plugins": [ - "plugins/markdown" - ], - "opts": { - "template": "node_modules/docdash", - "encoding": "utf8", - "destination": "docs/", - "recurse": true, - "verbose": true - }, - "markdown": { - "parser": "gfm", - "hardwrap": true, - "idInHeadings": true - }, - "templates": { - "cleverLinks": false, - "monospaceLinks": false, - "default": { - "outputSourceFiles": true, - "includeDate": false, - "useLongnameInNav": true - } - }, - "docdash": { - "static": true, - "sort": true, - "search": true, - "collapse": true, - "typedefs": true, - "removeQuotes": "none", - "wrap": true, - "menu": { - "Git Repository": { - "href":"https://git.ssh.surf/mitask/linkup-bot-lib", - "target":"_blank", - "class":"menu-item", - "id":"gitrepo_link" - } - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index 8278389..8b1a3bf 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,16 @@ { "name": "linkup-bot-lib", - "version": "1.0.0", - "main": "src/Client.js", + "version": "1.1.0", + "main": "lib/Client.js", + "types": "lib/*.ts", "scripts": { - "generate-docs": "jsdoc --configure jsdoc.json" + "prepublishOnly": "typedoc", + "build": "typedoc" }, - "keywords": [], - "author": "", - "type": "module", + "keywords": [ + "linkup" + ], + "author": "LinkUp Team", "license": "ISC", "description": "", "dependencies": { @@ -18,7 +21,8 @@ "serve-drive": "^5.0.8" }, "devDependencies": { - "docdash": "^2.0.2", - "jsdoc": "^4.0.3" + "@types/b4a": "^1.6.4", + "@types/node": "^20.14.2", + "typedoc-plugin-merge-modules": "^5.1.0" } } diff --git a/src/Client.js b/src/Client.ts similarity index 61% rename from src/Client.js rename to src/Client.ts index e794f3d..6603f50 100644 --- a/src/Client.js +++ b/src/Client.ts @@ -1,34 +1,45 @@ import path from 'path'; -import Hyperswarm from 'hyperswarm'; -import EventEmitter from 'node:events'; -import b4a from "b4a"; -import TextMessage from "./message/TextMessage.js"; -import FileMessage from "./message/FileMessage.js"; -import AudioMessage from "./message/AudioMessage.js"; -import Message from "./message/Message.js"; -import IconMessage from "./message/IconMessage.js"; +import Hyperswarm, { PeerDiscovery } from 'hyperswarm'; +import TextMessage from "./message/TextMessage"; +import FileMessage from "./message/FileMessage"; +import AudioMessage from "./message/AudioMessage"; +import Message from "./message/Message"; import Corestore from 'corestore'; import Hyperdrive from 'hyperdrive'; import fs from 'fs'; +// @ts-ignore import ServeDrive from 'serve-drive'; +import IconMessage from "./message/IconMessage"; +import TypedEventEmitter from "./util/TypedEventEmitter"; /** * This class is the core component of the bot system. It handles connections to the Hyperswarm network, manages message sending and receiving, and emits events for various actions. - * @emits Client#onMessage - * @emits Client#onFile - * @emits Client#onAudio - * @emits Client#onIcon */ -class Client extends EventEmitter { +class Client extends TypedEventEmitter { + public botName: string = ""; + public servePort: number | null = 0; + public storagePath: string | undefined; + public swarm: Hyperswarm | undefined; + public drive: Hyperdrive | undefined; + public store: Corestore | undefined; + public joinedRooms: Set | undefined; + public currentTopic: string | null = null; + public botAvatar: string = ""; + public iconMessage: IconMessage | undefined; + public discovery: PeerDiscovery | undefined; + /** - * @param {String} botName The name of the bot. + * @param botName The name of the bot. * @since 1.0 * @constructor * @author snxraven */ - constructor(botName) { + constructor(botName: string) { super(); - if (!botName) return console.error("Bot Name is not defined!"); + if (!botName) { + console.error("Bot Name is not defined!"); + return; + } this.botName = botName; this.swarm = new Hyperswarm(); this.joinedRooms = new Set(); // Track the rooms the bot has joined @@ -75,6 +86,7 @@ class Client extends EventEmitter { this.servePort = this.getRandomPort(); const serve = new ServeDrive({ port: this.servePort, + // @ts-ignore get: ({ key, filename, version }) => this.drive }); await serve.ready(); @@ -87,23 +99,23 @@ class Client extends EventEmitter { * @description Returns a random port number. * @since 1.0 * @author snxraven - * @return {Number} Random port number. + * @return Random port number. */ - getRandomPort() { + getRandomPort(): number { return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152; } /** * @description Fetches and sets the bot's avatar from a local file. - * @param {String} filePath path to the local avatar file. + * @param filePath path to the local avatar file. * @since 1.0 * @author snxraven */ - async fetchAvatar(filePath) { + async fetchAvatar(filePath: string) { try { - await this.drive.ready(); + await this.drive?.ready(); const iconBuffer = fs.readFileSync(filePath); - await this.drive.put(`/icons/${this.botName}.png`, iconBuffer); + await this.drive?.put(`/icons/${this.botName}.png`, iconBuffer); this.botAvatar = `http://localhost:${this.servePort}/icons/${this.botName}.png`; // Cache the icon message @@ -119,15 +131,15 @@ class Client extends EventEmitter { * @author snxraven */ setupSwarm() { - this.swarm.on('connection', (peer) => { + this.swarm?.on('connection', (peer) => { // Send the cached icon message to the new peer if (this.iconMessage) { peer.write(this.iconMessage.toJsonString()); } - peer.on('data', async message => { + peer.on('data', async (message: {}) => { const messageObj = JSON.parse(message.toString()); - if (this.joinedRooms.has(messageObj.topic)) { // Process message only if it is from a joined room + if (this.joinedRooms?.has(messageObj.topic)) { // Process message only if it is from a joined room this.currentTopic = messageObj.topic; // Set the current topic from the incoming message const msgType = messageObj.type; @@ -137,35 +149,23 @@ class Client extends EventEmitter { if (msgType === "message") - /** - * Triggered when a new message is received. - * - * @event Client#onMessage - * @property peer - HyperSwarm peer object - * @property {TextMessage} textMessage -Class with all of the information about received text message - * @example - * const bot = new Client("MyBot"); - * bot.on('onMessage', (peer, message) => { - * console.log(`Message from ${message.peerName}: ${message.message}`); - * }); - */ - this.emit('onMessage', peer, new TextMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.message)); + this.emit('onMessage', new TextMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.message)); if (msgType === "file") { - const fileBuffer = await this.drive.get(`/files/${messageObj.fileName}`); + const fileBuffer = await this.drive?.get(`/files/${messageObj.fileName}`); /** * Triggered when a file message is received. * * @event Client#onFile * @property peer - HyperSwarm peer object - * @property {FileMessage} fileMessage - Class with all of the information about received file + * @property {FileMessage} FileMessage - Class with all of the information about received file * @example * const bot = new Client("MyBot"); * bot.on('onFile', (peer, message) => { * console.log(`Received file from ${message.peerName}`); * }); */ - this.emit('onFile', peer, new FileMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.fileName, `http://localhost:${this.servePort}/files/${messageObj.fileName}`, messageObj.fileType, messageObj.fileData)); + this.emit('onFile', new FileMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.fileName, `http://localhost:${this.servePort}/files/${messageObj.fileName}`, messageObj.fileType, messageObj.fileData)); } if (msgType === "icon") @@ -174,62 +174,65 @@ class Client extends EventEmitter { * * @event Client#onIcon * @property peer - HyperSwarm peer object - * @property {IconMessage} iconMessage - Class with all of the information about received peer icon + * @property {IconMessage} IconMessage - Class with all of the information about received peer icon * @example * const bot = new Client("MyBot"); * bot.on('onIcon', (peer, message) => { * console.log(`Received new Icon from ${message.peerName}`); * }); */ - this.emit('onIcon', peer, new IconMessage(peerName, peerAvatar, timestamp)); + this.emit('onIcon', new IconMessage(peerName, peerAvatar, timestamp)); if (msgType === "audio") { - const audioBuffer = await this.drive.get(`/audio/${messageObj.audioName}`); + const audioBuffer = await this.drive?.get(`/audio/${messageObj.audioName}`); /** * Triggered when an audio message is received. * * @event Client#onAudio * @property peer - HyperSwarm peer object - * @property {AudioMessage} audioMessage - Class with all of the information about received audio file + * @property {AudioMessage} AudioMessage - Class with all of the information about received audio file * @example + * ```js * const bot = new Client("MyBot"); * bot.on('onAudio', (peer, message) => { * console.log(`Received audio file from ${message.peerName}`); * }); + * ``` */ - this.emit('onAudio', peer, new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData)); + this.emit('onAudio', new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData)); } } }); - peer.on('error', e => { - this.emit('onError', e); - console.error(`Connection error: ${e}`); + peer.on('error', (err: any) => { + this.emit('onError', err); + console.error(`Connection error: ${err}`); }); }); - this.swarm.on('update', () => { - console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`); + // @ts-ignore + this.swarm.on("update", () => { + console.log(`Connections count: ${this.swarm?.connections.size} / Peers count: ${this.swarm?.peers.size}`); }); } /** * @description Joins a specified chat room. * @since 1.0 * @author snxraven - * @param {String} chatRoomID Chat room topic string + * @param chatRoomID Chat room topic string */ - joinChatRoom(chatRoomID) { - if (!chatRoomID || typeof chatRoomID !== 'string') { + joinChatRoom(chatRoomID: string) { + if (!chatRoomID) { console.error("Invalid chat room ID!"); return; } - this.joinedRooms.add(chatRoomID); // Add the room to the list of joined rooms + this.joinedRooms?.add(chatRoomID); // Add the room to the list of joined rooms this.currentTopic = chatRoomID; // Store the current topic - this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); - this.discovery.flushed().then(() => { + this.discovery = this.swarm?.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); + this.discovery?.flushed().then(() => { console.log(`Bot ${this.botName} joined the chat room.`); - this.emit('onBotJoinRoom'); + this.emit('onBotJoinRoom', chatRoomID); }); } @@ -237,9 +240,9 @@ class Client extends EventEmitter { * @description Sends a text message. * @since 1.0 * @author MiTask - * @param {String} message Text message to send to the bot's current chat room. + * @param message Text message to send to the bot's current chat room. */ - sendTextMessage(message) { + sendTextMessage(message: string) { console.log(`Preparing to send text message: ${message}`); this.sendMessage(TextMessage.new(this, message)); } @@ -248,15 +251,15 @@ class Client extends EventEmitter { * @description Sends a file message. * @since 1.0 * @author snxraven - * @param {String} filePath Path to the file to send. - * @param {String} fileType Type of the file to send. + * @param filePath Path to the file to send. + * @param fileType Type of the file to send. */ - async sendFileMessage(filePath, fileType) { + async sendFileMessage(filePath: string, fileType: string) { try { - await this.drive.ready(); + await this.drive?.ready(); const fileBuffer = fs.readFileSync(filePath); const fileName = path.basename(filePath); - await this.drive.put(`/files/${fileName}`, fileBuffer); + await this.drive?.put(`/files/${fileName}`, fileBuffer); const fileUrl = `http://localhost:${this.servePort}/files/${fileName}`; const fileMessage = FileMessage.new(this, fileName, fileUrl, fileType, fileBuffer); // Pass fileBuffer to the new method this.sendMessage(fileMessage); @@ -269,15 +272,15 @@ class Client extends EventEmitter { * @description Sends an audio message. * @since 1.0 * @author snxraven - * @param {String} filePath Path to the audio file to send. - * @param {String} audioType Type of the audio file to send. + * @param filePath Path to the audio file to send. + * @param audioType Type of the audio file to send. */ - async sendAudioMessage(filePath, audioType) { + async sendAudioMessage(filePath: string, audioType: string) { try { - await this.drive.ready(); + await this.drive?.ready(); const audioBuffer = fs.readFileSync(filePath); const audioName = path.basename(filePath); - await this.drive.put(`/audio/${audioName}`, audioBuffer); + await this.drive?.put(`/audio/${audioName}`, audioBuffer); const audioUrl = `http://localhost:${this.servePort}/audio/${audioName}`; const audioMessage = AudioMessage.new(this, audioUrl, audioType, audioBuffer); // Pass audioBuffer to the new method this.sendMessage(audioMessage); @@ -291,17 +294,12 @@ class Client extends EventEmitter { * @description Sends a generic message. * @since 1.0 * @author MiTask - * @param {Message} message Message class (TextMessage, FileMessage or AudioMessage) + * @param message Message class (TextMessage, FileMessage or AudioMessage) */ - sendMessage(message) { - if (!(message instanceof Message)) { - console.error(`message does not extend Message class (TextMessage, FileMessage, AudioMessage).`, message); - return; - } - + sendMessage(message: Message) { console.log("Sending message:", message); const data = message.toJsonString(); - const peers = [...this.swarm.connections]; + const peers = [...this.swarm?.connections]; if (peers.length === 0) { console.warn("No active peer connections found."); return; @@ -324,9 +322,29 @@ class Client extends EventEmitter { * @author snxraven */ async destroy() { - await this.swarm.destroy(); + await this.swarm?.destroy(); console.log(`Bot ${this.botName} disconnected.`); } } +export type LinkUpEvents = { + /** + * Triggered when a new message is received. + * + * @event Client#onMessage + * @property {TextMessage} TextMessage - Class with all of the information about received text message + * @example + * const bot = new Client("MyBot"); + * bot.on('onMessage', (message) => { + * console.log(`Message from ${message.peerName}: ${message.message}`); + * }); + */ + 'onMessage': [textMessage: TextMessage] + 'onFile': [fileMessage: FileMessage] + 'onAudio': [audioMessage: AudioMessage] + 'onIcon': [iconMessage: IconMessage] + 'onError': [error: any] + 'onBotJoinRoom': [currentTopic: string] +} + export default Client; diff --git a/src/message/AudioMessage.js b/src/message/AudioMessage.ts similarity index 55% rename from src/message/AudioMessage.js rename to src/message/AudioMessage.ts index dcd7810..662d83c 100644 --- a/src/message/AudioMessage.js +++ b/src/message/AudioMessage.ts @@ -1,21 +1,29 @@ -import Message from "./Message.js"; +/** + * @module Message + */ + +import Message from "./Message"; import b4a from "b4a"; +import Client from "../Client"; class AudioMessage extends Message { + audioUrl: string; + audioType: string; + audioData: string; /** * @description Creates a new Audio message. * @since 1.0 * @author MiTask * @constructor - * @param {String} peerName Peer username - * @param {String} peerAvatar Peer avatar URL - * @param {String} topic Chat room topic string - * @param {Number} timestamp UNIX Timestamp - * @param {String} audioUrl URL to the audio file - * @param {String} audioType Type of the audio file - * @param {String} audioData Audio file data in base64 String format + * @param peerName Peer username + * @param peerAvatar Peer avatar URL + * @param topic Chat room topic string + * @param timestamp UNIX Timestamp + * @param audioUrl URL to the audio file + * @param audioType Type of the audio file + * @param audioData Audio file data in base64 String format */ - constructor(peerName, peerAvatar, topic, timestamp, audioUrl, audioType, audioData) { + constructor(peerName: string, peerAvatar: string, topic: string | null, timestamp: number, audioUrl: string, audioType: string, audioData: string) { super("audio", peerName, peerAvatar, topic, timestamp); this.audioUrl = audioUrl; this.audioType = audioType; @@ -27,7 +35,7 @@ class AudioMessage extends Message { * @author MiTask * @returns {String} JSON String with all of the information about the message */ - toJsonString() { + toJsonString(): string { return JSON.stringify({ ...this.toJson(), audioUrl: this.audioUrl, @@ -40,13 +48,13 @@ class AudioMessage extends Message { * @description Creates a new audio message instance. * @since 1.0 * @author MiTask - * @param {Client} bot Bot Client class - * @param {String} audioUrl URL to the audio file - * @param {String} audioType Type of the audio file - * @param {Buffer} audioBuffer Audio file data + * @param bot Bot Client class + * @param audioUrl URL to the audio file + * @param audioType Type of the audio file + * @param audioBuffer Audio file data * @returns {AudioMessage} AudioMessage instance. */ - static new(bot, audioUrl, audioType, audioBuffer) { + static new(bot: Client, audioUrl: string, audioType: string, audioBuffer: Buffer): AudioMessage { const audioData = b4a.toString(audioBuffer, 'base64'); // Convert audio buffer to base64 return new AudioMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), audioUrl, audioType, audioData); } diff --git a/src/message/FileMessage.js b/src/message/FileMessage.ts similarity index 55% rename from src/message/FileMessage.js rename to src/message/FileMessage.ts index f9be0b9..ed47c06 100644 --- a/src/message/FileMessage.js +++ b/src/message/FileMessage.ts @@ -1,22 +1,31 @@ -import Message from "./Message.js"; +/** + * @module Message + */ + +import Message from "./Message"; import b4a from "b4a"; +import Client from "../Client"; class FileMessage extends Message { + fileName: string; + fileUrl: string; + fileType: string; + fileData: string; /** * @description Creates a new file message. * @since 1.0 * @author MiTask * @constructor - * @param {String} peerName Peer username - * @param {String} peerAvatar Peer avatar URL - * @param {String} topic Chat room topic string - * @param {Number} timestamp UNIX Timestamp - * @param {String} fileName File name - * @param {String} fileUrl URL to the file - * @param {String} fileType Type of the file - * @param {String} fileData File data in base64 String format + * @param peerName Peer username + * @param peerAvatar Peer avatar URL + * @param topic Chat room topic string + * @param timestamp UNIX Timestamp + * @param fileName File name + * @param fileUrl URL to the file + * @param fileType Type of the file + * @param fileData File data in base64 String format */ - constructor(peerName, peerAvatar, topic, timestamp, fileName, fileUrl, fileType, fileData) { + constructor(peerName: string, peerAvatar: string, topic: string | null, timestamp: number, fileName: string, fileUrl: string, fileType: string, fileData: string) { super("file", peerName, peerAvatar, topic, timestamp); this.fileName = fileName; this.fileUrl = fileUrl; @@ -29,7 +38,7 @@ class FileMessage extends Message { * @author MiTask * @returns {String} JSON String with all of the information about the message */ - toJsonString() { + toJsonString(): string { return JSON.stringify({ ...this.toJson(), fileName: this.fileName, @@ -43,14 +52,14 @@ class FileMessage extends Message { * @description Creates a new file message instance. * @since 1.0 * @author MiTask - * @param {Client} bot Bot Client class - * @param {String} fileName File name - * @param {String} fileUrl URL to the file - * @param {String} fileType Type of the file - * @param {Buffer} fileBuffer File data + * @param bot Bot Client class + * @param fileName File name + * @param fileUrl URL to the file + * @param fileType Type of the file + * @param fileBuffer File data * @returns {FileMessage} FileMessage instance. */ - static new(bot, fileName, fileUrl, fileType, fileBuffer) { + static new(bot: Client, fileName: string, fileUrl: string, fileType: string, fileBuffer: Buffer): FileMessage { const fileData = b4a.toString(fileBuffer, 'base64'); // Convert file buffer to base64 return new FileMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), fileName, fileUrl, fileType, fileData); } diff --git a/src/message/IconMessage.js b/src/message/IconMessage.ts similarity index 51% rename from src/message/IconMessage.js rename to src/message/IconMessage.ts index a30ab56..9df1e66 100644 --- a/src/message/IconMessage.js +++ b/src/message/IconMessage.ts @@ -1,5 +1,10 @@ -import Message from "./Message.js"; +/** + * @module Message + */ + +import Message from "./Message"; import b4a from "b4a"; +import Client from "../Client"; class IconMessage extends Message { /** @@ -7,11 +12,11 @@ class IconMessage extends Message { * @since 1.0 * @author MiTask * @constructor - * @param {String} peerName Peer username - * @param {String} peerAvatar Peer avatar URL - * @param {Number} timestamp UNIX Timestamp + * @param peerName Peer username + * @param peerAvatar Peer avatar URL + * @param timestamp UNIX Timestamp */ - constructor(peerName, peerAvatar, timestamp) { + constructor(peerName: string, peerAvatar: string, timestamp: number) { super("icon", peerName, peerAvatar, null, timestamp); } @@ -19,11 +24,11 @@ class IconMessage extends Message { * @description Creates a new icon message instance. * @since 1.0 * @author MiTask - * @param {Client} bot Bot Client class - * @param {String} avatarBuffer Bot Avatar buffer - * @returns {IconMessage} IconMessage instance + * @param bot Bot Client class + * @param avatarBuffer Bot Avatar buffer + * @returns IconMessage instance */ - static new(bot, avatarBuffer) { + static new(bot: Client, avatarBuffer: Buffer): IconMessage { return new IconMessage(bot.botName, b4a.toString(avatarBuffer, 'base64'), Date.now()); } } diff --git a/src/message/Message.js b/src/message/Message.js deleted file mode 100644 index 800a62b..0000000 --- a/src/message/Message.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @description Base class for all messages - * @since 1.0 - * @author MiTask - */ -class Message { - /** - * @since 1.0 - * @author MiTask - * @constructor - * @param {String} messageType Type of the message (text, file, audio, icon) - * @param {String} peerName Peer username - * @param {String} peerAvatar Peer avatar URL - * @param {String} topic Chat room topic string - * @param {Number} timestamp UNIX Timestamp - */ - constructor(messageType, peerName, peerAvatar, topic, timestamp) { - this.type = messageType; - this.peerName = peerName; - this.peerAvatar = peerAvatar; - this.topic = topic; - this.timestamp = timestamp; - } - - /** - * @since 1.0 - * @author MiTask - * @returns {{name: String, topic: String, avatar: String, type: String, timestamp: Number}} JSON Object with all of the information about the message - */ - toJson() { - return { - type: this.type, - name: this.peerName, - avatar: this.peerAvatar, - topic: this.topic, - timestamp: this.timestamp - }; - } - - /** - * @since 1.0 - * @author MiTask - * @returns {String} JSON String with all of the information about the message - */ - toJsonString() { - return JSON.stringify({ - ...this.toJson() - }); - } -} - -export default Message; diff --git a/src/message/Message.ts b/src/message/Message.ts new file mode 100644 index 0000000..a208cd2 --- /dev/null +++ b/src/message/Message.ts @@ -0,0 +1,59 @@ +/** + * @description Base class for all messages + * @since 1.0 + * @author MiTask + * @module Message + */ +class Message { + type: string; + peerName: string; + peerAvatar: string; + topic: string | null; + timestamp: number; + + /** + * @since 1.0 + * @author MiTask + * @constructor + * @param messageType Type of the message (text, file, audio, icon) + * @param peerName Peer username + * @param peerAvatar Peer avatar URL + * @param topic Chat room topic string + * @param timestamp UNIX Timestamp + */ + constructor(messageType: string, peerName: string, peerAvatar: string, topic: string | null, timestamp: number) { + this.type = messageType; + this.peerName = peerName; + this.peerAvatar = peerAvatar; + this.topic = topic; + this.timestamp = timestamp; + } + + /** + * @since 1.0 + * @author MiTask + * @returns JSON Object with all of the information about the message + */ + protected toJson(): {name: String, topic: String, avatar: String, type: String, timestamp: Number} { + return { + type: this.type, + name: this.peerName, + avatar: this.peerAvatar, + topic: this.topic ? this.topic : "", + timestamp: this.timestamp + }; + } + + /** + * @since 1.0 + * @author MiTask + * @returns {string} JSON String with all of the information about the message + */ + toJsonString(): string { + return JSON.stringify({ + ...this.toJson() + }); + } +} + +export default Message; diff --git a/src/message/TextMessage.js b/src/message/TextMessage.ts similarity index 56% rename from src/message/TextMessage.js rename to src/message/TextMessage.ts index 32064f5..ad08aaa 100644 --- a/src/message/TextMessage.js +++ b/src/message/TextMessage.ts @@ -1,18 +1,24 @@ -import Message from "./Message.js"; +/** + * @module Message + */ + +import Message from "./Message"; +import Client from "../Client"; class TextMessage extends Message { + message: string; /** * @description Creates a new text message. * @since 1.0 * @author MiTask * @constructor - * @param {String} peerName Peer username - * @param {String} peerAvatar Peer avatar URL - * @param {String} topic Chat room topic string - * @param {Number} timestamp UNIX Timestamp - * @param {String} message Text of the message + * @param peerName Peer username + * @param peerAvatar Peer avatar URL + * @param topic Chat room topic string + * @param timestamp UNIX Timestamp + * @param message Text of the message */ - constructor(peerName, peerAvatar, topic, timestamp, message) { + constructor(peerName: string, peerAvatar: string, topic: string | null, timestamp: number, message: string) { super("message", peerName, peerAvatar, topic, timestamp); this.message = message; } @@ -22,7 +28,7 @@ class TextMessage extends Message { * @author MiTask * @returns {String} JSON String with all of the information about the message */ - toJsonString() { + toJsonString(): string { return JSON.stringify({ ...this.toJson(), message: this.message, @@ -33,11 +39,11 @@ class TextMessage extends Message { * @description Creates a new text message instance. * @since 1.0 * @author MiTask - * @param {Client} bot Bot Client class - * @param {String} message Text of the message + * @param bot Bot Client class + * @param message Text of the message * @returns {TextMessage} TextMessage instance */ - static new(bot, message) { + static new(bot: Client, message: string): TextMessage { return new TextMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), message); } } diff --git a/src/util/TypedEventEmitter.ts b/src/util/TypedEventEmitter.ts new file mode 100644 index 0000000..7a92f39 --- /dev/null +++ b/src/util/TypedEventEmitter.ts @@ -0,0 +1,39 @@ +import {EventEmitter} from "stream"; + +/** + * This class is used for TypeSafe events. + * @internal + */ +export default class TypedEventEmitter> { + private emitter = new EventEmitter() + + /** + * @internal + */ + emit( + eventName: TEventName, + ...eventArg: TEvents[TEventName] + ) { + this.emitter.emit(eventName, ...(eventArg as [])) + } + + /** + * @internal + */ + on( + eventName: TEventName, + handler: (...eventArg: TEvents[TEventName]) => void + ) { + this.emitter.on(eventName, handler as any) + } + + /** + * @internal + */ + off( + eventName: TEventName, + handler: (...eventArg: TEvents[TEventName]) => void + ) { + this.emitter.off(eventName, handler as any) + } +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..e901e22 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,19 @@ +{ + "out": "./docs", + "includes": "./src", + "entryPoints": ["./src/**/*"], + "exclude": ["./src/util/*"], + "entryPointStrategy": "expand", + "cleanOutputDir": true, + "jsDocCompatibility": true, + "tsconfig": "tsconfig.json", + "githubPages": false, + "plugin": ["typedoc-plugin-merge-modules"], + "excludeReferences": true, + "mergeModulesRenameDefaults": true, + "mergeModulesMergeMode": "module-category", + "emit": "both", + "disableSources": true, + "excludeExternals": true, + "excludeInternal": true +} \ No newline at end of file