mirror of
https://github.com/matrixgpt/matrix-chatgpt-bot.git
synced 2024-09-27 19:45:38 +00:00
Merge pull request #18 from bertybuttface/main
A large, non-atomic pull request to get us back in sync
This commit is contained in:
commit
80b6927fbd
@ -68,6 +68,8 @@ USER pptruser
|
||||
|
||||
VOLUME /storage
|
||||
|
||||
ENV DATA_PATH="/storage"
|
||||
|
||||
# We run a fake display and run our script.
|
||||
# Start script on Xvfb
|
||||
CMD xvfb-run --server-args="-screen 0 1024x768x24" yarn start
|
35
README.md
35
README.md
@ -17,6 +17,8 @@ You should not be using this ChatGPT account while the bot is using it, because
|
||||
|
||||
If your OpenAI account uses Google Auth, you shouldn't encounter any of the more complicated Recaptchas — and can avoid using paid third-party CAPTCHA solving providers. To use Google auth, make sure your OpenAI account is using Google and then set IS_GOOGLE_LOGIN to true.
|
||||
|
||||
If you want to get an access token without exposing your password to this bot you can follow [how-to-get-an-access-token-for-element-riot-matrix](https://webapps.stackexchange.com/questions/131056/how-to-get-an-access-token-for-element-riot-matrix).
|
||||
|
||||
# Usage
|
||||
- Create an (encrypted if enabled) room
|
||||
- Add the bot
|
||||
@ -24,31 +26,36 @@ If your OpenAI account uses Google Auth, you shouldn't encounter any of the more
|
||||
|
||||
# Features
|
||||
- Shows typing indicator as ChatGPT is thinking!
|
||||
- Doesn't yet support encryption
|
||||
- Two lines of code can be uncommented to enable it, however "unable to decrypt" messages appear
|
||||
- If you have time to look into fixing this PRs very welcome :)
|
||||
- Supports encryption
|
||||
|
||||
# Setting up the account
|
||||
- Create a new Matrix account on Matrix.org (or your favourite server)
|
||||
- Go to the settings and get the access token
|
||||
- Add the details to your environment vars. One way of doing this is adding this to a file called `.env`:
|
||||
```
|
||||
# https://matrix.org if your account is on matrix.org.
|
||||
MATRIX_HOMESERVER_URL=
|
||||
MATRIX_ACCESS_TOKEN=
|
||||
|
||||
# ChatGPT Settings (required)
|
||||
OPENAI_EMAIL=
|
||||
OPENAI_PASSWORD=
|
||||
IS_GOOGLE_LOGIN=true
|
||||
OPENAI_LOGIN_TYPE="google"
|
||||
|
||||
# With the @ and :DOMAIN, ie @SOMETHING:DOMAIN
|
||||
# Matrix Static Settings (required, see notes)
|
||||
# Defaults to "https://matrix.org"
|
||||
MATRIX_HOMESERVER_URL=
|
||||
# With the @ and :DOMAIN, ie @SOMETHING:DOMAIN, needs to always be set
|
||||
MATRIX_BOT_USERNAME=
|
||||
# Set `MATRIX_BOT_PASSWORD` the bot will print an `MATRIX_ACCESS_TOKEN` to the terminal
|
||||
MATRIX_ACCESS_TOKEN=
|
||||
# Once `MATRIX_ACCESS_TOKEN` is set this is no longer used.
|
||||
MATRIX_BOT_PASSWORD=
|
||||
MATRIX_AUTO_JOIN=true
|
||||
MATRIX_ENCRYPTION=true
|
||||
|
||||
# needs to be ./storage/ if you aren't using Docker or /storage/ if you are.
|
||||
DATA_PATH=/storage/
|
||||
# Matrix Configurable Settings Defaults (optional)
|
||||
MATRIX_DEFAULT_PREFIX="!chatgpt " # Leave prefix blank to reply to all messages
|
||||
MATRIX_DEFAULT_PREFIX_REPLY=false
|
||||
|
||||
# Matrix Feature Flags (optional)
|
||||
MATRIX_AUTOJOIN=true
|
||||
MATRIX_ENCRYPTION=true
|
||||
MATRIX_THREADS=true
|
||||
```
|
||||
|
||||
# Discussion
|
||||
@ -70,7 +77,7 @@ recomend following the prompts at https://element.io/get-started to download and
|
||||
|
||||
```
|
||||
docker build . -t matrix-chatgpt-bot
|
||||
docker run --cap-add=SYS_ADMIN -it -v ./storage:/storage matrix-chatgpt-bot
|
||||
docker run -it -v /full-path-not-relative-path/storage:/storage matrix-chatgpt-bot
|
||||
```
|
||||
|
||||
Note: Without -it flags in the command above you won't be able to stop the container using Ctrl-C
|
||||
|
@ -15,11 +15,13 @@
|
||||
"typecheck": "npx tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"chatgpt": "^3.3.6",
|
||||
"chatgpt": "^3.3.8",
|
||||
"dotenv": "^14.2.0",
|
||||
"matrix-bot-sdk": "^0.6.2",
|
||||
"matrix-bot-sdk": "^0.6.3",
|
||||
"puppeteer": "^19.4.1",
|
||||
"typescript": "^4.5.2"
|
||||
"typescript": "^4.5.2",
|
||||
"znv": "^0.3.2",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
import * as dotenv from 'dotenv';
|
||||
// Support .env file
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* How to get access token:
|
||||
* https://webapps.stackexchange.com/questions/131056/how-to-get-an-access-token-for-element-riot-matrix
|
||||
*/
|
||||
export const accessToken = process.env.MATRIX_ACCESS_TOKEN as string;
|
||||
export const homeserverUrl = process.env.MATRIX_HOMESERVER_URL as string;
|
||||
|
||||
/** The full username: eg @bot:server.com */
|
||||
export const matrixBotUsername = process.env.MATRIX_BOT_USERNAME as string;
|
||||
export const matrixBotPassword = process.env.MATRIX_BOT_PASSWORD as string;
|
||||
export const matrixAutojoin = process.env.MATRIX_AUTO_JOIN && process.env.MATRIX_AUTO_JOIN.toLowerCase() === "true" as string;
|
||||
export const matrixEncryption = process.env.MATRIX_ENCRYPTION && process.env.MATRIX_ENCRYPTION.toLowerCase() === "true" as string;
|
||||
|
||||
export const dataPath = process.env.DATA_PATH as string;
|
||||
|
||||
/** ChatGPT specific stuff */
|
||||
export const openAiEmail = process.env.OPENAI_EMAIL as string;
|
||||
export const openAiPassword = process.env.OPENAI_PASSWORD as string;
|
||||
export const isGoogleLogin = process.env.IS_GOOGLE_LOGIN && process.env.IS_GOOGLE_LOGIN.toLowerCase() === "true";
|
||||
|
||||
|
||||
if(dataPath === undefined) {
|
||||
console.error("DATA_PATH env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(homeserverUrl === undefined) {
|
||||
console.error("MATRIX_HOMESERVER_URL env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(accessToken === undefined) {
|
||||
console.error("MATRIX_ACCESS_TOKEN env variable is undefined, set it to empty string to use username and password");
|
||||
process.exit(1);
|
||||
if(matrixBotUsername === undefined) {
|
||||
console.error("MATRIX_BOT_USERNAME env variable is undefined, set it to empty string to use access token");
|
||||
process.exit(1);
|
||||
}
|
||||
if(matrixBotPassword === undefined) {
|
||||
console.error("MATRIX_BOT_PASSWORD env variable is undefined, set it to empty string to use access token");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if(matrixAutojoin === undefined) {
|
||||
console.error("MATRIX_AUTO_JOIN env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(matrixEncryption === undefined) {
|
||||
console.error("MATRIX_ENCRYPTION env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(openAiEmail === undefined) {
|
||||
console.error("OPENAI_EMAIL env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(openAiPassword === undefined) {
|
||||
console.error("OPENAI_PASSWORD env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
||||
if(isGoogleLogin === undefined) {
|
||||
console.error("IS_GOOGLE_LOGIN env variable is undefined");
|
||||
process.exit(1);
|
||||
}
|
50
src/env.ts
Normal file
50
src/env.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { parseEnv } from "znv";
|
||||
import { z } from "zod";
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
export const {
|
||||
DATA_PATH,
|
||||
/** Matrix Bot Settings */
|
||||
MATRIX_HOMESERVER_URL,
|
||||
MATRIX_ACCESS_TOKEN,
|
||||
MATRIX_BOT_USERNAME,
|
||||
MATRIX_BOT_PASSWORD,
|
||||
/** Matrix Bot Features */
|
||||
MATRIX_AUTOJOIN,
|
||||
MATRIX_ENCRYPTION,
|
||||
MATRIX_THREADS,
|
||||
/** Matrix Bot Runtime Config */
|
||||
MATRIX_DEFAULT_PREFIX,
|
||||
MATRIX_DEFAULT_PREFIX_REPLY,
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION,
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION_IN_DM,
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION_IN_REPLY,
|
||||
/** ChatGPT Settings */
|
||||
OPENAI_EMAIL,
|
||||
OPENAI_PASSWORD,
|
||||
OPENAI_LOGIN_TYPE,
|
||||
CHATGPT_TIMEOUT
|
||||
} = parseEnv(process.env, {
|
||||
DATA_PATH: {schema: z.string().default("./storage"), description: "Set to /storage/ if using docker, ./storage if running without"},
|
||||
/** Matrix Bot Settings */
|
||||
MATRIX_HOMESERVER_URL: {schema: z.string().default("https://matrix.org")},
|
||||
MATRIX_ACCESS_TOKEN: {schema: z.string().optional(), description: "Set MATRIX_BOT_USERNAME & MATRIX_BOT_PASSWORD to print ACCESS_TOKEN or follow https://webapps.stackexchange.com/questions/131056/how-to-get-an-access-token-for-element-riot-matrix"},
|
||||
MATRIX_BOT_USERNAME: {schema: z.string().min(3), description: "Set full username: eg @bot:server.com"},
|
||||
MATRIX_BOT_PASSWORD: {schema: z.string().optional(), description: "Set AccessToken which supersedes MATRIX_BOT_PASSWORD"},
|
||||
/** Matrix Bot Features */
|
||||
MATRIX_AUTOJOIN: {schema: z.boolean().default(true)},
|
||||
MATRIX_ENCRYPTION: {schema: z.boolean().default(true)},
|
||||
MATRIX_THREADS: {schema: z.boolean().default(true)},
|
||||
/** Matrix Bot Runtime Config */
|
||||
MATRIX_DEFAULT_PREFIX: {schema: z.string().default(""), description: "Set this to empty string if you don't want to use it. Trailing space matters."},
|
||||
MATRIX_DEFAULT_PREFIX_REPLY: {schema: z.boolean().default(false)},
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION: {schema: z.boolean().default(false)},
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION_IN_DM: {schema: z.boolean().default(false)},
|
||||
MATRIX_DEFAULT_REQUIRE_MENTION_IN_REPLY: {schema: z.boolean().default(false)},
|
||||
/** ChatGPT Settings */
|
||||
OPENAI_EMAIL: {schema: z.string().min(3)},
|
||||
OPENAI_PASSWORD: {schema: z.string().min(1)},
|
||||
OPENAI_LOGIN_TYPE: {schema: z.enum(["google", "openai", "microsoft"]).default("google")},
|
||||
CHATGPT_TIMEOUT: {schema: z.number().default(2 * 60 * 1000)}
|
||||
});
|
@ -1,7 +1,8 @@
|
||||
import { ChatGPTAPIBrowser } from "chatgpt";
|
||||
import { ChatGPTAPIBrowser, ChatResponse } from "chatgpt";
|
||||
import { MatrixClient } from "matrix-bot-sdk";
|
||||
import { matrixBotUsername } from "./config.js";
|
||||
import { isEventAMessage } from "./utils.js";
|
||||
import { CHATGPT_TIMEOUT, MATRIX_BOT_USERNAME, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX} from "./env.js";
|
||||
import { RelatesTo, StoredConversation } from "./interfaces.js";
|
||||
import { isEventAMessage, sendError, sendThreadReply } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Run when *any* room event is received. The bot only sends a message if needed.
|
||||
@ -10,41 +11,51 @@ import { isEventAMessage } from "./utils.js";
|
||||
*/
|
||||
export async function handleRoomEvent(client: MatrixClient, chatGPT: ChatGPTAPIBrowser): Promise<(roomId: string, event: any) => Promise<void>> {
|
||||
return async (roomId: string, event: any) => {
|
||||
if (event.sender === matrixBotUsername) {
|
||||
return;
|
||||
}
|
||||
if (Date.now() - event.origin_server_ts > 10000) {
|
||||
// Don't send notifications for old events if the bot shuts down for some reason.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEventAMessage(event)) {
|
||||
const question: string = event.content.body;
|
||||
if (question === undefined) {
|
||||
await client.sendReadReceipt(roomId, event.event_id);
|
||||
await client.sendText(roomId,
|
||||
`Question is undefined. I don't currently support encrypted chats, maybe that's the issue?
|
||||
Please add me to an unencrypted chat.`);
|
||||
await client.sendReadReceipt(roomId, event.event_id);
|
||||
return;
|
||||
}
|
||||
await client.sendReadReceipt(roomId, event.event_id);
|
||||
await client.setTyping(roomId, true, 10000)
|
||||
try {
|
||||
// timeout after 2 minutes (which will also abort the underlying HTTP request)
|
||||
const result = await chatGPT.sendMessage(question, {
|
||||
timeoutMs: 2 * 60 * 1000
|
||||
})
|
||||
await client.setTyping(roomId, false, 500)
|
||||
const relatesTo: RelatesTo | undefined = event.content["m.relates_to"];
|
||||
const rootEventId: string = (relatesTo !== undefined && relatesTo.event_id !== undefined) ? relatesTo.event_id : event.event_id;
|
||||
const storedValue: string = await client.storageProvider.readValue('gpt-' + rootEventId)
|
||||
const storedConversation: StoredConversation = (storedValue !== undefined) ? JSON.parse(storedValue) : undefined;
|
||||
const config = (storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : undefined;
|
||||
|
||||
await client.sendText(roomId, `${result.response}`);
|
||||
await client.sendReadReceipt(roomId, event.event_id);
|
||||
const MATRIX_PREFIX_REPLY = (config === undefined) ? MATRIX_DEFAULT_PREFIX_REPLY : config.MATRIX_PREFIX_REPLY
|
||||
const shouldBePrefixed: boolean = ((Boolean(MATRIX_DEFAULT_PREFIX)) && (MATRIX_PREFIX_REPLY || (relatesTo === undefined)));
|
||||
|
||||
if (event.sender === MATRIX_BOT_USERNAME) return; // Don't reply to ourself
|
||||
if (Date.now() - event.origin_server_ts > 10000) return; // Don't reply to old messages
|
||||
if (shouldBePrefixed && !event.content.body.startsWith(MATRIX_DEFAULT_PREFIX)) return; // Don't reply without prefix if prefixed
|
||||
|
||||
await Promise.all([client.sendReadReceipt(roomId, event.event_id), client.setTyping(roomId, true, 10000)]);
|
||||
|
||||
const trimLength: number = shouldBePrefixed ? MATRIX_DEFAULT_PREFIX.length : 0
|
||||
const question: string = event.content.body.slice(trimLength).trimStart();
|
||||
if ((question === undefined) || !question) {
|
||||
await sendError(client, "Error with question: " + question, roomId, event.event_id);
|
||||
return;
|
||||
}
|
||||
|
||||
let result: ChatResponse
|
||||
if (storedConversation !== undefined) {
|
||||
result = await chatGPT.sendMessage(question, {
|
||||
timeoutMs: CHATGPT_TIMEOUT,
|
||||
conversationId: storedConversation.conversationId,
|
||||
parentMessageId: storedConversation.messageId
|
||||
});
|
||||
} else {
|
||||
result = await chatGPT.sendMessage(question, {timeoutMs: CHATGPT_TIMEOUT});
|
||||
}
|
||||
|
||||
await Promise.all([client.setTyping(roomId, false, 500), sendThreadReply(client, `${result.response}`, roomId, rootEventId)]);
|
||||
|
||||
await client.storageProvider.storeValue('gpt-' + rootEventId, JSON.stringify({
|
||||
conversationId: result.conversationId,
|
||||
messageId: result.messageId,
|
||||
config: ((storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {}),
|
||||
}));
|
||||
} catch (e) {
|
||||
await client.setTyping(roomId, false, 500)
|
||||
await client.sendText(roomId, `ChatGPT returned an error :(`);
|
||||
await client.sendReadReceipt(roomId, event.event_id);
|
||||
console.error("ChatGPS returned an error:");
|
||||
console.error(e);
|
||||
await sendError(client, "Bot error, terminating.", roomId, event.event_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
src/index.ts
30
src/index.ts
@ -7,7 +7,7 @@ import {
|
||||
} from "matrix-bot-sdk";
|
||||
|
||||
import * as path from "path";
|
||||
import { dataPath, openAiEmail, openAiPassword, isGoogleLogin, homeserverUrl, accessToken, matrixAutojoin, matrixBotPassword, matrixBotUsername, matrixEncryption } from './config.js'
|
||||
import { DATA_PATH, OPENAI_EMAIL, OPENAI_PASSWORD, OPENAI_LOGIN_TYPE, MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, MATRIX_AUTOJOIN, MATRIX_BOT_PASSWORD, MATRIX_BOT_USERNAME, MATRIX_ENCRYPTION } from './env.js'
|
||||
import { parseMatrixUsernamePretty } from './utils.js';
|
||||
import { handleRoomEvent } from './handlers.js';
|
||||
import { ChatGPTAPIBrowser } from 'chatgpt'
|
||||
@ -22,34 +22,35 @@ LogService.setLevel(LogLevel.INFO);
|
||||
// LogService.muteModule("Metrics");
|
||||
LogService.trace = LogService.debug;
|
||||
|
||||
const storage = new SimpleFsStorageProvider(path.join(dataPath, "bot.json")); // /storage/bot.json
|
||||
const storage = new SimpleFsStorageProvider(path.join(DATA_PATH, "bot.json")); // /storage/bot.json
|
||||
|
||||
// Prepare a crypto store if we need that
|
||||
let cryptoStore: ICryptoStorageProvider;
|
||||
if (matrixEncryption) {
|
||||
cryptoStore = new RustSdkCryptoStorageProvider(path.join(dataPath, "encrypted")); // /storage/encrypted
|
||||
if (MATRIX_ENCRYPTION) {
|
||||
cryptoStore = new RustSdkCryptoStorageProvider(path.join(DATA_PATH, "encrypted")); // /storage/encrypted
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const botUsernameWithoutDomain = parseMatrixUsernamePretty(matrixBotUsername);
|
||||
if (!accessToken){
|
||||
const authedClient = await (new MatrixAuth(homeserverUrl)).passwordLogin(botUsernameWithoutDomain, matrixBotPassword);
|
||||
const botUsernameWithoutDomain = parseMatrixUsernamePretty(MATRIX_BOT_USERNAME);
|
||||
if (!MATRIX_ACCESS_TOKEN){
|
||||
const authedClient = await (new MatrixAuth(MATRIX_HOMESERVER_URL)).passwordLogin(botUsernameWithoutDomain, MATRIX_BOT_PASSWORD);
|
||||
console.log(authedClient.homeserverUrl + " token: \n" + authedClient.accessToken)
|
||||
console.log("Set MATRIX_ACCESS_TOKEN to above token, MATRIX_ACCESS_USERNAME and MATRIX_ACCESS_PASSWORD can now be blank")
|
||||
console.log("Set MATRIX_ACCESS_TOKEN to above token, MATRIX_BOT_PASSWORD can now be blank")
|
||||
return;
|
||||
}
|
||||
const client = new MatrixClient(homeserverUrl, accessToken, storage);
|
||||
const client = new MatrixClient(MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, storage, cryptoStore);
|
||||
|
||||
// use puppeteer to bypass cloudflare (headful because of captchas)
|
||||
const chatGPT = new ChatGPTAPIBrowser({
|
||||
email: openAiEmail,
|
||||
password: openAiPassword,
|
||||
isGoogleLogin: isGoogleLogin
|
||||
email: OPENAI_EMAIL,
|
||||
password: OPENAI_PASSWORD,
|
||||
isGoogleLogin: (OPENAI_LOGIN_TYPE == "google"),
|
||||
isMicrosoftLogin: (OPENAI_LOGIN_TYPE == "microsoft")
|
||||
})
|
||||
await chatGPT.initSession()
|
||||
|
||||
// Automatically join rooms the bot is invited to
|
||||
if (matrixAutojoin) {
|
||||
if (MATRIX_AUTOJOIN) {
|
||||
AutojoinRoomsMixin.setupOnClient(client);
|
||||
}
|
||||
|
||||
@ -61,10 +62,9 @@ async function main() {
|
||||
|
||||
client.on("room.join", async (roomId: string, _event: any) => {
|
||||
LogService.info("index", `Bot joined room ${roomId}`);
|
||||
|
||||
await client.sendMessage(roomId, {
|
||||
"msgtype": "m.notice",
|
||||
"body": `👋 Hello, I'm the ChatGPT bot! Encrypted message support: ${ matrixEncryption }`,
|
||||
"body": `👋 Hello, I'm the ChatGPT bot! Encrypted message support: ${MATRIX_ENCRYPTION }`,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -4,12 +4,28 @@ type CommonMatrixEventFields = {
|
||||
event_id: string
|
||||
}
|
||||
|
||||
export type RelatesTo = {
|
||||
// The m.thread relationship structure
|
||||
event_id: string // note: always references the *thread root*
|
||||
rel_type: "m.thread" | string
|
||||
// The rich reply structure (for non thread aware client fallback)
|
||||
"m.in_reply_to"?: {
|
||||
// The most recent message known to the client in the thread.
|
||||
// Something easy to render for other client like a `m.room.message` event.
|
||||
"event_id": string
|
||||
},
|
||||
// A flag to denote that this is a thread with reply fallback
|
||||
"is_falling_back": string
|
||||
}
|
||||
|
||||
export type MessageEvent = CommonMatrixEventFields & {
|
||||
content: {
|
||||
body: string,
|
||||
msgtype: "m.text" | string
|
||||
"org.matrix.msc1767.text"?: string
|
||||
"org.matrix.msc1767.text"?: string,
|
||||
"m.relates_to"?: RelatesTo
|
||||
},
|
||||
raw: any,
|
||||
"type": "m.room.message",
|
||||
unsigned: Object;
|
||||
}
|
||||
@ -51,3 +67,13 @@ export type MatrixInviteEvent = CommonMatrixEventFields & {
|
||||
event_id: string
|
||||
}
|
||||
export type MembershipType = 'leave' | 'invite' | 'join'
|
||||
|
||||
export type StoredConversationConfig = {
|
||||
MATRIX_PREFIX_REPLY?: boolean;
|
||||
}
|
||||
|
||||
export type StoredConversation = {
|
||||
conversationId: string;
|
||||
messageId: string;
|
||||
config: StoredConversationConfig;
|
||||
}
|
||||
|
28
src/utils.ts
28
src/utils.ts
@ -1,3 +1,4 @@
|
||||
import { MatrixClient } from "matrix-bot-sdk";
|
||||
import { MessageEvent } from "./interfaces.js";
|
||||
|
||||
export function parseMatrixUsernamePretty(matrix_username: string): string {
|
||||
@ -11,3 +12,30 @@ export function parseMatrixUsernamePretty(matrix_username: string): string {
|
||||
export function isEventAMessage(event: any): event is MessageEvent {
|
||||
return event.type === 'm.room.message'
|
||||
}
|
||||
|
||||
export async function sendError(client: MatrixClient, text: string, roomId: string, eventId: string): Promise<void> {
|
||||
Promise.all([client.setTyping(roomId, false, 500), client.sendText(roomId, text), client.sendReadReceipt(roomId, eventId)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a thread reply.
|
||||
* @param client Matrix client
|
||||
* @param param1 Object containing text, root_event_id and roomId. root_event_id is the event_id
|
||||
* of the message the thread "replying" to.
|
||||
*/
|
||||
export async function sendThreadReply(client: MatrixClient, text: string, roomId: string, root_event_id: string): Promise<void> {
|
||||
const content = {
|
||||
body: text,
|
||||
msgtype: "m.text",
|
||||
"org.matrix.msc1767.text": text,
|
||||
"m.relates_to": {
|
||||
event_id: root_event_id,
|
||||
is_falling_back: true,
|
||||
"m.in_reply_to": {
|
||||
"event_id": root_event_id
|
||||
},
|
||||
rel_type: "m.thread"
|
||||
}
|
||||
}
|
||||
await client.sendEvent(roomId, "m.room.message", content);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user