diff --git a/README.md b/README.md index 73420c4..e3ac09c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ MATRIX_WHITELIST= # Matrix Feature Flags (optional) MATRIX_AUTOJOIN=true MATRIX_ENCRYPTION=true +# If you turn threads off you will have problems if you don't set CHATGPT_CONTEXT=room +MATRIX_THREADS=true MATRIX_PREFIX_DM=false MATRIX_RICH_TEXT=true ``` diff --git a/src/env.ts b/src/env.ts index 8f4b0d3..98e0714 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,6 +13,7 @@ export const { /** Matrix Bot Features */ MATRIX_AUTOJOIN, MATRIX_ENCRYPTION, + MATRIX_THREADS, MATRIX_PREFIX_DM, MATRIX_RICH_TEXT, /** Matrix Access Control */ @@ -38,6 +39,7 @@ export const { /** Matrix Bot Features */ MATRIX_AUTOJOIN: { schema: z.boolean().default(true), description: "Set to true if you want the bot to autojoin when invited" }, MATRIX_ENCRYPTION: { schema: z.boolean().default(true), description: "Set to true if you want the bot to support encrypted channels" }, + MATRIX_THREADS: { schema: z.boolean().default(true), description: "Set to true if you want the bot to answer always in a new thread/conversation" }, MATRIX_PREFIX_DM: { schema: z.boolean().default(false), description: "Set to false if you want the bot to answer to all messages in a one-to-one room" }, MATRIX_RICH_TEXT: { schema: z.boolean().default(true), description: "Set to true if you want the bot to answer with enriched text" }, /** Matrix Access Control */ diff --git a/src/handlers.ts b/src/handlers.ts index 635054f..6c75be4 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -1,8 +1,8 @@ import { ChatGPTAPIBrowser } from "chatgpt"; import { LogService, MatrixClient, UserID } from "matrix-bot-sdk"; -import { CHATGPT_CONTEXT, CHATGPT_TIMEOUT, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX, MATRIX_BLACKLIST, MATRIX_WHITELIST, MATRIX_RICH_TEXT, MATRIX_PREFIX_DM } from "./env.js"; +import { CHATGPT_CONTEXT, CHATGPT_TIMEOUT, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX, MATRIX_BLACKLIST, MATRIX_WHITELIST, MATRIX_RICH_TEXT, MATRIX_PREFIX_DM, MATRIX_THREADS } from "./env.js"; import { RelatesTo, MessageEvent, StoredConversation, StoredConversationConfig } from "./interfaces.js"; -import { sendChatGPTMessage, sendError, sendThreadReply } from "./utils.js"; +import { sendChatGPTMessage, sendError, sendReply } from "./utils.js"; export default class CommandHandler { @@ -124,7 +124,7 @@ export default class CommandHandler { const result = await sendChatGPTMessage(this.chatGPT, await bodyWithoutPrefix, storedConversation); await Promise.all([ this.client.setTyping(roomId, false, 500), - sendThreadReply(this.client, roomId, this.getRootEventId(event), `${result.response}`, MATRIX_RICH_TEXT) + sendReply(this.client, roomId, this.getRootEventId(event), `${result.response}`, MATRIX_THREADS, MATRIX_RICH_TEXT) ]); const storedConfig = ((storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {}) diff --git a/src/index.ts b/src/index.ts index 6319aa9..00cbcce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { } from "matrix-bot-sdk"; import * as path from "path"; -import { DATA_PATH, OPENAI_EMAIL, OPENAI_PASSWORD, OPENAI_LOGIN_TYPE, OPENAI_PRO, MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, MATRIX_AUTOJOIN, MATRIX_BOT_PASSWORD, MATRIX_BOT_USERNAME, MATRIX_ENCRYPTION } from './env.js' +import { DATA_PATH, OPENAI_EMAIL, OPENAI_PASSWORD, OPENAI_LOGIN_TYPE, OPENAI_PRO, MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, MATRIX_AUTOJOIN, MATRIX_BOT_PASSWORD, MATRIX_BOT_USERNAME, MATRIX_ENCRYPTION, MATRIX_THREADS, CHATGPT_CONTEXT } from './env.js' import { parseMatrixUsernamePretty } from './utils.js'; import CommandHandler from "./handlers.js" import { ChatGPTAPIBrowser } from 'chatgpt' @@ -38,6 +38,7 @@ async function main() { console.log("Set MATRIX_ACCESS_TOKEN to above token, MATRIX_BOT_PASSWORD can now be blank") return; } + if (!MATRIX_THREADS && CHATGPT_CONTEXT !== "room") throw Error("You must set CHATGPT_CONTEXT to 'room' if you set MATRIX_THREADS to false") const client: MatrixClient = new MatrixClient(MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, storage, cryptoStore); // use puppeteer to bypass cloudflare (headful because of captchas) diff --git a/src/utils.ts b/src/utils.ts index d7ea2a9..124e623 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -28,13 +28,17 @@ export async function sendError(client: MatrixClient, text: string, roomId: stri * @param {string} roomId the room ID the event being replied to resides in * @param {string} rootEventId the root event of the thread * @param {string} text the plain text to reply with + * @param {boolean} thread reply as a thread * @param {boolean} rich should the plain text be rendered to html using markdown? */ -export async function sendThreadReply(client: MatrixClient, roomId: string, rootEventId: string, text: string, rich:boolean = false): Promise { +export async function sendReply(client: MatrixClient, roomId: string, rootEventId: string, text: string, thread: boolean = false, rich:boolean = false): Promise { const contentCommon = { body: text, msgtype: "m.text", + } + + const contentThreadOnly = { "m.relates_to": { event_id: rootEventId, is_falling_back: true, @@ -67,8 +71,9 @@ export async function sendThreadReply(client: MatrixClient, roomId: string, root } const content = rich ? { ...contentCommon, ...contentRichOnly } : { ...contentCommon, ...contentTextOnly }; + const finalContent = thread ? { ...content, ...contentThreadOnly } : content - await client.sendEvent(roomId, "m.room.message", content); + await client.sendEvent(roomId, "m.room.message", finalContent); } export async function sendChatGPTMessage(chatGPT: ChatGPTAPIBrowser, question: string, storedConversation: StoredConversation) {