2020-03-24 16:27:42 +00:00
|
|
|
/*
|
2021-11-12 13:07:57 +00:00
|
|
|
Copyright 2020 mx-puppet-xmpp
|
2020-03-24 16:27:42 +00:00
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
2020-05-02 08:53:39 +00:00
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
2020-03-24 16:27:42 +00:00
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
import { Log, IRemoteRoom } from "mx-puppet-bridge";
|
2020-03-24 16:27:42 +00:00
|
|
|
import { EventEmitter } from "events";
|
2021-11-12 13:07:57 +00:00
|
|
|
import { client, xml } from "@xmpp/client";
|
|
|
|
import { Client as XmppClient } from "@xmpp/client-core";
|
2023-04-30 16:50:04 +00:00
|
|
|
import * as Parser from "node-html-parser";
|
2021-11-12 13:07:57 +00:00
|
|
|
|
|
|
|
const log = new Log("XmppPuppet:client");
|
|
|
|
|
|
|
|
type Contact = {
|
|
|
|
personId: string,
|
|
|
|
workloads: any,
|
|
|
|
mri: string,
|
|
|
|
blocked: boolean,
|
|
|
|
authorized: boolean,
|
|
|
|
creationTime: Date,
|
|
|
|
displayName: string,
|
|
|
|
displayNameSource: any, // tslint:disable-line no-any
|
|
|
|
profile: {
|
|
|
|
roomId: string,
|
|
|
|
avatarUrl: string | undefined,
|
|
|
|
name: {
|
|
|
|
first: string | undefined,
|
|
|
|
surname: string | undefined,
|
|
|
|
nickname: string | undefined,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2020-03-24 16:27:42 +00:00
|
|
|
|
|
|
|
export class Client extends EventEmitter {
|
2021-11-12 13:07:57 +00:00
|
|
|
public contacts: Map<string, Contact> = new Map();
|
|
|
|
public conversations: Map<string, any> = new Map();
|
|
|
|
private api: XmppClient;
|
2020-03-24 16:27:42 +00:00
|
|
|
constructor(
|
|
|
|
private loginUsername: string,
|
|
|
|
private password: string,
|
2021-11-12 13:07:57 +00:00
|
|
|
) { super(); }
|
2020-03-24 16:27:42 +00:00
|
|
|
|
|
|
|
public get username(): string {
|
2021-11-12 13:07:57 +00:00
|
|
|
return this.loginUsername.split("@")[0].trim();
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
|
|
|
|
2023-04-30 16:50:04 +00:00
|
|
|
public get domain(): string {
|
2021-11-12 13:07:57 +00:00
|
|
|
return this.loginUsername.split("@")[1].trim();
|
2020-03-24 21:18:38 +00:00
|
|
|
}
|
|
|
|
|
2023-04-30 16:50:04 +00:00
|
|
|
public async getWebsocket(): Promise<string> {
|
|
|
|
const response = await fetch(`https://${this.domain}/.well-known/host-meta`);
|
|
|
|
const xmlData = await response.text();
|
|
|
|
const document = Parser.parse(xmlData) as unknown as HTMLElement;
|
|
|
|
const relValue = "urn:xmpp:alt-connections:websocket"
|
|
|
|
const line = document.querySelectorAll(`[rel="${relValue}"]`);
|
|
|
|
return line[0].getAttribute('href') as string;
|
|
|
|
}
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public get getState() {
|
|
|
|
return {};
|
|
|
|
}
|
2020-03-24 16:27:42 +00:00
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public async connect() {
|
2023-04-30 16:50:04 +00:00
|
|
|
const websocketUrl = await this.getWebsocket();
|
|
|
|
log.info("Connecting to ", websocketUrl);
|
2021-11-12 13:07:57 +00:00
|
|
|
log.info(this.username);
|
2023-04-30 16:50:04 +00:00
|
|
|
log.info(this.domain);
|
2021-11-12 13:07:57 +00:00
|
|
|
|
|
|
|
this.api = client({
|
2023-04-30 16:50:04 +00:00
|
|
|
service: websocketUrl,
|
|
|
|
domain: this.domain,
|
2021-11-12 13:07:57 +00:00
|
|
|
resource: "mx_bridge",
|
|
|
|
username: this.username,
|
|
|
|
password: this.password,
|
|
|
|
});
|
2020-03-26 18:05:37 +00:00
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
await this.startupApi();
|
2020-03-25 22:48:12 +00:00
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
this.api.on("error", (err: Error) => {
|
|
|
|
log.error("An error occured", err);
|
|
|
|
this.emit("error", err);
|
|
|
|
});
|
|
|
|
this.api.start();
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async disconnect() {
|
2020-03-29 11:11:10 +00:00
|
|
|
if (this.api) {
|
2021-11-12 13:07:57 +00:00
|
|
|
await this.api.stop();
|
2020-03-25 22:48:12 +00:00
|
|
|
}
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public async getContact(username: string): Promise<any> {
|
|
|
|
log.debug(`Fetching contact from: ` + username);
|
|
|
|
if (this.contacts.has(username)) {
|
|
|
|
const ret = this.contacts.get(username);
|
2020-04-22 17:15:15 +00:00
|
|
|
return ret;
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
|
|
|
try {
|
2021-11-12 13:07:57 +00:00
|
|
|
const contact = {
|
|
|
|
personId: username,
|
2020-03-24 16:27:42 +00:00
|
|
|
workloads: null,
|
2021-11-12 13:07:57 +00:00
|
|
|
mri: username,
|
2020-03-24 16:27:42 +00:00
|
|
|
blocked: false,
|
|
|
|
authorized: true,
|
|
|
|
creationTime: new Date(),
|
2021-11-12 13:07:57 +00:00
|
|
|
displayName: username,
|
2020-03-24 16:27:42 +00:00
|
|
|
displayNameSource: "profile" as any, // tslint:disable-line no-any
|
|
|
|
profile: {
|
2021-11-12 13:07:57 +00:00
|
|
|
roomId: username,
|
|
|
|
avatarUrl: undefined,
|
2020-03-24 16:27:42 +00:00
|
|
|
name: {
|
2021-11-12 13:07:57 +00:00
|
|
|
first: undefined,
|
|
|
|
surname: undefined,
|
|
|
|
nickname: username,
|
2020-03-24 16:27:42 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
2021-11-12 13:07:57 +00:00
|
|
|
this.contacts.set(contact.mri, contact);
|
2020-04-22 17:15:15 +00:00
|
|
|
log.debug("Returning new result");
|
|
|
|
log.silly(contact);
|
2020-03-24 16:27:42 +00:00
|
|
|
return contact || null;
|
|
|
|
} catch (err) {
|
|
|
|
// contact not found
|
2020-04-22 17:15:15 +00:00
|
|
|
log.debug("No such contact found");
|
2020-04-23 09:40:35 +00:00
|
|
|
log.debug(err.body || err);
|
2020-03-24 16:27:42 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public async getConversation(room: IRemoteRoom): Promise<any> {
|
|
|
|
log.info(`Fetching conversation`, room);
|
|
|
|
log.info(`Fetching conversation puppetId=${room.puppetId} roomId=${room.roomId}`);
|
2020-03-24 16:27:42 +00:00
|
|
|
let id = room.roomId;
|
|
|
|
if (this.conversations.has(id)) {
|
2021-11-12 13:07:57 +00:00
|
|
|
log.info("Returning cached result");
|
2020-04-22 17:15:15 +00:00
|
|
|
const ret = this.conversations.get(id) || null;
|
|
|
|
log.silly(ret);
|
|
|
|
return ret;
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
|
|
|
try {
|
2021-11-12 13:07:57 +00:00
|
|
|
const conversation = {id: room.roomId, members: []};
|
|
|
|
this.conversations.set(room.roomId, conversation || null);
|
|
|
|
log.info("Returning new result");
|
|
|
|
log.info(conversation);
|
2020-03-24 16:27:42 +00:00
|
|
|
return conversation || null;
|
|
|
|
} catch (err) {
|
|
|
|
// conversation not found
|
2021-11-12 13:07:57 +00:00
|
|
|
log.error("No such conversation found");
|
|
|
|
log.error(err.body || err);
|
2020-03-24 16:27:42 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public async downloadFile(url: string, type: string = "imgpsh_fullsize_anim") {
|
|
|
|
// TODO
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
public async sendMessage(conversationId: string, msg: string) {
|
|
|
|
return await this.api.send(xml(
|
|
|
|
"message",
|
|
|
|
{ type: "chat", to: conversationId },
|
|
|
|
xml("body", {}, msg),
|
|
|
|
));
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
2020-03-24 20:10:49 +00:00
|
|
|
|
|
|
|
public async sendEdit(conversationId: string, messageId: string, msg: string) {
|
2021-11-12 13:07:57 +00:00
|
|
|
// TODO
|
|
|
|
// return await this.api.sendEdit({
|
|
|
|
// textContent: msg,
|
|
|
|
// }, conversationId, messageId);
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async sendDelete(conversationId: string, messageId: string) {
|
2021-11-12 13:07:57 +00:00
|
|
|
// TODO
|
|
|
|
// return await this.api.sendDelete(conversationId, messageId);
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async sendAudio(
|
|
|
|
conversationId: string,
|
2021-11-12 13:07:57 +00:00
|
|
|
opts: any,
|
|
|
|
) {
|
|
|
|
// TODO
|
|
|
|
// return await this.api.sendAudio(opts, conversationId);
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async sendDocument(
|
|
|
|
conversationId: string,
|
2021-11-12 13:07:57 +00:00
|
|
|
opts: any,
|
|
|
|
) {
|
|
|
|
// TODO
|
|
|
|
// return await this.api.sendDocument(opts, conversationId);
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async sendImage(
|
|
|
|
conversationId: string,
|
2021-11-12 13:07:57 +00:00
|
|
|
opts: any,
|
|
|
|
) {
|
|
|
|
// TODO
|
|
|
|
// return await this.api.sendImage(opts, conversationId);
|
2020-03-24 20:10:49 +00:00
|
|
|
}
|
2020-03-25 22:48:12 +00:00
|
|
|
|
|
|
|
private async startupApi() {
|
2021-11-12 13:07:57 +00:00
|
|
|
this.api.on("stanza", async (stanza) => {
|
|
|
|
if (stanza.is("message")) {
|
|
|
|
this.emit("text", stanza);
|
2020-03-25 22:48:12 +00:00
|
|
|
}
|
|
|
|
});
|
2021-11-12 13:07:57 +00:00
|
|
|
|
|
|
|
this.api.on("online", async (address) => {
|
|
|
|
await this.api.send(xml("presence"));
|
|
|
|
});
|
2020-03-25 22:48:12 +00:00
|
|
|
|
2021-11-12 13:07:57 +00:00
|
|
|
// const contacts = await this.api.getContacts();
|
|
|
|
// for (const contact of contacts) {
|
|
|
|
// this.contacts.set(contact.mri, contact);
|
|
|
|
// }
|
2020-03-25 22:48:12 +00:00
|
|
|
}
|
2020-03-24 16:27:42 +00:00
|
|
|
}
|
2021-11-12 13:07:57 +00:00
|
|
|
|