mirror of
https://git.sr.ht/~emerson/reflectionircd
synced 2025-04-13 09:59:52 +00:00
272 lines
No EOL
10 KiB
TypeScript
272 lines
No EOL
10 KiB
TypeScript
import axios from "axios";
|
|
import { randomUUID } from "crypto";
|
|
import { Channel } from "./Channel.js";
|
|
import { Client } from "./Client.js";
|
|
import { MatrixUser } from "./MatrixUser.js";
|
|
import { IRCMessage } from "./Message.js";
|
|
import { Server } from "./Server.js";
|
|
|
|
export class IRCUser {
|
|
private clients: Set<Client>
|
|
public channels: Set<Channel>
|
|
public nick: string
|
|
private ident: string
|
|
private hostname: string
|
|
public accountName: string
|
|
public realname: string
|
|
private txnIdStore: Map<string, Client>
|
|
public nextBatch: string
|
|
private initialSync: boolean
|
|
private isSyncing: boolean
|
|
private currentSyncTime: number
|
|
private syncIntervalID: NodeJS.Timeout;
|
|
constructor(public mxid: string, private accessToken: string, public homeserver: string, private server: Server) {
|
|
this.clients = new Set();
|
|
this.channels = new Set();
|
|
const mxidSplit = mxid.split(':')
|
|
this.nick = mxidSplit[0].substr(1);
|
|
this.ident = this.nick;
|
|
this.hostname = mxidSplit[1];
|
|
this.accountName = mxid.slice(1);
|
|
this.realname = this.accountName;
|
|
this.txnIdStore = new Map();
|
|
this.nextBatch = "";
|
|
this.initialSync = false;
|
|
this.isSyncing = false;
|
|
this.currentSyncTime = 0;
|
|
this.syncIntervalID = setInterval(this.doSync.bind(this), 2000);
|
|
}
|
|
|
|
isSynced() {
|
|
return this.nextBatch !== "";
|
|
}
|
|
|
|
getVerification() {
|
|
return axios.get(`https://${this.homeserver}/_matrix/client/v3/account/whoami?access_token=${this.accessToken}`, {
|
|
validateStatus: function (status) {
|
|
return status < 500;
|
|
}
|
|
});
|
|
}
|
|
|
|
getMask(): string {
|
|
return `${this.nick}!${this.ident}@${this.hostname}`;
|
|
}
|
|
|
|
getClients(): Set<Client> {
|
|
return this.clients;
|
|
}
|
|
|
|
addClient(client: Client, passedTags: Map<string, string>) {
|
|
this.clients.add(client);
|
|
if (this.nextBatch !== "") {
|
|
for (const channel of this.channels.values()) {
|
|
channel.joinNewIRCClient(client, passedTags);
|
|
}
|
|
}
|
|
}
|
|
|
|
doSync(): void {
|
|
if (!this.isSynced()) {
|
|
console.log("not syncing, initial sync not completed");
|
|
return;
|
|
}
|
|
if (this.isSyncing) {
|
|
if ((Date.now() - this.currentSyncTime) > 15000)
|
|
console.log(`Sync is lagging, current sync has been running for ${Date.now() - this.currentSyncTime} milliseconds`);
|
|
return;
|
|
}
|
|
this.currentSyncTime = Date.now();
|
|
this.isSyncing = true;
|
|
const endpoint = `https://${this.homeserver}/_matrix/client/v3/sync?access_token=${this.accessToken}&since=${this.nextBatch}&timeout=15000`;
|
|
axios.get(endpoint).then(response => {
|
|
const data = response.data;
|
|
this.nextBatch = data.next_batch;
|
|
const rooms = data.rooms;
|
|
if (rooms && rooms['join']) {
|
|
for (const roomId of Object.keys(rooms.join)) {
|
|
const targetChannel = this.server.getOrCreateIRCChannel(roomId);
|
|
rooms.join[roomId].timeline.events.forEach((nextEvent: any) => {
|
|
targetChannel.routeMatrixEvent(nextEvent)
|
|
});
|
|
}
|
|
}
|
|
this.isSyncing = false;
|
|
}).catch((error) => {
|
|
console.log(error);
|
|
if (error.response) {
|
|
console.log(`Response error: ${error.response.status}`);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
console.log(error.config);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
this.isSyncing = false;
|
|
});
|
|
}
|
|
|
|
inviteMatrixUser(client: Client, channel: Channel, target: MatrixUser, reason: string, passedTags: Map<string, string> = new Map()) {
|
|
const data = {
|
|
"reason": reason,
|
|
"user_id": target.mxid
|
|
}
|
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/invite?access_token=${this.accessToken}`, data).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
})
|
|
}
|
|
|
|
kickMatrixUser(client: Client, channel: Channel, target: string, reason: string, passedTags: Map<string, string> = new Map()) {
|
|
const data = {
|
|
"reason": reason,
|
|
"user_id": target
|
|
}
|
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/kick?access_token=${this.accessToken}`, data).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
})
|
|
}
|
|
|
|
partMatrixRoom(client: Client, channel: Channel, reason: string, passedTags: Map<string, string> = new Map()) {
|
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/leave?access_token=${this.accessToken}`, {"reason": reason}).then(response => {
|
|
if (response.status === 200) {
|
|
this.clients.forEach(c => {
|
|
c.sendMessage(this.getMask(), "PART", [channel.name, reason], passedTags);
|
|
})
|
|
this.channels.delete(channel);
|
|
}
|
|
else {
|
|
client.sendMessage(this.server.name, "NOTICE", [this.nick, JSON.stringify(response.data)], passedTags);
|
|
}
|
|
}).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
})
|
|
}
|
|
|
|
changeRoomTopic(client: Client, channel: Channel, topic: string, passedTags: Map<string, string> = new Map()) {
|
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/state/m.room.topic?access_token=${this.accessToken}`, {"topic": topic}).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
})
|
|
}
|
|
|
|
sendMessageToMatrix(message: IRCMessage, client: Client) {
|
|
const channel = this.server.ircChannels.get(message.params[0]);
|
|
if (!channel) {
|
|
return;
|
|
}
|
|
let msgtype = 'm.text';
|
|
let msgbody = message.params[1];
|
|
if (message.command === 'NOTICE')
|
|
msgtype = 'm.notice';
|
|
else if (message.params[1].startsWith('\x01')) {
|
|
msgtype = 'm.emote';
|
|
msgbody = msgbody.replace(/\x01(ACTION\s)?/gi, '');
|
|
}
|
|
const roomId = channel.roomId;
|
|
const content = {
|
|
"body": msgbody,
|
|
"msgtype": msgtype,
|
|
"m.relates_to": {}
|
|
}
|
|
if (message.tags.has("+draft/reply")) {
|
|
content["m.relates_to"] = {
|
|
"m.in_reply_to": {
|
|
"event_id": message.tags.get("+draft/reply")
|
|
}
|
|
}
|
|
}
|
|
const newTxnid = randomUUID();
|
|
this.txnIdStore.set(newTxnid, client);
|
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/send/m.room.message/${newTxnid}?access_token=${this.accessToken}`, content).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
});
|
|
}
|
|
|
|
sendTagToMatrix(message: IRCMessage, client: Client) {
|
|
const channel = this.server.ircChannels.get(message.params[0]);
|
|
if (!channel) {
|
|
return;
|
|
}
|
|
console.log(message.tags)
|
|
if (message.tags.has("+draft/react") && message.tags.has("+draft/reply")) {
|
|
const content = {
|
|
"m.relates_to": {
|
|
"event_id": message.tags.get("+draft/reply"),
|
|
"key": message.tags.get("+draft/react"),
|
|
"rel_type": "m.annotation"
|
|
}
|
|
}
|
|
const newTxnid = randomUUID();
|
|
this.txnIdStore.set(newTxnid, client);
|
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/send/m.reaction/${newTxnid}?access_token=${this.accessToken}`, content).catch(function (error) {
|
|
if (error.response) {
|
|
console.log(error.response.data);
|
|
} else if (error.request) {
|
|
console.log(error.request);
|
|
} else {
|
|
console.log('Error', error.message);
|
|
console.log(error.config);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
sendToAll(prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
|
|
this.clients.forEach(client => {
|
|
if (client !== skipClient) {
|
|
client.sendMessage(prefix, command, params, tags);
|
|
}
|
|
})
|
|
}
|
|
|
|
sendToAllWithCap(cap: string, prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
|
|
this.clients.forEach(client => {
|
|
if (client !== skipClient && client.enabledCaps.has(cap)) {
|
|
client.sendMessage(prefix, command, params, tags);
|
|
}
|
|
})
|
|
}
|
|
|
|
sendToAllWithoutCap(cap: string, prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
|
|
this.clients.forEach(client => {
|
|
if (client !== skipClient && !client.enabledCaps.has(cap)) {
|
|
client.sendMessage(prefix, command, params, tags);
|
|
}
|
|
})
|
|
}
|
|
} |