Fix race conditions and duplicate stars in starboard
This commit is contained in:
parent
1484f6b9a7
commit
e1e1854041
5 changed files with 63 additions and 9 deletions
|
@ -19,10 +19,22 @@ export class GuildStarboardReactions extends BaseGuildRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createStarboardReaction(messageId: string, reactorId: string) {
|
async createStarboardReaction(messageId: string, reactorId: string) {
|
||||||
|
const existingReaction = await this.allStarboardReactions.findOne({
|
||||||
|
where: {
|
||||||
|
guild_id: this.guildId,
|
||||||
|
message_id: messageId,
|
||||||
|
reactor_id: reactorId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingReaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.allStarboardReactions.insert({
|
await this.allStarboardReactions.insert({
|
||||||
|
guild_id: this.guildId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
reactor_id: reactorId,
|
reactor_id: reactorId,
|
||||||
guild_id: this.guildId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { MigrationInterface, QueryRunner, TableIndex, TableUnique } from "typeorm";
|
||||||
|
|
||||||
|
export class FixStarboardReactionsIndices1608692857722 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
// Remove previously-added duplicate stars
|
||||||
|
await queryRunner.query(`
|
||||||
|
DELETE r1.* FROM starboard_reactions AS r1
|
||||||
|
INNER JOIN starboard_reactions AS r2
|
||||||
|
ON r2.guild_id = r1.guild_id AND r2.message_id = r1.message_id AND r2.reactor_id = r1.reactor_id AND r2.id < r1.id
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.dropIndex("starboard_reactions", "IDX_dd871a4ef459dd294aa368e736");
|
||||||
|
await queryRunner.createIndex(
|
||||||
|
"starboard_reactions",
|
||||||
|
new TableIndex({
|
||||||
|
isUnique: true,
|
||||||
|
columnNames: ["guild_id", "message_id", "reactor_id"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||||
|
await queryRunner.dropIndex("starboard_reactions", "IDX_d08ee47552c92ec8afd1a5bd1b");
|
||||||
|
await queryRunner.createIndex("starboard_reactions", new TableIndex({ columnNames: ["reactor_id", "message_id"] }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,8 @@ export const StarboardReactionAddEvt = starboardEvt({
|
||||||
categoryId: (msg.channel as TextChannel).parentID,
|
categoryId: (msg.channel as TextChannel).parentID,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const boardLock = await pluginData.locks.acquire(`starboards`);
|
||||||
|
|
||||||
const applicableStarboards = Object.values(config.boards)
|
const applicableStarboards = Object.values(config.boards)
|
||||||
.filter(board => board.enabled)
|
.filter(board => board.enabled)
|
||||||
// Can't star messages in the starboard channel itself
|
// Can't star messages in the starboard channel itself
|
||||||
|
@ -59,8 +61,6 @@ export const StarboardReactionAddEvt = starboardEvt({
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const starboard of applicableStarboards) {
|
for (const starboard of applicableStarboards) {
|
||||||
const boardLock = await pluginData.locks.acquire(`starboards-channel-${starboard.channel_id}`);
|
|
||||||
|
|
||||||
// Save reaction into the database
|
// Save reaction into the database
|
||||||
await pluginData.state.starboardReactions.createStarboardReaction(msg.id, userId).catch(noop);
|
await pluginData.state.starboardReactions.createStarboardReaction(msg.id, userId).catch(noop);
|
||||||
|
|
||||||
|
@ -92,8 +92,8 @@ export const StarboardReactionAddEvt = starboardEvt({
|
||||||
// Otherwise, if the star count exceeds the required star count, save the message to the starboard
|
// Otherwise, if the star count exceeds the required star count, save the message to the starboard
|
||||||
await saveMessageToStarboard(pluginData, msg, starboard);
|
await saveMessageToStarboard(pluginData, msg, starboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
boardLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardLock.unlock();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,9 @@ export const StarboardReactionRemoveEvt = starboardEvt({
|
||||||
event: "messageReactionRemove",
|
event: "messageReactionRemove",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
|
const boardLock = await meta.pluginData.locks.acquire(`starboards`);
|
||||||
await meta.pluginData.state.starboardReactions.deleteStarboardReaction(meta.args.message.id, meta.args.member.id);
|
await meta.pluginData.state.starboardReactions.deleteStarboardReaction(meta.args.message.id, meta.args.member.id);
|
||||||
|
boardLock.unlock();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,6 +14,8 @@ export const StarboardReactionRemoveAllEvt = starboardEvt({
|
||||||
event: "messageReactionRemoveAll",
|
event: "messageReactionRemoveAll",
|
||||||
|
|
||||||
async listener(meta) {
|
async listener(meta) {
|
||||||
|
const boardLock = await meta.pluginData.locks.acquire(`starboards`);
|
||||||
await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id);
|
await meta.pluginData.state.starboardReactions.deleteAllStarboardReactionsForMessageId(meta.args.message.id);
|
||||||
|
boardLock.unlock();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,10 @@ import { Client, GuildTextableChannel, Message } from "eris";
|
||||||
import { noop } from "../../../utils";
|
import { noop } from "../../../utils";
|
||||||
import { createStarboardPseudoFooterForMessage } from "./createStarboardPseudoFooterForMessage";
|
import { createStarboardPseudoFooterForMessage } from "./createStarboardPseudoFooterForMessage";
|
||||||
import { TStarboardOpts } from "../types";
|
import { TStarboardOpts } from "../types";
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
|
const DEBOUNCE_DELAY = 1000;
|
||||||
|
const debouncedUpdates: Record<string, Timeout> = {};
|
||||||
|
|
||||||
export async function updateStarboardMessageStarCount(
|
export async function updateStarboardMessageStarCount(
|
||||||
starboard: TStarboardOpts,
|
starboard: TStarboardOpts,
|
||||||
|
@ -10,8 +14,16 @@ export async function updateStarboardMessageStarCount(
|
||||||
starEmoji: string,
|
starEmoji: string,
|
||||||
starCount: number,
|
starCount: number,
|
||||||
) {
|
) {
|
||||||
const embed = starboardMessage.embeds[0]!;
|
const key = `${originalMessage.id}-${starboardMessage.id}`;
|
||||||
embed.fields!.shift(); // Remove pseudo footer
|
if (debouncedUpdates[key]) {
|
||||||
embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, originalMessage, starEmoji, starCount)); // Create new pseudo footer
|
clearTimeout(debouncedUpdates[key]);
|
||||||
await starboardMessage.edit({ embed });
|
}
|
||||||
|
|
||||||
|
debouncedUpdates[key] = setTimeout(() => {
|
||||||
|
delete debouncedUpdates[key];
|
||||||
|
const embed = starboardMessage.embeds[0]!;
|
||||||
|
embed.fields!.shift(); // Remove pseudo footer
|
||||||
|
embed.fields!.push(createStarboardPseudoFooterForMessage(starboard, originalMessage, starEmoji, starCount)); // Create new pseudo footer
|
||||||
|
starboardMessage.edit({ embed });
|
||||||
|
}, DEBOUNCE_DELAY);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue