@hardlydifficult/chat 1.0.1

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/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @hardlydifficult/chat
2
+
3
+ Unified API for posting messages with emoji reactions to Discord and Slack.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hardlydifficult/chat
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createChatClient } from '@hardlydifficult/chat';
15
+
16
+ // Uses DISCORD_TOKEN and DISCORD_GUILD_ID env vars
17
+ const client = createChatClient({ type: 'discord' });
18
+
19
+ // Or for Slack: uses SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars
20
+ const client = createChatClient({ type: 'slack' });
21
+
22
+ const channel = await client.connect(channelId);
23
+
24
+ await channel
25
+ .postMessage('Vote: (1) Pizza, (2) Burgers, (3) Salad')
26
+ .addReactions(['1️⃣', '2️⃣', '3️⃣']);
27
+
28
+ channel.onReaction((event) => {
29
+ console.log(`${event.user.username} voted ${event.emoji}`);
30
+ });
31
+ ```
32
+
33
+ ## API
34
+
35
+ ### `createChatClient(config)`
36
+
37
+ ```typescript
38
+ // Discord - env vars: DISCORD_TOKEN, DISCORD_GUILD_ID
39
+ createChatClient({ type: 'discord' });
40
+ createChatClient({ type: 'discord', token: '...', guildId: '...' }); // override
41
+
42
+ // Slack - env vars: SLACK_BOT_TOKEN, SLACK_APP_TOKEN
43
+ createChatClient({ type: 'slack' });
44
+ createChatClient({ type: 'slack', token: '...', appToken: '...' }); // override
45
+ ```
46
+
47
+ ### `client.connect(channelId): Promise<Channel>`
48
+
49
+ Connect to a channel.
50
+
51
+ ### `channel.postMessage(text): Message`
52
+
53
+ Post a message. Returns a chainable `Message`.
54
+
55
+ ### `message.addReactions(emojis): Message`
56
+
57
+ Add reactions. Chainable and awaitable.
58
+
59
+ ```typescript
60
+ await channel.postMessage('Pick one').addReactions(['1️⃣', '2️⃣']);
61
+ ```
62
+
63
+ ### `channel.onReaction(callback): () => void`
64
+
65
+ Listen for reactions. Returns unsubscribe function.
66
+
67
+ ```typescript
68
+ const unsubscribe = channel.onReaction((event) => {
69
+ // event.emoji, event.user.id, event.user.username, event.messageId
70
+ });
71
+ ```
72
+
73
+ ### `client.disconnect(): Promise<void>`
74
+
75
+ Disconnect from the platform.
76
+
77
+ ## Example: Poll
78
+
79
+ ```typescript
80
+ import { createChatClient } from '@hardlydifficult/chat';
81
+
82
+ const client = createChatClient({ type: 'discord' });
83
+ const channel = await client.connect(process.env.CHANNEL_ID);
84
+
85
+ const options = ['Pizza', 'Burgers', 'Salad'];
86
+ const emojis = ['1️⃣', '2️⃣', '3️⃣'];
87
+
88
+ const message = await channel
89
+ .postMessage(options.map((o, i) => `${emojis[i]} ${o}`).join('\n'))
90
+ .addReactions(emojis);
91
+
92
+ channel.onReaction((event) => {
93
+ if (event.messageId !== message.id) return;
94
+
95
+ const choice = options[emojis.indexOf(event.emoji)];
96
+ if (choice) {
97
+ console.log(`${event.user.username} voted for ${choice}`);
98
+ }
99
+ });
100
+ ```
101
+
102
+ ## Platform Setup
103
+
104
+ ### Discord
105
+
106
+ 1. Create bot at [Discord Developer Portal](https://discord.com/developers/applications)
107
+ 2. Enable Gateway Intents: `GUILDS`, `GUILD_MESSAGES`, `GUILD_MESSAGE_REACTIONS`
108
+ 3. Bot permissions: `Send Messages`, `Add Reactions`, `Read Message History`
109
+ 4. Set `DISCORD_TOKEN` and `DISCORD_GUILD_ID` env vars
110
+
111
+ ### Slack
112
+
113
+ 1. Create app at [Slack API](https://api.slack.com/apps)
114
+ 2. Enable Socket Mode, generate App Token
115
+ 3. Bot scopes: `chat:write`, `reactions:write`, `reactions:read`
116
+ 4. Subscribe to: `reaction_added`
117
+ 5. Set `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` env vars
@@ -0,0 +1,41 @@
1
+ import type { Platform, ReactionCallback, MessageData } from './types.js';
2
+ import { Message, type ReactionAdder } from './Message.js';
3
+ /**
4
+ * Interface for platform-specific channel operations
5
+ */
6
+ export interface ChannelOperations extends ReactionAdder {
7
+ postMessage(channelId: string, text: string): Promise<MessageData>;
8
+ subscribeToReactions(channelId: string, callback: ReactionCallback): () => void;
9
+ }
10
+ /**
11
+ * Represents a connected channel with messaging capabilities
12
+ */
13
+ export declare class Channel {
14
+ readonly id: string;
15
+ readonly platform: Platform;
16
+ private operations;
17
+ private reactionCallbacks;
18
+ private unsubscribeFromPlatform;
19
+ constructor(id: string, platform: Platform, operations: ChannelOperations);
20
+ /**
21
+ * Post a message to this channel
22
+ * @param text - Message content
23
+ * @returns Message object with chainable reaction methods
24
+ */
25
+ postMessage(text: string): Message;
26
+ /**
27
+ * Register a callback for reaction events on this channel
28
+ * @param callback - Function called when users add reactions
29
+ * @returns Unsubscribe function
30
+ */
31
+ onReaction(callback: ReactionCallback): () => void;
32
+ /**
33
+ * Emit a reaction event to all registered callbacks
34
+ */
35
+ private emitReaction;
36
+ /**
37
+ * Disconnect from this channel (cleanup)
38
+ */
39
+ disconnect(): void;
40
+ }
41
+ //# sourceMappingURL=Channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Channel.d.ts","sourceRoot":"","sources":["../src/Channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACnE,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAAC;CACjF;AAED;;GAEG;AACH,qBAAa,OAAO;IAClB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,QAAQ,EAAE,QAAQ,CAAC;IAEnC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,uBAAuB,CAA6B;gBAEhD,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB;IAWzE;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAQlC;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOlD;;OAEG;YACW,YAAY;IAS1B;;OAEG;IACH,UAAU,IAAI,IAAI;CAOnB"}
@@ -0,0 +1,105 @@
1
+ import { Message } from './Message.js';
2
+ /**
3
+ * Represents a connected channel with messaging capabilities
4
+ */
5
+ export class Channel {
6
+ id;
7
+ platform;
8
+ operations;
9
+ reactionCallbacks = new Set();
10
+ unsubscribeFromPlatform = null;
11
+ constructor(id, platform, operations) {
12
+ this.id = id;
13
+ this.platform = platform;
14
+ this.operations = operations;
15
+ // Subscribe to platform reactions and forward to our callbacks
16
+ this.unsubscribeFromPlatform = this.operations.subscribeToReactions(id, (event) => this.emitReaction(event));
17
+ }
18
+ /**
19
+ * Post a message to this channel
20
+ * @param text - Message content
21
+ * @returns Message object with chainable reaction methods
22
+ */
23
+ postMessage(text) {
24
+ const messagePromise = this.operations.postMessage(this.id, text);
25
+ // Create a Message that will resolve once the post completes
26
+ const pendingMessage = new PendingMessage(messagePromise, this.operations);
27
+ return pendingMessage;
28
+ }
29
+ /**
30
+ * Register a callback for reaction events on this channel
31
+ * @param callback - Function called when users add reactions
32
+ * @returns Unsubscribe function
33
+ */
34
+ onReaction(callback) {
35
+ this.reactionCallbacks.add(callback);
36
+ return () => {
37
+ this.reactionCallbacks.delete(callback);
38
+ };
39
+ }
40
+ /**
41
+ * Emit a reaction event to all registered callbacks
42
+ */
43
+ async emitReaction(event) {
44
+ const promises = Array.from(this.reactionCallbacks).map((cb) => Promise.resolve(cb(event)).catch((err) => {
45
+ console.error('Reaction callback error:', err);
46
+ }));
47
+ await Promise.all(promises);
48
+ }
49
+ /**
50
+ * Disconnect from this channel (cleanup)
51
+ */
52
+ disconnect() {
53
+ if (this.unsubscribeFromPlatform) {
54
+ this.unsubscribeFromPlatform();
55
+ this.unsubscribeFromPlatform = null;
56
+ }
57
+ this.reactionCallbacks.clear();
58
+ }
59
+ }
60
+ /**
61
+ * A Message that is still being posted - supports the same chainable API
62
+ */
63
+ class PendingMessage extends Message {
64
+ postPromise;
65
+ resolvedData = null;
66
+ constructor(postPromise, reactionAdder) {
67
+ // Initialize with placeholder data
68
+ super({ id: '', channelId: '', platform: 'discord' }, reactionAdder);
69
+ this.postPromise = postPromise;
70
+ // Update our data when the post resolves
71
+ void this.postPromise.then((data) => {
72
+ this.resolvedData = data;
73
+ // Update the readonly properties via Object.defineProperty
74
+ Object.defineProperty(this, 'id', { value: data.id });
75
+ Object.defineProperty(this, 'channelId', { value: data.channelId });
76
+ Object.defineProperty(this, 'platform', { value: data.platform });
77
+ });
78
+ }
79
+ /**
80
+ * Override addReactions to wait for post to complete first
81
+ */
82
+ addReactions(emojis) {
83
+ // Chain after the post completes
84
+ void this.postPromise.then(() => {
85
+ super.addReactions(emojis);
86
+ });
87
+ return this;
88
+ }
89
+ /**
90
+ * Wait for post and all reactions to complete
91
+ */
92
+ async then(onFulfilled, onRejected) {
93
+ try {
94
+ await this.postPromise;
95
+ return await super.then(onFulfilled, onRejected);
96
+ }
97
+ catch (err) {
98
+ if (onRejected) {
99
+ return onRejected(err);
100
+ }
101
+ throw err;
102
+ }
103
+ }
104
+ }
105
+ //# sourceMappingURL=Channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Channel.js","sourceRoot":"","sources":["../src/Channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ChatConfig } from './types.js';
2
+ import type { Channel } from './Channel.js';
3
+ /**
4
+ * Abstract base class for chat platform clients
5
+ * Provides a unified API for Discord and Slack
6
+ */
7
+ export declare abstract class ChatClient {
8
+ protected readonly config: ChatConfig;
9
+ constructor(config: ChatConfig);
10
+ /**
11
+ * Connect to the chat platform and return a channel object
12
+ * @param channelId - Platform-specific channel identifier
13
+ * @returns Channel object for interacting with the channel
14
+ */
15
+ abstract connect(channelId: string): Promise<Channel>;
16
+ /**
17
+ * Disconnect from the chat platform
18
+ */
19
+ abstract disconnect(): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=ChatClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatClient.d.ts","sourceRoot":"","sources":["../src/ChatClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;GAGG;AACH,8BAAsB,UAAU;IAClB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU;gBAAlB,MAAM,EAAE,UAAU;IAEjD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAErD;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CACrC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Abstract base class for chat platform clients
3
+ * Provides a unified API for Discord and Slack
4
+ */
5
+ export class ChatClient {
6
+ config;
7
+ constructor(config) {
8
+ this.config = config;
9
+ }
10
+ }
11
+ //# sourceMappingURL=ChatClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatClient.js","sourceRoot":"","sources":["../src/ChatClient.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,OAAgB,UAAU;IACC;IAA/B,YAA+B,MAAkB;QAAlB,WAAM,GAAN,MAAM,CAAY;IAAG,CAAC;CAatD"}
@@ -0,0 +1,29 @@
1
+ import type { MessageData, Platform } from './types.js';
2
+ /**
3
+ * Interface for the platform-specific reaction adder
4
+ */
5
+ export interface ReactionAdder {
6
+ addReaction(messageId: string, channelId: string, emoji: string): Promise<void>;
7
+ }
8
+ /**
9
+ * Represents a posted message with chainable reaction methods
10
+ */
11
+ export declare class Message {
12
+ readonly id: string;
13
+ readonly channelId: string;
14
+ readonly platform: Platform;
15
+ private pendingReactions;
16
+ private reactionAdder;
17
+ constructor(data: MessageData, reactionAdder: ReactionAdder);
18
+ /**
19
+ * Add multiple emoji reactions to this message
20
+ * @param emojis - Array of emojis to add
21
+ * @returns this for chaining
22
+ */
23
+ addReactions(emojis: string[]): this;
24
+ /**
25
+ * Wait for all pending reactions to complete
26
+ */
27
+ then<T>(onFulfilled?: ((value: Message) => T | PromiseLike<T>) | null, _onRejected?: ((reason: unknown) => T | PromiseLike<T>) | null): Promise<T>;
28
+ }
29
+ //# sourceMappingURL=Message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../src/Message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjF;AAED;;GAEG;AACH,qBAAa,OAAO;IAClB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,QAAQ,EAAE,QAAQ,CAAC;IAEnC,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,aAAa,CAAgB;gBAEzB,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa;IAO3D;;;;OAIG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;IASpC;;OAEG;IACG,IAAI,CAAC,CAAC,EACV,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAC7D,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAC7D,OAAO,CAAC,CAAC,CAAC;CAOd"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Represents a posted message with chainable reaction methods
3
+ */
4
+ export class Message {
5
+ id;
6
+ channelId;
7
+ platform;
8
+ pendingReactions = Promise.resolve();
9
+ reactionAdder;
10
+ constructor(data, reactionAdder) {
11
+ this.id = data.id;
12
+ this.channelId = data.channelId;
13
+ this.platform = data.platform;
14
+ this.reactionAdder = reactionAdder;
15
+ }
16
+ /**
17
+ * Add multiple emoji reactions to this message
18
+ * @param emojis - Array of emojis to add
19
+ * @returns this for chaining
20
+ */
21
+ addReactions(emojis) {
22
+ for (const emoji of emojis) {
23
+ this.pendingReactions = this.pendingReactions.then(() => this.reactionAdder.addReaction(this.id, this.channelId, emoji));
24
+ }
25
+ return this;
26
+ }
27
+ /**
28
+ * Wait for all pending reactions to complete
29
+ */
30
+ async then(onFulfilled, _onRejected) {
31
+ await this.pendingReactions;
32
+ if (onFulfilled) {
33
+ return onFulfilled(this);
34
+ }
35
+ return this;
36
+ }
37
+ }
38
+ //# sourceMappingURL=Message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Message.js","sourceRoot":"","sources":["../src/Message.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,OAAO,OAAO;IACF,EAAE,CAAS;IACX,SAAS,CAAS;IAClB,QAAQ,CAAW;IAE3B,gBAAgB,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IACpD,aAAa,CAAgB;IAErC,YAAY,IAAiB,EAAE,aAA4B;QACzD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,MAAgB;QAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,CACtD,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAC/D,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI,CACR,WAA6D,EAC7D,WAA8D;QAE9D,MAAM,IAAI,CAAC,gBAAgB,CAAC;QAC5B,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,IAAoB,CAAC;IAC9B,CAAC;CACF"}
@@ -0,0 +1,49 @@
1
+ import { ChatClient } from '../ChatClient.js';
2
+ import { Channel, type ChannelOperations } from '../Channel.js';
3
+ import type { DiscordConfig, ReactionCallback, MessageData } from '../types.js';
4
+ /**
5
+ * Discord chat client implementation using discord.js
6
+ */
7
+ export declare class DiscordChatClient extends ChatClient implements ChannelOperations {
8
+ private client;
9
+ private reactionListeners;
10
+ private readonly token;
11
+ private readonly guildId;
12
+ constructor(config: DiscordConfig);
13
+ /**
14
+ * Set up the global reaction listener that routes events to channel-specific callbacks
15
+ */
16
+ private setupReactionListener;
17
+ /**
18
+ * Connect to Discord and return a channel object
19
+ * @param channelId - Discord channel ID
20
+ * @returns Channel object for interacting with the channel
21
+ */
22
+ connect(channelId: string): Promise<Channel>;
23
+ /**
24
+ * Disconnect from Discord
25
+ */
26
+ disconnect(): Promise<void>;
27
+ /**
28
+ * Post a message to a Discord channel
29
+ * @param channelId - Channel to post to
30
+ * @param text - Message content
31
+ * @returns Message data with ID
32
+ */
33
+ postMessage(channelId: string, text: string): Promise<MessageData>;
34
+ /**
35
+ * Add a reaction to a message
36
+ * @param messageId - Message to react to
37
+ * @param channelId - Channel containing the message
38
+ * @param emoji - Emoji to add
39
+ */
40
+ addReaction(messageId: string, channelId: string, emoji: string): Promise<void>;
41
+ /**
42
+ * Subscribe to reaction events on a channel
43
+ * @param channelId - Channel to monitor
44
+ * @param callback - Function to call when reactions are added
45
+ * @returns Unsubscribe function
46
+ */
47
+ subscribeToReactions(channelId: string, callback: ReactionCallback): () => void;
48
+ }
49
+ //# sourceMappingURL=DiscordChatClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiscordChatClient.d.ts","sourceRoot":"","sources":["../../src/discord/DiscordChatClient.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EAGZ,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,UAAW,YAAW,iBAAiB;IAC5E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,MAAM,EAAE,aAAa;IAgBjC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA+C7B;;;;OAIG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYlD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC;;;;;OAKG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgBxE;;;;;OAKG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;CAkBhF"}
@@ -0,0 +1,140 @@
1
+ import { Client, GatewayIntentBits, TextChannel, } from 'discord.js';
2
+ import { ChatClient } from '../ChatClient.js';
3
+ import { Channel } from '../Channel.js';
4
+ /**
5
+ * Discord chat client implementation using discord.js
6
+ */
7
+ export class DiscordChatClient extends ChatClient {
8
+ client;
9
+ reactionListeners = new Map();
10
+ token;
11
+ guildId;
12
+ constructor(config) {
13
+ super(config);
14
+ this.token = config.token ?? process.env.DISCORD_TOKEN ?? '';
15
+ this.guildId = config.guildId ?? process.env.DISCORD_GUILD_ID ?? '';
16
+ this.client = new Client({
17
+ intents: [
18
+ GatewayIntentBits.Guilds,
19
+ GatewayIntentBits.GuildMessages,
20
+ GatewayIntentBits.GuildMessageReactions,
21
+ ],
22
+ });
23
+ this.setupReactionListener();
24
+ }
25
+ /**
26
+ * Set up the global reaction listener that routes events to channel-specific callbacks
27
+ */
28
+ setupReactionListener() {
29
+ this.client.on('messageReactionAdd', (reaction, user) => {
30
+ void (async () => {
31
+ // Handle partial reactions
32
+ if (reaction.partial) {
33
+ try {
34
+ await reaction.fetch();
35
+ }
36
+ catch (error) {
37
+ console.error('Failed to fetch partial reaction:', error);
38
+ return;
39
+ }
40
+ }
41
+ const channelId = reaction.message.channelId;
42
+ const callbacks = this.reactionListeners.get(channelId);
43
+ if (!callbacks || callbacks.size === 0) {
44
+ return;
45
+ }
46
+ const reactionUser = { id: user.id, username: user.username ?? undefined };
47
+ const event = {
48
+ emoji: reaction.emoji.name ?? reaction.emoji.id ?? '',
49
+ user: reactionUser,
50
+ messageId: reaction.message.id,
51
+ channelId: channelId,
52
+ timestamp: new Date(),
53
+ };
54
+ for (const callback of callbacks) {
55
+ try {
56
+ await callback(event);
57
+ }
58
+ catch (error) {
59
+ console.error('Reaction callback error:', error);
60
+ }
61
+ }
62
+ })();
63
+ });
64
+ }
65
+ /**
66
+ * Connect to Discord and return a channel object
67
+ * @param channelId - Discord channel ID
68
+ * @returns Channel object for interacting with the channel
69
+ */
70
+ async connect(channelId) {
71
+ await this.client.login(this.token);
72
+ const discordChannel = await this.client.channels.fetch(channelId);
73
+ if (!discordChannel || !(discordChannel instanceof TextChannel)) {
74
+ throw new Error(`Channel ${channelId} not found or is not a text channel`);
75
+ }
76
+ return new Channel(channelId, 'discord', this);
77
+ }
78
+ /**
79
+ * Disconnect from Discord
80
+ */
81
+ async disconnect() {
82
+ this.reactionListeners.clear();
83
+ await this.client.destroy();
84
+ }
85
+ /**
86
+ * Post a message to a Discord channel
87
+ * @param channelId - Channel to post to
88
+ * @param text - Message content
89
+ * @returns Message data with ID
90
+ */
91
+ async postMessage(channelId, text) {
92
+ const channel = await this.client.channels.fetch(channelId);
93
+ if (!channel || !(channel instanceof TextChannel)) {
94
+ throw new Error(`Channel ${channelId} not found or is not a text channel`);
95
+ }
96
+ const message = await channel.send({ content: text });
97
+ return {
98
+ id: message.id,
99
+ channelId: channelId,
100
+ platform: 'discord',
101
+ };
102
+ }
103
+ /**
104
+ * Add a reaction to a message
105
+ * @param messageId - Message to react to
106
+ * @param channelId - Channel containing the message
107
+ * @param emoji - Emoji to add
108
+ */
109
+ async addReaction(messageId, channelId, emoji) {
110
+ const channel = await this.client.channels.fetch(channelId);
111
+ if (!channel || !(channel instanceof TextChannel)) {
112
+ throw new Error(`Channel ${channelId} not found or is not a text channel`);
113
+ }
114
+ const message = await channel.messages.fetch(messageId);
115
+ await message.react(emoji);
116
+ }
117
+ /**
118
+ * Subscribe to reaction events on a channel
119
+ * @param channelId - Channel to monitor
120
+ * @param callback - Function to call when reactions are added
121
+ * @returns Unsubscribe function
122
+ */
123
+ subscribeToReactions(channelId, callback) {
124
+ if (!this.reactionListeners.has(channelId)) {
125
+ this.reactionListeners.set(channelId, new Set());
126
+ }
127
+ const callbacks = this.reactionListeners.get(channelId);
128
+ if (!callbacks) {
129
+ throw new Error(`Callbacks not found for channel ${channelId}`);
130
+ }
131
+ callbacks.add(callback);
132
+ return () => {
133
+ callbacks.delete(callback);
134
+ if (callbacks.size === 0) {
135
+ this.reactionListeners.delete(channelId);
136
+ }
137
+ };
138
+ }
139
+ }
140
+ //# sourceMappingURL=DiscordChatClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiscordChatClient.js","sourceRoot":"","sources":["../../src/discord/DiscordChatClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { DiscordChatClient } from './DiscordChatClient.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/discord/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { DiscordChatClient } from './DiscordChatClient.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/discord/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,30 @@
1
+ export { type User, type DiscordConfig, type SlackConfig, type ChatConfig, type Platform, type ReactionEvent, type ReactionCallback, } from './types.js';
2
+ export { ChatClient } from './ChatClient.js';
3
+ export { Channel } from './Channel.js';
4
+ export { Message } from './Message.js';
5
+ export { DiscordChatClient } from './discord/index.js';
6
+ export { SlackChatClient } from './slack/index.js';
7
+ import type { ChatConfig } from './types.js';
8
+ import { ChatClient } from './ChatClient.js';
9
+ /**
10
+ * Factory function to create a chat client based on config type
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Discord (uses env vars by default)
15
+ * const client = createChatClient({ type: 'discord' });
16
+ *
17
+ * // Slack (uses env vars by default)
18
+ * const client = createChatClient({ type: 'slack' });
19
+ *
20
+ * // Usage
21
+ * const channel = await client.connect(channelId);
22
+ * await channel.postMessage('Vote: 1, 2, or 3').addReactions(['1️⃣', '2️⃣', '3️⃣']);
23
+ *
24
+ * channel.onReaction((event) => {
25
+ * console.log(`${event.user.username ?? event.user.id} voted ${event.emoji}`);
26
+ * });
27
+ * ```
28
+ */
29
+ export declare function createChatClient(config: ChatConfig): ChatClient;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAS/D"}
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ // Core classes
2
+ export { ChatClient } from './ChatClient.js';
3
+ export { Channel } from './Channel.js';
4
+ export { Message } from './Message.js';
5
+ // Platform implementations
6
+ export { DiscordChatClient } from './discord/index.js';
7
+ export { SlackChatClient } from './slack/index.js';
8
+ import { DiscordChatClient } from './discord/index.js';
9
+ import { SlackChatClient } from './slack/index.js';
10
+ /**
11
+ * Factory function to create a chat client based on config type
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Discord (uses env vars by default)
16
+ * const client = createChatClient({ type: 'discord' });
17
+ *
18
+ * // Slack (uses env vars by default)
19
+ * const client = createChatClient({ type: 'slack' });
20
+ *
21
+ * // Usage
22
+ * const channel = await client.connect(channelId);
23
+ * await channel.postMessage('Vote: 1, 2, or 3').addReactions(['1️⃣', '2️⃣', '3️⃣']);
24
+ *
25
+ * channel.onReaction((event) => {
26
+ * console.log(`${event.user.username ?? event.user.id} voted ${event.emoji}`);
27
+ * });
28
+ * ```
29
+ */
30
+ export function createChatClient(config) {
31
+ switch (config.type) {
32
+ case 'discord':
33
+ return new DiscordChatClient(config);
34
+ case 'slack':
35
+ return new SlackChatClient(config);
36
+ default:
37
+ throw new Error(`Unknown chat platform: ${config.type}`);
38
+ }
39
+ }
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,eAAe;AACf,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACvC,KAAK,OAAO;YACV,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QACrC;YACE,MAAM,IAAI,KAAK,CAAC,0BAA2B,MAA2B,CAAC,IAAI,EAAE,CAAC,CAAC;IACnF,CAAC;AACH,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { ChatClient } from '../ChatClient.js';
2
+ import { Channel, type ChannelOperations } from '../Channel.js';
3
+ import type { SlackConfig, MessageData, ReactionCallback } from '../types.js';
4
+ /**
5
+ * Slack chat client implementation using @slack/bolt
6
+ */
7
+ export declare class SlackChatClient extends ChatClient implements ChannelOperations {
8
+ private app;
9
+ private reactionCallbacks;
10
+ constructor(config: SlackConfig);
11
+ /**
12
+ * Connect to Slack and return a channel object
13
+ */
14
+ connect(channelId: string): Promise<Channel>;
15
+ /**
16
+ * Disconnect from Slack
17
+ */
18
+ disconnect(): Promise<void>;
19
+ /**
20
+ * Post a message to a Slack channel
21
+ */
22
+ postMessage(channelId: string, text: string): Promise<MessageData>;
23
+ /**
24
+ * Add a reaction to a message
25
+ */
26
+ addReaction(messageId: string, channelId: string, emoji: string): Promise<void>;
27
+ /**
28
+ * Subscribe to reaction events for a specific channel
29
+ */
30
+ subscribeToReactions(channelId: string, callback: ReactionCallback): () => void;
31
+ }
32
+ //# sourceMappingURL=SlackChatClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackChatClient.d.ts","sourceRoot":"","sources":["../../src/slack/SlackChatClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAuB,MAAM,aAAa,CAAC;AAEnG;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAW,YAAW,iBAAiB;IAC1E,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,iBAAiB,CAA4C;gBAEzD,MAAM,EAAE,WAAW;IAyC/B;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKlD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgBxE;;OAEG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWrF;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;CAmBhF"}
@@ -0,0 +1,109 @@
1
+ import { App } from '@slack/bolt';
2
+ import { ChatClient } from '../ChatClient.js';
3
+ import { Channel } from '../Channel.js';
4
+ /**
5
+ * Slack chat client implementation using @slack/bolt
6
+ */
7
+ export class SlackChatClient extends ChatClient {
8
+ app;
9
+ reactionCallbacks = new Map();
10
+ constructor(config) {
11
+ super(config);
12
+ const token = config.token ?? process.env.SLACK_BOT_TOKEN;
13
+ const appToken = config.appToken ?? process.env.SLACK_APP_TOKEN;
14
+ this.app = new App({
15
+ token,
16
+ appToken,
17
+ socketMode: config.socketMode ?? true,
18
+ });
19
+ // Set up global reaction event listener
20
+ this.app.event('reaction_added', async ({ event }) => {
21
+ const channelId = event.item.channel;
22
+ const callbacks = this.reactionCallbacks.get(channelId);
23
+ if (!callbacks || callbacks.size === 0) {
24
+ return;
25
+ }
26
+ const user = { id: event.user, username: undefined };
27
+ const reactionEvent = {
28
+ emoji: event.reaction,
29
+ user,
30
+ messageId: event.item.ts,
31
+ channelId: channelId,
32
+ timestamp: new Date(parseFloat(event.event_ts) * 1000),
33
+ };
34
+ for (const callback of callbacks) {
35
+ try {
36
+ await Promise.resolve(callback(reactionEvent));
37
+ }
38
+ catch (err) {
39
+ console.error('Reaction callback error:', err);
40
+ }
41
+ }
42
+ });
43
+ }
44
+ /**
45
+ * Connect to Slack and return a channel object
46
+ */
47
+ async connect(channelId) {
48
+ await this.app.start();
49
+ return new Channel(channelId, 'slack', this);
50
+ }
51
+ /**
52
+ * Disconnect from Slack
53
+ */
54
+ async disconnect() {
55
+ await this.app.stop();
56
+ this.reactionCallbacks.clear();
57
+ }
58
+ /**
59
+ * Post a message to a Slack channel
60
+ */
61
+ async postMessage(channelId, text) {
62
+ const result = await this.app.client.chat.postMessage({
63
+ channel: channelId,
64
+ text: text,
65
+ });
66
+ if (result.ts === undefined) {
67
+ throw new Error('Slack API did not return a message timestamp');
68
+ }
69
+ return {
70
+ id: result.ts,
71
+ channelId: channelId,
72
+ platform: 'slack',
73
+ };
74
+ }
75
+ /**
76
+ * Add a reaction to a message
77
+ */
78
+ async addReaction(messageId, channelId, emoji) {
79
+ // Strip colons from emoji name (e.g., ":thumbsup:" -> "thumbsup")
80
+ const emojiName = emoji.replace(/^:|:$/g, '');
81
+ await this.app.client.reactions.add({
82
+ channel: channelId,
83
+ timestamp: messageId,
84
+ name: emojiName,
85
+ });
86
+ }
87
+ /**
88
+ * Subscribe to reaction events for a specific channel
89
+ */
90
+ subscribeToReactions(channelId, callback) {
91
+ let callbacks = this.reactionCallbacks.get(channelId);
92
+ if (!callbacks) {
93
+ callbacks = new Set();
94
+ this.reactionCallbacks.set(channelId, callbacks);
95
+ }
96
+ callbacks.add(callback);
97
+ // Return unsubscribe function
98
+ return () => {
99
+ const channelCallbacks = this.reactionCallbacks.get(channelId);
100
+ if (channelCallbacks) {
101
+ channelCallbacks.delete(callback);
102
+ if (channelCallbacks.size === 0) {
103
+ this.reactionCallbacks.delete(channelId);
104
+ }
105
+ }
106
+ };
107
+ }
108
+ }
109
+ //# sourceMappingURL=SlackChatClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlackChatClient.js","sourceRoot":"","sources":["../../src/slack/SlackChatClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAA0B,MAAM,eAAe,CAAC;AAGhE;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,UAAU;IACrC,GAAG,CAAM;IACT,iBAAiB,GAAG,IAAI,GAAG,EAAiC,CAAC;IAErE,YAAY,MAAmB;QAC7B,KAAK,CAAC,MAAM,CAAC,CAAC;QAEd,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAEhE,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC;YACjB,KAAK;YACL,QAAQ;YACR,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;SACtC,CAAC,CAAC;QAEH,wCAAwC;QACxC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACnD,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC;YACrC,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAExD,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvC,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAS,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;YAE3D,MAAM,aAAa,GAAkB;gBACnC,KAAK,EAAE,KAAK,CAAC,QAAQ;gBACrB,IAAI;gBACJ,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;gBACxB,SAAS,EAAE,SAAS;gBACpB,SAAS,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;aACvD,CAAC;YAEF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;gBACjD,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB;QAC7B,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACvB,OAAO,IAAI,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,IAAY;QAC/C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YACpD,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAClE,CAAC;QACD,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,SAAS,EAAE,SAAS;YACpB,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,SAAiB,EAAE,KAAa;QACnE,kEAAkE;QAClE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAE9C,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC;YAClC,OAAO,EAAE,SAAS;YAClB,SAAS,EAAE,SAAS;YACpB,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,SAAiB,EAAE,QAA0B;QAChE,IAAI,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExB,8BAA8B;QAC9B,OAAO,GAAG,EAAE;YACV,MAAM,gBAAgB,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC/D,IAAI,gBAAgB,EAAE,CAAC;gBACrB,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAClC,IAAI,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBAChC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export { SlackChatClient } from './SlackChatClient.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { SlackChatClient } from './SlackChatClient.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/slack/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Configuration for Discord client
3
+ */
4
+ export interface DiscordConfig {
5
+ type: 'discord';
6
+ token?: string;
7
+ guildId?: string;
8
+ }
9
+ /**
10
+ * Configuration for Slack client
11
+ */
12
+ export interface SlackConfig {
13
+ type: 'slack';
14
+ token?: string;
15
+ appToken?: string;
16
+ socketMode?: boolean;
17
+ }
18
+ export type ChatConfig = DiscordConfig | SlackConfig;
19
+ /**
20
+ * Platform identifier
21
+ */
22
+ export type Platform = 'discord' | 'slack';
23
+ /**
24
+ * User who performed an action (e.g., added a reaction)
25
+ */
26
+ export interface User {
27
+ id: string;
28
+ username?: string;
29
+ }
30
+ /**
31
+ * Data provided to reaction callbacks
32
+ */
33
+ export interface ReactionEvent {
34
+ /** The emoji that was added */
35
+ emoji: string;
36
+ /** User who added the reaction */
37
+ user: User;
38
+ /** ID of the message that received the reaction */
39
+ messageId: string;
40
+ /** ID of the channel containing the message */
41
+ channelId: string;
42
+ /** Timestamp of the reaction */
43
+ timestamp: Date;
44
+ }
45
+ /**
46
+ * Callback function type for reaction events
47
+ */
48
+ export type ReactionCallback = (event: ReactionEvent) => void | Promise<void>;
49
+ /**
50
+ * Internal message data passed between classes
51
+ */
52
+ export interface MessageData {
53
+ id: string;
54
+ channelId: string;
55
+ platform: Platform;
56
+ }
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,WAAW,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@hardlydifficult/chat",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "test:coverage": "vitest run --coverage",
15
+ "lint": "tsc --noEmit",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {
19
+ "@slack/bolt": "3.22.0",
20
+ "discord.js": "14.25.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "20.19.31",
24
+ "typescript": "5.8.3",
25
+ "vitest": "1.6.1"
26
+ }
27
+ }