hackchat-engine 1.1.8 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Client.js CHANGED
@@ -186,7 +186,8 @@ class Client extends EventEmitter {
186
186
 
187
187
  const success = this.ws.connect(gw);
188
188
  if (!success) {
189
- return reject(new Error('Connection failed to start (Socket likely not IDLE)'));
189
+ reject(new Error('Connection failed to start (Socket likely not IDLE)'));
190
+ return;
190
191
  }
191
192
 
192
193
  this.ws.connection.once('error', reject);
@@ -405,6 +406,42 @@ class Client extends EventEmitter {
405
406
  signedTransaction,
406
407
  });
407
408
  }
409
+
410
+ /**
411
+ * Send `updateMessage` operation to the server
412
+ * @param {string} customId The customId of the target message
413
+ * @param {string} text The text to apply
414
+ * @param {string} [mode='overwrite'] The update mode:
415
+ * 'overwrite', 'append', 'prepend', or 'complete'
416
+ */
417
+ updateMessage(customId, text, mode = 'overwrite') {
418
+ this.ws.send({
419
+ cmd: OPCodes.UPDATE_MESSAGE,
420
+ channel: this.channel, // @todo Multichannel
421
+ customId,
422
+ text,
423
+ mode,
424
+ });
425
+ }
426
+
427
+ /**
428
+ * Request the public wallet address of a specific user
429
+ * @param {number|string} target The userid (number) or nickname (string) of the user
430
+ */
431
+ getWallet(target) {
432
+ const payload = {
433
+ cmd: OPCodes.GET_WALLET,
434
+ channel: this.channel, // @todo Multichannel
435
+ };
436
+
437
+ if (typeof target === 'number') {
438
+ payload.userid = target;
439
+ } else {
440
+ payload.nick = target;
441
+ }
442
+
443
+ this.ws.send(payload);
444
+ }
408
445
  }
409
446
 
410
447
  export default Client;
package/README.md CHANGED
@@ -1,60 +1,181 @@
1
1
  # hackchat-engine
2
2
 
