diff --git a/matrix.mjs b/matrix.mjs
deleted file mode 100644
index 5fd1c3e..0000000
--- a/matrix.mjs
+++ /dev/null
@@ -1,361 +0,0 @@
-import { createClient } from "matrix-js-sdk";
-import EventEmitter from "events";
-
-export default class matrix extends EventEmitter {
- constructor(options) {
- super();
- this.options = options || {};
- this.baseUrl = options.baseUrl || "https://matrix.org";
- this.token = options.token || null;
- this.userId = options.userId || null;
- if (!this.userId && this.token) {
- // Try to derive userId if not provided but we have a token?
- // Actually usually you need userId to init client or it can be inferred?
- // sdk.createClient({ baseUrl, accessToken, userId })
- }
-
- this.set = this.options.set || "all";
- this.network = "Matrix";
-
- // Custom logger to suppress FetchHttpApi debug spam
- const silentLogger = {
- debug: () => {}, // Suppress debug
- info: (msg, ...args) => console.info(`[Matrix] ${msg}`, ...args),
- warn: (msg, ...args) => console.warn(`[Matrix] ${msg}`, ...args),
- error: (msg, ...args) => console.error(`[Matrix] ${msg}`, ...args),
- trace: () => {},
- getChild: () => silentLogger
- };
-
- this.client = createClient({
- baseUrl: this.baseUrl,
- accessToken: this.token,
- userId: this.userId,
- logger: silentLogger
- });
-
- this.server = {
- set: this.set,
- channel: new Map(),
- user: new Map(),
- me: {},
- download: (mxcUrl) => this.download(mxcUrl)
- };
-
- return (async () => {
- await this.start();
- return this;
- })();
- }
-
- async start() {
- await this.client.startClient({ initialSyncLimit: 10 });
-
- this.client.once('sync', async (state, prevState, res) => {
- if (state === 'PREPARED') {
- const me = await this.client.getProfileInfo(this.client.getUserId());
- this.server.me = {
- nickname: me.displayname || this.client.getUserId(),
- username: this.client.getUserId(),
- account: this.client.getUserId(),
- prefix: `${this.client.getUserId()}!matrix`, // simplified
- id: this.client.getUserId()
- };
- this.emit("data", ["ready", "matrix"]);
-
- if (this.options.channels) {
- for (const channel of this.options.channels) {
- try {
- await this.client.joinRoom(channel);
- console.log(`[Matrix] Joined room: ${channel}`);
- } catch (e) {
- console.error(`[Matrix] Failed to join room ${channel}:`, e);
- }
- }
- }
- }
- });
-
- this.client.on("Room.timeline", (event, room, toStartOfTimeline) => {
- if (toStartOfTimeline) return;
-
- if (event.getType() !== "m.room.message") return;
-
- // Ignore own messages
- if (event.getSender() === this.client.getUserId()) return;
-
- const content = event.getContent();
- if (!content || !content.body) return;
-
- if (['m.text', 'm.notice', 'm.emote'].indexOf(content.msgtype) === -1) return;
-
- const age = Date.now() - event.getTs();
- if (age > 60000) return; // Ignore old events (1m)
-
- // Check if DM (<= 2 members)
- const memberCount = room.getJoinedMemberCount();
- // if (memberCount <= 2) {
- // We can log it if needed, but return early to avoid trigger processing
- // console.log(`[Matrix] Ignoring DM from ${event.getSender()}`);
- // return;
- // }
-
- (async () => {
- try {
- const msgData = await this.reply(event, room);
- this.emit("data", ["message", msgData]);
- } catch (err) {
- console.error(`[Matrix] Error processing message:`, err);
- }
- })();
- });
-
- // Error handling?
- // this.client.on("error", ...);
- }
-
- async reply(event, room) {
- const senderId = event.getSender();
- const senderMember = room.getMember(senderId);
- const senderName = senderMember ? senderMember.name : senderId; // fallback
-
- const content = event.getContent();
-
- let message = content.body;
- if (content.msgtype === 'm.emote') {
- message = `* ${senderName} ${message}`;
- }
-
- // Handle replies
- let replyTo = null;
- const relation = content['m.relates_to'];
-
- if (relation && relation['m.in_reply_to']) {
- const relatedEventId = relation['m.in_reply_to'].event_id;
- try {
- let relatedEvent = room.findEventById(relatedEventId);
-
- if (!relatedEvent) {
- try {
- const fetched = await this.client.fetchRoomEvent(room.roomId, relatedEventId);
- if (fetched.getContent) {
- relatedEvent = fetched;
- } else {
- // Create a mock object if it's raw JSON
- relatedEvent = {
- getContent: () => fetched.content,
- getSender: () => fetched.sender,
- getType: () => fetched.type
- };
- }
- } catch (fetchErr) {
- console.error(`[Matrix] Failed to fetch remote event ${relatedEventId}:`, fetchErr);
- }
- }
-
- if (relatedEvent) {
- const relatedContent = relatedEvent.getContent();
- const isMedia = ['m.image', 'm.video', 'm.audio', 'm.file'].includes(relatedContent.msgtype);
- let mediaUrl = null;
- let mxcUrl = null;
- if (isMedia && relatedContent.url) {
- mxcUrl = relatedContent.url;
- mediaUrl = this.client.mxcUrlToHttp(relatedContent.url);
- }
-
- replyTo = {
- sender: relatedEvent.getSender(),
- message: relatedContent.body,
- url: mediaUrl,
- mxcUrl: mxcUrl,
- type: relatedContent.msgtype,
- filename: relatedContent.filename || relatedContent.body // fallback to body if filename missing
- };
- }
- } catch (e) {
- console.warn(`[Matrix] Failed to resolve reply event ${relatedEventId}:`, e);
- }
- }
-
- return {
- type: "matrix",
- network: "Matrix",
- channel: room.name || "Room",
- channelid: room.roomId,
- user: {
- prefix: `${senderId}!${senderId}`,
- nick: senderName,
- username: senderId, // Matrix users are defined by ID usually
- account: senderId
- },
- message: message,
- replyTo: replyTo,
- time: ~~(event.getTs() / 1000),
- raw: event,
- self: this.server,
- reply: (msg) => this.send(room.roomId, this.format(msg)),
- replyAction: (msg) => this.send(room.roomId, this.format(msg), "emote"),
- replyNotice: (msg) => this.send(room.roomId, this.format(msg), "notice")
- };
- }
-
- async download(mxcUrl) {
- if (!mxcUrl) throw new Error("No MXC URL provided");
-
- const accessToken = this.client.getAccessToken();
- if (!accessToken) console.warn("[Matrix] No access token available for download!");
-
- const tryDownload = async (url) => {
- const res = await fetch(url, {
- headers: {
- "Authorization": `Bearer ${accessToken}`
- }
- });
- if (!res.ok) {
- const errText = await res.text();
- throw new Error(`HTTP Error ${res.status}: ${res.statusText} - ${errText}`);
- }
- return await res.arrayBuffer();
- }
-
- try {
- // Parse MXC
- const cleanMxc = mxcUrl.replace('mxc://', '');
- const [serverName, mediaId] = cleanMxc.split('/');
-
- if (!serverName || !mediaId) {
- throw new Error(`Invalid MXC URL format: ${mxcUrl}`);
- }
-
- // Strategy 1: Authenticated Client API (MSC3916 / Matrix 1.11+)
- const baseUrl = this.client.baseUrl;
- const authUrl = `${baseUrl}/_matrix/client/v1/media/download/${serverName}/${mediaId}`;
-
- try {
- const ab = await tryDownload(authUrl);
- return Buffer.from(ab);
- } catch (e1) {
- // console.warn(`[Matrix] Authenticated Client API failed: ${e1.message}`);
- }
-
- // Strategy 2: Legacy Media API (v3) with Auth
- const v3Url = `${baseUrl}/_matrix/media/v3/download/${serverName}/${mediaId}`;
- try {
- const ab = await tryDownload(v3Url);
- return Buffer.from(ab);
- } catch (e2) {
- // console.warn(`[Matrix] Legacy v3 API failed: ${e2.message}`);
- }
-
- throw new Error("All download strategies failed.");
- } catch (e) {
- console.error(`[Matrix] Download failed:`, e);
- throw e;
- }
- }
-
- format(msg) {
- if (typeof msg === 'object') return msg; // Should probably be stringified or handled?
- // simple bbcode to markdown/html or just strip?
- // Matrix supports HTML.
- // Reuse logic from others if needed, but for now simple replacement
- return msg.toString()
- .replace(/\[b\](.*?)\[\/b\]/g, "**$1**")
- .replace(/\[i\](.*?)\[\/i\]/g, "*$1*");
- }
-
- async send(roomId, msg, type = "text") {
- let content = {
- msgtype: "m.text",
- body: ""
- };
-
- if (type === 'emote') content.msgtype = 'm.emote';
- if (type === 'notice') content.msgtype = 'm.notice';
-
- if (typeof msg === 'object') {
- content.body = msg.body || JSON.stringify(msg);
- if (msg.formatted_body) {
- content.format = "org.matrix.custom.html";
- content.formatted_body = msg.formatted_body;
- }
- } else {
- // String handling
- content.body = msg.toString();
- // Simple auto-formatting check
- if (content.body.includes('**') || content.body.includes('*')) {
- content.format = "org.matrix.custom.html";
- content.formatted_body = content.body
- .replace(/\*\*(.*?)\*\*/g, "$1")
- .replace(/\*(.*?)\*/g, "$1");
- }
- }
-
- try {
- // Ensure roomId is valid
- if (!roomId) throw new Error("No roomId provided for send");
-
- await this.client.sendEvent(roomId, "m.room.message", content, "");
- } catch (e) {
- console.error("[Matrix] Failed to send message:", e);
- }
- }
-
- async sendmsg(mode, recipient, msg) {
- if (recipient.startsWith('@')) {
- console.log(`[Matrix] Sending DM to ${recipient}`);
- try {
- // Try to find existing DM
- // Note: implementation details vary by SDK version, simplified approach:
- const accountData = this.client.getAccountData('m.direct');
- let roomId;
-
- if (accountData) {
- const directRooms = accountData.getContent();
- const rooms = directRooms[recipient];
- if (rooms && rooms.length > 0) {
- roomId = rooms[0]; // Pick first one
- // Check if we are still in it? Assumed yes for now.
- }
- }
-
- if (!roomId) {
- // Create new DM
- const createOpts = {
- visibility: "private",
- invite: [recipient],
- is_direct: true
- };
- const result = await this.client.createRoom(createOpts);
- roomId = result.room_id;
-
- // Manually update m.direct to ensure we remember this room for this user
- if (roomId) {
- try {
- const newDirects = accountData ? accountData.getContent() : {};
- const userRooms = newDirects[recipient] || [];
- if (!userRooms.includes(roomId)) {
- userRooms.push(roomId);
- newDirects[recipient] = userRooms;
- await this.client.setAccountData('m.direct', newDirects);
- console.log(`[Matrix] Updated m.direct for ${recipient} with ${roomId}`);
- }
- } catch (err) {
- console.error(`[Matrix] Failed to update m.direct:`, err);
- }
- }
- }
-
- if (roomId) {
- await this.send(roomId, msg);
- }
- } catch(e) {
- console.error(`[Matrix] Failed to send DM to ${recipient}:`, e);
- }
- return;
- }
-
- // recipient should be a roomId for channel/room messages
- await this.send(recipient, msg);
- }
-}