Compare commits

..

No commits in common. "623645090d8a8317e087296fed1acba61615e165" and "f0a1f53c24ea327392a515c12d9d00401b81d4e7" have entirely different histories.

14 changed files with 279 additions and 337 deletions

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
.idea/ .idea/
package-lock.json package-lock.json
node_modules node_modules
docs docs
storage

51
jsdoc.json Normal file
View File

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

View File

@ -1,46 +1,34 @@
import path from 'path'; import path from 'path';
import Hyperswarm, { PeerDiscovery } from 'hyperswarm'; import Hyperswarm from 'hyperswarm';
import {TextMessage} from "./message/TextMessage"; import EventEmitter from 'node:events';
import {FileMessage} from "./message/FileMessage"; import b4a from "b4a";
import {AudioMessage} from "./message/AudioMessage"; import TextMessage from "./message/TextMessage.js";
import {Message} from "./message/Message"; 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 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";
import {LinkUpEvents} from "./LinkUpEvents";
/** /**
* 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
*/ */
export class Client extends TypedEventEmitter<LinkUpEvents> { class Client extends EventEmitter {
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 botName The name of the bot. * @param {String} botName The name of the bot.
* @since 1.0 * @since 1.0
* @constructor * @constructor
* @author snxraven * @author snxraven
*/ */
constructor(botName: string) { constructor(botName) {
super(); super();
if (!botName) { if (!botName) return console.error("Bot Name is not defined!");
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
@ -87,7 +75,6 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
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();
@ -100,23 +87,23 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @description Returns a random port number. * @description Returns a random port number.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @return Random port number. * @return {Number} Random port number.
*/ */
getRandomPort(): number { getRandomPort() {
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 filePath path to the local avatar file. * @param {String} filePath path to the local avatar file.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
*/ */
async fetchAvatar(filePath: string) { async fetchAvatar(filePath) {
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
@ -132,15 +119,15 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @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;
@ -150,23 +137,35 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
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', new FileMessage(peerName, peerAvatar, this.currentTopic, timestamp, messageObj.fileName, `http://localhost:${this.servePort}/files/${messageObj.fileName}`, messageObj.fileType, messageObj.fileData)); this.emit('onFile', peer, 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")
@ -175,65 +174,62 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* *
* @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', new IconMessage(peerName, peerAvatar, timestamp)); this.emit('onIcon', peer, 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', new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData)); this.emit('onAudio', peer, new AudioMessage(peerName, peerAvatar, this.currentTopic, timestamp, `http://localhost:${this.servePort}/audio/${messageObj.audioName}`, messageObj.audioType, messageObj.audioData));
} }
} }
}); });
peer.on('error', (err: any) => { peer.on('error', e => {
this.emit('onError', err); this.emit('onError', e);
console.error(`Connection error: ${err}`); console.error(`Connection error: ${e}`);
}); });
}); });
// @ts-ignore this.swarm.on('update', () => {
this.swarm.on("update", () => { console.log(`Connections count: ${this.swarm.connections.size} / Peers count: ${this.swarm.peers.size}`);
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 chatRoomID Chat room topic string * @param {String} chatRoomID Chat room topic string
*/ */
joinChatRoom(chatRoomID: string) { joinChatRoom(chatRoomID) {
if (!chatRoomID) { if (!chatRoomID || typeof chatRoomID !== 'string') {
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', chatRoomID); this.emit('onBotJoinRoom');
}); });
} }
@ -241,9 +237,9 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @description Sends a text message. * @description Sends a text message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param message Text message to send to the bot's current chat room. * @param {String} message Text message to send to the bot's current chat room.
*/ */
sendTextMessage(message: string) { sendTextMessage(message) {
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));
} }
@ -252,15 +248,15 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @description Sends a file message. * @description Sends a file message.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @param filePath Path to the file to send. * @param {String} filePath Path to the file to send.
* @param fileType Type of the file to send. * @param {String} fileType Type of the file to send.
*/ */
async sendFileMessage(filePath: string, fileType: string) { async sendFileMessage(filePath, fileType) {
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);
@ -273,15 +269,15 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @description Sends an audio message. * @description Sends an audio message.
* @since 1.0 * @since 1.0
* @author snxraven * @author snxraven
* @param filePath Path to the audio file to send. * @param {String} filePath Path to the audio file to send.
* @param audioType Type of the audio file to send. * @param {String} audioType Type of the audio file to send.
*/ */
async sendAudioMessage(filePath: string, audioType: string) { async sendAudioMessage(filePath, audioType) {
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);
@ -295,12 +291,17 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @description Sends a generic message. * @description Sends a generic message.
* @since 1.0 * @since 1.0
* @author MiTask * @author MiTask
* @param message Message class (TextMessage, FileMessage or AudioMessage) * @param {Message} message Message class (TextMessage, FileMessage or AudioMessage)
*/ */
sendMessage(message: Message) { sendMessage(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;
@ -323,7 +324,9 @@ export class Client extends TypedEventEmitter<LinkUpEvents> {
* @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 default Client;

View File

@ -1,24 +0,0 @@
import {TextMessage} from "./message/TextMessage";
import {FileMessage} from "./message/FileMessage";
import {AudioMessage} from "./message/AudioMessage";
import {IconMessage} from "./message/IconMessage";
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]
}

View File

@ -1,29 +1,21 @@
/** import Message from "./Message.js";
* @module Message
*/
import {Message} from "./Message";
import b4a from "b4a"; import b4a from "b4a";
import {Client} from "../Client";
export 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 peerName Peer username * @param {String} peerName Peer username
* @param peerAvatar Peer avatar URL * @param {String} peerAvatar Peer avatar URL
* @param topic Chat room topic string * @param {String} topic Chat room topic string
* @param timestamp UNIX Timestamp * @param {Number} timestamp UNIX Timestamp
* @param audioUrl URL to the audio file * @param {String} audioUrl URL to the audio file
* @param audioType Type of the audio file * @param {String} audioType Type of the audio file
* @param audioData Audio file data in base64 String format * @param {String} audioData Audio file data in base64 String format
*/ */
constructor(peerName: string, peerAvatar: string, topic: string | null, timestamp: number, audioUrl: string, audioType: string, audioData: string) { constructor(peerName, peerAvatar, topic, timestamp, audioUrl, audioType, audioData) {
super("audio", peerName, peerAvatar, topic, timestamp); super("audio", peerName, peerAvatar, topic, timestamp);
this.audioUrl = audioUrl; this.audioUrl = audioUrl;
this.audioType = audioType; this.audioType = audioType;
@ -35,7 +27,7 @@ export 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(): string { toJsonString() {
return JSON.stringify({ return JSON.stringify({
...this.toJson(), ...this.toJson(),
audioUrl: this.audioUrl, audioUrl: this.audioUrl,
@ -48,14 +40,16 @@ export 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 bot Bot Client class * @param {Client} bot Bot Client class
* @param audioUrl URL to the audio file * @param {String} audioUrl URL to the audio file
* @param audioType Type of the audio file * @param {String} audioType Type of the audio file
* @param audioBuffer Audio file data * @param {Buffer} audioBuffer Audio file data
* @returns {AudioMessage} AudioMessage instance. * @returns {AudioMessage} AudioMessage instance.
*/ */
static new(bot: Client, audioUrl: string, audioType: string, audioBuffer: Buffer): AudioMessage { static new(bot, audioUrl, audioType, audioBuffer) {
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);
} }
} }
export default AudioMessage;

View File

@ -1,31 +1,22 @@
/** import Message from "./Message.js";
* @module Message
*/
import {Message} from "./Message";
import b4a from "b4a"; import b4a from "b4a";
import {Client} from "../Client";
export 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 peerName Peer username * @param {String} peerName Peer username
* @param peerAvatar Peer avatar URL * @param {String} peerAvatar Peer avatar URL
* @param topic Chat room topic string * @param {String} topic Chat room topic string
* @param timestamp UNIX Timestamp * @param {Number} timestamp UNIX Timestamp
* @param fileName File name * @param {String} fileName File name
* @param fileUrl URL to the file * @param {String} fileUrl URL to the file
* @param fileType Type of the file * @param {String} fileType Type of the file
* @param fileData File data in base64 String format * @param {String} fileData File data in base64 String format
*/ */
constructor(peerName: string, peerAvatar: string, topic: string | null, timestamp: number, fileName: string, fileUrl: string, fileType: string, fileData: string) { constructor(peerName, peerAvatar, topic, timestamp, fileName, fileUrl, fileType, fileData) {
super("file", peerName, peerAvatar, topic, timestamp); super("file", peerName, peerAvatar, topic, timestamp);
this.fileName = fileName; this.fileName = fileName;
this.fileUrl = fileUrl; this.fileUrl = fileUrl;
@ -38,7 +29,7 @@ export 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(): string { toJsonString() {
return JSON.stringify({ return JSON.stringify({
...this.toJson(), ...this.toJson(),
fileName: this.fileName, fileName: this.fileName,
@ -52,15 +43,17 @@ export 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 bot Bot Client class * @param {Client} bot Bot Client class
* @param fileName File name * @param {String} fileName File name
* @param fileUrl URL to the file * @param {String} fileUrl URL to the file
* @param fileType Type of the file * @param {String} fileType Type of the file
* @param fileBuffer File data * @param {Buffer} fileBuffer File data
* @returns {FileMessage} FileMessage instance. * @returns {FileMessage} FileMessage instance.
*/ */
static new(bot: Client, fileName: string, fileUrl: string, fileType: string, fileBuffer: Buffer): FileMessage { static new(bot, fileName, fileUrl, fileType, fileBuffer) {
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);
} }
} }
export default FileMessage;

View File

@ -0,0 +1,31 @@
import Message from "./Message.js";
import b4a from "b4a";
class IconMessage extends Message {
/**
* @description Creates a new icon message.
* @since 1.0
* @author MiTask
* @constructor
* @param {String} peerName Peer username
* @param {String} peerAvatar Peer avatar URL
* @param {Number} timestamp UNIX Timestamp
*/
constructor(peerName, peerAvatar, timestamp) {
super("icon", peerName, peerAvatar, null, timestamp);
}
/**
* @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
*/
static new(bot, avatarBuffer) {
return new IconMessage(bot.botName, b4a.toString(avatarBuffer, 'base64'), Date.now());
}
}
export default IconMessage;

View File

@ -1,34 +0,0 @@
/**
* @module Message
*/
import {Message} from "./Message";
import b4a from "b4a";
import {Client} from "../Client";
export class IconMessage extends Message {
/**
* @description Creates a new icon message.
* @since 1.0
* @author MiTask
* @constructor
* @param peerName Peer username
* @param peerAvatar Peer avatar URL
* @param timestamp UNIX Timestamp
*/
constructor(peerName: string, peerAvatar: string, timestamp: number) {
super("icon", peerName, peerAvatar, null, timestamp);
}
/**
* @description Creates a new icon message instance.
* @since 1.0
* @author MiTask
* @param bot Bot Client class
* @param avatarBuffer Bot Avatar buffer
* @returns IconMessage instance
*/
static new(bot: Client, avatarBuffer: Buffer): IconMessage {
return new IconMessage(bot.botName, b4a.toString(avatarBuffer, 'base64'), Date.now());
}
}

52
src/message/Message.js Normal file
View File

@ -0,0 +1,52 @@
/**
* @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;

View File

@ -1,57 +0,0 @@
/**
* @description Base class for all messages
* @since 1.0
* @author MiTask
* @module Message
*/
export 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()
});
}
}

View File

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

View File

@ -1,39 +0,0 @@
import {EventEmitter} from "stream";
/**
* This class is used for TypeSafe events.
* @internal
*/
export 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)
}
}

View File

@ -1,19 +0,0 @@
{
"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
}