@hardlydifficult/chat 1.1.125 → 1.1.126

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.
Files changed (2) hide show
  1. package/README.md +137 -12
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -404,7 +404,7 @@ import type { Member, Message, Thread, MessageBatch } from "@hardlydifficult/cha
404
404
  ### Streaming Behavior
405
405
 
406
406
  | Feature | Discord | Slack |
407
- |---------|:-------:|:-----:|
407
+ |---------|:-----:|:-----:|
408
408
  | Message editing | ✅ | ✅ |
409
409
  | Stream chunking | Automatic, 1000 chars | Automatic, 2000 chars |
410
410
  | Truncation | Oldest first | Oldest first |
@@ -667,18 +667,143 @@ console.log(MESSAGE_LIMITS.SLACK_MAX_MESSAGE_LENGTH); // 4000
667
667
 
668
668
  ### Platform Differences
669
669
 
670
- | Feature | Discord | Slack |
671
- |------------------------|-----------------------------------|-----------------------------------|
672
- | Typing indicators | ✅ Supported | ❌ No API support (no-op) |
673
- | Message length limit | 2000 characters | 4000 characters |
674
- | Thread creation | Explicit thread channel | Implicit via parent message ts |
675
- | Bulk delete | ✅ Up to 100 messages at once | ❌ Must delete one-by-one |
676
- | Emoji format | Plain Unicode or `:name:` | Colon-wrapped `:name:` |
677
- | File uploads | As attachments | Via `filesUploadV2` API |
670
+ | Feature | Discord | Slack |
671
+ |---------|:-----:|:-----:|
672
+ | Typing indicators | ✅ Supported | ❌ No API support (no-op) |
673
+ | Message length limit | 2000 characters | 4000 characters |
674
+ | Thread creation | Explicit thread channel | Implicit via parent message ts |
675
+ | Bulk delete | ✅ Up to 100 messages at once | ❌ Must delete one-by-one |
676
+ | Emoji format | Plain Unicode or `:name:` | Colon-wrapped `:name:` |
677
+ | File uploads | As attachments | Via `filesUploadV2` API |
678
678
 
679
679
  ### Message Limits
680
680
 
681
681
  | Platform | Max Message Length | Notes |
