Remade lib to TypeScript

This commit is contained in:
MrMasrozYTLIVE 2024-06-16 11:57:11 +03:00
parent f0a1f53c24
commit d7ad22b115
12 changed files with 308 additions and 243 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
package-lock.json package-lock.json
node_modules node_modules
docs docs
storage

View File

@ -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"
}
}
}
}

View File

@ -1,13 +1,16 @@
{ {
"name": "linkup-bot-lib", "name": "linkup-bot-lib",
"version": "1.0.0", "version": "1.1.0",
"main": "src/Client.js", "main": "lib/Client.js",
"types": "lib/*.ts",
"scripts": { "scripts": {
"generate-docs": "jsdoc --configure jsdoc.json" "prepublishOnly": "typedoc",
"build": "typedoc"
}, },
"keywords": [], "keywords": [
"author": "", "linkup"
"type": "module", ],
"author": "LinkUp Team",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
@ -18,7 +21,8 @@
"serve-drive": "^5.0.8" "serve-drive": "^5.0.8"
}, },
"devDependencies": { "devDependencies": {
"docdash": "^2.0.2", "@types/b4a": "^1.6.4",
"jsdoc": "^4.0.3" "@types/node": "^20.14.2",
"typedoc-plugin-merge-modules": "^5.1.0"
} }
} }

View File

