mirror of
https://github.com/matrixgpt/matrix-chatgpt-bot.git
synced 2024-09-28 20:15:56 +00:00
commit
16f2852303
@ -37,7 +37,7 @@ If you want to get an access token without exposing your password to this bot yo
|
||||
OPENAI_EMAIL=
|
||||
OPENAI_PASSWORD=
|
||||
# What type of Login it is, possibility's are google, openai, microsoft
|
||||
OPENAI_LOGIN_TYPE="google"
|
||||
OPENAI_LOGIN_TYPE=google
|
||||
|
||||
# Matrix Static Settings (required, see notes)
|
||||
# Defaults to "https://matrix.org"
|
||||
|
120
src/handlers.ts
120
src/handlers.ts
@ -1,64 +1,88 @@
|
||||
import { ChatGPTAPIBrowser, ChatResponse } from "chatgpt";
|
||||
import { MatrixClient } from "matrix-bot-sdk";
|
||||
import { LogService, MatrixClient, UserID } from "matrix-bot-sdk";
|
||||
import { CHATGPT_TIMEOUT, MATRIX_BOT_USERNAME, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX} from "./env.js";
|
||||
import { RelatesTo, StoredConversation, StoredConversationConfig } from "./interfaces.js";
|
||||
import { isEventAMessage, sendError, sendThreadReply } from "./utils.js";
|
||||
import { sendError, sendThreadReply } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Run when *any* room event is received. The bot only sends a message if needed.
|
||||
* @param client
|
||||
* @returns Room event handler, which itself returnings nothing
|
||||
*/
|
||||
export async function handleRoomEvent(client: MatrixClient, chatGPT: ChatGPTAPIBrowser): Promise<(roomId: string, event: any) => Promise<void>> {
|
||||
return async (roomId: string, event: any) => {
|
||||
if (isEventAMessage(event)) {
|
||||
try {
|
||||
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;
|
||||
export default class CommandHandler {
|
||||
|
||||
const config: StoredConversationConfig = (storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {};
|
||||
const MATRIX_PREFIX: string = (config.MATRIX_PREFIX === undefined) ? MATRIX_DEFAULT_PREFIX : config.MATRIX_PREFIX
|
||||
const MATRIX_PREFIX_REPLY:boolean = (config.MATRIX_PREFIX_REPLY === undefined) ? MATRIX_DEFAULT_PREFIX_REPLY : config.MATRIX_PREFIX_REPLY
|
||||
// Variables so we can cache the bot's display name and ID for command matching later.
|
||||
private displayName: string;
|
||||
private userId: string;
|
||||
private localpart: string;
|
||||
|
||||
const shouldBePrefixed: boolean = ((MATRIX_PREFIX) && (relatesTo === undefined)) || (MATRIX_PREFIX_REPLY && (relatesTo !== undefined));
|
||||
constructor(private client: MatrixClient, private chatGPT:ChatGPTAPIBrowser) {}
|
||||
|
||||
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_PREFIX)) return; // Don't reply without prefix if prefixed
|
||||
public async start() {
|
||||
await this.prepareProfile(); // Populate the variables above (async)
|
||||
this.client.on("room.message", this.onMessage.bind(this)); // Set up the event handler
|
||||
}
|
||||
|
||||
await Promise.all([client.sendReadReceipt(roomId, event.event_id), client.setTyping(roomId, true, 10000)]);
|
||||
private async prepareProfile() {
|
||||
this.userId = await this.client.getUserId();
|
||||
this.localpart = new UserID(this.userId).localpart;
|
||||
try {
|
||||
const profile = await this.client.getUserProfile(this.userId);
|
||||
if (profile && profile['displayname']) this.displayName = profile['displayname'];
|
||||
} catch (e) {
|
||||
// Non-fatal error - we'll just log it and move on.
|
||||
LogService.warn("CommandHandler", e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Run when *any* room message is received. The bot only sends a message if needed.
|
||||
* @returns Room event handler, which itself returnings nothing
|
||||
*/
|
||||
private async onMessage(roomId: string, event: any) {
|
||||
try {
|
||||
if (event.sender === MATRIX_BOT_USERNAME) return; // Ignore ourself
|
||||
if (Date.now() - event.origin_server_ts > 10000) return; // Ignore old messages
|
||||
const relatesTo: RelatesTo | undefined = event.content["m.relates_to"];
|
||||
if ((relatesTo !== undefined) && (relatesTo["rel_type"] === "m.replace")) return; // Ignore edits
|
||||
|
||||
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});
|
||||
}
|
||||
const rootEventId: string = (relatesTo !== undefined && relatesTo.event_id !== undefined) ? relatesTo.event_id : event.event_id;
|
||||
const storedValue: string = await this.client.storageProvider.readValue('gpt-' + rootEventId)
|
||||
const storedConversation: StoredConversation = (storedValue !== undefined) ? JSON.parse(storedValue) : undefined;
|
||||
const config: StoredConversationConfig = (storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {};
|
||||
const MATRIX_PREFIX: string = (config.MATRIX_PREFIX === undefined) ? MATRIX_DEFAULT_PREFIX : config.MATRIX_PREFIX
|
||||
const MATRIX_PREFIX_REPLY:boolean = (config.MATRIX_PREFIX_REPLY === undefined) ? MATRIX_DEFAULT_PREFIX_REPLY : config.MATRIX_PREFIX_REPLY
|
||||
|
||||
await Promise.all([client.setTyping(roomId, false, 500), sendThreadReply(client, `${result.response}`, roomId, rootEventId)]);
|
||||
const shouldBePrefixed: boolean = ((MATRIX_PREFIX) && (relatesTo === undefined)) || (MATRIX_PREFIX_REPLY && (relatesTo !== undefined));
|
||||
const prefixes = [MATRIX_PREFIX, `${this.localpart}:`, `${this.displayName}:`, `${this.userId}:`];
|
||||
const prefixUsed = prefixes.find(p => event.content.body.startsWith(p));
|
||||
if (shouldBePrefixed && !prefixUsed) return; // Ignore without prefix if prefixed
|
||||
await Promise.all([this.client.sendReadReceipt(roomId, event.event_id), this.client.setTyping(roomId, true, 10000)]);
|
||||
|
||||
await client.storageProvider.storeValue('gpt-' + rootEventId, JSON.stringify({
|
||||
conversationId: result.conversationId,
|
||||
messageId: result.messageId,
|
||||
config: ((storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {}),
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await sendError(client, "Bot error, terminating.", roomId, event.event_id);
|
||||
const trimLength: number = shouldBePrefixed ? prefixUsed.length : 0
|
||||
const question: string = event.content.body.slice(trimLength).trimStart();
|
||||
|
||||
if ((question === undefined) || !question) {
|
||||
await sendError(this.client, "Error with question: " + question, roomId, event.event_id);
|
||||
return;
|
||||
}
|
||||
|
||||
let result: ChatResponse
|
||||
if (storedConversation !== undefined) {
|
||||
result = await this.chatGPT.sendMessage(question, {
|
||||
timeoutMs: CHATGPT_TIMEOUT,
|
||||
conversationId: storedConversation.conversationId,
|
||||
parentMessageId: storedConversation.messageId
|
||||
});
|
||||
} else {
|
||||
result = await this.chatGPT.sendMessage(question, {timeoutMs: CHATGPT_TIMEOUT});
|
||||
}
|
||||
|
||||
await Promise.all([this.client.setTyping(roomId, false, 500), sendThreadReply(this.client, `${result.response}`, roomId, rootEventId)]);
|
||||
|
||||
await this.client.storageProvider.storeValue('gpt-' + rootEventId, JSON.stringify({
|
||||
conversationId: result.conversationId,
|
||||
messageId: result.messageId,
|
||||
config: ((storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : {}),
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await sendError(this.client, "Bot error, terminating.", roomId, event.event_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import * as path from "path";
|
||||
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 CommandHandler from "./handlers.js"
|
||||
import { ChatGPTAPIBrowser } from 'chatgpt'
|
||||
|
||||
LogService.setLogger(new RichConsoleLogger());
|
||||
@ -68,7 +68,9 @@ async function main() {
|
||||
});
|
||||
});
|
||||
|
||||
client.on("room.event", await handleRoomEvent(client, chatGPT));
|
||||
// Prepare the command handler
|
||||
const commands = new CommandHandler(client, chatGPT);
|
||||
await commands.start();
|
||||
|
||||
LogService.info("index", "Starting bot...");
|
||||
await client.start()
|
||||
|
@ -9,10 +9,6 @@ export function parseMatrixUsernamePretty(matrix_username: string): string {
|
||||
return withoutUrl.split('@')[1]
|
||||
}
|
||||
|
||||
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)]);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user