Skip to content

Commit

Permalink
support reaction redaction from Matrix -> Discord
Browse files Browse the repository at this point in the history
  • Loading branch information
mangofeet committed Feb 15, 2023
1 parent 9472a10 commit ded5ef1
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 7 deletions.
2 changes: 1 addition & 1 deletion config/config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ bridge:
disableRoomTopicNotifications: false
# Enable reactions from Matrix to Discord. This will only respond to
# the first reaction of each emoji and is intended for cases where
# the bridge is used by a single matrix used
# the bridge is used by a single matrix user
enableMatrixReactions: false
# Auto-determine the language of code blocks (this can be CPU-intensive)
determineCodeLanguage: false
Expand Down
63 changes: 58 additions & 5 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DiscordClientFactory } from "./clientfactory";
import { DiscordStore } from "./store";
import { DbEmoji } from "./db/dbdataemoji";
import { DbEvent } from "./db/dbdataevent";
import { DbReaction } from "./db/dbdatareaction";
import { DiscordMessageProcessor } from "./discordmessageprocessor";
import { IDiscordMessageParserResult } from "@mx-puppet/matrix-discord-parser";
import { MatrixEventProcessor, MatrixEventProcessorOpts, IMatrixEventProcessorResult } from "./matrixeventprocessor";
Expand Down Expand Up @@ -656,12 +657,20 @@ export class DiscordBot {
log.info(`Got redact request for ${event.redacts}`);
log.verbose(`Event:`, event);

const storeEvent = await this.store.Get(DbEvent, {matrix_id: `${event.redacts};${event.room_id}`});
const storeEvent = await this.store.Get(DbEvent, { matrix_id: `${event.redacts};${event.room_id}` });
const storeReaction = await this.store.Get(DbReaction, { matrix_id: `${event.redacts};${event.room_id}` });

if (!storeEvent || !storeEvent.Result) {
log.warn(`Could not redact because the event was not in the store.`);
return;
if (storeEvent && storeEvent.Result) {
return this.ProcessMatrixRedactEvent(event, storeEvent);
} else if (storeReaction && storeReaction.Result) {
return this.ProcessMatrixRedactReaction(event, storeReaction);
}

log.warn(`Could not redact because the event was not in the store.`);
return;
}

public async ProcessMatrixRedactEvent(event: IMatrixEvent, storeEvent: DbEvent) {
log.info(`Redact event matched ${storeEvent.ResultCount} entries`);
while (storeEvent.Next()) {
log.info(`Deleting discord msg ${storeEvent.DiscordId}`);
Expand All @@ -680,6 +689,31 @@ export class DiscordBot {
}
}

public async ProcessMatrixRedactReaction(event: IMatrixEvent, storeReaction: DbReaction) {
log.info(`Redact reaction matched ${storeReaction.ResultCount} entries`);
while (storeReaction.Next()) {
log.info(`Deleting discord reaction ${storeReaction.DiscordId}`);
const result = await this.LookupRoom(storeReaction.GuildId, storeReaction.ChannelId, event.sender);
const chan = result.channel;

const msg = await chan.messages.fetch(storeReaction.DiscordId);
try {
this.channelLock.set(msg.channel.id);

const reaction = msg.reactions.resolve(storeReaction.Emoji)
if (!reaction) {
log.warn(`Could not find reaction for emoji ${storeReaction.Emoji}`)
continue
}
await reaction.users.remove()
this.channelLock.release(msg.channel.id);
log.info(`Deleted reaction`);
} catch (ex) {
log.warn(`Failed to delete message`, ex);
}
}
}

public async ProcessMatrixReaction(event: IMatrixEvent) {
if (!this.config.bridge.enableMatrixReactions) {
return;
Expand All @@ -692,7 +726,13 @@ export class DiscordBot {

const relatesTo = event.content['m.relates_to'];

const storeEvent = await this.store.Get(DbEvent, { matrix_id: `${relatesTo.event_id};${event.room_id}` });
const matrixID = `${relatesTo.event_id};${event.room_id}`
const storeEvent = await this.store.Get(DbEvent, { matrix_id: matrixID });
const storeReaction = await this.store.Get(DbReaction, { matrix_id: matrixID, emoji: relatesTo.key });

if (storeReaction && storeReaction.Result) {
log.verbose(`ignoring duplicate reaction`)
}

if (!storeEvent || !storeEvent.Result) {
log.warn(`Could not react because the event was not in the store.`);
Expand All @@ -713,6 +753,19 @@ export class DiscordBot {
} catch (ex) {
log.warn(`Failed to react to message`, ex);
}

try {
const dbReaction = new DbReaction()
dbReaction.MatrixId = `${event.event_id};${event.room_id}`;
dbReaction.DiscordId = storeEvent.DiscordId;
dbReaction.ChannelId = storeEvent.ChannelId;
dbReaction.GuildId = storeEvent.GuildId;
dbReaction.Emoji = relatesTo.key;
await this.store.Insert(dbReaction);
} catch (ex) {
log.warn(`Failed to store reaction event`)
log.verbose(ex)
}
}
}

Expand Down
159 changes: 159 additions & 0 deletions src/db/dbdatareaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
Copyright 2017, 2018 matrix-appservice-discord
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
http://www.apache.org/licenses/LICENSE-2.0
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.
*/

import { DiscordStore } from "../store";
import { ISqlCommandParameters } from "./connector";
import { IDbDataMany } from "./dbdatainterface";

export class DbReaction implements IDbDataMany {
public MatrixId: string;
public DiscordId: string;
public GuildId: string;
public ChannelId: string;
public Result: boolean;
public Emoji: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private rows: any[];

get ResultCount(): number {
return this.rows.length;
}

public async RunQuery(store: DiscordStore, params: ISqlCommandParameters): Promise<void> {
this.rows = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let rowsM: any[] | null = null;
if (params.matrix_id && params.emoji) {
rowsM = await store.db.All(`
SELECT *
FROM reaction_store
WHERE matrix_id = $id AND emoji = $emoji`, {
id: params.matrix_id,
emoji: params.emoji,
});
} else if (params.matrix_id) {
rowsM = await store.db.All(`
SELECT *
FROM reaction_store
WHERE matrix_id = $id`, {
id: params.matrix_id
});
} else if (params.discord_id) {
rowsM = await store.db.All(`
SELECT *
FROM reaction_store
WHERE discord_id = $id`, {
id: params.discord_id,
});
} else {
throw new Error("Unknown/incorrect id given as a param");
}

for (const rowM of rowsM) {
const row = {
/* eslint-disable @typescript-eslint/naming-convention */
discord_id: rowM.discord_id,
matrix_id: rowM.matrix_id,
emoji: rowM.emoji,
/* eslint-enable @typescript-eslint/naming-convention */
};
for (const rowD of await store.db.All(`
SELECT *
FROM discord_msg_store
WHERE msg_id = $id`, {
id: rowM.discord_id,
})) {
this.rows.push({
/* eslint-disable @typescript-eslint/naming-convention */
...row,
guild_id: rowD.guild_id,
channel_id: rowD.channel_id,
/* eslint-enable @typescript-eslint/naming-convention */
});
}
}
this.Result = this.rows.length !== 0;
}

public Next(): boolean {
if (!this.Result || this.ResultCount === 0) {
return false;
}
const item = this.rows.shift();
this.MatrixId = item.matrix_id;
this.DiscordId = item.discord_id;
this.Emoji = item.emoji;
this.GuildId = item.guild_id;
this.ChannelId = item.channel_id;
return true;
}

public async Insert(store: DiscordStore): Promise<void> {
await store.db.Run(`
INSERT INTO reaction_store
(matrix_id,discord_id,emoji)
VALUES ($matrix_id,$discord_id,$emoji);`, {
/* eslint-disable @typescript-eslint/naming-convention */
discord_id: this.DiscordId,
matrix_id: this.MatrixId,
emoji: this.Emoji,
/* eslint-enable @typescript-eslint/naming-convention */
});
// Check if the discord item exists?
const msgExists = await store.db.Get(`
SELECT *
FROM discord_msg_store
WHERE msg_id = $id`, {
id: this.DiscordId,
}) != null;
if (msgExists) {
return;
}
return store.db.Run(`
INSERT INTO discord_msg_store
(msg_id, guild_id, channel_id)
VALUES ($msg_id, $guild_id, $channel_id);`, {
/* eslint-disable @typescript-eslint/naming-convention */
channel_id: this.ChannelId,
guild_id: this.GuildId,
msg_id: this.DiscordId,
/* eslint-enable @typescript-eslint/naming-convention */
});
}

public async Update(store: DiscordStore): Promise<void> {
throw new Error("Update is not implemented");
}

public async Delete(store: DiscordStore): Promise<void> {
await store.db.Run(`
DELETE FROM reaction_store
WHERE matrix_id = $matrix_id
AND discord_id = $discord_id;`, {
/* eslint-disable @typescript-eslint/naming-convention */
discord_id: this.DiscordId,
matrix_id: this.MatrixId,
/* eslint-enable @typescript-eslint/naming-convention */
});
return store.db.Run(`
DELETE FROM discord_msg_store
WHERE msg_id = $discord_id;`, {
/* eslint-disable @typescript-eslint/naming-convention */
discord_id: this.DiscordId,
/* eslint-enable @typescript-eslint/naming-convention */
});
}
}
37 changes: 37 additions & 0 deletions src/db/schema/v13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2017, 2018 matrix-appservice-discord
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
http://www.apache.org/licenses/LICENSE-2.0
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.
*/

import { DiscordStore } from "../../store";
import { IDbSchema } from "./dbschema";

export class Schema implements IDbSchema {
public description = "create event_store table";
public async run(store: DiscordStore): Promise<void> {
await store.createTable(`
CREATE TABLE reaction_store (
matrix_id TEXT NOT NULL,
discord_id TEXT NOT NULL,
emoji TEXT NOT NULL,
PRIMARY KEY(matrix_id, discord_id)
);`, "event_store");
}

public async rollBack(store: DiscordStore): Promise<void> {
await store.db.Run(
`DROP TABLE IF EXISTS reaction_store;`,
);
}
}
2 changes: 1 addition & 1 deletion src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { DbUserStore } from "./db/userstore";
import { IAppserviceStorageProvider } from "matrix-bot-sdk";
import { UserActivitySet, UserActivity } from "matrix-appservice-bridge";
const log = new Log("DiscordStore");
export const CURRENT_SCHEMA = 12;
export const CURRENT_SCHEMA = 13;
/**
* Stores data for specific users and data not specific to rooms.
*/
Expand Down

0 comments on commit ded5ef1

Please sign in to comment.