198 lines
8.3 KiB
JavaScript
198 lines
8.3 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import os from "os";
|
|
import { log } from "./logger.js";
|
|
const CONFIG_FILENAMES = ["matrix.json", "matrix.jsonc"];
|
|
function mergeDefaults(raw, envOverrides) {
|
|
const base = {
|
|
homeserver: envOverrides.homeserver || "https://matrix.org",
|
|
userId: envOverrides.userId,
|
|
accessToken: envOverrides.accessToken,
|
|
password: envOverrides.password,
|
|
deviceId: envOverrides.deviceId || "opencode-matrix-plugin",
|
|
autoJoin: envOverrides.autoJoin !== false,
|
|
triggerPatterns: envOverrides.triggerPatterns || ["!oc "],
|
|
ignoreRooms: envOverrides.ignoreRooms || [],
|
|
ignoreUsers: envOverrides.ignoreUsers || [],
|
|
allowedUsers: envOverrides.allowedUsers || [],
|
|
formatHtml: envOverrides.formatHtml || false,
|
|
threadIsolation: envOverrides.threadIsolation !== false,
|
|
respondToThreadReplies: envOverrides.respondToThreadReplies !== false,
|
|
rateLimitSeconds: envOverrides.rateLimitSeconds || 5,
|
|
botName: envOverrides.botName || "opencode",
|
|
storagePath: envOverrides.storagePath,
|
|
};
|
|
if (!raw)
|
|
return base;
|
|
return {
|
|
homeserver: raw.homeserver || base.homeserver,
|
|
userId: raw.userId || base.userId,
|
|
accessToken: raw.accessToken || base.accessToken,
|
|
password: raw.password || base.password,
|
|
deviceId: raw.deviceId || base.deviceId,
|
|
autoJoin: raw.autoJoin !== undefined ? raw.autoJoin : base.autoJoin,
|
|
triggerPatterns: raw.triggerPatterns || base.triggerPatterns,
|
|
ignoreRooms: raw.ignoreRooms || base.ignoreRooms,
|
|
ignoreUsers: raw.ignoreUsers || base.ignoreUsers,
|
|
allowedUsers: raw.allowedUsers || base.allowedUsers,
|
|
formatHtml: raw.formatHtml !== undefined ? raw.formatHtml : base.formatHtml,
|
|
threadIsolation: raw.threadIsolation !== undefined ? raw.threadIsolation : base.threadIsolation,
|
|
respondToThreadReplies: raw.respondToThreadReplies !== undefined ? raw.respondToThreadReplies : base.respondToThreadReplies,
|
|
rateLimitSeconds: raw.rateLimitSeconds || base.rateLimitSeconds,
|
|
botName: raw.botName || base.botName,
|
|
storagePath: raw.storagePath || base.storagePath,
|
|
};
|
|
}
|
|
function tryReadConfig(dir) {
|
|
for (const filename of CONFIG_FILENAMES) {
|
|
const filepath = path.join(dir, filename);
|
|
if (!fs.existsSync(filepath))
|
|
continue;
|
|
try {
|
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
const parsed = JSON.parse(content);
|
|
if (typeof parsed === "object" && parsed !== null) {
|
|
return parsed;
|
|
}
|
|
}
|
|
catch (e) {
|
|
log(`Failed to read config: ${filepath}`);
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function findProjectConfigDir() {
|
|
let dir = process.cwd();
|
|
const root = path.parse(dir).root;
|
|
while (dir && dir !== root) {
|
|
const opencodeDir = path.join(dir, ".opencode");
|
|
if (fs.existsSync(opencodeDir) && fs.statSync(opencodeDir).isDirectory()) {
|
|
return opencodeDir;
|
|
}
|
|
dir = path.dirname(dir);
|
|
}
|
|
const currentOpencode = path.join(process.cwd(), ".opencode");
|
|
if (fs.existsSync(currentOpencode) && fs.statSync(currentOpencode).isDirectory()) {
|
|
return currentOpencode;
|
|
}
|
|
return null;
|
|
}
|
|
function findGlobalConfigDir() {
|
|
return path.join(os.homedir(), ".config", "opencode");
|
|
}
|
|
const DEFAULT_CONFIG_CONTENT = JSON.stringify({
|
|
homeserver: "https://matrix.org",
|
|
userId: "",
|
|
accessToken: "",
|
|
password: "",
|
|
deviceId: "opencode-matrix-plugin",
|
|
autoJoin: true,
|
|
triggerPatterns: ["!oc "],
|
|
ignoreRooms: [],
|
|
ignoreUsers: [],
|
|
allowedUsers: [],
|
|
formatHtml: false,
|
|
threadIsolation: true,
|
|
respondToThreadReplies: true,
|
|
rateLimitSeconds: 5,
|
|
botName: "opencode",
|
|
enabled: true,
|
|
}, null, 2);
|
|
async function ensureGlobalConfig(globalConfigDir) {
|
|
log(`ensureGlobalConfig called, dir=${globalConfigDir}`);
|
|
for (const filename of CONFIG_FILENAMES) {
|
|
const filepath = path.join(globalConfigDir, filename);
|
|
if (fs.existsSync(filepath)) {
|
|
log(`config already exists: ${filepath}`);
|
|
return filepath;
|
|
}
|
|
}
|
|
log("no config found, creating default");
|
|
try {
|
|
log("mkdirSync globalConfigDir");
|
|
fs.mkdirSync(globalConfigDir, { recursive: true });
|
|
const filepath = path.join(globalConfigDir, "matrix.json");
|
|
log("writing file: " + filepath);
|
|
fs.writeFileSync(filepath, DEFAULT_CONFIG_CONTENT, { mode: 0o600 });
|
|
log("Created default config: " + filepath);
|
|
return filepath;
|
|
}
|
|
catch (err) {
|
|
log("Failed to create default config: " + err);
|
|
return null;
|
|
}
|
|
}
|
|
export async function loadConfig(pluginOptions = {}) {
|
|
pluginOptions = pluginOptions || {};
|
|
const envOverrides = {
|
|
homeserver: process.env.MATRIX_HOMESERVER,
|
|
userId: process.env.MATRIX_USER_ID,
|
|
accessToken: process.env.MATRIX_ACCESS_TOKEN,
|
|
password: process.env.MATRIX_PASSWORD,
|
|
deviceId: process.env.MATRIX_DEVICE_ID || undefined,
|
|
triggerPatterns: process.env.MATRIX_TRIGGER ? [process.env.MATRIX_TRIGGER] : undefined,
|
|
allowedUsers: process.env.MATRIX_ALLOWED_USERS ? process.env.MATRIX_ALLOWED_USERS.split(",").map(s => s.trim()).filter(Boolean) : undefined,
|
|
storagePath: process.env.MATRIX_STORAGE_PATH,
|
|
};
|
|
const projectConfigDir = findProjectConfigDir();
|
|
log(`projectConfigDir=${projectConfigDir}`);
|
|
let rawConfig = null;
|
|
let configPath = null;
|
|
if (projectConfigDir) {
|
|
const projectConfig = tryReadConfig(projectConfigDir);
|
|
log(`projectConfig=${projectConfig ? "found" : "null"}`);
|
|
if (projectConfig) {
|
|
rawConfig = projectConfig;
|
|
const foundFile = CONFIG_FILENAMES.find((f) => fs.existsSync(path.join(projectConfigDir, f)));
|
|
if (foundFile) {
|
|
configPath = path.join(projectConfigDir, foundFile);
|
|
}
|
|
}
|
|
}
|
|
const globalConfigDir = findGlobalConfigDir();
|
|
const globalConfig = tryReadConfig(globalConfigDir);
|
|
log(`globalConfigDir=${globalConfigDir}`);
|
|
log(`globalConfig=${globalConfig ? "found" : "null"}`);
|
|
log(`rawConfig=${rawConfig ? "found" : "null"}`);
|
|
if (!rawConfig && globalConfig) {
|
|
rawConfig = globalConfig;
|
|
configPath = path.join(globalConfigDir, "matrix.json");
|
|
log("using global config");
|
|
}
|
|
if (!rawConfig) {
|
|
log("no config found, calling ensureGlobalConfig");
|
|
const createdPath = await ensureGlobalConfig(globalConfigDir);
|
|
log(`ensureGlobalConfig returned=${createdPath}`);
|
|
if (createdPath) {
|
|
configPath = createdPath;
|
|
}
|
|
}
|
|
if (rawConfig && rawConfig.enabled === false) {
|
|
return {
|
|
options: { ...mergeDefaults(null, envOverrides), homeserver: "" },
|
|
configPath,
|
|
};
|
|
}
|
|
const merged = mergeDefaults(rawConfig, envOverrides);
|
|
const finalOptions = {
|
|
homeserver: pluginOptions.homeserver || merged.homeserver,
|
|
userId: pluginOptions.userId || merged.userId,
|
|
accessToken: pluginOptions.accessToken || merged.accessToken,
|
|
password: pluginOptions.password || merged.password,
|
|
deviceId: pluginOptions.deviceId || merged.deviceId,
|
|
autoJoin: pluginOptions.autoJoin !== undefined ? pluginOptions.autoJoin : merged.autoJoin,
|
|
triggerPatterns: pluginOptions.triggerPatterns || merged.triggerPatterns,
|
|
ignoreRooms: pluginOptions.ignoreRooms || merged.ignoreRooms,
|
|
ignoreUsers: pluginOptions.ignoreUsers || merged.ignoreUsers,
|
|
allowedUsers: pluginOptions.allowedUsers || merged.allowedUsers,
|
|
formatHtml: pluginOptions.formatHtml !== undefined ? pluginOptions.formatHtml : merged.formatHtml,
|
|
threadIsolation: pluginOptions.threadIsolation !== undefined ? pluginOptions.threadIsolation : merged.threadIsolation,
|
|
respondToThreadReplies: pluginOptions.respondToThreadReplies !== undefined ? pluginOptions.respondToThreadReplies : merged.respondToThreadReplies,
|
|
rateLimitSeconds: pluginOptions.rateLimitSeconds || merged.rateLimitSeconds,
|
|
botName: pluginOptions.botName || merged.botName,
|
|
storagePath: pluginOptions.storagePath || merged.storagePath,
|
|
};
|
|
return { options: finalOptions, configPath };
|
|
}
|