mirror of
https://github.com/matrixgpt/matrix-chatgpt-bot.git
synced 2024-10-01 01:25:41 -04: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
|
VOLUME /storage
|
||||||
|
|
||||||
|
ENV DATA_PATH="/storage"
|
||||||
|
|
||||||
# We run a fake display and run our script.
|
# We run a fake display and run our script.
|
||||||
# Start script on Xvfb
|
# Start script on Xvfb
|
||||||
CMD xvfb-run --server-args="-screen 0 1024x768x24" yarn start
|
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 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
|
# Usage
|
||||||
- Create an (encrypted if enabled) room
|
- Create an (encrypted if enabled) room
|
||||||
- Add the bot
|
- Add the bot
|
||||||
@ -24,31 +26,36 @@ If your OpenAI account uses Google Auth, you shouldn't encounter any of the more
|
|||||||
|
|
||||||
# Features
|
# Features
|
||||||
- Shows typing indicator as ChatGPT is thinking!
|
- Shows typing indicator as ChatGPT is thinking!
|
||||||
- Doesn't yet support encryption
|
- Supports 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 :)
|
|
||||||
|
|
||||||
# Setting up the account
|
# Setting up the account
|
||||||
- Create a new Matrix account on Matrix.org (or your favourite server)
|
- Create a new Matrix account on Matrix.org (or your favourite server)
|
||||||
- Go to the settings and get the access token
|
- 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`:
|
- 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.
|
# ChatGPT Settings (required)
|
||||||
MATRIX_HOMESERVER_URL=
|
|
||||||
MATRIX_ACCESS_TOKEN=
|
|
||||||
|
|
||||||
OPENAI_EMAIL=
|
OPENAI_EMAIL=
|
||||||
OPENAI_PASSWORD=
|
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=
|
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_BOT_PASSWORD=
|
||||||
MATRIX_AUTO_JOIN=true
|
|
||||||
MATRIX_ENCRYPTION=true
|
|
||||||
|
|
||||||
# needs to be ./storage/ if you aren't using Docker or /storage/ if you are.
|
# Matrix Configurable Settings Defaults (optional)
|
||||||
DATA_PATH=/storage/
|
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
|
# 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 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
|
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"
|
"typecheck": "npx tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chatgpt": "^3.3.6",
|
"chatgpt": "^3.3.8",
|
||||||
"dotenv": "^14.2.0",
|
"dotenv": "^14.2.0",
|
||||||
"matrix-bot-sdk": "^0.6.2",
|
"matrix-bot-sdk": "^0.6.3",
|
||||||
"puppeteer": "^19.4.1",
|
"puppeteer": "^19.4.1",
|
||||||
"typescript": "^4.5.2"
|
"typescript": "^4.5.2",
|
||||||
|
"znv": "^0.3.2",
|
||||||
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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 { MatrixClient } from "matrix-bot-sdk";
|
||||||
import { matrixBotUsername } from "./config.js";
|
import { CHATGPT_TIMEOUT, MATRIX_BOT_USERNAME, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX} from "./env.js";
|
||||||
import { isEventAMessage } from "./utils.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.
|
* 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>> {
|
export async function handleRoomEvent(client: MatrixClient, chatGPT: ChatGPTAPIBrowser): Promise<(roomId: string, event: any) => Promise<void>> {
|
||||||
return async (roomId: string, event: any) => {
|
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)) {
|
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 {
|
try {
|
||||||
// timeout after 2 minutes (which will also abort the underlying HTTP request)
|
const relatesTo: RelatesTo | undefined = event.content["m.relates_to"];
|
||||||
const result = await chatGPT.sendMessage(question, {
|
const rootEventId: string = (relatesTo !== undefined && relatesTo.event_id !== undefined) ? relatesTo.event_id : event.event_id;
|
||||||
timeoutMs: 2 * 60 * 1000
|
const storedValue: string = await client.storageProvider.readValue('gpt-' + rootEventId)
|
||||||
})
|
const storedConversation: StoredConversation = (storedValue !== undefined) ? JSON.parse(storedValue) : undefined;
|
||||||
await client.setTyping(roomId, false, 500)
|
const config = (storedConversation !== undefined && storedConversation.config !== undefined) ? storedConversation.config : undefined;
|
||||||
|
|
||||||
await client.sendText(roomId, `${result.response}`);
|
const MATRIX_PREFIX_REPLY = (config === undefined) ? MATRIX_DEFAULT_PREFIX_REPLY : config.MATRIX_PREFIX_REPLY
|
||||||
await client.sendReadReceipt(roomId, event.event_id);
|
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) {
|
} 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);
|
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";
|
} from "matrix-bot-sdk";
|
||||||
|
|
||||||
import * as path from "path";
|
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 { parseMatrixUsernamePretty } from './utils.js';
|
||||||
import { handleRoomEvent } from './handlers.js';
|
import { handleRoomEvent } from './handlers.js';
|
||||||
import { ChatGPTAPIBrowser } from 'chatgpt'
|
import { ChatGPTAPIBrowser } from 'chatgpt'
|
||||||
@ -22,34 +22,35 @@ LogService.setLevel(LogLevel.INFO);
|
|||||||
// LogService.muteModule("Metrics");
|
// LogService.muteModule("Metrics");
|
||||||
LogService.trace = LogService.debug;
|
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
|
// Prepare a crypto store if we need that
|
||||||
let cryptoStore: ICryptoStorageProvider;
|
let cryptoStore: ICryptoStorageProvider;
|
||||||
if (matrixEncryption) {
|
if (MATRIX_ENCRYPTION) {
|
||||||
cryptoStore = new RustSdkCryptoStorageProvider(path.join(dataPath, "encrypted")); // /storage/encrypted
|
cryptoStore = new RustSdkCryptoStorageProvider(path.join(DATA_PATH, "encrypted")); // /storage/encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const botUsernameWithoutDomain = parseMatrixUsernamePretty(matrixBotUsername);
|
const botUsernameWithoutDomain = parseMatrixUsernamePretty(MATRIX_BOT_USERNAME);
|
||||||
if (!accessToken){
|
if (!MATRIX_ACCESS_TOKEN){
|
||||||
const authedClient = await (new MatrixAuth(homeserverUrl)).passwordLogin(botUsernameWithoutDomain, matrixBotPassword);
|
const authedClient = await (new MatrixAuth(MATRIX_HOMESERVER_URL)).passwordLogin(botUsernameWithoutDomain, MATRIX_BOT_PASSWORD);
|
||||||
console.log(authedClient.homeserverUrl + " token: \n" + authedClient.accessToken)
|
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;
|
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)
|
// use puppeteer to bypass cloudflare (headful because of captchas)
|
||||||
const chatGPT = new ChatGPTAPIBrowser({
|
const chatGPT = new ChatGPTAPIBrowser({
|
||||||
email: openAiEmail,
|
email: OPENAI_EMAIL,
|
||||||
password: openAiPassword,
|
password: OPENAI_PASSWORD,
|
||||||
isGoogleLogin: isGoogleLogin
|
isGoogleLogin: (OPENAI_LOGIN_TYPE == "google"),
|
||||||
|
isMicrosoftLogin: (OPENAI_LOGIN_TYPE == "microsoft")
|
||||||
})
|
})
|
||||||
await chatGPT.initSession()
|
await chatGPT.initSession()
|
||||||
|
|
||||||
// Automatically join rooms the bot is invited to
|
// Automatically join rooms the bot is invited to
|
||||||
if (matrixAutojoin) {
|
if (MATRIX_AUTOJOIN) {
|
||||||
AutojoinRoomsMixin.setupOnClient(client);
|
AutojoinRoomsMixin.setupOnClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,10 +62,9 @@ async function main() {
|
|||||||
|
|
||||||
client.on("room.join", async (roomId: string, _event: any) => {
|
client.on("room.join", async (roomId: string, _event: any) => {
|
||||||
LogService.info("index", `Bot joined room ${roomId}`);
|
LogService.info("index", `Bot joined room ${roomId}`);
|
||||||
|
|
||||||
await client.sendMessage(roomId, {
|
await client.sendMessage(roomId, {
|
||||||
"msgtype": "m.notice",
|
"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
|
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 & {
|
export type MessageEvent = CommonMatrixEventFields & {
|
||||||
content: {
|
content: {
|
||||||
body: string,
|
body: string,
|
||||||
msgtype: "m.text" | 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",
|
"type": "m.room.message",
|
||||||
unsigned: Object;
|
unsigned: Object;
|
||||||
}
|
}
|
||||||
@ -51,3 +67,13 @@ export type MatrixInviteEvent = CommonMatrixEventFields & {
|
|||||||
event_id: string
|
event_id: string
|
||||||
}
|
}
|
||||||
export type MembershipType = 'leave' | 'invite' | 'join'
|
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";
|
import { MessageEvent } from "./interfaces.js";
|
||||||
|
|
||||||
export function parseMatrixUsernamePretty(matrix_username: string): string {
|
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 {
|
export function isEventAMessage(event: any): event is MessageEvent {
|
||||||
return event.type === 'm.room.message'
|
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