3
- A NodeJS and browser friendly JavaScript library used to interact with a hackchat server.
3
+ A modern, NodeJS and browser-friendly ES6 WebSocket library for interacting with [hack.chat](https://hack.chat) servers. This engine provides an object-oriented interface, automatic keep-alives, and structured event handling.
4
4
 
5
- # Installation
5
+ ## Installation
6
6
 
7
- ## Prerequisites
7
+ ### Prerequisites
8
8
 
9
- - [node.js 14.15](https://nodejs.org/en/download/package-manager/) or higher
10
- - [npm 6](https://nodejs.org/en/download/package-manager/) or higher
9
+ * **Node.js 14+** (This package uses ES Modules)
10
+ * **NPM 6+**
11
11
 
12
- ## Install
12
+ ### Install
13
13
 
14
- `npm i hackchat-engine`
14
+ ```bash
15
+ npm install hackchat-engine
15
16
 
16
- # Usage
17
+ ```
18
+
19
+ ---
17
20
 
18
- ## Minimum Usage
21
+ ## Quick Start
22
+
23
+ This library uses **ES Modules**. You must use `import` syntax.
19
24
 
20
25
  ```javascript
21
- const { Client } = require('hackchat-engine');
22
- const hcClient = new Client();
26
+ import { Client } from 'hackchat-engine';
23
27
 
24
- const testName = 'testBot';
25
- const testPass = 'testBot';
26
- const testChannel = 'programming';
28
+ const botName = 'MyBot';
29
+ const botPass = 'secretPassword';
30
+ const channel = 'programming';
27
31
 
28
- hcClient.on('connected', () => console.log('Connected!'));
32
+ // Initialize the client
33
+ const client = new Client({
34
+ debug: true
35
+ });
29
36
 
30
- hcClient.on('session', (payload) => {
31
- console.log(payload);
32
- hcClient.join(testName, testPass, testChannel);
33
- });
37
+ // Triggered when the WebSocket connects to the gateway
38
+ client.on('connected', () => {
39
+ console.log('Connected to server!');
40
+ client.ws.send({ cmd: 'getchannels' });
41
+ client.join(botName, botPass, channel);
42
+ });
34
43
 
35
- hcClient.on('channelJoined', (payload) => {
36
- console.log(payload);
37
- hcClient.say(testChannel, 'Bep boop i r bot');
38
- });
44
+ // Triggered when the server assigns a session ID
45
+ client.on('session', (session) => {
46
+ console.log(`Session ID: ${session.sessionID}`);
47
+ });
39
48
 
40
- hcClient.on('message', (payload) => {
41
- console.log(payload);
42
- hcClient.say(testChannel, 'No u');
43
- });
44
- ```
49
+ // Triggered when the client successfully joins the channel
50
+ client.on('channelJoined', (data) => {
51
+ console.log(`Joined channel: ${data.channel}`);
52
+ client.say(channel, 'Hello world! I am a bot.');
53
+ });
45
54
 
46
- ## Advanced Usage
55
+ // Triggered on every new message
56
+ client.on('message', (message) => {
57
+ // Ignore our own messages
58
+ if (message.user.isMine) return;
47
59
 
48
- ** Need to update this, still **
60
+ console.log(`[${message.channel}] ${message.user.name}: ${message.content}`);
61
+
62
+ if (message.content === '!ping') {
63
+ // Helper method to reply directly to the channel
64
+ message.reply('Pong!');
65
+ }
66
+ });
67
+
68
+ ```
49
69
 
50
- ### Changing the connection
70
+ ---
51
71
 
52
- By default, this engine will connect to 'wss://hack.chat/chat-ws'. To change this, add a `ws.gateway` property to the `options` object, for example:
72
+ ## Configuration
73
+
74
+ You can pass an options object to the `Client` constructor to customize the connection.
53
75
 
54
76
  ```javascript
55
- const hcClient = new Client({
77
+ const client = new Client({
78
+ // Connection details
56
79
  ws: {
57
- gateway: 'ws://1.1.1.1:6060/',
58
- }
80
+ gateway: 'wss://hack.chat/chat-ws', // Default
81
+ // gateway: 'ws://127.0.0.1:6060/' // For local development
82
+ },
83
+
84
+ // Set to true to see internal logs and raw packets
85
+ debug: false,
86
+
87
+ // If true, identifies as a bot to the server (default: true)
88
+ isBot: true,
89
+ });
90
+
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Events
96
+
97
+ The client extends `EventEmitter`. You can listen for the following events:
98
+
99
+ | Event | Payload | Description |
100
+ | --- | --- | --- |
101
+ | `connected` | `Client` | Socket connection established. |
102
+ | `session` | `SessionStruct` | Server has assigned a session ID. |
103
+ | `channelJoined` | `Object` | Client has successfully joined a channel and received the user list. |
104
+ | `message` | `MessageStruct` | A new message was received. |
105
+ | `userJoined` | `UserStruct` | A user joined the channel. |
106
+ | `userLeft` | `UserStruct` | A user left the channel. |
107
+ | `userUpdate` | `UserStruct` | A user changed their flair, color, or status. |
108
+ | `whisper` | `WhisperStruct` | Received a private whisper. |
109
+ | `invite` | `InviteStruct` | Received an invitation to another channel. |
110
+ | `emote` | `EmoteStruct` | A user sent a `/me` emote. |
111
+ | `warning` | `WarningStruct` | Server sent a warning (e.g., rate limit). |
112
+ | `gotCaptcha` | `CaptchaStruct` | Server requires a captcha solution. |
113
+ | `hackAttempt` | `HackAttemptStruct` | A user attempted a disallowed action (admin alerts). |
114
+ | `updateMessage` | `UpdateMessageStruct` | A previously sent message was edited or deleted. |
115
+
116
+ ---
117
+
118
+ ## Key Methods
119
+
120
+ ### Core
121
+
122
+ * **`client.join(nick, password, channel)`**: Authenticate and join a specific channel.
123
+ * **`client.say(channel, text)`**: Send a message to a channel.
124
+ * **`client.changeColor(hex)`**: Change your nickname color (e.g., `CCCCCC`).
125
+ * **`client.changeUsername(newNick)`**: Request a nickname change.
126
+
127
+ ### Moderation
128
+
129
+ * **`client.kick(channel, user)`**: Kick a user (requires permissions).
130
+ * **`client.ban(channel, user)`**: Ban a user (requires permissions).
131
+ * **`client.enableCaptcha(channel)`**: Turn on captcha for the channel.
132
+ * **`client.lockChannel(channel)`**: Lock the channel (admin only).
133
+
134
+ ### Interactions
135
+
136
+ The `MessageStruct` (passed in the `message` event) has helper methods:
137
+
138
+ * **`message.reply(text)`**: Automatically replies to the channel the message originated from.
139
+
140
+ ### Message Manipulation
141
+
142
+ * **`client.updateMessage(customId, text, mode)`**: Edit a message you previously sent.
143
+ * `mode`: `'overwrite'`, `'append'`, `'prepend'`, or `'complete'` (delete).
144
+
145
+
146
+
147
+ ---
148
+
149
+ ## Advanced Usage
150
+
151
+ ### Handling Custom Commands
152
+
153
+ If the server implements custom opcodes that are not natively handled by the engine, you can register a listener for them:
154
+
155
+ ```javascript
156
+ client.onCommand('customEvent', (payload) => {
157
+ console.log('Received custom packet:', payload);
59
158
  });
159
+
60
160
  ```
161
+
162
+ ### Accessing Users
163
+
164
+ Users are stored in an extended Map for easy access.
165
+
166
+ ```javascript
167
+ // Get a user by ID
168
+ const user = client.users.get(12345);
169
+
170
+ // Find a user by name
171
+ const user = client.users.find(u => u.username === 'Admin');
172
+
173
+ if (user) {
174
+ console.log(`${user.username} is currently ${user.isOnline ? 'Online' : 'Offline'}`);
175
+ }
176
+
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT
@@ -14,6 +14,8 @@ import PublicChannels from './PublicChannels.js';
14
14
  import HackAttempt from './HackAttempt.js';
15
15
  import SignMessage from './SignMessage.js';
16
16
  import SignTransaction from './SignTransaction.js';
17
+ import UpdateMessage from './UpdateMessage.js';
18
+ import WalletInfo from './WalletInfo.js';
17
19
 
18
20
  /**
19
21
  * This class routes incoming event data to it's proper handler
@@ -42,6 +44,8 @@ class EventsManager {
42
44
  this.HackAttempt = new HackAttempt(this.client);
43
45
  this.SignMessage = new SignMessage(this.client);
44
46
  this.SignTransaction = new SignTransaction(this.client);
47
+ this.UpdateMessage = new UpdateMessage(this.client);
48
+ this.WalletInfo = new WalletInfo(this.client);
45
49
  }
46
50
  }
47
51
 
@@ -5,7 +5,7 @@ import HackAttemptStruct from '../structures/HackAttemptStruct.js';
5
5
  * This class handles an incoming `hackattempt` event from the server
6
6
  * @private
7
7
  */
8
- class PublicChannels extends AbstractEvent {
8
+ class HackAttempt extends AbstractEvent {
9
9
  /**
10
10
  * Event handler function
11
11
  * @param {object} data Incoming event data
@@ -21,4 +21,4 @@ class PublicChannels extends AbstractEvent {
21
21
  }
22
22
  }
23
23
 
24
- export default PublicChannels;
24
+ export default HackAttempt;
@@ -0,0 +1,24 @@
1
+ import AbstractEvent from './AbstractEvent.js';
2
+ import UpdateMessageStruct from '../structures/UpdateMessageStruct.js';
3
+
4
+ /**
5
+ * This class handles an incoming `updateMessage` event from the server
6
+ * @private
7
+ */
8
+ class UpdateMessage extends AbstractEvent {
9
+ /**
10
+ * Event handler function
11
+ * @param {object} data Incoming event data
12
+ * @returns {object}
13
+ */
14
+ handle(data) {
15
+ const { client } = this;
16
+ const update = new UpdateMessageStruct(client, data);
17
+
18
+ return {
19
+ update,
20
+ };
21
+ }
22
+ }
23
+
24
+ export default UpdateMessage;
@@ -0,0 +1,24 @@
1
+ import AbstractEvent from './AbstractEvent.js';
2
+ import WalletInfoStruct from '../structures/WalletInfoStruct.js';
3
+
4
+ /**
5
+ * This class handles an incoming `walletInfo` event from the server
6
+ * @private
7
+ */
8
+ class WalletInfo extends AbstractEvent {
9
+ /**
10
+ * Event handler function
11
+ * @param {object} data Incoming event data
12
+ * @returns {object}
13
+ */
14
+ handle(data) {
15
+ const { client } = this;
16
+ const info = new WalletInfoStruct(client, data);
17
+
18
+ return {
19
+ info,
20
+ };
21
+ }
22
+ }
23
+
24
+ export default WalletInfo;
package/package.json CHANGED
@@ -59,5 +59,5 @@
59
59
  "lintfix": "eslint --fix --ignore-path .gitignore .",
60
60
  "test": "echo \"Error: no test specified\" && exit 1"
61
61
  },
62
- "version": "1.1.8"
62
+ "version": "1.1.11"
63
63
  }
@@ -58,7 +58,7 @@ class HackAttemptStruct {
58
58
  this.lib = data.lib;
59
59
 
60
60
  /**
61
- * The timestamp the publicchannels was sent at
61
+ * The timestamp the hackattempt was sent at
62
62
  * @type {number}
63
63
  */
64
64
  this.timestamp = new Date();
@@ -0,0 +1,64 @@
1
+ /**
2
+ * This class handles parsing of the data of an `updateMessage` event
3
+ */
4
+ class UpdateMessageStruct {
5
+ /**
6
+ * @param {Client} client Main client reference
7
+ * @param {object} data Incoming event data
8
+ */
9
+ constructor(client, data) {
10
+ /**
11
+ * Add client reference
12
+ * @type {Client}
13
+ * @readonly
14
+ */
15
+ Object.defineProperty(this, 'client', { value: client });
16
+
17
+ if (data) this.setup(data);
18
+ }
19
+
20
+ /**
21
+ * Fill in this structure with provided data
22
+ * @param {object} data Incoming event data
23
+ * @returns {void}
24
+ */
25
+ setup(data) {
26
+ /**
27
+ * The channel the message resides in
28
+ * @type {string}
29
+ */
30
+ this.channel = data.channel;
31
+
32
+ /**
33
+ * The customId of the target message
34
+ * @type {string}
35
+ */
36
+ this.customId = data.customId;
37
+
38
+ /**
39
+ * The update mode (overwrite, append, prepend, complete)
40
+ * @type {string}
41
+ */
42
+ this.mode = data.mode;
43
+
44
+ /**
45
+ * The new text content (or text to append/prepend)
46
+ * @type {string}
47
+ */
48
+ this.text = data.text;
49
+
50
+ /**
51
+ * The user who initiated the update
52
+ * @type {User}
53
+ */
54
+ this.user = this.client.users.get(data.userid);
55
+
56
+ /**
57
+ * The timestamp of the update
58
+ * @type {Date}
59
+ */
60
+ this.timestamp = new Date();
61
+ }
62
+ }
63
+
64
+ export default UpdateMessageStruct;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * This class handles parsing of the data of a `walletInfo` event
3
+ */
4
+ class WalletInfoStruct {
5
+ /**
6
+ * @param {Client} client Main client reference
7
+ * @param {object} data Incoming event data
8
+ */
9
+ constructor(client, data) {
10
+ /**
11
+ * Add client reference
12
+ * @type {Client}
13
+ * @readonly
14
+ */
15
+ Object.defineProperty(this, 'client', { value: client });
16
+
17
+ if (data) this.setup(data);
18
+ }
19
+
20
+ /**
21
+ * Fill in this structure with provided data
22
+ * @param {object} data Incoming event data
23
+ * @returns {void}
24
+ */
25
+ setup(data) {
26
+ /**
27
+ * The channel where the request originated
28
+ * @type {string}
29
+ */
30
+ this.channel = data.channel;
31
+
32
+ /**
33
+ * The user ID of the target user
34
+ * @type {number}
35
+ */
36
+ this.userid = data.userid;
37
+
38
+ /**
39
+ * The nickname of the target user
40
+ * @type {string}
41
+ */
42
+ this.nick = data.nick;
43
+
44
+ /**
45
+ * The public wallet address of the user
46
+ * @type {string}
47
+ */
48
+ this.address = data.address;
49
+
50
+ /**
51
+ * The user object associated with this wallet info
52
+ * @type {User|undefined}
53
+ */
54
+ this.user = this.client.users.get(data.userid);
55
+ }
56
+ }
57
+
58
+ export default WalletInfoStruct;
package/util/Constants.js CHANGED
@@ -76,6 +76,8 @@ export const OPCodes = {
76
76
  REQUEST_SIW: 'siw',
77
77
  SIGN_SIW: 'signsiw',
78
78
  CONFIRM_TX: 'confirmtx',
79
+ UPDATE_MESSAGE: 'updateMessage',
80
+ GET_WALLET: 'getwallet',
79
81
  };
80
82
 
81
83
  /**
@@ -107,6 +109,7 @@ export const Events = {
107
109
  HACK_ATTEMPT: 'hackAttempt',
108
110
  SIGN_MESSAGE: 'signMessage',
109
111
  SIGN_TRANSACTION: 'signTransaction',
112
+ WALLET_INFO: 'walletInfo',
110
113
  };
111
114
 
112
115
  /**
@@ -130,4 +133,6 @@ export const WSEvents = {
130
133
  HACK_ATTEMPT: 'hackAttempt',
131
134
  SIGN_MESSAGE: 'signMessage',
132
135
  SIGN_TRANSACTION: 'signTransaction',
136
+ UPDATE_MESSAGE: 'updateMessage',
137
+ WALLET_INFO: 'walletInfo',
133
138
  };
@@ -194,7 +194,7 @@ class SocketHandler extends EventEmitter {
194
194
  this.client.setTimeout(() => {
195
195
  this.connect(gateway, 0, force);
196
196
  },
197
- after);
197
+ after);
198
198
 
199
199
  return false;
200
200
  }
@@ -21,6 +21,8 @@ import PubChannelsHandler from './handlers/PubChannelsHandler.js';
21
21
  import HackAttemptHandler from './handlers/HackAttemptHandler.js';
22
22
  import SignMessageHandler from './handlers/SignMessageHandler.js';
23
23
  import SignTransactionHandler from './handlers/SignTransactionHandler.js';
24
+ import UpdateMessageHandler from './handlers/UpdateMessageHandler.js';
25
+ import WalletInfoHandler from './handlers/WalletInfoHandler.js';
24
26
 
25
27
  const BeforeReadyWhitelist = [
26
28
  WSEvents.SESSION,
@@ -77,6 +79,8 @@ class PacketRouter {
77
79
  this.registerEvent(WSEvents.HACK_ATTEMPT, HackAttemptHandler);
78
80
  this.registerEvent(WSEvents.SIGN_MESSAGE, SignMessageHandler);
79
81
  this.registerEvent(WSEvents.SIGN_TRANSACTION, SignTransactionHandler);
82
+ this.registerEvent(WSEvents.UPDATE_MESSAGE, UpdateMessageHandler);
83
+ this.registerEvent(WSEvents.WALLET_INFO, WalletInfoHandler);
80
84
  }
81
85
 
82
86
  /**
@@ -0,0 +1,30 @@
1
+ import AbstractHandler from './AbstractHandler.js';
2
+ import { Events } from '../../../util/Constants.js';
3
+
4
+ /**
5
+ * Handles an updateMessage packet received from the server
6
+ * @private
7
+ */
8
+ class UpdateMessageHandler extends AbstractHandler {
9
+ /**
10
+ * Parses incoming packet data and emits related events
11
+ * @param {object} packet Incoming packet data
12
+ * @returns {void}
13
+ */
14
+ handle(packet) {
15
+ const { client } = this.packetRouter;
16
+ const response = client.events.UpdateMessage.handle(packet);
17
+
18
+ /**
19
+ * Emitted when a message update is received
20
+ * @event Client#updateMessage
21
+ * @param {UpdateMessageStruct} update The update details
22
+ */
23
+ client.emit(Events.UPDATE_MESSAGE, response.update);
24
+
25
+ // Emit debug info
26
+ client.emit(Events.DEBUG, `[${Events.UPDATE_MESSAGE}]: ${packet}`);
27
+ }
28
+ }
29
+
30
+ export default UpdateMessageHandler;
@@ -0,0 +1,30 @@
1
+ import AbstractHandler from './AbstractHandler.js';
2
+ import { Events } from '../../../util/Constants.js';
3
+
4
+ /**
5
+ * Handles a walletInfo packet received from the server
6
+ * @private
7
+ */
8
+ class WalletInfoHandler extends AbstractHandler {
9
+ /**
10
+ * Parses incoming packet data and emits related events
11
+ * @param {object} packet Incoming packet data
12
+ * @returns {void}
13
+ */
14
+ handle(packet) {
15
+ const { client } = this.packetRouter;
16
+ const response = client.events.WalletInfo.handle(packet);
17
+
18
+ /**
19
+ * Emitted when wallet information is received
20
+ * @event Client#walletInfo
21
+ * @param {WalletInfoStruct} info The wallet information
22
+ */
23
+ client.emit(Events.WALLET_INFO, response.info);
24
+
25
+ // Emit debug info
26
+ client.emit(Events.DEBUG, `[${Events.WALLET_INFO}]: ${packet}`);
27
+ }
28
+ }
29
+
30
+ export default WalletInfoHandler;