@ -1,34 +1,45 @@
import path from 'path'; import path from 'path';
import Hyperswarm from 'hyperswarm'; import Hyperswarm, { PeerDiscovery } from 'hyperswarm';
import EventEmitter from 'node:events'; import TextMessage from "./message/TextMessage";
import b4a from "b4a"; import FileMessage from "./message/FileMessage";
import TextMessage from "./message/TextMessage.js"; import AudioMessage from "./message/AudioMessage";
import FileMessage from "./message/FileMessage.js"; import Message from "./message/Message";
import AudioMessage from "./message/AudioMessage.js";
import Message from "./message/Message.js";
import IconMessage from "./message/IconMessage.js";
import Corestore from 'corestore'; import Corestore from 'corestore';
import Hyperdrive from 'hyperdrive'; import Hyperdrive from 'hyperdrive';
import fs from 'fs'; import fs from 'fs';
// @ts-ignore
import ServeDrive from 'serve-drive'; 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. * 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<LinkUpEvents> {
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<string> | 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 * @since 1.0
* @constructor * @constructor
* @author snxraven * @author snxraven
*/ */
constructor(botName) { constructor(botName: string) {
super(); 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.botName = botName;
this.swarm = new Hyperswarm(); this.swarm = new Hyperswarm();
this.joinedRooms = new Set(); // Track the rooms the bot has joined this.joinedRooms = new Set(); // Track the rooms the bot has joined
@ -75,6 +86,7 @@ class Client extends EventEmitter {
this.servePort = this.getRandomPort(); this.servePort = this.getRandomPort();
const serve = new ServeDrive({ const serve = new ServeDrive({
port: this.servePort, port: this.servePort,
// @ts-ignore
get: ({ key, filename, version }) => this.drive get: ({ key, filename, version }) => this.drive
}); });
await serve.ready(); await serve.ready();
@ -87,23 +99,23 @@ class Client extends EventEmitter {
* @description Returns a random port number. * @description Returns a random port number.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @return {Number} Random port number. * @return Random port number.
*/ */
getRandomPort() { getRandomPort(): number {
return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152; return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
} }
/** /**
* @description Fetches and sets the bot's avatar from a local file. * @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 * @since 1.0
* @author snxraven * @author snxraven
*/ */
async fetchAvatar(filePath) { async fetchAvatar(filePath: string) {
try { try {
await this.drive.ready(); await this.drive?.ready();
const iconBuffer = fs.readFileSync(filePath); 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`; this.botAvatar = `http://localhost:${this.servePort}/icons/${this.botName}.png`;
// Cache the icon message // Cache the icon message
@ -119,15 +131,15 @@ class Client extends EventEmitter {
* @author snxraven * @author snxraven
*/ */
setupSwarm() { setupSwarm() {
this.swarm.on('connection', (peer) => { this.swarm?.on('connection', (peer) => {
// Send the cached icon message to the new peer // Send the cached icon message to the new peer
if (this.iconMessage) { if (this.iconMessage) {
peer.write(this.iconMessage.toJsonString()); peer.write(this.iconMessage.toJsonString());
} }
peer.on('data', async message => { peer.on('data', async (message: {}) => {
const messageObj = JSON.parse(message.toString()); 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 this.currentTopic = messageObj.topic; // Set the current topic from the incoming message
const msgType = messageObj.type; const msgType = messageObj.type;
@ -137,35 +149,23 @@ class Client extends EventEmitter {
if (msgType === "message") if (msgType === "message")
/** this.emit('onMessage', new TextMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.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));
if (msgType === "file") { 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. * Triggered when a file message is received.
* *
* @event Client#onFile * @event Client#onFile
* @property peer - HyperSwarm peer object * @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 * @example
* const bot = new Client("MyBot"); * const bot = new Client("MyBot");
* bot.on('onFile', (peer, message) => { * bot.on('onFile', (peer, message) => {
* console.log(`Received file from ${message.peerName}`); * 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") if (msgType === "icon")
@ -174,62 +174,65 @@ class Client extends EventEmitter {
* *
* @event Client#onIcon * @event Client#onIcon
* @property peer - HyperSwarm peer object * @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 * @example
* const bot = new Client("MyBot"); * const bot = new Client("MyBot");
* bot.on('onIcon', (peer, message) => { * bot.on('onIcon', (peer, message) => {
* console.log(`Received new Icon from ${message.peerName}`); * 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") { 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. * Triggered when an audio message is received.
* *
* @event Client#onAudio * @event Client#onAudio
* @property peer - HyperSwarm peer object * @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 * @example
* ```js
* const bot = new Client("MyBot"); * const bot = new Client("MyBot");
* bot.on('onAudio', (peer, message) => { * bot.on('onAudio', (peer, message) => {
* console.log(`Received audio file from ${message.peerName}`); * 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 => { peer.on('error', (err: any) => {
this.emit('onError', e); this.emit('onError', err);
console.error(`Connection error: ${e}`); console.error(`Connection error: ${err}`);
}); });
}); });
this.swarm.on('update', () => { // @ts-ignore
console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`); this.swarm.on("update", () => {
console.log(`Connections count: ${this.swarm?.connections.size} / Peers count: ${this.swarm?.peers.size}`);
}); });
} }
/** /**
* @description Joins a specified chat room. * @description Joins a specified chat room.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @param {String} chatRoomID Chat room topic string * @param chatRoomID Chat room topic string
*/ */
joinChatRoom(chatRoomID) { joinChatRoom(chatRoomID: string) {
if (!chatRoomID || typeof chatRoomID !== 'string') { if (!chatRoomID) {
console.error("Invalid chat room ID!"); console.error("Invalid chat room ID!");
return; 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.currentTopic = chatRoomID; // Store the current topic
this.discovery = this.swarm.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true }); this.discovery = this.swarm?.join(Buffer.from(chatRoomID, 'hex'), { client: true, server: true });
this.discovery.flushed().then(() => { this.discovery?.flushed().then(() => {
console.log(`Bot ${this.botName} joined the chat room.`); 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. * @description Sends a text message.
* @since 1.0 * @since 1.0
* @author MiTask * @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}`); console.log(`Preparing to send text message: ${message}`);
this.sendMessage(TextMessage.new(this, message)); this.sendMessage(TextMessage.new(this, message));
} }
@ -248,15 +251,15 @@ class Client extends EventEmitter {
* @description Sends a file message. * @description Sends a file message.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @param {String} filePath Path to the file to send. * @param filePath Path to the file to send.
* @param {String} fileType Type of the file to send. * @param fileType Type of the file to send.
*/ */
async sendFileMessage(filePath, fileType) { async sendFileMessage(filePath: string, fileType: string) {
try { try {
await this.drive.ready(); await this.drive?.ready();
const fileBuffer = fs.readFileSync(filePath); const fileBuffer = fs.readFileSync(filePath);
const fileName = path.basename(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 fileUrl = `http://localhost:${this.servePort}/files/${fileName}`;
const fileMessage = FileMessage.new(this, fileName, fileUrl, fileType, fileBuffer); // Pass fileBuffer to the new method const fileMessage = FileMessage.new(this, fileName, fileUrl, fileType, fileBuffer); // Pass fileBuffer to the new method
this.sendMessage(fileMessage); this.sendMessage(fileMessage);
@ -269,15 +272,15 @@ class Client extends EventEmitter {
* @description Sends an audio message. * @description Sends an audio message.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @param {String} filePath Path to the audio file to send. * @param filePath Path to the audio file to send.
* @param {String} audioType Type of 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 { try {
await this.drive.ready(); await this.drive?.ready();
const audioBuffer = fs.readFileSync(filePath); const audioBuffer = fs.readFileSync(filePath);
const audioName = path.basename(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 audioUrl = `http://localhost:${this.servePort}/audio/${audioName}`;
const audioMessage = AudioMessage.new(this, audioUrl, audioType, audioBuffer); // Pass audioBuffer to the new method const audioMessage = AudioMessage.new(this, audioUrl, audioType, audioBuffer); // Pass audioBuffer to the new method
this.sendMessage(audioMessage); this.sendMessage(audioMessage);
@ -291,17 +294,12 @@ class Client extends EventEmitter {
* @description Sends a generic message. * @description Sends a generic message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param {Message} message Message class (TextMessage, FileMessage or AudioMessage) * @param message Message class (TextMessage, FileMessage or AudioMessage)
*/ */
sendMessage(message) { sendMessage(message: Message) {
if (!(message instanceof Message)) {
console.error(`message does not extend Message class (TextMessage, FileMessage, AudioMessage).`, message);
return;
}
console.log("Sending message:", message); console.log("Sending message:", message);
const data = message.toJsonString(); const data = message.toJsonString();
const peers = [...this.swarm.connections]; const peers = [...this.swarm?.connections];
if (peers.length === 0) { if (peers.length === 0) {
console.warn("No active peer connections found."); console.warn("No active peer connections found.");
return; return;
@ -324,9 +322,29 @@ class Client extends EventEmitter {
* @author snxraven * @author snxraven
*/ */
async destroy() { async destroy() {
await this.swarm.destroy(); await this.swarm?.destroy();
console.log(`Bot ${this.botName} disconnected.`); 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; export default Client;

View File

@ -1,21 +1,29 @@
import Message from "./Message.js"; /**
* @module Message
*/
import Message from "./Message";
import b4a from "b4a"; import b4a from "b4a";
import Client from "../Client";
class AudioMessage extends Message { class AudioMessage extends Message {
audioUrl: string;
audioType: string;
audioData: string;
/** /**
* @description Creates a new Audio message. * @description Creates a new Audio message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @constructor * @constructor
* @param {String} peerName Peer username * @param peerName Peer username
* @param {String} peerAvatar Peer avatar URL * @param peerAvatar Peer avatar URL
* @param {String} topic Chat room topic string * @param topic Chat room topic string
* @param {Number} timestamp UNIX Timestamp * @param timestamp UNIX Timestamp
* @param {String} audioUrl URL to the audio file * @param audioUrl URL to the audio file
* @param {String} audioType Type of the audio file * @param audioType Type of the audio file
* @param {String} audioData Audio file data in base64 String format * @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); super("audio", peerName, peerAvatar, topic, timestamp);
this.audioUrl = audioUrl; this.audioUrl = audioUrl;
this.audioType = audioType; this.audioType = audioType;
@ -27,7 +35,7 @@ class AudioMessage extends Message {
* @author MiTask * @author MiTask
* @returns {String} JSON String with all of the information about the message * @returns {String} JSON String with all of the information about the message
*/ */
toJsonString() { toJsonString(): string {
return JSON.stringify({ return JSON.stringify({
...this.toJson(), ...this.toJson(),
audioUrl: this.audioUrl, audioUrl: this.audioUrl,
@ -40,13 +48,13 @@ class AudioMessage extends Message {
* @description Creates a new audio message instance. * @description Creates a new audio message instance.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param {Client} bot Bot Client class * @param bot Bot Client class
* @param {String} audioUrl URL to the audio file * @param audioUrl URL to the audio file
* @param {String} audioType Type of the audio file * @param audioType Type of the audio file
* @param {Buffer} audioBuffer Audio file data * @param audioBuffer Audio file data
* @returns {AudioMessage} AudioMessage instance. * @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 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); return new AudioMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), audioUrl, audioType, audioData);
} }

View File

@ -1,22 +1,31 @@
import Message from "./Message.js"; /**
* @module Message
*/
import Message from "./Message";
import b4a from "b4a"; import b4a from "b4a";
import Client from "../Client";
class FileMessage extends Message { class FileMessage extends Message {
fileName: string;
fileUrl: string;
fileType: string;
fileData: string;
/** /**
* @description Creates a new file message. * @description Creates a new file message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @constructor * @constructor
* @param {String} peerName Peer username * @param peerName Peer username
* @param {String} peerAvatar Peer avatar URL * @param peerAvatar Peer avatar URL
* @param {String} topic Chat room topic string * @param topic Chat room topic string
* @param {Number} timestamp UNIX Timestamp * @param timestamp UNIX Timestamp
* @param {String} fileName File name * @param fileName File name
* @param {String} fileUrl URL to the file * @param fileUrl URL to the file
* @param {String} fileType Type of the file * @param fileType Type of the file
* @param {String} fileData File data in base64 String format * @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); super("file", peerName, peerAvatar, topic, timestamp);
this.fileName = fileName; this.fileName = fileName;
this.fileUrl = fileUrl; this.fileUrl = fileUrl;
@ -29,7 +38,7 @@ class FileMessage extends Message {
* @author MiTask * @author MiTask
* @returns {String} JSON String with all of the information about the message * @returns {String} JSON String with all of the information about the message
*/ */
toJsonString() { toJsonString(): string {
return JSON.stringify({ return JSON.stringify({
...this.toJson(), ...this.toJson(),
fileName: this.fileName, fileName: this.fileName,
@ -43,14 +52,14 @@ class FileMessage extends Message {
* @description Creates a new file message instance. * @description Creates a new file message instance.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param {Client} bot Bot Client class * @param bot Bot Client class
* @param {String} fileName File name * @param fileName File name
* @param {String} fileUrl URL to the file * @param fileUrl URL to the file
* @param {String} fileType Type of the file * @param fileType Type of the file
* @param {Buffer} fileBuffer File data * @param fileBuffer File data
* @returns {FileMessage} FileMessage instance. * @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 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); return new FileMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), fileName, fileUrl, fileType, fileData);
} }

View File

@ -1,5 +1,10 @@
import Message from "./Message.js"; /**
* @module Message
*/
import Message from "./Message";
import b4a from "b4a"; import b4a from "b4a";
import Client from "../Client";
class IconMessage extends Message { class IconMessage extends Message {
/** /**
@ -7,11 +12,11 @@ class IconMessage extends Message {
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @constructor * @constructor
* @param {String} peerName Peer username * @param peerName Peer username
* @param {String} peerAvatar Peer avatar URL * @param peerAvatar Peer avatar URL
* @param {Number} timestamp UNIX Timestamp * @param timestamp UNIX Timestamp
*/ */
constructor(peerName, peerAvatar, timestamp) { constructor(peerName: string, peerAvatar: string, timestamp: number) {
super("icon", peerName, peerAvatar, null, timestamp); super("icon", peerName, peerAvatar, null, timestamp);
} }
@ -19,11 +24,11 @@ class IconMessage extends Message {
* @description Creates a new icon message instance. * @description Creates a new icon message instance.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param {Client} bot Bot Client class * @param bot Bot Client class
* @param {String} avatarBuffer Bot Avatar buffer * @param avatarBuffer Bot Avatar buffer
* @returns {IconMessage} IconMessage instance * @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()); return new IconMessage(bot.botName, b4a.toString(avatarBuffer, 'base64'), Date.now());
} }
} }

View File

@ -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;

59
src/message/Message.ts Normal file
View File

@ -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;

View File

@ -1,18 +1,24 @@
import Message from "./Message.js"; /**
* @module Message
*/
import Message from "./Message";
import Client from "../Client";
class TextMessage extends Message { class TextMessage extends Message {
message: string;
/** /**
* @description Creates a new text message. * @description Creates a new text message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @constructor * @constructor
* @param {String} peerName Peer username * @param peerName Peer username
* @param {String} peerAvatar Peer avatar URL * @param peerAvatar Peer avatar URL
* @param {String} topic Chat room topic string * @param topic Chat room topic string
* @param {Number} timestamp UNIX Timestamp * @param timestamp UNIX Timestamp
* @param {String} message Text of the message * @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); super("message", peerName, peerAvatar, topic, timestamp);
this.message = message; this.message = message;
} }
@ -22,7 +28,7 @@ class TextMessage extends Message {
* @author MiTask * @author MiTask
* @returns {String} JSON String with all of the information about the message * @returns {String} JSON String with all of the information about the message
*/ */
toJsonString() { toJsonString(): string {
return JSON.stringify({ return JSON.stringify({
...this.toJson(), ...this.toJson(),
message: this.message, message: this.message,
@ -33,11 +39,11 @@ class TextMessage extends Message {
* @description Creates a new text message instance. * @description Creates a new text message instance.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param {Client} bot Bot Client class * @param bot Bot Client class
* @param {String} message Text of the message * @param message Text of the message
* @returns {TextMessage} TextMessage instance * @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); return new TextMessage(bot.botName, bot.botAvatar, bot.currentTopic, Date.now(), message);
} }
} }

View File

@ -0,0 +1,39 @@
import {EventEmitter} from "stream";
/**
* This class is used for TypeSafe events.
* @internal
*/
export default class TypedEventEmitter<TEvents extends Record<string, any>> {
private emitter = new EventEmitter()
/**
* @internal
*/
emit<TEventName extends keyof TEvents & string>(
eventName: TEventName,
...eventArg: TEvents[TEventName]
) {
this.emitter.emit(eventName, ...(eventArg as []))
}
/**
* @internal
*/
on<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void
) {
this.emitter.on(eventName, handler as any)
}
/**
* @internal
*/
off<TEventName extends keyof TEvents & string>(
eventName: TEventName,
handler: (...eventArg: TEvents[TEventName]) => void
) {
this.emitter.off(eventName, handler as any)
}
}

19
typedoc.json Normal file
View File

@ -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
}