682
- |----------|--------------------|-------|
683
- | Discord | 2000 | Embed-only messages may be larger |
684
- | Slack | 4000 | Per block element; message may contain many |
682
+ |---------|-------------------|-------|
683
+ | Discord | 2000 | Embed-only messages may be larger |
684
+ | Slack | 4000 | Per block element; message may contain many |
685
+
686
+ ## Core Components
687
+
688
+ ### Chat Clients
689
+ - **`DiscordChatClient`** - Discord implementation using `discord.js`
690
+ - **`SlackChatClient`** - Slack implementation using `@slack/bolt`
691
+ - **`ChatClient`** - Abstract base class shared by both implementations
692
+
693
+ ### Channel & Message Abstraction
694
+ - **`Channel`** - Unified channel interface supporting posts, threads, reactions, and message tracking
695
+ - **`Message`** - Represents a message with operations for updates, deletes, and reactions
696
+ - **`PendingMessage`** - A message still being posted, awaitable and chainable
697
+ - **`Thread`** - Thread management with streaming and reply subscription
698
+ - **`MessageBatch`** - Logical group of messages for batch operations
699
+ - **`batchStore`** - In-memory storage for tracking message batches
700
+
701
+ ### Streaming & Output
702
+ - **`StreamingReply`** - Buffers and flushes text as multiple messages with auto-chunking
703
+ - **`StreamingThread`** - Extends streaming for thread context with auto-creation and cleanup
704
+ - **`StreamConsumer`** - Converts an async generator into a streaming reply
705
+
706
+ ### Command Handling
707
+ - **`CommandParser`** - Parses user input with channel awareness and member lookups
708
+ - **`findBestMemberMatch`** - Fuzzy member matching by ID, mention, name, email, etc.
709
+ - **`MessageContext`** - Contextual info for command execution
710
+ - **`CommandContext`** - Full execution context for commands
711
+
712
+ ### Utilities
713
+ - **`channelIdToName`** - Extracts readable channel name from ID
714
+ - **`extractMentionId`** - Extracts user ID from Discord mention format
715
+ - **`isDocument`** - Type guard for Document objects in content
716
+ - **`toDiscordEmbed`** - Converts Document blocks to Discord embed format
717
+ - **`toSlackBlocks`** - Converts Document blocks to Slack Block Kit format
718
+
719
+ ### Job Lifecycle
720
+ - **`setupJobLifecycle`** - Manages cancel/dismiss flow for long-running jobs
721
+ - **`EMOJI_CANCEL`** - ❌ Emoji for canceling jobs
722
+ - **`EMOJI_DISMISS`** - 🗑️ Emoji for deleting completed jobs
723
+
724
+ ## Usage
725
+
726
+ ### Creating a client
727
+
728
+ ```ts
729
+ import { DiscordChatClient, SlackChatClient } from "@hardlydifficult/chat";
730
+
731
+ // Discord
732
+ const discordClient = new DiscordChatClient({
733
+ token: process.env.DISCORD_TOKEN!,
734
+ guildId: process.env.DISCORD_GUILD_ID!,
735
+ });
736
+
737
+ // Slack
738
+ const slackClient = new SlackChatClient({
739
+ token: process.env.SLACK_BOT_TOKEN!,
740
+ appToken: process.env.SLACK_APP_TOKEN!,
741
+ });
742
+ ```
743
+
744
+ ### Subscribing to messages and commands
745
+
746
+ ```ts
747
+ const channel = await client.connect(channelId);
748
+
749
+ channel.onMessage(async (event) => {
750
+ if (event.content.startsWith("!")) {
751
+ const { command, args, context } = new CommandParser(channel).parse(event.content);
752
+ if (command === "help") {
753
+ await channel.postMessage("Here's how to use this bot...");
754
+ }
755
+ }
756
+ });
757
+ ```
758
+
759
+ ### Posting with formatting
760
+
761
+ ```ts
762
+ import { Document, heading, text, list } from "@hardlydifficult/document-generator";
763
+
764
+ const doc = new Document()
765
+ .addBlock(heading("My Title", 1))
766
+ .addBlock(text("This is some **bold** text."))
767
+ .addBlock(list(["Item 1", "Item 2", "Item 3"]));
768
+
769
+ await channel.postMessage(doc);
770
+ ```
771
+
772
+ ### Streaming replies
773
+
774
+ ```ts
775
+ import { StreamingReply, StreamConsumer } from "@hardlydifficult/chat";
776
+
777
+ const stream = new StreamingReply(channel);
778
+ stream.append("Hello ");
779
+ await stream.flush();
780
+
781
+ // Later...
782
+ stream.append("world!");
783
+ await stream.flush();
784
+
785
+ // Or stream from an async generator
786
+ const asyncGenerator = (function* () {
787
+ yield "Part 1\n";
788
+ yield "Part 2\n";
789
+ yield "Part 3\n";
790
+ })();
791
+
792
+ await new StreamConsumer(stream, asyncGenerator()).consume();
793
+ ```
794
+
795
+ ## Installation
796
+
797
+ ```bash
798
+ npm install @hardlydifficult/chat
799
+ ```
800
+
801
+ ## Dependencies
802
+
803
+ - `discord.js` for Discord integration
804
+ - `@slack/bolt` for Slack integration
805
+ - `@hardlydifficult/document-generator` for structured content
806
+
807
+ ## License
808
+
809
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/chat",
3
- "version": "1.1.125",
3
+ "version": "1.1.126",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -19,13 +19,13 @@
19
19
  "discord.js": "14.25.1"
20
20
  },
21
21
  "devDependencies": {
22
- "@hardlydifficult/document-generator": "1.1.12",
22
+ "@hardlydifficult/document-generator": "1.1.13",
23
23
  "@types/node": "25.3.0",
24
24
  "typescript": "5.9.3",
25
25
  "vitest": "4.0.18"
26
26
  },
27
27
  "peerDependencies": {
28
- "@hardlydifficult/document-generator": "1.1.12"
28
+ "@hardlydifficult/document-generator": "1.1.13"
29
29
  },
30
30
  "type": "commonjs",
31
31
  "engines": {