2020-11-02 14:48:22 +00:00
|
|
|
// first we import a few needed things again
|
|
|
|
import {
|
|
|
|
PuppetBridge,
|
|
|
|
IRemoteUser,
|
|
|
|
IReceiveParams,
|
|
|
|
IRemoteRoom,
|
|
|
|
IMessageEvent,
|
|
|
|
IFileEvent,
|
|
|
|
MessageDeduplicator,
|
|
|
|
Log,
|
2020-11-03 19:02:44 +00:00
|
|
|
ISendingUser,
|
2020-11-02 14:48:22 +00:00
|
|
|
} from "mx-puppet-bridge";
|
|
|
|
|
2020-11-20 08:45:31 +00:00
|
|
|
import { VK, MessageContext, Context, AttachmentType, MessageForwardsCollection } from "vk-io";
|
2020-11-02 14:48:22 +00:00
|
|
|
import { userInfo } from "os";
|
|
|
|
import { runInThisContext } from "vm";
|
2020-11-03 19:02:44 +00:00
|
|
|
import { lookup } from "dns";
|
2020-11-20 08:45:31 +00:00
|
|
|
import { Converter } from "showdown";
|
2020-11-02 14:48:22 +00:00
|
|
|
|
|
|
|
// here we create our log instance
|
|
|
|
const log = new Log("VKPuppet:vk");
|
|
|
|
|
|
|
|
// this interface is to hold all data on a single puppet
|
|
|
|
interface IEchoPuppet {
|
|
|
|
// this is usually a client class that connects to the remote protocol
|
|
|
|
// as we just echo back, unneeded in our case
|
|
|
|
client: VK;
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-02 14:48:22 +00:00
|
|
|
data: any; // and let's keep a copy of the data associated with a puppet
|
|
|
|
}
|
|
|
|
|
|
|
|
// we can hold multiple puppets at once...
|
|
|
|
interface IEchoPuppets {
|
|
|
|
[puppetId: number]: IEchoPuppet;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class VkPuppet {
|
|
|
|
private puppets: IEchoPuppets = {};
|
2020-11-20 08:45:31 +00:00
|
|
|
private converter: Converter = new Converter({
|
|
|
|
simplifiedAutoLink: true,
|
|
|
|
excludeTrailingPunctuationFromURLs: true,
|
|
|
|
strikethrough: true,
|
|
|
|
simpleLineBreaks: true,
|
|
|
|
requireSpaceBeforeHeadingText: true,
|
|
|
|
});
|
2020-11-02 14:48:22 +00:00
|
|
|
constructor(
|
|
|
|
private puppet: PuppetBridge,
|
|
|
|
) { }
|
|
|
|
|
2020-11-03 14:18:20 +00:00
|
|
|
public async getSendParams(puppetId: number, peerId: number, senderId: number, eventId?: string | undefined):
|
2020-11-03 16:38:06 +00:00
|
|
|
Promise<IReceiveParams> {
|
2020-11-02 14:48:22 +00:00
|
|
|
// we will use this function internally to create the send parameters
|
|
|
|
// needed to send a message, a file, reactions, ... to matrix
|
2020-11-13 03:14:53 +00:00
|
|
|
// log.info(`Creating send params for ${peerId}...`);
|
2020-11-02 14:48:22 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
room: await this.getRemoteRoom(puppetId, peerId),
|
|
|
|
user: await this.getRemoteUser(puppetId, senderId),
|
2020-11-03 14:18:20 +00:00
|
|
|
eventId,
|
2020-11-02 14:48:22 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getRemoteUser(puppetId: number, userId: number): Promise<IRemoteUser> {
|
|
|
|
const p = this.puppets[puppetId];
|
2020-11-20 15:43:32 +00:00
|
|
|
// log.debug("User id:", userId, userId.toString());
|
2020-11-02 14:48:22 +00:00
|
|
|
if (userId < 0) {
|
|
|
|
const info = await p.client.api.groups.getById({ group_id: Math.abs(userId).toString() });
|
|
|
|
const response: IRemoteUser = {
|
|
|
|
puppetId,
|
|
|
|
userId: userId.toString(),
|
|
|
|
name: info[0].name,
|
|
|
|
avatarUrl: info[0].photo_200,
|
2020-11-20 08:45:31 +00:00
|
|
|
externalUrl: `https://vk.com/${info[0].screen_name}`,
|
2020-11-02 14:48:22 +00:00
|
|
|
};
|
|
|
|
return response;
|
|
|
|
} else {
|
2020-11-20 08:45:31 +00:00
|
|
|
const info = await p.client.api.users.get({ user_ids: userId.toString(), fields: ["photo_max", "screen_name"] });
|
2020-11-02 14:48:22 +00:00
|
|
|
const response: IRemoteUser = {
|
|
|
|
puppetId,
|
|
|
|
userId: userId.toString(),
|
|
|
|
name: `${info[0].first_name} ${info[0].last_name}`,
|
|
|
|
avatarUrl: info[0].photo_max,
|
2020-11-20 08:45:31 +00:00
|
|
|
externalUrl: `https://vk.com/${info[0].screen_name}`,
|
2020-11-02 14:48:22 +00:00
|
|
|
};
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getRemoteRoom(puppetId: number, peerId: number): Promise<IRemoteRoom> {
|
|
|
|
const p = this.puppets[puppetId];
|
|
|
|
const info = await p.client.api.messages.getConversationsById({ peer_ids: peerId, fields: ["photo_max"] });
|
|
|
|
let response: IRemoteRoom;
|
2020-11-13 06:42:45 +00:00
|
|
|
switch (info.items[0]?.peer.type || "chat") {
|
2020-11-02 14:48:22 +00:00
|
|
|
case "user":
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-shadowed-variable
|
2020-11-02 14:48:22 +00:00
|
|
|
const userInfo = await p.client.api.users.get({ user_ids: info.items[0].peer.id, fields: ["photo_max"] });
|
|
|
|
response = {
|
|
|
|
puppetId,
|
|
|
|
roomId: peerId.toString(),
|
|
|
|
name: `${userInfo[0].first_name} ${userInfo[0].last_name}`,
|
|
|
|
avatarUrl: userInfo[0].photo_max,
|
|
|
|
isDirect: true,
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "chat":
|
|
|
|
response = {
|
|
|
|
puppetId,
|
|
|
|
roomId: peerId.toString(),
|
2020-11-13 06:42:45 +00:00
|
|
|
name: info.items[0]?.chat_settings.title || `VK chat №${(peerId - 2000000000).toString()}`,
|
|
|
|
topic: info.count === 0 ? "To recieve chat name and avatar, puppet needs admin rights on VK side" : null,
|
|
|
|
avatarUrl: info.items[0]?.chat_settings.photo?.photo_200,
|
2020-11-02 14:48:22 +00:00
|
|
|
};
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
response = {
|
|
|
|
puppetId,
|
|
|
|
roomId: peerId.toString(),
|
|
|
|
name: peerId.toString(),
|
|
|
|
// avatarUrl: info.items['chat_settings']['photo_200'],
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-02 14:48:22 +00:00
|
|
|
public async newPuppet(puppetId: number, data: any) {
|
|
|
|
// this is called when we need to create a new puppet
|
|
|
|
// the puppetId is the ID associated with that puppet and the data its data
|
|
|
|
if (this.puppets[puppetId]) {
|
|
|
|
// the puppet somehow already exists, delete it first
|
|
|
|
await this.deletePuppet(puppetId);
|
|
|
|
}
|
|
|
|
// usually we create a client class of some sorts to the remote protocol
|
|
|
|
// and listen to incoming messages from it
|
|
|
|
const client = new VK({ token: data.token, apiLimit: 20 });
|
|
|
|
log.info("Trying to init listener with", data.token);
|
|
|
|
|
2020-11-13 01:40:53 +00:00
|
|
|
client.updates.on("message_new", async (context) => {
|
2020-11-02 14:48:22 +00:00
|
|
|
try {
|
|
|
|
log.info("Recieved something!");
|
|
|
|
await this.handleVkMessage(puppetId, context);
|
|
|
|
} catch (err) {
|
|
|
|
log.error("Error handling vk message event", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
});
|
2020-11-13 01:40:53 +00:00
|
|
|
client.updates.on("message_edit", async (context) => {
|
|
|
|
try {
|
|
|
|
log.info("Edit recieved!");
|
|
|
|
await this.handleVkEdit(puppetId, context);
|
|
|
|
} catch (err) {
|
|
|
|
log.error("Error handling vk message event", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
});
|
2020-11-02 14:48:22 +00:00
|
|
|
client.updates.on("message_typing_state", async (context) => {
|
|
|
|
if (context.isUser) {
|
|
|
|
const params = await this.getSendParams(puppetId, context.fromId, context.fromId);
|
|
|
|
await this.puppet.setUserTyping(params, context.isTyping);
|
|
|
|
} else {
|
|
|
|
const params = await this.getSendParams(puppetId, 2000000000 + (context?.chatId ?? 0), context.fromId);
|
|
|
|
await this.puppet.setUserTyping(params, context.isTyping);
|
|
|
|
}
|
|
|
|
});
|
2020-11-22 09:59:39 +00:00
|
|
|
try {
|
|
|
|
await client.api.groups.getById({});
|
|
|
|
log.info("Got group token");
|
|
|
|
data.isUserToken = false;
|
|
|
|
} catch (err) {
|
|
|
|
log.info("Got user token");
|
|
|
|
data.isUserToken = true;
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
this.puppets[puppetId] = {
|
|
|
|
client,
|
|
|
|
data,
|
|
|
|
};
|
2020-11-22 09:59:39 +00:00
|
|
|
try {
|
|
|
|
await client.updates.start();
|
|
|
|
} catch (err) {
|
2020-12-05 10:23:15 +00:00
|
|
|
log.error("Failed to initialize update listener", err);
|
2020-11-22 09:59:39 +00:00
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async deletePuppet(puppetId: number) {
|
|
|
|
// this is called when we need to delte a puppet
|
|
|
|
const p = this.puppets[puppetId];
|
|
|
|
if (!p) {
|
|
|
|
// puppet doesn't exist, nothing to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await p.client.updates.stop();
|
|
|
|
delete this.puppets[puppetId]; // and finally delete our local copy
|
|
|
|
}
|
|
|
|
|
2020-11-03 16:38:06 +00:00
|
|
|
//////////////////////////
|
|
|
|
// Matrix -> VK section //
|
|
|
|
//////////////////////////
|
|
|
|
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-13 06:42:45 +00:00
|
|
|
public async handleMatrixMessage(room: IRemoteRoom, data: IMessageEvent, asUser: ISendingUser | null, event: any) {
|
2020-11-02 14:48:22 +00:00
|
|
|
// this is called every time we receive a message from matrix and need to
|
|
|
|
// forward it to the remote protocol.
|
|
|
|
|
|
|
|
// first we check if the puppet exists
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-13 06:42:45 +00:00
|
|
|
|
|
|
|
if (asUser) {
|
|
|
|
const MAX_NAME_LENGTH = 80;
|
|
|
|
const displayname = (new TextEncoder().encode(asUser.displayname));
|
|
|
|
asUser.displayname = (new TextDecoder().decode(displayname.slice(0, MAX_NAME_LENGTH)));
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
// usually you'd send it here to the remote protocol via the client object
|
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.send({
|
2020-11-13 06:42:45 +00:00
|
|
|
peer_ids: Number(room.roomId),
|
|
|
|
message: asUser ? `${asUser.displayname}: ${data.body}` : data.body,
|
2020-11-02 14:48:22 +00:00
|
|
|
random_id: new Date().getTime(),
|
|
|
|
});
|
2020-11-13 06:42:45 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? response[0]["message_id"].toString() : response[0]["conversation_message_id"].toString());
|
2020-11-02 14:48:22 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
2020-11-03 16:38:06 +00:00
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
|
2020-11-13 06:42:45 +00:00
|
|
|
public async handleMatrixEdit(room: IRemoteRoom, eventId: string, data: IMessageEvent, asUser: ISendingUser | null) {
|
2020-11-13 01:40:53 +00:00
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-13 06:42:45 +00:00
|
|
|
|
|
|
|
if (asUser) {
|
|
|
|
const MAX_NAME_LENGTH = 80;
|
|
|
|
const displayname = (new TextEncoder().encode(asUser.displayname));
|
|
|
|
asUser.displayname = (new TextDecoder().decode(displayname.slice(0, MAX_NAME_LENGTH)));
|
|
|
|
}
|
2020-11-13 01:40:53 +00:00
|
|
|
// usually you'd send it here to the remote protocol via the client object
|
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.edit({
|
|
|
|
peer_id: Number(room.roomId),
|
2020-11-22 09:59:39 +00:00
|
|
|
conversation_message_id: p.data.isUserToken ? undefined : Number(eventId),
|
|
|
|
message_id: p.data.isUserToken ? Number(eventId) : undefined,
|
2020-11-13 06:42:45 +00:00
|
|
|
message: asUser ? `${asUser.displayname}: ${data.body}` : data.body,
|
2020-11-13 01:40:53 +00:00
|
|
|
random_id: new Date().getTime(),
|
|
|
|
});
|
2020-11-22 09:59:39 +00:00
|
|
|
log.info("SYNC Matrix edit", response);
|
2020-11-13 01:40:53 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!, response.toString());
|
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending edit to vk", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 06:42:45 +00:00
|
|
|
public async handleMatrixRedact(room: IRemoteRoom, eventId: string, asUser: ISendingUser | null) {
|
2020-11-13 02:02:21 +00:00
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-13 06:42:45 +00:00
|
|
|
|
|
|
|
if (asUser) {
|
|
|
|
const MAX_NAME_LENGTH = 80;
|
|
|
|
const displayname = (new TextEncoder().encode(asUser.displayname));
|
|
|
|
asUser.displayname = (new TextDecoder().decode(displayname.slice(0, MAX_NAME_LENGTH)));
|
|
|
|
}
|
2020-11-20 15:43:32 +00:00
|
|
|
|
2020-11-13 02:02:21 +00:00
|
|
|
try {
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? await p.client.api.messages.delete({
|
2020-11-13 02:02:21 +00:00
|
|
|
spam: 0,
|
|
|
|
delete_for_all: 1,
|
|
|
|
message_ids: Number(eventId),
|
2020-11-22 09:59:39 +00:00
|
|
|
})
|
|
|
|
: await this.handleMatrixEdit(room, eventId, { body: "[ДАННЫЕ УДАЛЕНЫ]", eventId }, asUser);
|
2020-11-13 02:02:21 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending edit to vk", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 01:40:53 +00:00
|
|
|
|
2020-11-03 16:38:06 +00:00
|
|
|
public async handleMatrixReply(
|
|
|
|
room: IRemoteRoom,
|
|
|
|
eventId: string,
|
|
|
|
data: IMessageEvent,
|
2020-11-13 06:42:45 +00:00
|
|
|
asUser: ISendingUser | null,
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-03 16:38:06 +00:00
|
|
|
event: any,
|
|
|
|
) {
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-13 06:42:45 +00:00
|
|
|
|
|
|
|
if (asUser) {
|
|
|
|
const MAX_NAME_LENGTH = 80;
|
|
|
|
const displayname = (new TextEncoder().encode(asUser.displayname));
|
|
|
|
asUser.displayname = (new TextDecoder().decode(displayname.slice(0, MAX_NAME_LENGTH)));
|
|
|
|
}
|
|
|
|
|
2020-11-03 16:38:06 +00:00
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.send({
|
2020-11-13 06:42:45 +00:00
|
|
|
peer_ids: Number(room.roomId),
|
|
|
|
message: asUser ? `${asUser.displayname}: ${await this.stripReply(data.body)}` : await this.stripReply(data.body),
|
2020-11-03 16:38:06 +00:00
|
|
|
random_id: new Date().getTime(),
|
2020-11-22 09:59:39 +00:00
|
|
|
forward: p.data.isUserToken ? undefined : `{"peer_id":${Number(room.roomId)},"conversation_message_ids":${Number(eventId)},"is_reply": true}`,
|
2020-12-05 10:23:15 +00:00
|
|
|
reply_to: p.data.isUserToken ? Number(eventId) : undefined,
|
2020-11-03 16:38:06 +00:00
|
|
|
});
|
2020-11-13 06:42:45 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? response[0]["message_id"].toString() : response[0]["conversation_message_id"].toString());
|
2020-11-03 16:38:06 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 19:02:44 +00:00
|
|
|
public async handleMatrixImage(
|
|
|
|
room: IRemoteRoom,
|
|
|
|
data: IFileEvent,
|
|
|
|
asUser: ISendingUser | null,
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-03 19:02:44 +00:00
|
|
|
event: any,
|
|
|
|
) {
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const MAXFILESIZE = 50000000;
|
|
|
|
const size = data.info ? data.info.size || 0 : 0;
|
|
|
|
|
2020-11-13 06:42:45 +00:00
|
|
|
if (asUser) {
|
|
|
|
const MAX_NAME_LENGTH = 80;
|
|
|
|
const displayname = (new TextEncoder().encode(asUser.displayname));
|
|
|
|
asUser.displayname = (new TextDecoder().decode(displayname.slice(0, MAX_NAME_LENGTH)));
|
|
|
|
}
|
|
|
|
|
2020-11-03 19:02:44 +00:00
|
|
|
if (size < MAXFILESIZE) {
|
|
|
|
try {
|
|
|
|
const attachment = await p.client.upload.messagePhoto({
|
|
|
|
peer_id: Number(room.roomId),
|
|
|
|
source: {
|
|
|
|
value: data.url,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const response = await p.client.api.messages.send({
|
2020-11-13 06:42:45 +00:00
|
|
|
peer_ids: Number(room.roomId),
|
2020-11-03 19:02:44 +00:00
|
|
|
random_id: new Date().getTime(),
|
2020-11-13 06:42:45 +00:00
|
|
|
message: asUser ? `${asUser.displayname} sent a photo:` : undefined,
|
2020-11-03 19:02:44 +00:00
|
|
|
attachment: [`photo${attachment.ownerId}_${attachment.id}`],
|
|
|
|
});
|
2020-11-13 06:42:45 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? response[0]["message_id"].toString() : response[0]["conversation_message_id"].toString());
|
2020-11-03 19:02:44 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.send({
|
|
|
|
peer_id: Number(room.roomId),
|
|
|
|
message: `File ${data.filename} was sent, but it is too big for VK. You may download it there:\n${data.url}`,
|
|
|
|
random_id: new Date().getTime(),
|
|
|
|
});
|
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!, response.toString());
|
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async handleMatrixFile(
|
|
|
|
room: IRemoteRoom,
|
|
|
|
data: IFileEvent,
|
|
|
|
asUser: ISendingUser | null,
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: no-any
|
2020-11-03 19:02:44 +00:00
|
|
|
event: any,
|
|
|
|
) {
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const MAXFILESIZE = 50000000;
|
|
|
|
const size = data.info ? data.info.size || 0 : 0;
|
|
|
|
|
|
|
|
if (size < MAXFILESIZE) {
|
|
|
|
try {
|
|
|
|
const attachment = await p.client.upload.messageDocument({
|
|
|
|
peer_id: Number(room.roomId),
|
|
|
|
source: {
|
|
|
|
value: data.url,
|
|
|
|
filename: data.filename,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const response = await p.client.api.messages.send({
|
|
|
|
peer_id: Number(room.roomId),
|
|
|
|
random_id: new Date().getTime(),
|
2020-11-13 06:42:45 +00:00
|
|
|
message: asUser ? `${asUser.displayname} sent a file:` : undefined,
|
2020-11-03 19:02:44 +00:00
|
|
|
attachment: [`doc${attachment.ownerId}_${attachment.id}`],
|
|
|
|
});
|
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!, response.toString());
|
|
|
|
} catch (err) {
|
2020-11-13 00:26:02 +00:00
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.send({
|
2020-11-13 06:42:45 +00:00
|
|
|
peer_ids: Number(room.roomId),
|
2020-11-13 00:26:02 +00:00
|
|
|
message: `File ${data.filename} was sent, but VK refused to recieve it. You may download it there:\n${data.url}`,
|
|
|
|
random_id: new Date().getTime(),
|
|
|
|
});
|
2020-11-13 06:42:45 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? response[0]["message_id"].toString() : response[0]["conversation_message_id"].toString());
|
2020-11-13 00:26:02 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
2020-11-03 19:02:44 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.send({
|
2020-11-13 06:42:45 +00:00
|
|
|
peer_ids: Number(room.roomId),
|
2020-11-03 19:02:44 +00:00
|
|
|
message: `File ${data.filename} was sent, but it is too big for VK. You may download it there:\n${data.url}`,
|
|
|
|
random_id: new Date().getTime(),
|
|
|
|
});
|
2020-11-13 06:42:45 +00:00
|
|
|
await this.puppet.eventSync.insert(room, data.eventId!,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? response[0]["message_id"].toString() : response[0]["conversation_message_id"].toString());
|
2020-11-03 19:02:44 +00:00
|
|
|
} catch (err) {
|
|
|
|
log.error("Error sending to vk", err.error || err.body || err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-20 15:43:32 +00:00
|
|
|
|
|
|
|
// Never called on my server for some reason, but
|
|
|
|
// if being called, should work
|
2020-11-20 08:45:31 +00:00
|
|
|
public async handleMatrixTyping(
|
|
|
|
room: IRemoteRoom,
|
|
|
|
typing: boolean,
|
|
|
|
asUser: ISendingUser | null,
|
|
|
|
event: any,
|
|
|
|
) {
|
|
|
|
if (typing) {
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const response = await p.client.api.messages.setActivity({
|
|
|
|
peer_id: Number(room.roomId),
|
|
|
|
type: "typing",
|
2020-11-20 15:43:32 +00:00
|
|
|
});
|
2020-11-20 08:45:31 +00:00
|
|
|
} catch (err) {
|
2020-11-20 15:43:32 +00:00
|
|
|
log.error("Error sending typing presence to vk", err.error || err.body || err);
|
2020-11-20 08:45:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-03 19:02:44 +00:00
|
|
|
|
2020-11-02 14:48:22 +00:00
|
|
|
public async createRoom(room: IRemoteRoom): Promise<IRemoteRoom | null> {
|
|
|
|
const p = this.puppets[room.puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
log.info(`Received create request for channel update puppetId=${room.puppetId} roomId=${room.roomId}`);
|
|
|
|
|
|
|
|
return await this.getRemoteRoom(room.puppetId, Number(room.roomId));
|
|
|
|
}
|
|
|
|
|
2020-11-03 16:38:06 +00:00
|
|
|
//////////////////////////
|
|
|
|
// VK -> Matrix section //
|
|
|
|
//////////////////////////
|
2020-11-02 14:48:22 +00:00
|
|
|
|
|
|
|
public async handleVkMessage(puppetId: number, context: MessageContext) {
|
|
|
|
const p = this.puppets[puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-20 15:43:32 +00:00
|
|
|
log.debug("Received new message!", context);
|
2020-11-03 16:38:06 +00:00
|
|
|
if (context.isOutbox) {
|
|
|
|
return; // Deduping
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
|
2020-11-13 06:42:45 +00:00
|
|
|
const params = await this.getSendParams(puppetId, context.peerId, context.senderId,
|
2020-11-22 09:59:39 +00:00
|
|
|
p.data.isUserToken ? context.id.toString() : context.conversationMessageId?.toString() || context.id.toString());
|
2020-11-02 14:48:22 +00:00
|
|
|
|
2020-11-20 08:45:31 +00:00
|
|
|
if (context.hasText || context.hasForwards) {
|
|
|
|
let msgText: string = context.text || "";
|
|
|
|
if (context.hasForwards) {
|
|
|
|
msgText = await this.appendForwards(puppetId, msgText, context.forwards);
|
|
|
|
}
|
2020-11-03 16:38:06 +00:00
|
|
|
if (context.hasReplyMessage) {
|
|
|
|
if (this.puppet.eventSync.getMatrix(params.room, context.replyMessage!.id.toString())) {
|
2020-11-03 19:02:44 +00:00
|
|
|
const opts: IMessageEvent = {
|
2020-11-20 08:45:31 +00:00
|
|
|
body: msgText || "Attachment",
|
|
|
|
formattedBody: this.converter.makeHtml(msgText),
|
2020-11-03 19:02:44 +00:00
|
|
|
};
|
2020-11-03 16:38:06 +00:00
|
|
|
// We got referenced message in room, using matrix reply
|
|
|
|
await this.puppet.sendReply(params, context.replyMessage!.id.toString(), opts);
|
|
|
|
} else {
|
|
|
|
// Using a fallback
|
2020-11-03 19:02:44 +00:00
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: await this.prependReply(
|
2020-11-20 08:45:31 +00:00
|
|
|
puppetId, msgText || "",
|
2020-11-03 19:02:44 +00:00
|
|
|
context.replyMessage?.text || "",
|
|
|
|
context.senderId.toString(),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const opts: IMessageEvent = {
|
2020-11-20 08:45:31 +00:00
|
|
|
body: msgText || "Attachment",
|
|
|
|
formattedBody: this.converter.makeHtml(msgText),
|
2020-11-03 19:02:44 +00:00
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (context.hasAttachments()) {
|
2020-12-05 10:24:40 +00:00
|
|
|
const attachments = p.data.isUserToken
|
|
|
|
? (await p.client.api.messages.getById({message_ids: context.id})).items[0].attachments!
|
|
|
|
: context.attachments;
|
|
|
|
for (const f of attachments) {
|
2020-11-13 01:40:53 +00:00
|
|
|
switch (f.type) {
|
|
|
|
case AttachmentType.PHOTO:
|
|
|
|
try {
|
2020-12-05 10:24:40 +00:00
|
|
|
if (p.data.isUserToken) {
|
|
|
|
// VK API is weird. Very weird.
|
|
|
|
let url: string = "";
|
|
|
|
f["photo"]["sizes"].forEach((element) => {
|
|
|
|
if (element["type"] === "w") {
|
|
|
|
url = element["url"] || "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (url === "") {
|
|
|
|
f["photo"]["sizes"].forEach((element) => {
|
|
|
|
if (element["type"] === "z") {
|
|
|
|
url = element["url"] || "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (url === undefined) {
|
|
|
|
f["photo"]["sizes"].forEach((element) => {
|
|
|
|
if (element["type"] === "y") {
|
|
|
|
url = element["url"];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
await this.puppet.sendFileDetect(params, url);
|
|
|
|
} else {
|
|
|
|
await this.puppet.sendFileDetect(params, f["largeSizeUrl"]);
|
|
|
|
}
|
2020-11-13 01:40:53 +00:00
|
|
|
} catch (err) {
|
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: `Image was sent: ${f["largeSizeUrl"]}`,
|
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case AttachmentType.STICKER:
|
|
|
|
try {
|
2020-12-05 10:24:40 +00:00
|
|
|
p.data.isUserToken ? await this.puppet.sendFileDetect(params, f["sticker"]["images_with_background"][4]["url"])
|
|
|
|
: await this.puppet.sendFileDetect(params, f["imagesWithBackground"][4]["url"]);
|
2020-11-13 01:40:53 +00:00
|
|
|
} catch (err) {
|
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: `Sticker was sent: ${f["imagesWithBackground"][4]["url"]}`,
|
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case AttachmentType.AUDIO_MESSAGE:
|
|
|
|
try {
|
|
|
|
await this.puppet.sendAudio(params, f["oggUrl"]);
|
|
|
|
} catch (err) {
|
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: `Audio message was sent: ${f["url"]}`,
|
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
break;
|
2020-11-13 02:02:21 +00:00
|
|
|
case AttachmentType.DOCUMENT:
|
|
|
|
try {
|
2020-12-05 10:24:40 +00:00
|
|
|
p.data.isUserToken ? await this.puppet.sendFileDetect(params, f["doc"]["url"], f["doc"]["title"])
|
|
|
|
: await this.puppet.sendFileDetect(params, f["url"], f["title"]);
|
2020-11-13 02:02:21 +00:00
|
|
|
} catch (err) {
|
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: `Document was sent: ${f["url"]}`,
|
|
|
|
};
|
|
|
|
await this.puppet.sendMessage(params, opts);
|
|
|
|
}
|
|
|
|
break;
|
2020-11-13 01:40:53 +00:00
|
|
|
default:
|
|
|
|
break;
|
2020-11-03 16:38:06 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-03 16:38:06 +00:00
|
|
|
|
2020-11-13 01:40:53 +00:00
|
|
|
public async handleVkEdit(puppetId: number, context: MessageContext) {
|
|
|
|
const p = this.puppets[puppetId];
|
|
|
|
if (!p) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-22 09:59:39 +00:00
|
|
|
log.info(context);
|
2020-11-13 01:40:53 +00:00
|
|
|
// As VK always sends edit as outbox, we won't work with any edits from groups
|
2020-11-22 09:59:39 +00:00
|
|
|
if (!p.data.isUserToken && context.senderType === "group") {
|
|
|
|
return; // Deduping
|
|
|
|
}
|
|
|
|
|
|
|
|
// With users it works ok
|
|
|
|
if (p.data.isUserToken && context.isOutbox === true) {
|
2020-11-13 01:40:53 +00:00
|
|
|
return; // Deduping
|
|
|
|
}
|
|
|
|
|
|
|
|
const params = await this.getSendParams(puppetId, context.peerId, context.senderId, context.id.toString());
|
|
|
|
if (context.hasText) {
|
2020-11-13 03:14:53 +00:00
|
|
|
const opts: IMessageEvent = {
|
|
|
|
body: context.text || "Attachment",
|
|
|
|
};
|
|
|
|
await this.puppet.sendEdit(params, context.id.toString(), opts);
|
2020-11-13 01:40:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-03 16:38:06 +00:00
|
|
|
////////////////
|
|
|
|
// Formatters //
|
|
|
|
////////////////
|
|
|
|
|
|
|
|
public async prependReply(puppetId: number, body: string, reply: string, userid: string) {
|
|
|
|
const user = await this.getRemoteUser(puppetId, Number(userid));
|
|
|
|
const replySplitted = reply.split("\n");
|
|
|
|
let formatted: string = `> <${user.name}>\n`;
|
|
|
|
replySplitted.forEach((element) => {
|
2020-11-20 08:45:31 +00:00
|
|
|
formatted += `> ${element}\n`;
|
2020-11-03 16:38:06 +00:00
|
|
|
});
|
|
|
|
formatted += `\n\n${body}`;
|
|
|
|
return formatted;
|
|
|
|
}
|
2020-11-13 01:40:53 +00:00
|
|
|
|
|
|
|
public async stripReply(body: string) {
|
2020-11-13 03:14:53 +00:00
|
|
|
// tslint:disable-next-line: prefer-const
|
2020-11-13 01:40:53 +00:00
|
|
|
let splitted = body.split("\n");
|
|
|
|
let isCitate = true;
|
|
|
|
while (isCitate) {
|
|
|
|
if (splitted[0].startsWith(">")) {
|
|
|
|
splitted.splice(0, 1);
|
|
|
|
} else {
|
|
|
|
isCitate = false;
|
|
|
|
}
|
|
|
|
}
|
2020-11-13 03:14:53 +00:00
|
|
|
return (splitted.join("\n").trim());
|
2020-11-13 01:40:53 +00:00
|
|
|
}
|
2020-11-20 08:45:31 +00:00
|
|
|
|
|
|
|
public async appendForwards(puppetId: number, body: string, forwards: MessageForwardsCollection) {
|
|
|
|
let formatted = `${body}\n`;
|
|
|
|
for (const f of forwards) {
|
|
|
|
const user = await this.getRemoteUser(puppetId, Number(f.senderId));
|
2020-11-20 15:43:32 +00:00
|
|
|
formatted += `> <[${user.name}](${user.externalUrl})>\n`;
|
2020-11-20 08:45:31 +00:00
|
|
|
f.text?.split("\n").forEach((element) => {
|
|
|
|
formatted += `> ${element}\n`;
|
2020-11-20 15:43:32 +00:00
|
|
|
});
|
2020-11-20 08:45:31 +00:00
|
|
|
if (f.hasAttachments()) {
|
|
|
|
f.attachments.forEach((attachment) => {
|
|
|
|
switch (attachment.type) {
|
|
|
|
case AttachmentType.PHOTO:
|
2020-11-20 15:43:32 +00:00
|
|
|
formatted += `> 🖼️ [Photo](${attachment["largeSizeUrl"]})\n`;
|
2020-11-20 08:45:31 +00:00
|
|
|
break;
|
|
|
|
case AttachmentType.STICKER:
|
2020-11-20 15:43:32 +00:00
|
|
|
formatted += `> 🖼️ [Sticker](${attachment["imagesWithBackground"][4]["url"]})\n`;
|
2020-11-20 08:45:31 +00:00
|
|
|
break;
|
|
|
|
case AttachmentType.AUDIO_MESSAGE:
|
2020-11-20 15:43:32 +00:00
|
|
|
formatted += `> 🗣️ [Audio message](${attachment["oggUrl"]})\n`;
|
2020-11-20 08:45:31 +00:00
|
|
|
break;
|
|
|
|
case AttachmentType.DOCUMENT:
|
2020-11-20 15:43:32 +00:00
|
|
|
formatted += `> 📁 [File ${attachment["title"]}](${attachment["url"]})\n`;
|
2020-11-20 08:45:31 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2020-11-20 15:43:32 +00:00
|
|
|
});
|
2020-11-20 08:45:31 +00:00
|
|
|
}
|
|
|
|
if (f.hasForwards) {
|
|
|
|
(await this.appendForwards(puppetId, "", f.forwards)).trim().split("\n").forEach((element) => {
|
|
|
|
formatted += `> ${element}\n`;
|
2020-11-20 15:43:32 +00:00
|
|
|
});
|
2020-11-20 08:45:31 +00:00
|
|
|
}
|
|
|
|
formatted += "\n";
|
|
|
|
}
|
|
|
|
return formatted;
|
|
|
|
}
|
2020-11-02 14:48:22 +00:00
|
|
|
}
|