mirror of
https://github.com/plantroon/mx-puppet-xmpp.git
synced 2024-12-22 06:31:41 +00:00
Basic XMPP
This commit is contained in:
parent
282e4da983
commit
db76a70295
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +1,8 @@
|
||||
node_modules
|
||||
config.yaml
|
||||
build
|
||||
skype-registration.yaml
|
||||
registration.yaml
|
||||
xmpp-registration.yaml
|
||||
*.db
|
||||
*-audit.json
|
||||
*.log.*
|
24
Dockerfile
24
Dockerfile
@ -1,11 +1,11 @@
|
||||
FROM node:alpine AS builder
|
||||
|
||||
WORKDIR /opt/mx-puppet-skype
|
||||
WORKDIR /opt/mx-puppet-xmpp
|
||||
|
||||
# run build process as user in case of npm pre hooks
|
||||
# pre hooks are not executed while running as root
|
||||
RUN chown node:node /opt/mx-puppet-skype
|
||||
RUN apk --no-cache add git python make g++ pkgconfig \
|
||||
RUN chown node:node /opt/mx-puppet-xmpp
|
||||
RUN apk update && apk --no-cache add git python3 make g++ pkgconfig \
|
||||
build-base \
|
||||
cairo-dev \
|
||||
jpeg-dev \
|
||||
@ -15,7 +15,8 @@ RUN apk --no-cache add git python make g++ pkgconfig \
|
||||
pixman-dev \
|
||||
pangomm-dev \
|
||||
libjpeg-turbo-dev \
|
||||
freetype-dev
|
||||
freetype-dev \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
RUN wget -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
|
||||
wget -O glibc-2.32-r0.apk https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.32-r0/glibc-2.32-r0.apk && \
|
||||
@ -38,10 +39,10 @@ FROM node:alpine
|
||||
VOLUME /data
|
||||
|
||||
ENV CONFIG_PATH=/data/config.yaml \
|
||||
REGISTRATION_PATH=/data/skype-registration.yaml
|
||||
REGISTRATION_PATH=/data/xmpp-registration.yaml
|
||||
|
||||
# su-exec is used by docker-run.sh to drop privileges
|
||||
RUN apk add --no-cache su-exec \
|
||||
RUN apk update && apk add --no-cache su-exec \
|
||||
cairo \
|
||||
jpeg \
|
||||
pango \
|
||||
@ -50,15 +51,16 @@ RUN apk add --no-cache su-exec \
|
||||
pixman \
|
||||
pangomm \
|
||||
libjpeg-turbo \
|
||||
freetype
|
||||
freetype \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
|
||||
WORKDIR /opt/mx-puppet-skype
|
||||
WORKDIR /opt/mx-puppet-xmpp
|
||||
COPY docker-run.sh ./
|
||||
COPY --from=builder /opt/mx-puppet-skype/node_modules/ ./node_modules/
|
||||
COPY --from=builder /opt/mx-puppet-skype/build/ ./build/
|
||||
COPY --from=builder /opt/mx-puppet-xmpp/node_modules/ ./node_modules/
|
||||
COPY --from=builder /opt/mx-puppet-xmpp/build/ ./build/
|
||||
|
||||
# change workdir to /data so relative paths in the config.yaml
|
||||
# point to the persisten volume
|
||||
WORKDIR /data
|
||||
ENTRYPOINT ["/opt/mx-puppet-skype/docker-run.sh"]
|
||||
ENTRYPOINT ["/opt/mx-puppet-xmpp/docker-run.sh"]
|
||||
|
40
README.md
40
README.md
@ -1,11 +1,17 @@
|
||||
[![Support room on Matrix](https://img.shields.io/matrix/mx-puppet-bridge:sorunome.de.svg?label=%23mx-puppet-bridge%3Asorunome.de&logo=matrix&server_fqdn=sorunome.de)](https://matrix.to/#/#mx-puppet-bridge:sorunome.de)[![donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Sorunome/donate)
|
||||
[![donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/rkd/donate)
|
||||
|
||||
# mx-puppet-skype
|
||||
This is a skype puppeting bridge for matrix. It is based on [mx-puppet-bridge](https://github.com/Sorunome/mx-puppet-bridge) and provide multi-user instances.
|
||||
# [WIP] mx-puppet-xmpp
|
||||
This is a xmpp puppeting bridge for matrix. It is based on [mx-puppet-bridge](https://github.com/Sorunome/mx-puppet-bridge) and provide multi-user instances.
|
||||
|
||||
##
|
||||
|
||||
## Quick start using Docker
|
||||
|
||||
Docker image can be found at https://hub.docker.com/r/sorunome/mx-puppet-skype
|
||||
To build docker image:
|
||||
|
||||
```
|
||||
docker build -t mx-puppet-xmpp:latest .
|
||||
```
|
||||
|
||||
For docker you probably want the following changes in `config.yaml`:
|
||||
|
||||
@ -21,16 +27,16 @@ Also check the config for other values, like your homeserver domain.
|
||||
|
||||
* Clone and install:
|
||||
```
|
||||
git clone https://github.com/Sorunome/mx-puppet-skype.git
|
||||
cd mx-puppet-skype
|
||||
git clone https://github.com/Sorunome/mx-puppet-xmpp.git
|
||||
cd mx-puppet-xmpp
|
||||
npm install
|
||||
* Edit the configuration file and generate the registration file:
|
||||
```
|
||||
cp sample.config.yaml config.yaml
|
||||
# fill info about your homeserver and skype app credentials to config.yaml manually
|
||||
# fill info about your homeserver and xmpp app credentials to config.yaml manually
|
||||
npm run start -- -r # generate registration file
|
||||
or
|
||||
docker run -v </path/to/host>/data:/data -it sorunome/mx-puppet-skype -r
|
||||
docker run -v </path/to/host>/data:/data -it mx-puppet-xmpp -r
|
||||
```
|
||||
* Copy the registration file to your synapse config directory.
|
||||
* Add the registration file to the list under `app_service_config_files:` in your synapse config.
|
||||
@ -39,9 +45,9 @@ Also check the config for other values, like your homeserver domain.
|
||||
```
|
||||
npm run start
|
||||
```
|
||||
* Start a direct chat with the bot user (`@_skypepuppet_bot:domain.tld` unless you changed the config).
|
||||
* Start a direct chat with the bot user (`@_xmpppuppet_bot:domain.tld` unless you changed the config).
|
||||
(Give it some time after the invite, it'll join after a minute maybe.)
|
||||
* Get your Skype username and password as below, and tell the bot user to link your skype account:
|
||||
* Get your Xmpp username and password as below, and tell the bot user to link your xmpp account:
|
||||
```
|
||||
link <username> <password>
|
||||
```
|
||||
@ -50,3 +56,17 @@ Also check the config for other values, like your homeserver domain.
|
||||
list
|
||||
```
|
||||
Clicking rooms in the list will result in you receiving an invite to the bridged room.
|
||||
|
||||
## Working
|
||||
|
||||
- link
|
||||
- text messages (mx -> xmpp)
|
||||
- text messages (xmpp -> mx)
|
||||
|
||||
## TODO
|
||||
|
||||
- replies
|
||||
- edits
|
||||
- deletes
|
||||
- images
|
||||
- files
|
@ -32,7 +32,7 @@ else
|
||||
fi
|
||||
|
||||
# $su_exec is used in case we have to drop the privileges
|
||||
exec $su_exec /usr/local/bin/node '/opt/mx-puppet-skype/build/index.js' \
|
||||
exec $su_exec /usr/local/bin/node '/opt/mx-puppet-xmpp/build/index.js' \
|
||||
-c "$CONFIG_PATH" \
|
||||
-f "$REGISTRATION_PATH" \
|
||||
$args
|
||||
|
7617
package-lock.json
generated
7617
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "mx-puppet-skype",
|
||||
"name": "mx-puppet-xmpp",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
@ -9,9 +9,9 @@
|
||||
"start": "npm run-script build && node ./build/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Sorunome",
|
||||
"author": "rkd",
|
||||
"dependencies": {
|
||||
"@sorunome/skype-http": "^1.5.2",
|
||||
"@xmpp/client": "^0.13.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"command-line-args": "^5.1.1",
|
||||
"command-line-usage": "^5.0.5",
|
||||
@ -20,7 +20,7 @@
|
||||
"events": "^3.0.0",
|
||||
"expire-set": "^1.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"mx-puppet-bridge": "0.1.4",
|
||||
"mx-puppet-bridge": "0.1.6",
|
||||
"node-emoji": "^1.10.0",
|
||||
"node-html-parser": "^1.2.13",
|
||||
"tough-cookie": "^4.0.0",
|
||||
|
379
src/client.ts
379
src/client.ts
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 mx-puppet-skype
|
||||
Copyright 2020 mx-puppet-xmpp
|
||||
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
|
||||
@ -11,185 +11,108 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Log, IRemoteRoom, Util } from "mx-puppet-bridge";
|
||||
import { Log, IRemoteRoom } from "mx-puppet-bridge";
|
||||
import { EventEmitter } from "events";
|
||||
import * as skypeHttp from "@sorunome/skype-http";
|
||||
import { Contact as SkypeContact } from "@sorunome/skype-http/dist/lib/types/contact";
|
||||
import { NewMediaMessage as SkypeNewMediaMessage } from "@sorunome/skype-http/dist/lib/interfaces/api/api";
|
||||
import { Context as SkypeContext } from "@sorunome/skype-http/dist/lib/interfaces/api/context";
|
||||
import ExpireSet from "expire-set";
|
||||
import * as toughCookie from "tough-cookie";
|
||||
import { client, xml } from "@xmpp/client";
|
||||
import { Client as XmppClient } from "@xmpp/client-core";
|
||||
|
||||
const log = new Log("SkypePuppet:client");
|
||||
const log = new Log("XmppPuppet:client");
|
||||
|
||||
// tslint:disable no-magic-numbers
|
||||
const ID_TIMEOUT = 60000;
|
||||
const CONTACTS_DELTA_INTERVAL = 5 * 60 * 1000;
|
||||
// tslint:enable no-magic-numbers
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export class Client extends EventEmitter {
|
||||
public contacts: Map<string, SkypeContact> = new Map();
|
||||
public conversations: Map<string, skypeHttp.Conversation> = new Map();
|
||||
private api: skypeHttp.Api;
|
||||
private handledIds: ExpireSet<string>;
|
||||
private contactsInterval: NodeJS.Timeout | null = null;
|
||||
public contacts: Map<string, Contact> = new Map();
|
||||
public conversations: Map<string, any> = new Map();
|
||||
private api: XmppClient;
|
||||
constructor(
|
||||
private loginUsername: string,
|
||||
private password: string,
|
||||
private state?: SkypeContext.Json,
|
||||
) {
|
||||
super();
|
||||
this.handledIds = new ExpireSet(ID_TIMEOUT);
|
||||
}
|
||||
) { super(); }
|
||||
|
||||
public get username(): string {
|
||||
return "8:" + this.api.context.username;
|
||||
return this.loginUsername.split("@")[0].trim();
|
||||
}
|
||||
|
||||
public get getState(): SkypeContext.Json {
|
||||
return this.api.getState();
|
||||
public get host(): string {
|
||||
return this.loginUsername.split("@")[1].trim();
|
||||
}
|
||||
|
||||
public get getState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
let connectedWithAuth = false;
|
||||
if (this.state) {
|
||||
try {
|
||||
this.api = await skypeHttp.connect({ state: this.state, verbose: true });
|
||||
connectedWithAuth = true;
|
||||
} catch (err) {
|
||||
this.api = await skypeHttp.connect({
|
||||
credentials: {
|
||||
username: this.loginUsername,
|
||||
password: this.password,
|
||||
},
|
||||
verbose: true,
|
||||
});
|
||||
connectedWithAuth = false;
|
||||
}
|
||||
} else {
|
||||
this.api = await skypeHttp.connect({
|
||||
credentials: {
|
||||
username: this.loginUsername,
|
||||
password: this.password,
|
||||
},
|
||||
verbose: true,
|
||||
});
|
||||
connectedWithAuth = false;
|
||||
}
|
||||
log.info("Connecting to ", this.host);
|
||||
log.info(this.username);
|
||||
|
||||
try {
|
||||
await this.startupApi();
|
||||
} catch (err) {
|
||||
if (!connectedWithAuth) {
|
||||
throw err;
|
||||
}
|
||||
this.api = await skypeHttp.connect({
|
||||
credentials: {
|
||||
username: this.loginUsername,
|
||||
this.api = client({
|
||||
service: "ws://"+this.host+":5280/xmpp-websocket",
|
||||
domain: this.host,
|
||||
resource: "mx_bridge",
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
},
|
||||
verbose: true,
|
||||
});
|
||||
connectedWithAuth = false;
|
||||
await this.startupApi();
|
||||
}
|
||||
|
||||
const registerErrorHandler = () => {
|
||||
await this.startupApi();
|
||||
|
||||
this.api.on("error", (err: Error) => {
|
||||
log.error("An error occured", err);
|
||||
this.emit("error", err);
|
||||
});
|
||||
};
|
||||
|
||||
if (connectedWithAuth) {
|
||||
let resolved = false;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const TIMEOUT_SUCCESS = 5000;
|
||||
setTimeout(() => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
registerErrorHandler();
|
||||
resolve();
|
||||
}, TIMEOUT_SUCCESS);
|
||||
this.api.once("error", async () => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
// alright, re-try as normal user
|
||||
try {
|
||||
await this.api.stopListening();
|
||||
this.api = await skypeHttp.connect({
|
||||
credentials: {
|
||||
username: this.loginUsername,
|
||||
password: this.password,
|
||||
},
|
||||
verbose: true,
|
||||
});
|
||||
await this.startupApi();
|
||||
registerErrorHandler();
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
await this.api.listen();
|
||||
}).then(async () => {
|
||||
await this.api.setStatus("Online");
|
||||
});
|
||||
} else {
|
||||
registerErrorHandler();
|
||||
await this.api.listen();
|
||||
await this.api.setStatus("Online");
|
||||
}
|
||||
this.api.start();
|
||||
}
|
||||
|
||||
public async disconnect() {
|
||||
if (this.api) {
|
||||
await this.api.stopListening();
|
||||
}
|
||||
if (this.contactsInterval) {
|
||||
clearInterval(this.contactsInterval);
|
||||
this.contactsInterval = null;
|
||||
await this.api.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public async getContact(id: string): Promise<SkypeContact | null> {
|
||||
log.debug(`Fetching contact ${id}`);
|
||||
const hasStart = Boolean(id.match(/^\d+:/));
|
||||
const fullId = hasStart ? id : `8:${id}`;
|
||||
if (this.contacts.has(fullId)) {
|
||||
log.debug("Returning cached result");
|
||||
const ret = this.contacts.get(fullId) || null;
|
||||
log.silly(ret);
|
||||
public async getContact(username: string): Promise<any> {
|
||||
log.debug(`Fetching contact from: ` + username);
|
||||
if (this.contacts.has(username)) {
|
||||
const ret = this.contacts.get(username);
|
||||
return ret;
|
||||
}
|
||||
if (hasStart) {
|
||||
id = id.substr(id.indexOf(":") + 1);
|
||||
}
|
||||
try {
|
||||
const rawContact = await this.api.getContact(id);
|
||||
const contact: SkypeContact = {
|
||||
personId: rawContact.id.raw,
|
||||
const contact = {
|
||||
personId: username,
|
||||
workloads: null,
|
||||
mri: rawContact.id.raw,
|
||||
mri: username,
|
||||
blocked: false,
|
||||
authorized: true,
|
||||
creationTime: new Date(),
|
||||
displayName: (rawContact.name && rawContact.name.displayName) || rawContact.id.id,
|
||||
displayName: username,
|
||||
displayNameSource: "profile" as any, // tslint:disable-line no-any
|
||||
profile: {
|
||||
avatarUrl: rawContact.avatarUrl || undefined,
|
||||
roomId: username,
|
||||
avatarUrl: undefined,
|
||||
name: {
|
||||
first: (rawContact.name && rawContact.name).first || undefined,
|
||||
surname: (rawContact.name && rawContact.name).surname || undefined,
|
||||
nickname: (rawContact.name && rawContact.name).nickname || undefined,
|
||||
first: undefined,
|
||||
surname: undefined,
|
||||
nickname: username,
|
||||
},
|
||||
},
|
||||
};
|
||||
this.contacts.set(contact.mri, contact || null);
|
||||
this.contacts.set(contact.mri, contact);
|
||||
log.debug("Returning new result");
|
||||
log.silly(contact);
|
||||
return contact || null;
|
||||
@ -201,181 +124,93 @@ export class Client extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public async getConversation(room: IRemoteRoom): Promise<skypeHttp.Conversation | null> {
|
||||
log.debug(`Fetching conversation puppetId=${room.puppetId} roomId=${room.roomId}`);
|
||||
public async getConversation(room: IRemoteRoom): Promise<any> {
|
||||
log.info(`Fetching conversation`, room);
|
||||
log.info(`Fetching conversation puppetId=${room.puppetId} roomId=${room.roomId}`);
|
||||
let id = room.roomId;
|
||||
const match = id.match(/^dm-\d+-/);
|
||||
if (match) {
|
||||
const [_, puppetId] = id.split("-");
|
||||
if (Number(puppetId) !== room.puppetId) {
|
||||
return null;
|
||||
}
|
||||
id = id.substr(match[0].length);
|
||||
}
|
||||
if (this.conversations.has(id)) {
|
||||
log.debug("Returning cached result");
|
||||
log.info("Returning cached result");
|
||||
const ret = this.conversations.get(id) || null;
|
||||
log.silly(ret);
|
||||
return ret;
|
||||
}
|
||||
try {
|
||||
const conversation = await this.api.getConversation(id);
|
||||
this.conversations.set(conversation.id, conversation || null);
|
||||
log.debug("Returning new result");
|
||||
log.silly(conversation);
|
||||
const conversation = {id: room.roomId, members: []};
|
||||
this.conversations.set(room.roomId, conversation || null);
|
||||
log.info("Returning new result");
|
||||
log.info(conversation);
|
||||
return conversation || null;
|
||||
} catch (err) {
|
||||
// conversation not found
|
||||
log.debug("No such conversation found");
|
||||
log.debug(err.body || err);
|
||||
log.error("No such conversation found");
|
||||
log.error(err.body || err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadFile(url: string, type: string = "imgpsh_fullsize_anim"): Promise<Buffer> {
|
||||
if (url.startsWith("https://api.asm.skype.com/") && !url.includes("/views/")) {
|
||||
url = `${url}/views/${type}`;
|
||||
}
|
||||
const cookieJar = new toughCookie.CookieJar(this.api.context.cookies);
|
||||
return await Util.DownloadFile(url, {
|
||||
responseType: "buffer",
|
||||
headers: {
|
||||
Authorization: "skypetoken=" + this.api.context.skypeToken.value,
|
||||
RegistrationToken: this.api.context.registrationToken.raw,
|
||||
},
|
||||
cookieJar: {
|
||||
setCookie: async (rawCookie: string, cookieUrl: string) =>
|
||||
new Promise((resolve, reject) =>
|
||||
cookieJar.setCookie(rawCookie, cookieUrl, (err, value) =>
|
||||
err ? reject(err) : resolve(value),
|
||||
),
|
||||
),
|
||||
getCookieString: async (cookieUrl: string) =>
|
||||
new Promise((resolve, reject) =>
|
||||
cookieJar.getCookieString(cookieUrl, (err, value) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (url.startsWith("https://api.asm.skype.com/")) {
|
||||
if (value) {
|
||||
value += "; ";
|
||||
}
|
||||
value += "skypetoken_asm=" + encodeURIComponent(this.api.context.skypeToken.value);
|
||||
}
|
||||
resolve(value);
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
public async downloadFile(url: string, type: string = "imgpsh_fullsize_anim") {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public async sendMessage(conversationId: string, msg: string): Promise<skypeHttp.Api.SendMessageResult> {
|
||||
return await this.api.sendMessage({
|
||||
textContent: msg,
|
||||
}, conversationId);
|
||||
public async sendMessage(conversationId: string, msg: string) {
|
||||
return await this.api.send(xml(
|
||||
"message",
|
||||
{ type: "chat", to: conversationId },
|
||||
xml("body", {}, msg),
|
||||
));
|
||||
}
|
||||
|
||||
public async sendEdit(conversationId: string, messageId: string, msg: string) {
|
||||
return await this.api.sendEdit({
|
||||
textContent: msg,
|
||||
}, conversationId, messageId);
|
||||
// TODO
|
||||
// return await this.api.sendEdit({
|
||||
// textContent: msg,
|
||||
// }, conversationId, messageId);
|
||||
}
|
||||
|
||||
public async sendDelete(conversationId: string, messageId: string) {
|
||||
return await this.api.sendDelete(conversationId, messageId);
|
||||
// TODO
|
||||
// return await this.api.sendDelete(conversationId, messageId);
|
||||
}
|
||||
|
||||
public async sendAudio(
|
||||
conversationId: string,
|
||||
opts: SkypeNewMediaMessage,
|
||||
): Promise<skypeHttp.Api.SendMessageResult> {
|
||||
return await this.api.sendAudio(opts, conversationId);
|
||||
opts: any,
|
||||
) {
|
||||
// TODO
|
||||
// return await this.api.sendAudio(opts, conversationId);
|
||||
}
|
||||
|
||||
public async sendDocument(
|
||||
conversationId: string,
|
||||
opts: SkypeNewMediaMessage,
|
||||
): Promise<skypeHttp.Api.SendMessageResult> {
|
||||
return await this.api.sendDocument(opts, conversationId);
|
||||
opts: any,
|
||||
) {
|
||||
// TODO
|
||||
// return await this.api.sendDocument(opts, conversationId);
|
||||
}
|
||||
|
||||
public async sendImage(
|
||||
conversationId: string,
|
||||
opts: SkypeNewMediaMessage,
|
||||
): Promise<skypeHttp.Api.SendMessageResult> {
|
||||
return await this.api.sendImage(opts, conversationId);
|
||||
opts: any,
|
||||
) {
|
||||
// TODO
|
||||
// return await this.api.sendImage(opts, conversationId);
|
||||
}
|
||||
|
||||
private async startupApi() {
|
||||
this.api.on("event", (evt: skypeHttp.events.EventMessage) => {
|
||||
if (!evt || !evt.resource) {
|
||||
return;
|
||||
}
|
||||
const resource = evt.resource;
|
||||
log.debug(`Got new event of type ${resource.type}`);
|
||||
log.silly(evt);
|
||||
const [type, subtype] = resource.type.split("/");
|
||||
switch (type) {
|
||||
case "RichText":
|
||||
if (evt.resourceType === "NewMessage") {
|
||||
if (resource.native.skypeeditedid || this.handledIds.has(resource.id)) {
|
||||
break;
|
||||
}
|
||||
this.handledIds.add(resource.id);
|
||||
if (subtype === "Location") {
|
||||
this.emit("location", resource);
|
||||
} else if (subtype) {
|
||||
this.emit("file", resource);
|
||||
} else {
|
||||
this.emit("text", resource);
|
||||
}
|
||||
} else if (evt.resourceType === "MessageUpdate") {
|
||||
this.emit("edit", resource);
|
||||
}
|
||||
break;
|
||||
case "Control":
|
||||
if (subtype === "Typing" || subtype === "ClearTyping") {
|
||||
this.emit("typing", resource, subtype === "Typing");
|
||||
}
|
||||
break;
|
||||
case "ThreadActivity":
|
||||
if (subtype === "MemberConsumptionHorizonUpdate") {
|
||||
this.emit("presence", resource);
|
||||
}
|
||||
break;
|
||||
this.api.on("stanza", async (stanza) => {
|
||||
if (stanza.is("message")) {
|
||||
this.emit("text", stanza);
|
||||
}
|
||||
});
|
||||
|
||||
const contacts = await this.api.getContacts();
|
||||
for (const contact of contacts) {
|
||||
this.contacts.set(contact.mri, contact);
|
||||
this.api.on("online", async (address) => {
|
||||
await this.api.send(xml("presence"));
|
||||
});
|
||||
|
||||
// const contacts = await this.api.getContacts();
|
||||
// for (const contact of contacts) {
|
||||
// this.contacts.set(contact.mri, contact);
|
||||
// }
|
||||
}
|
||||
const conversations = await this.api.getConversations();
|
||||
for (const conversation of conversations) {
|
||||
this.conversations.set(conversation.id, conversation);
|
||||
}
|
||||
|
||||
if (this.contactsInterval) {
|
||||
clearInterval(this.contactsInterval);
|
||||
this.contactsInterval = null;
|
||||
}
|
||||
this.contactsInterval = setInterval(this.updateContacts.bind(this), CONTACTS_DELTA_INTERVAL);
|
||||
}
|
||||
|
||||
private async updateContacts() {
|
||||
log.verbose("Getting contacts diff....");
|
||||
try {
|
||||
const contacts = await this.api.getContacts(true);
|
||||
const MANY_CONTACTS = 5;
|
||||
for (const contact of contacts) {
|
||||
const oldContact = this.contacts.get(contact.mri) || null;
|
||||
this.contacts.set(contact.mri, contact);
|
||||
this.emit("updateContact", oldContact, contact);
|
||||
}
|
||||
} catch (err) {
|
||||
log.error("Failed to get contacts diff", err);
|
||||
this.emit("error", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
68
src/index.ts
68
src/index.ts
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 mx-puppet-skype
|
||||
Copyright 2020 mx-puppet-xmpp
|
||||
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
|
||||
@ -23,10 +23,10 @@ import * as commandLineArgs from "command-line-args";
|
||||
import * as commandLineUsage from "command-line-usage";
|
||||
import * as fs from "fs";
|
||||
import * as yaml from "js-yaml";
|
||||
import { Skype } from "./skype";
|
||||
import { Xmpp } from "./xmpp";
|
||||
import { Client } from "./client";
|
||||
|
||||
const log = new Log("SkypePuppet:index");
|
||||
const log = new Log("XmppPuppet:index");
|
||||
|
||||
const commandOptions = [
|
||||
{ name: "register", alias: "r", type: Boolean },
|
||||
@ -36,7 +36,7 @@ const commandOptions = [
|
||||
];
|
||||
const options = Object.assign({
|
||||
"register": false,
|
||||
"registration-file": "skype-registration.yaml",
|
||||
"registration-file": "xmpp-registration.yaml",
|
||||
"config": "config.yaml",
|
||||
"help": false,
|
||||
}, commandLineArgs(commandOptions));
|
||||
@ -45,8 +45,8 @@ if (options.help) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(commandLineUsage([
|
||||
{
|
||||
header: "Matrix Skype Puppet Bridge",
|
||||
content: "A matrix puppet bridge for Skype",
|
||||
header: "Matrix Xmpp Puppet Bridge",
|
||||
content: "A matrix puppet bridge for Xmpp",
|
||||
},
|
||||
{
|
||||
header: "Options",
|
||||
@ -58,16 +58,16 @@ if (options.help) {
|
||||
|
||||
const protocol: IProtocolInformation = {
|
||||
features: {
|
||||
image: true,
|
||||
audio: true,
|
||||
file: true,
|
||||
edit: true,
|
||||
reply: true,
|
||||
image: false,
|
||||
audio: false,
|
||||
file: false,
|
||||
edit: false,
|
||||
reply: false,
|
||||
globalNamespace: true,
|
||||
},
|
||||
id: "skype",
|
||||
displayname: "Skype",
|
||||
externalUrl: "https://skype.com/",
|
||||
id: "xmpp",
|
||||
displayname: "Xmpp",
|
||||
externalUrl: "https://xmpp.com/",
|
||||
};
|
||||
|
||||
const puppet = new PuppetBridge(options["registration-file"], options.config, protocol);
|
||||
@ -77,8 +77,8 @@ if (options.register) {
|
||||
puppet.readConfig(false);
|
||||
try {
|
||||
puppet.generateRegistration({
|
||||
prefix: "_skypepuppet_",
|
||||
id: "skype-puppet",
|
||||
prefix: "_xmpppuppet_",
|
||||
id: "xmpp-puppet",
|
||||
url: `http://${puppet.Config.bridge.bindAddress}:${puppet.Config.bridge.port}`,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -90,24 +90,24 @@ if (options.register) {
|
||||
|
||||
async function run() {
|
||||
await puppet.init();
|
||||
const skype = new Skype(puppet);
|
||||
puppet.on("puppetNew", skype.newPuppet.bind(skype));
|
||||
puppet.on("puppetDelete", skype.deletePuppet.bind(skype));
|
||||
puppet.on("message", skype.handleMatrixMessage.bind(skype));
|
||||
puppet.on("edit", skype.handleMatrixEdit.bind(skype));
|
||||
puppet.on("reply", skype.handleMatrixReply.bind(skype));
|
||||
puppet.on("redact", skype.handleMatrixRedact.bind(skype));
|
||||
puppet.on("image", skype.handleMatrixImage.bind(skype));
|
||||
puppet.on("audio", skype.handleMatrixAudio.bind(skype));
|
||||
puppet.on("file", skype.handleMatrixFile.bind(skype));
|
||||
puppet.setCreateUserHook(skype.createUser.bind(skype));
|
||||
puppet.setCreateRoomHook(skype.createRoom.bind(skype));
|
||||
puppet.setGetDmRoomIdHook(skype.getDmRoom.bind(skype));
|
||||
puppet.setListUsersHook(skype.listUsers.bind(skype));
|
||||
puppet.setListRoomsHook(skype.listRooms.bind(skype));
|
||||
puppet.setGetUserIdsInRoomHook(skype.getUserIdsInRoom.bind(skype));
|
||||
const xmpp = new Xmpp(puppet);
|
||||
puppet.on("puppetNew", xmpp.newPuppet.bind(xmpp));
|
||||
puppet.on("puppetDelete", xmpp.deletePuppet.bind(xmpp));
|
||||
puppet.on("message", xmpp.handleMatrixMessage.bind(xmpp));
|
||||
puppet.on("edit", xmpp.handleMatrixEdit.bind(xmpp));
|
||||
puppet.on("reply", xmpp.handleMatrixReply.bind(xmpp));
|
||||
puppet.on("redact", xmpp.handleMatrixRedact.bind(xmpp));
|
||||
puppet.on("image", xmpp.handleMatrixImage.bind(xmpp));
|
||||
puppet.on("audio", xmpp.handleMatrixAudio.bind(xmpp));
|
||||
puppet.on("file", xmpp.handleMatrixFile.bind(xmpp));
|
||||
puppet.setCreateUserHook(xmpp.createUser.bind(xmpp));
|
||||
puppet.setCreateRoomHook(xmpp.createRoom.bind(xmpp));
|
||||
puppet.setGetDmRoomIdHook(xmpp.getDmRoom.bind(xmpp));
|
||||
puppet.setListUsersHook(xmpp.listUsers.bind(xmpp));
|
||||
puppet.setListRoomsHook(xmpp.listRooms.bind(xmpp));
|
||||
puppet.setGetUserIdsInRoomHook(xmpp.getUserIdsInRoom.bind(xmpp));
|
||||
puppet.setGetDescHook(async (puppetId: number, data: any): Promise<string> => {
|
||||
let s = "Skype";
|
||||
let s = "Xmpp";
|
||||
if (data.username) {
|
||||
s += ` as \`${data.username}\``;
|
||||
}
|
||||
@ -139,7 +139,7 @@ async function run() {
|
||||
return retData;
|
||||
});
|
||||
puppet.setBotHeaderMsgHook((): string => {
|
||||
return "Skype Puppet Bridge";
|
||||
return "Xmpp Puppet Bridge";
|
||||
});
|
||||
await puppet.start();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 mx-puppet-skype
|
||||
Copyright 2020 mx-puppet-xmpp
|
||||
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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 mx-puppet-skype
|
||||
Copyright 2020 mx-puppet-xmpp
|
||||
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
|
||||
@ -16,46 +16,46 @@ import {
|
||||
IRetList, IReplyEvent,
|
||||
} from "mx-puppet-bridge";
|
||||
import { Client } from "./client";
|
||||
import * as skypeHttp from "@sorunome/skype-http";
|
||||
import { Contact as SkypeContact } from "@sorunome/skype-http/dist/lib/types/contact";
|
||||
import { NewMediaMessage as SkypeNewMediaMessage } from "@sorunome/skype-http/dist/lib/interfaces/api/api";
|
||||
import { UnexpectedHttpStatusError } from "@sorunome/skype-http/dist/lib/errors";
|
||||
import * as decodeHtml from "decode-html";
|
||||
import * as escapeHtml from "escape-html";
|
||||
import { MatrixMessageParser } from "./matrixmessageparser";
|
||||
import { SkypeMessageParser } from "./skypemessageparser";
|
||||
import { XmppMessageParser } from "./xmppmessageparser";
|
||||
import * as cheerio from "cheerio";
|
||||
import ExpireSet from "expire-set";
|
||||
|
||||
const log = new Log("SkypePuppet:skype");
|
||||
const log = new Log("XmppPuppet:xmpp");
|
||||
|
||||
const ROOM_TYPE_DM = 8;
|
||||
|
||||
interface ISkypePuppet {
|
||||
interface IXmppPuppet {
|
||||
client: Client;
|
||||
data: any;
|
||||
deletedMessages: ExpireSet<string>;
|
||||
restarting: boolean;
|
||||
}
|
||||
|
||||
interface ISkypePuppets {
|
||||
[puppetId: number]: ISkypePuppet;
|
||||
interface IXmppPuppets {
|
||||
[puppetId: number]: IXmppPuppet;
|
||||
}
|
||||
|
||||
export class Skype {
|
||||
private puppets: ISkypePuppets = {};
|
||||
interface IStanza {
|
||||
attrs: {to: string, from:string, id: string};
|
||||
|
||||
getChild(path: string): {text: () => string}
|
||||
}
|
||||
|
||||
export class Xmpp {
|
||||
private puppets: IXmppPuppets = {};
|
||||
private messageDeduplicator: MessageDeduplicator;
|
||||
private matrixMessageParser: MatrixMessageParser;
|
||||
private skypeMessageParser: SkypeMessageParser;
|
||||
private xmppMessageParser: XmppMessageParser;
|
||||
constructor(
|
||||
private puppet: PuppetBridge,
|
||||
) {
|
||||
this.messageDeduplicator = new MessageDeduplicator();
|
||||
this.matrixMessageParser = new MatrixMessageParser();
|
||||
this.skypeMessageParser = new SkypeMessageParser();
|
||||
this.xmppMessageParser = new XmppMessageParser();
|
||||
}
|
||||
|
||||
public getUserParams(puppetId: number, contact: SkypeContact): IRemoteUser {
|
||||
public getUserParams(puppetId: number, contact: any): IRemoteUser {
|
||||
return {
|
||||
puppetId,
|
||||
userId: contact.mri,
|
||||
@ -64,55 +64,38 @@ export class Skype {
|
||||
};
|
||||
}
|
||||
|
||||
public getRoomParams(puppetId: number, conversation: skypeHttp.Conversation): IRemoteRoom {
|
||||
const roomType = Number(conversation.id.split(":")[0]);
|
||||
const isDirect = roomType === ROOM_TYPE_DM;
|
||||
if (isDirect) {
|
||||
return {
|
||||
puppetId,
|
||||
roomId: `dm-${puppetId}-${conversation.id}`,
|
||||
isDirect: true,
|
||||
};
|
||||
}
|
||||
public getRoomParams(puppetId: number, conversation: any): IRemoteRoom {
|
||||
let avatarUrl: string | null = null;
|
||||
let name: string | null = null;
|
||||
if (conversation.threadProperties) {
|
||||
name = conversation.threadProperties.topic || null;
|
||||
if (name) {
|
||||
name = decodeHtml(name);
|
||||
}
|
||||
const picture = conversation.threadProperties.picture;
|
||||
if (picture && picture.startsWith("URL@")) {
|
||||
avatarUrl = picture.slice("URL@".length);
|
||||
}
|
||||
}
|
||||
const p = this.puppets[puppetId];
|
||||
return {
|
||||
puppetId,
|
||||
roomId: conversation.id,
|
||||
name,
|
||||
avatarUrl,
|
||||
downloadFile: async (url: string): Promise<Buffer> => {
|
||||
downloadFile: async (url: string): Promise<any> => {
|
||||
return await p.client.downloadFile(url, "swx_avatar");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public async getSendParams(puppetId: number, resource: skypeHttp.resources.Resource): Promise<IReceiveParams | null> {
|
||||
const roomType = Number(resource.conversation.split(":")[0]);
|
||||
public async getSendParams(puppetId: number, stanza: IStanza): Promise<IReceiveParams | null> {
|
||||
const p = this.puppets[puppetId];
|
||||
const contact = await p.client.getContact(resource.from.raw);
|
||||
const contact = await p.client.getContact(stanza.attrs.from);
|
||||
log.info("stanza.attrs", stanza.attrs);
|
||||
const conversation = await p.client.getConversation({
|
||||
puppetId,
|
||||
roomId: resource.conversation,
|
||||
puppetId: puppetId,
|
||||
roomId: stanza.attrs.from.split("/")[0],
|
||||
});
|
||||
log.info("Received contact", contact);
|
||||
log.info("Received conversation", conversation);
|
||||
if (!contact || !conversation) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
user: this.getUserParams(puppetId, contact),
|
||||
room: this.getRoomParams(puppetId, conversation),
|
||||
eventId: resource.id, // tslint:disable-line no-any
|
||||
eventId: stanza.attrs.id, // tslint:disable-line no-any
|
||||
};
|
||||
}
|
||||
|
||||
@ -130,51 +113,58 @@ export class Skype {
|
||||
return;
|
||||
}
|
||||
await this.stopClient(puppetId);
|
||||
p.client = new Client(p.data.username, p.data.password, p.data.state);
|
||||
p.client = new Client(p.data.username, p.data.password);
|
||||
const client = p.client;
|
||||
client.on("text", async (resource: skypeHttp.resources.TextResource) => {
|
||||
client.on("text", async (stanza: any) => {
|
||||
try {
|
||||
await this.handleSkypeText(puppetId, resource);
|
||||
await this.handleXmppText(puppetId, stanza);
|
||||
} catch (err) {
|
||||
log.error("Error while handling text event", err);
|
||||
}
|
||||
});
|
||||
client.on("edit", async (resource: skypeHttp.resources.RichTextResource) => {
|
||||
client.on("edit", async (stanza: any) => {
|
||||
try {
|
||||
await this.handleSkypeEdit(puppetId, resource);
|
||||
await this.handleXmppEdit(puppetId, stanza);
|
||||
} catch (err) {
|
||||
log.error("Error while handling edit event", err);
|
||||
}
|
||||
});
|
||||
client.on("location", async (resource: skypeHttp.resources.RichTextLocationResource) => {
|
||||
client.on("location", async (stanza: any) => {
|
||||
try {
|
||||
|
||||
} catch (err) {
|
||||
log.error("Error while handling location event", err);
|
||||
}
|
||||
});
|
||||
client.on("file", async (resource: skypeHttp.resources.FileResource) => {
|
||||
client.on("file", async (stanza: any) => {
|
||||
try {
|
||||
await this.handleSkypeFile(puppetId, resource);
|
||||
await this.handleXmppFile(puppetId, stanza);
|
||||
} catch (err) {
|
||||
log.error("Error while handling file event", err);
|
||||
}
|
||||
});
|
||||
client.on("typing", async (resource: skypeHttp.resources.Resource, typing: boolean) => {
|
||||
client.on("typing", async (stanza: any, typing: boolean) => {
|
||||
try {
|
||||
await this.handleSkypeTyping(puppetId, resource, typing);
|
||||
await this.handleXmppTyping(puppetId, stanza, typing);
|
||||
} catch (err) {
|
||||
log.error("Error while handling typing event", err);
|
||||
}
|
||||
});
|
||||
client.on("presence", async (resource: skypeHttp.resources.Resource) => {
|
||||
client.on("presence", async (stanza: any) => {
|
||||
try {
|
||||
await this.handleSkypePresence(puppetId, resource);
|
||||
await this.handleXmppPresence(puppetId, stanza);
|
||||
} catch (err) {
|
||||
log.error("Error while handling presence event", err);
|
||||
}
|
||||
});
|
||||
client.on("updateContact", async (oldContact: SkypeContact | null, newContact: SkypeContact) => {
|
||||
client.on("receipt", async (stanza: any) => {
|
||||
try {
|
||||
await this.handleXmppPresence(puppetId, stanza);
|
||||
} catch (err) {
|
||||
log.error("Error while handling receipt event", err);
|
||||
}
|
||||
});
|
||||
client.on("updateContact", async (oldContact: any | null, newContact: any) => {
|
||||
try {
|
||||
let update = oldContact === null;
|
||||
const newUser = this.getUserParams(puppetId, newContact);
|
||||
@ -197,16 +187,7 @@ export class Skype {
|
||||
}
|
||||
p.restarting = true;
|
||||
const causeName = (err as any).cause ? (err as any).cause.name : "";
|
||||
log.error("Error when polling");
|
||||
log.error("name: ", err.name);
|
||||
const errr = err as any;
|
||||
if (errr.cause) {
|
||||
log.error("cause name: ", errr.cause.name);
|
||||
}
|
||||
log.error("code: ", errr.code);
|
||||
log.error("body: ", errr.body);
|
||||
log.error("cause: ", errr.cause);
|
||||
log.error("data: ", errr.data);
|
||||
log.error("Error when polling", err.message);
|
||||
log.error(err);
|
||||
if (causeName === "UnexpectedHttpStatus") {
|
||||
await this.puppet.sendStatusMessage(puppetId, "Error: " + err);
|
||||
@ -346,6 +327,7 @@ export class Skype {
|
||||
if (!p) {
|
||||
return null;
|
||||
}
|
||||
log.info("getUserIdsInRoom", room);
|
||||
const conversation = await p.client.getConversation(room);
|
||||
if (!conversation) {
|
||||
return null;
|
||||
@ -356,10 +338,12 @@ export class Skype {
|
||||
users.add(member);
|
||||
}
|
||||
}
|
||||
log.info("getUserIdsInRoom users", users);
|
||||
return users;
|
||||
}
|
||||
|
||||
public async handleMatrixMessage(room: IRemoteRoom, data: IMessageEvent) {
|
||||
log.info("handleMatrixMessage");
|
||||
const p = this.puppets[room.puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
@ -414,6 +398,7 @@ export class Skype {
|
||||
}
|
||||
|
||||
public async handleMatrixReply(room: IRemoteRoom, eventId: string, data: IReplyEvent) {
|
||||
log.info("handleMatrixReply");
|
||||
const p = this.puppets[room.puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
@ -481,230 +466,209 @@ export class Skype {
|
||||
}
|
||||
|
||||
public async handleMatrixImage(room: IRemoteRoom, data: IFileEvent) {
|
||||
await this.handleMatrixFile(room, data, "sendImage");
|
||||
// TODO
|
||||
// await this.handleMatrixFile(room, data, "sendImage");
|
||||
}
|
||||
|
||||
public async handleMatrixAudio(room: IRemoteRoom, data: IFileEvent) {
|
||||
await this.handleMatrixFile(room, data, "sendAudio");
|
||||
// TODO
|
||||
// await this.handleMatrixFile(room, data, "sendAudio");
|
||||
}
|
||||
|
||||
public async handleMatrixFile(room: IRemoteRoom, data: IFileEvent, method?: string) {
|
||||
if (!method) {
|
||||
method = "sendDocument";
|
||||
}
|
||||
const p = this.puppets[room.puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
log.info("Received file from matrix");
|
||||
const conversation = await p.client.getConversation(room);
|
||||
if (!conversation) {
|
||||
log.warn(`Room ${room.roomId} not found!`);
|
||||
return;
|
||||
}
|
||||
const buffer = await Util.DownloadFile(data.url);
|
||||
const opts: SkypeNewMediaMessage = {
|
||||
file: buffer,
|
||||
name: data.filename,
|
||||
};
|
||||
if (data.info) {
|
||||
if (data.info.w) {
|
||||
opts.width = data.info.w;
|
||||
}
|
||||
if (data.info.h) {
|
||||
opts.height = data.info.h;
|
||||
}
|
||||
}
|
||||
const dedupeKey = `${room.puppetId};${room.roomId}`;
|
||||
this.messageDeduplicator.lock(dedupeKey, p.client.username, `file:${data.filename}`);
|
||||
const ret = await p.client[method](conversation.id, opts);
|
||||
const eventId = ret && ret.MessageId;
|
||||
this.messageDeduplicator.unlock(dedupeKey, p.client.username, eventId);
|
||||
if (eventId) {
|
||||
await this.puppet.eventSync.insert(room, data.eventId!, eventId);
|
||||
}
|
||||
// TODO
|
||||
// if (!method) {
|
||||
// method = "sendDocument";
|
||||
// }
|
||||
// const p = this.puppets[room.puppetId];
|
||||
// if (!p) {
|
||||
// return;
|
||||
// }
|
||||
// log.info("Received file from matrix");
|
||||
// const conversation = await p.client.getConversation(room);
|
||||
// if (!conversation) {
|
||||
// log.warn(`Room ${room.roomId} not found!`);
|
||||
// return;
|
||||
// }
|
||||
// const buffer = await Util.DownloadFile(data.url);
|
||||
// const opts: XmppNewMediaMessage = {
|
||||
// file: buffer,
|
||||
// name: data.filename,
|
||||
// };
|
||||
// if (data.info) {
|
||||
// if (data.info.w) {
|
||||
// opts.width = data.info.w;
|
||||
// }
|
||||
// if (data.info.h) {
|
||||
// opts.height = data.info.h;
|
||||
// }
|
||||
// }
|
||||
// const dedupeKey = `${room.puppetId};${room.roomId}`;
|
||||
// this.messageDeduplicator.lock(dedupeKey, p.client.username, `file:${data.filename}`);
|
||||
// const ret = await p.client[method](conversation.id, opts);
|
||||
// const eventId = ret && ret.MessageId;
|
||||
// this.messageDeduplicator.unlock(dedupeKey, p.client.username, eventId);
|
||||
// if (eventId) {
|
||||
// await this.puppet.eventSync.insert(room, data.eventId!, eventId);
|
||||
// }
|
||||
}
|
||||
|
||||
private async handleSkypeText(
|
||||
private async handleXmppText(
|
||||
puppetId: number,
|
||||
resource: skypeHttp.resources.TextResource | skypeHttp.resources.RichTextResource,
|
||||
stanza: IStanza,
|
||||
) {
|
||||
const p = this.puppets[puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
const rich = resource.native.messagetype.startsWith("RichText");
|
||||
log.info("Got new skype message");
|
||||
log.silly(resource);
|
||||
const params = await this.getSendParams(puppetId, resource);
|
||||
log.info("Got new xmpp message");
|
||||
log.silly(stanza);
|
||||
const params = await this.getSendParams(puppetId, stanza);
|
||||
if (!params) {
|
||||
log.warn("Couldn't generate params");
|
||||
return;
|
||||
}
|
||||
let msg = resource.content;
|
||||
let msg = stanza.getChild("body").text();
|
||||
let emote = false;
|
||||
if (resource.native.skypeemoteoffset) {
|
||||
emote = true;
|
||||
msg = msg.substr(Number(resource.native.skypeemoteoffset));
|
||||
}
|
||||
const dedupeKey = `${puppetId};${params.room.roomId}`;
|
||||
if (rich && msg.trim().startsWith("<URIObject") && msg.trim().endsWith("</URIObject>")) {
|
||||
// okay, we might have a sticker or something...
|
||||
const $ = cheerio.load(msg);
|
||||
const obj = $("URIObject");
|
||||
let uri = obj.attr("uri");
|
||||
const filename = $(obj.find("OriginalName")).attr("v");
|
||||
if (uri) {
|
||||
if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, `file:${filename}`)) {
|
||||
log.silly("file message dedupe");
|
||||
return;
|
||||
}
|
||||
uri += "/views/thumblarge";
|
||||
uri = uri.replace("static.asm.skype.com", "static-asm.secure.skypeassets.com");
|
||||
const buffer = await p.client.downloadFile(uri);
|
||||
await this.puppet.sendFileDetect(params, buffer, filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, msg)) {
|
||||
log.silly("normal message dedupe");
|
||||
return;
|
||||
}
|
||||
if (rich && msg.trim().startsWith("<quote")) {
|
||||
if (msg.trim().startsWith("<quote")) {
|
||||
// TODO
|
||||
// okay, we might have a reply...
|
||||
const $ = cheerio.load(msg);
|
||||
const quote = $("quote");
|
||||
const messageid = quote.attr("messageid");
|
||||
if (messageid) {
|
||||
const sendQuoteMsg = this.skypeMessageParser.parse(msg, { noQuotes: true });
|
||||
await this.puppet.sendReply(params, messageid, sendQuoteMsg);
|
||||
return;
|
||||
}
|
||||
// const $ = cheerio.load(msg);
|
||||
// const quote = $("quote");
|
||||
// const messageid = quote.attr("messageid");
|
||||
// if (messageid) {
|
||||
// const sendQuoteMsg = this.xmppMessageParser.parse(msg, { noQuotes: true });
|
||||
// await this.puppet.sendReply(params, messageid, sendQuoteMsg);
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
let sendMsg: IMessageEvent;
|
||||
if (rich) {
|
||||
sendMsg = this.skypeMessageParser.parse(msg);
|
||||
} else {
|
||||
sendMsg = {
|
||||
body: msg,
|
||||
};
|
||||
}
|
||||
if (emote) {
|
||||
sendMsg.emote = true;
|
||||
}
|
||||
await this.puppet.sendMessage(params, sendMsg);
|
||||
}
|
||||
|
||||
private async handleSkypeEdit(
|
||||
private async handleXmppEdit(
|
||||
puppetId: number,
|
||||
resource: skypeHttp.resources.TextResource | skypeHttp.resources.RichTextResource,
|
||||
stanza: IStanza,
|
||||
) {
|
||||
const p = this.puppets[puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
const rich = resource.native.messagetype.startsWith("RichText");
|
||||
log.info("Got new skype edit");
|
||||
log.silly(resource);
|
||||
const params = await this.getSendParams(puppetId, resource);
|
||||
if (!params) {
|
||||
log.warn("Couldn't generate params");
|
||||
return;
|
||||
}
|
||||
let msg = resource.content;
|
||||
let emote = false;
|
||||
if (resource.native.skypeemoteoffset) {
|
||||
emote = true;
|
||||
msg = msg.substr(Number(resource.native.skypeemoteoffset));
|
||||
}
|
||||
const dedupeKey = `${puppetId};${params.room.roomId}`;
|
||||
if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, msg)) {
|
||||
log.silly("normal message dedupe");
|
||||
return;
|
||||
}
|
||||
let sendMsg: IMessageEvent;
|
||||
if (rich) {
|
||||
sendMsg = this.skypeMessageParser.parse(msg, { noQuotes: msg.trim().startsWith("<quote") });
|
||||
} else {
|
||||
sendMsg = {
|
||||
body: msg,
|
||||
};
|
||||
}
|
||||
if (emote) {
|
||||
sendMsg.emote = true;
|
||||
}
|
||||
if (resource.content) {
|
||||
await this.puppet.sendEdit(params, resource.id, sendMsg);
|
||||
} else if (p.deletedMessages.has(resource.id)) {
|
||||
log.silly("normal message redact dedupe");
|
||||
return;
|
||||
} else {
|
||||
await this.puppet.sendRedact(params, resource.id);
|
||||
}
|
||||
// TODO
|
||||
// const p = this.puppets[puppetId];
|
||||
// if (!p) {
|
||||
// return;
|
||||
// }
|
||||
// const rich = resource.native.messagetype.startsWith("RichText");
|
||||
// log.info("Got new xmpp edit");
|
||||
// log.silly(resource);
|
||||
// const params = await this.getSendParams(puppetId, resource);
|
||||
// if (!params) {
|
||||
// log.warn("Couldn't generate params");
|
||||
// return;
|
||||
// }
|
||||
// let msg = resource.content;
|
||||
// let emote = false;
|
||||
// if (resource.native.xmppemoteoffset) {
|
||||
// emote = true;
|
||||
// msg = msg.substr(Number(resource.native.xmppemoteoffset));
|
||||
// }
|
||||
// const dedupeKey = `${puppetId};${params.room.roomId}`;
|
||||
// if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, msg)) {
|
||||
// log.silly("normal message dedupe");
|
||||
// return;
|
||||
// }
|
||||
// let sendMsg: IMessageEvent;
|
||||
// if (rich) {
|
||||
// sendMsg = this.xmppMessageParser.parse(msg, { noQuotes: msg.trim().startsWith("<quote") });
|
||||
// } else {
|
||||
// sendMsg = {
|
||||
// body: msg,
|
||||
// };
|
||||
// }
|
||||
// if (emote) {
|
||||
// sendMsg.emote = true;
|
||||
// }
|
||||
// if (resource.content) {
|
||||
// await this.puppet.sendEdit(params, resource.id, sendMsg);
|
||||
// } else if (p.deletedMessages.has(resource.id)) {
|
||||
// log.silly("normal message redact dedupe");
|
||||
// return;
|
||||
// } else {
|
||||
// await this.puppet.sendRedact(params, resource.id);
|
||||
// }
|
||||
}
|
||||
|
||||
private async handleSkypeFile(puppetId: number, resource: skypeHttp.resources.FileResource) {
|
||||
const p = this.puppets[puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
log.info("Got new skype file");
|
||||
log.silly(resource);
|
||||
const params = await this.getSendParams(puppetId, resource);
|
||||
if (!params) {
|
||||
log.warn("Couldn't generate params");
|
||||
return;
|
||||
}
|
||||
const filename = resource.original_file_name;
|
||||
const dedupeKey = `${puppetId};${params.room.roomId}`;
|
||||
if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, `file:${filename}`)) {
|
||||
log.silly("file message dedupe");
|
||||
return;
|
||||
}
|
||||
const buffer = await p.client.downloadFile(resource.uri);
|
||||
await this.puppet.sendFileDetect(params, buffer, filename);
|
||||
private async handleXmppFile(puppetId: number, stanza: IStanza) {
|
||||
// TODO
|
||||
// const p = this.puppets[puppetId];
|
||||
// if (!p) {
|
||||
// return;
|
||||
// }
|
||||
// log.info("Got new xmpp file");
|
||||
// log.silly(resource);
|
||||
// const params = await this.getSendParams(puppetId, resource);
|
||||
// if (!params) {
|
||||
// log.warn("Couldn't generate params");
|
||||
// return;
|
||||
// }
|
||||
// const filename = resource.original_file_name;
|
||||
// const dedupeKey = `${puppetId};${params.room.roomId}`;
|
||||
// if (await this.messageDeduplicator.dedupe(dedupeKey, params.user.userId, params.eventId, `file:${filename}`)) {
|
||||
// log.silly("file message dedupe");
|
||||
// return;
|
||||
// }
|
||||
// const buffer = await p.client.downloadFile(resource.uri);
|
||||
// await this.puppet.sendFileDetect(params, buffer, filename);
|
||||
}
|
||||
|
||||
private async handleSkypeTyping(puppetId: number, resource: skypeHttp.resources.Resource, typing: boolean) {
|
||||
const p = this.puppets[puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
log.info("Got new skype typing event");
|
||||
log.silly(resource);
|
||||
const params = await this.getSendParams(puppetId, resource);
|
||||
if (!params) {
|
||||
log.warn("Couldn't generate params");
|
||||
return;
|
||||
}
|
||||
await this.puppet.setUserTyping(params, typing);
|
||||
private async handleXmppTyping(puppetId: number, stanza: IStanza, typing: boolean) {
|
||||
// TODO
|
||||
// const p = this.puppets[puppetId];
|
||||
// if (!p) {
|
||||
// return;
|
||||
// }
|
||||
// log.info("Got new xmpp typing event");
|
||||
// log.silly(resource);
|
||||
// const params = await this.getSendParams(puppetId, resource);
|
||||
// if (!params) {
|
||||
// log.warn("Couldn't generate params");
|
||||
// return;
|
||||
// }
|
||||
// await this.puppet.setUserTyping(params, typing);
|
||||
}
|
||||
|
||||
private async handleSkypePresence(puppetId: number, resource: skypeHttp.resources.Resource) {
|
||||
const p = this.puppets[puppetId];
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
log.info("Got new skype presence event");
|
||||
log.silly(resource);
|
||||
const content = JSON.parse(resource.native.content);
|
||||
const contact = await p.client.getContact(content.user);
|
||||
const conversation = await p.client.getConversation({
|
||||
puppetId,
|
||||
roomId: resource.conversation,
|
||||
});
|
||||
if (!contact || !conversation) {
|
||||
log.warn("Couldn't generate params");
|
||||
return;
|
||||
}
|
||||
const params: IReceiveParams = {
|
||||
user: this.getUserParams(puppetId, contact),
|
||||
room: this.getRoomParams(puppetId, conversation),
|
||||
};
|
||||
const [id, _, clientId] = content.consumptionhorizon.split(";");
|
||||
params.eventId = id;
|
||||
await this.puppet.sendReadReceipt(params);
|
||||
params.eventId = clientId;
|
||||
await this.puppet.sendReadReceipt(params);
|
||||
private async handleXmppPresence(puppetId: number, stanza: IStanza) {
|
||||
// TODO
|
||||
// const p = this.puppets[puppetId];
|
||||
// if (!p) {
|
||||
// return;
|
||||
// }
|
||||
// log.info("Got new xmpp presence event");
|
||||
// log.silly(resource);
|
||||
// const content = JSON.parse(resource.native.content);
|
||||
// const contact = await p.client.getContact(content.user);
|
||||
// const conversation = await p.client.getConversation({
|
||||
// puppetId,
|
||||
// roomId: resource.conversation,
|
||||
// });
|
||||
// if (!contact || !conversation) {
|
||||
// log.warn("Couldn't generate params");
|
||||
// return;
|
||||
// }
|
||||
// const params: IReceiveParams = {
|
||||
// user: this.getUserParams(puppetId, contact),
|
||||
// room: this.getRoomParams(puppetId, conversation),
|
||||
// };
|
||||
// const [id, _, clientId] = content.consumptionhorizon.split(";");
|
||||
// params.eventId = id;
|
||||
// await this.puppet.sendReadReceipt(params);
|
||||
// params.eventId = clientId;
|
||||
// await this.puppet.sendReadReceipt(params);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 mx-puppet-skype
|
||||
Copyright 2020 mx-puppet-xmpp
|
||||
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
|
||||
@ -17,12 +17,12 @@ import * as escapeHtml from "escape-html";
|
||||
import { IMessageEvent } from "mx-puppet-bridge";
|
||||
import * as emoji from "node-emoji";
|
||||
|
||||
interface ISkypeMessageParserOpts {
|
||||
interface IXmppMessageParserOpts {
|
||||
noQuotes?: boolean;
|
||||
}
|
||||
|
||||
export class SkypeMessageParser {
|
||||
public parse(msg: string, opts: ISkypeMessageParserOpts = {}): IMessageEvent {
|
||||
export class XmppMessageParser {
|
||||
public parse(msg: string, opts: IXmppMessageParserOpts = {}): IMessageEvent {
|
||||
opts = Object.assign({
|
||||
noQuotes: false,
|
||||
}, opts);
|
||||
@ -33,7 +33,7 @@ export class SkypeMessageParser {
|
||||
return this.walkNode(nodes, opts);
|
||||
}
|
||||
|
||||
private walkChildNodes(node: Parser.Node, opts: ISkypeMessageParserOpts): IMessageEvent {
|
||||
private walkChildNodes(node: Parser.Node, opts: IXmppMessageParserOpts): IMessageEvent {
|
||||
if (!node.childNodes.length) {
|
||||
return {
|
||||
body: "",
|
||||
@ -55,7 +55,7 @@ export class SkypeMessageParser {
|
||||
};
|
||||
}
|
||||
|
||||
private walkNode(node: Parser.Node, opts: ISkypeMessageParserOpts): IMessageEvent {
|
||||
private walkNode(node: Parser.Node, opts: IXmppMessageParserOpts): IMessageEvent {
|
||||
if (node.nodeType === Parser.NodeType.TEXT_NODE) {
|
||||
return this.escape((node as Parser.TextNode).text);
|
||||
} else if (node.nodeType === Parser.NodeType.ELEMENT_NODE) {
|
||||
@ -111,7 +111,7 @@ export class SkypeMessageParser {
|
||||
};
|
||||
}
|
||||
case "ss": {
|
||||
// skype emoji
|
||||
// xmpp emoji
|
||||
const type = nodeHtml.attributes.type;
|
||||
let emojiType = {
|
||||
smile: "slightly_smiling_face",
|
Loading…
Reference in New Issue
Block a user