opencode-discord-bot 0.0.4 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-discord-bot",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Discord bot bridge for a self-hosted opencode instance",
5
5
  "type": "module",
6
6
  "main": "src/Main.ts",
@@ -142,6 +142,27 @@ describe("makeChatSdkDiscord", () => {
142
142
  ])
143
143
  })
144
144
 
145
+ test("normalizes Discord user mention wrappers before chat-sdk output conversion", async () => {
146
+ const adapter = new FakeDiscordAdapter()
147
+ const discord = makeChatSdkDiscord(adapter)
148
+
149
+ const posted = await Effect.runPromise(discord.postMessage(scope, "hello <@999> and <@!888>"))
150
+ await Effect.runPromise(discord.editMessage(scope, posted.id, "edited <@777>"))
151
+ await Effect.runPromise(discord.postChannelMessage("g1", "c2", "channel <@666>"))
152
+
153
+ expect(adapter.calls).toEqual([
154
+ ["encodeThreadId", { guildId: "g1", channelId: "c1", threadId: "t1" }],
155
+ ["postMessage", { threadId: "discord:g1:c1:t1", message: "hello @999 and @888" }],
156
+ ["encodeThreadId", { guildId: "g1", channelId: "c1", threadId: "t1" }],
157
+ ["editMessage", { threadId: "discord:g1:c1:t1", messageId: "posted-1", message: "edited @777" }],
158
+ ["encodeThreadId", { guildId: "g1", channelId: "c2" }],
159
+ ["fetchChannelInfo", { channelId: "discord:g1:c2" }],
160
+ ["postChannelMessage", { channelId: "discord:g1:c2", message: "channel @666" }]
161
+ ])
162
+ })
163
+ })
164
+
165
+ describe("makeChatSdkDiscord REST operations", () => {
145
166
  test("routes channel posts, deletes, and raw REST adapter gaps", async () => {
146
167
  const adapter = new FakeDiscordAdapter()
147
168
  const requests: Array<readonly [string, RequestInit]> = []
@@ -142,6 +142,8 @@ const rawDiscord = (options: RawDiscordOptions | undefined, path: string, init:
142
142
  return await response.json()
143
143
  })
144
144
 
145
+ const normalizeMentionsForChatAdapter = (content: string): string => content.replace(/<@!?(\w+)>/g, "@$1")
146
+
145
147
  export const makeChatSdkDiscord = (adapter: ChatDiscordAdapter, raw: RawDiscordOptions | undefined = undefined): DiscordService => ({
146
148
  fetchContext: (scope, limit) =>
147
149
  tryAdapter(async () => {
@@ -158,11 +160,13 @@ export const makeChatSdkDiscord = (adapter: ChatDiscordAdapter, raw: RawDiscordO
158
160
  sendTyping: (scope) => tryAdapter(() => adapter.startTyping(threadIdFromScope(adapter, scope))).pipe(Effect.asVoid),
159
161
  postMessage: (scope, content) =>
160
162
  tryAdapter(async () => {
161
- const result = await adapter.postMessage(threadIdFromScope(adapter, scope), content)
163
+ const result = await adapter.postMessage(threadIdFromScope(adapter, scope), normalizeMentionsForChatAdapter(content))
162
164
  return { id: result.id }
163
165
  }),
164
166
  editMessage: (scope, messageId, content) =>
165
- tryAdapter(() => adapter.editMessage(threadIdFromScope(adapter, scope), messageId, content)).pipe(Effect.asVoid),
167
+ tryAdapter(() => adapter.editMessage(threadIdFromScope(adapter, scope), messageId, normalizeMentionsForChatAdapter(content))).pipe(
168
+ Effect.asVoid
169
+ ),
166
170
  deleteMessage: (scope, messageId) =>
167
171
  tryAdapter(() => adapter.deleteMessage(threadIdFromScope(adapter, scope), messageId)).pipe(Effect.asVoid),
168
172
  addReaction: (scope, messageId, emoji) =>
@@ -184,7 +188,10 @@ export const makeChatSdkDiscord = (adapter: ChatDiscordAdapter, raw: RawDiscordO
184
188
  tryAdapter(async () => {
185
189
  const encodedChannelId = adapter.encodeThreadId({ guildId, channelId })
186
190
  await validateGuildChannel(adapter, guildId, encodedChannelId)
187
- const result = await adapter.postChannelMessage(encodedChannelId, sanitizeGuildContent(guildId, content))
191
+ const result = await adapter.postChannelMessage(
192
+ encodedChannelId,
193
+ normalizeMentionsForChatAdapter(sanitizeGuildContent(guildId, content))
194
+ )
188
195
  return { id: result.id }
189
196
  }),
190
197
  pinMessage: (scope, messageId) =>
@@ -42,6 +42,7 @@ test("maps Discord messages through the chat-sdk adapter facade", async () => {
42
42
  const parsed = adapter.parseMessage(withAttachment)
43
43
 
44
44
  expect(adapter.encodeThreadId({ guildId: "g1", channelId: "c1", threadId: "t1" })).toBe("discord:g1:c1:t1")
45
+ expect(adapter.userName).toBe("self")
45
46
  expect(adapter.decodeThreadId("discord:g1:c1:t1")).toEqual({ guildId: "g1", channelId: "c1", threadId: "t1" })
46
47
  expect(adapter.channelIdFromThreadId("discord:g1:c1:t1")).toBe("c1")
47
48
  expect(parsed.threadId).toBe("discord:g1:c1")
@@ -175,7 +175,7 @@ const unsupported = (operation: string): Promise<never> => Promise.reject(new Er
175
175
 
176
176
  export const makeGatewayAdapter = (bot: BotIdentity): Adapter<DiscordScope, DiscordMessage> => ({
177
177
  name: "discord",
178
- userName: `<@${bot.userId}>`,
178
+ userName: bot.userId,
179
179
  botUserId: bot.userId,
180
180
  lockScope: "thread",
181
181
  initialize: () => Promise.resolve(),
@@ -208,7 +208,7 @@ export const makeChatGatewayIntake = (options: ChatGatewayIntakeOptions): ChatGa
208
208
  const chat = new Chat({
209
209
  adapters: { discord: adapter },
210
210
  state: makeTransientChatState(),
211
- userName: `<@${options.bot.userId}>`,
211
+ userName: options.bot.userId,
212
212
  concurrency: "concurrent",
213
213
  dedupeTtlMs: 5 * 60 * 1000
214
214
  })