2019-03-04 21:44:04 +02:00
|
|
|
import { decorators as d, IPluginOptions, Plugin } from "knub";
|
2018-07-30 01:44:03 +03:00
|
|
|
import { Invite, Message } from "eris";
|
|
|
|
import escapeStringRegexp from "escape-string-regexp";
|
|
|
|
import { GuildLogs } from "../data/GuildLogs";
|
|
|
|
import { LogType } from "../data/LogType";
|
2018-11-25 17:04:26 +02:00
|
|
|
import {
|
|
|
|
deactivateMentions,
|
|
|
|
disableCodeBlocks,
|
|
|
|
getInviteCodesInString,
|
|
|
|
getUrlsInString,
|
2019-02-16 14:13:19 +02:00
|
|
|
stripObjectToScalars,
|
2018-11-25 17:04:26 +02:00
|
|
|
} from "../utils";
|
2018-08-02 22:24:54 +03:00
|
|
|
import { ZalgoRegex } from "../data/Zalgo";
|
2018-11-24 17:12:36 +02:00
|
|
|
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
|
|
|
import { SavedMessage } from "../data/entities/SavedMessage";
|
2019-03-04 21:44:04 +02:00
|
|
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
|
|
|
|
|
|
|
interface ICensorPluginConfig {
|
|
|
|
filter_zalgo: boolean;
|
|
|
|
filter_invites: boolean;
|
|
|
|
invite_guild_whitelist: string[];
|
|
|
|
invite_guild_blacklist: string[];
|
|
|
|
invite_code_whitelist: string[];
|
|
|
|
invite_code_blacklist: string[];
|
|
|
|
allow_group_dm_invites: boolean;
|
|
|
|
|
|
|
|
filter_domains: boolean;
|
|
|
|
domain_whitelist: string[];
|
|
|
|
domain_blacklist: string[];
|
|
|
|
|
|
|
|
blocked_tokens: string[];
|
|
|
|
blocked_words: string[];
|
|
|
|
blocked_regex: string[];
|
|
|
|
}
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
export class CensorPlugin extends ZeppelinPlugin<ICensorPluginConfig> {
|
2019-02-16 14:13:19 +02:00
|
|
|
public static pluginName = "censor";
|
2019-01-03 06:15:28 +02:00
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
protected serverLogs: GuildLogs;
|
2018-11-24 17:12:36 +02:00
|
|
|
protected savedMessages: GuildSavedMessages;
|
|
|
|
|
|
|
|
private onMessageCreateFn;
|
|
|
|
private onMessageUpdateFn;
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
getDefaultOptions(): IPluginOptions<ICensorPluginConfig> {
|
2018-07-30 01:44:03 +03:00
|
|
|
return {
|
|
|
|
config: {
|
2018-08-02 22:24:54 +03:00
|
|
|
filter_zalgo: false,
|
2018-07-30 01:44:03 +03:00
|
|
|
filter_invites: false,
|
|
|
|
invite_guild_whitelist: null,
|
|
|
|
invite_guild_blacklist: null,
|
|
|
|
invite_code_whitelist: null,
|
|
|
|
invite_code_blacklist: null,
|
2019-02-16 15:54:39 +02:00
|
|
|
allow_group_dm_invites: false,
|
2018-07-30 01:44:03 +03:00
|
|
|
|
|
|
|
filter_domains: false,
|
|
|
|
domain_whitelist: null,
|
|
|
|
domain_blacklist: null,
|
|
|
|
|
|
|
|
blocked_tokens: null,
|
|
|
|
blocked_words: null,
|
2019-02-16 14:13:19 +02:00
|
|
|
blocked_regex: null,
|
2018-07-30 01:44:03 +03:00
|
|
|
},
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
permissions: {},
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
overrides: [
|
|
|
|
{
|
|
|
|
level: ">=50",
|
|
|
|
config: {
|
2018-08-02 22:24:54 +03:00
|
|
|
filter_zalgo: false,
|
2018-07-30 01:44:03 +03:00
|
|
|
filter_invites: false,
|
|
|
|
filter_domains: false,
|
|
|
|
blocked_tokens: null,
|
|
|
|
blocked_words: null,
|
2019-02-16 14:13:19 +02:00
|
|
|
blocked_regex: null,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
2018-07-30 01:44:03 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
onLoad() {
|
|
|
|
this.serverLogs = new GuildLogs(this.guildId);
|
2018-11-24 17:12:36 +02:00
|
|
|
this.savedMessages = GuildSavedMessages.getInstance(this.guildId);
|
|
|
|
|
|
|
|
this.onMessageCreateFn = this.onMessageCreate.bind(this);
|
|
|
|
this.onMessageUpdateFn = this.onMessageUpdate.bind(this);
|
|
|
|
this.savedMessages.events.on("create", this.onMessageCreateFn);
|
|
|
|
this.savedMessages.events.on("update", this.onMessageUpdateFn);
|
|
|
|
}
|
|
|
|
|
|
|
|
onUnload() {
|
|
|
|
this.savedMessages.events.off("create", this.onMessageCreateFn);
|
|
|
|
this.savedMessages.events.off("update", this.onMessageUpdateFn);
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
async censorMessage(savedMessage: SavedMessage, reason: string) {
|
|
|
|
this.serverLogs.ignoreLog(LogType.MESSAGE_DELETE, savedMessage.id);
|
2018-07-30 01:44:03 +03:00
|
|
|
|
|
|
|
try {
|
2018-11-24 17:12:36 +02:00
|
|
|
await this.bot.deleteMessage(savedMessage.channel_id, savedMessage.id, "Censored");
|
2018-07-30 01:44:03 +03:00
|
|
|
} catch (e) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
const member = this.guild.members.get(savedMessage.user_id);
|
|
|
|
const channel = this.guild.channels.get(savedMessage.channel_id);
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
this.serverLogs.log(LogType.CENSOR, {
|
2018-11-24 17:12:36 +02:00
|
|
|
member: stripObjectToScalars(member, ["user"]),
|
|
|
|
channel: stripObjectToScalars(channel),
|
2018-07-30 01:44:03 +03:00
|
|
|
reason,
|
2019-02-16 14:13:19 +02:00
|
|
|
messageText: disableCodeBlocks(deactivateMentions(savedMessage.data.content)),
|
2018-07-30 01:44:03 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-16 12:31:37 +02:00
|
|
|
/**
|
|
|
|
* Applies word censor filters to the message, if any apply.
|
|
|
|
* @return {boolean} Indicates whether the message was removed
|
|
|
|
*/
|
|
|
|
async applyFiltersToMsg(savedMessage: SavedMessage): Promise<boolean> {
|
|
|
|
if (!savedMessage.data.content) return false;
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
const config = this.getConfigForMemberIdAndChannelId(savedMessage.user_id, savedMessage.channel_id);
|
|
|
|
|
2018-08-02 22:24:54 +03:00
|
|
|
// Filter zalgo
|
2019-03-04 21:44:04 +02:00
|
|
|
const filterZalgo = config.filter_zalgo;
|
2018-11-24 17:12:36 +02:00
|
|
|
if (filterZalgo) {
|
|
|
|
const result = ZalgoRegex.exec(savedMessage.data.content);
|
2018-08-02 22:24:54 +03:00
|
|
|
if (result) {
|
2018-11-24 17:12:36 +02:00
|
|
|
this.censorMessage(savedMessage, "zalgo detected");
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-08-02 22:24:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
// Filter invites
|
2019-03-04 21:44:04 +02:00
|
|
|
const filterInvites = config.filter_invites;
|
2018-11-24 17:12:36 +02:00
|
|
|
if (filterInvites) {
|
2019-03-04 21:44:04 +02:00
|
|
|
const inviteGuildWhitelist = config.invite_guild_whitelist;
|
|
|
|
const inviteGuildBlacklist = config.invite_guild_blacklist;
|
|
|
|
const inviteCodeWhitelist = config.invite_code_whitelist;
|
|
|
|
const inviteCodeBlacklist = config.invite_code_blacklist;
|
|
|
|
const allowGroupDMInvites = config.allow_group_dm_invites;
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
const inviteCodes = getInviteCodesInString(savedMessage.data.content);
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
let invites: Invite[] = await Promise.all(inviteCodes.map(code => this.bot.getInvite(code).catch(() => null)));
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2018-08-03 19:24:40 +03:00
|
|
|
invites = invites.filter(v => !!v);
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
for (const invite of invites) {
|
2019-02-16 15:54:39 +02:00
|
|
|
if (!invite.guild && !allowGroupDMInvites) {
|
|
|
|
this.censorMessage(savedMessage, `group dm invites are not allowed`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2019-02-16 15:54:39 +02:00
|
|
|
}
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
if (inviteGuildWhitelist && !inviteGuildWhitelist.includes(invite.guild.id)) {
|
|
|
|
this.censorMessage(
|
2018-11-24 17:12:36 +02:00
|
|
|
savedMessage,
|
2019-02-16 14:13:19 +02:00
|
|
|
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) not found in whitelist`,
|
2018-07-30 01:44:03 +03:00
|
|
|
);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inviteGuildBlacklist && inviteGuildBlacklist.includes(invite.guild.id)) {
|
|
|
|
this.censorMessage(
|
2018-11-24 17:12:36 +02:00
|
|
|
savedMessage,
|
2019-02-16 14:13:19 +02:00
|
|
|
`invite guild (**${invite.guild.name}** \`${invite.guild.id}\`) found in blacklist`,
|
2018-07-30 01:44:03 +03:00
|
|
|
);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inviteCodeWhitelist && !inviteCodeWhitelist.includes(invite.code)) {
|
2018-11-24 17:12:36 +02:00
|
|
|
this.censorMessage(savedMessage, `invite code (\`${invite.code}\`) not found in whitelist`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inviteCodeBlacklist && inviteCodeBlacklist.includes(invite.code)) {
|
2018-11-24 17:12:36 +02:00
|
|
|
this.censorMessage(savedMessage, `invite code (\`${invite.code}\`) found in blacklist`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter domains
|
2019-03-04 21:44:04 +02:00
|
|
|
const filterDomains = config.filter_domains;
|
2018-11-24 17:12:36 +02:00
|
|
|
if (filterDomains) {
|
2019-03-04 21:44:04 +02:00
|
|
|
const domainWhitelist = config.domain_whitelist;
|
|
|
|
const domainBlacklist = config.domain_blacklist;
|
2018-07-30 01:44:03 +03:00
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
const urls = getUrlsInString(savedMessage.data.content);
|
2018-07-30 01:44:03 +03:00
|
|
|
for (const thisUrl of urls) {
|
|
|
|
if (domainWhitelist && !domainWhitelist.includes(thisUrl.hostname)) {
|
2018-11-24 17:12:36 +02:00
|
|
|
this.censorMessage(savedMessage, `domain (\`${thisUrl.hostname}\`) not found in whitelist`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (domainBlacklist && domainBlacklist.includes(thisUrl.hostname)) {
|
2018-11-24 17:12:36 +02:00
|
|
|
this.censorMessage(savedMessage, `domain (\`${thisUrl.hostname}\`) found in blacklist`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter tokens
|
2019-03-04 21:44:04 +02:00
|
|
|
const blockedTokens = config.blocked_tokens || [];
|
2018-07-30 01:44:03 +03:00
|
|
|
for (const token of blockedTokens) {
|
2018-11-24 17:12:36 +02:00
|
|
|
if (savedMessage.data.content.toLowerCase().includes(token.toLowerCase())) {
|
|
|
|
this.censorMessage(savedMessage, `blocked token (\`${token}\`) found`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter words
|
2019-03-04 21:44:04 +02:00
|
|
|
const blockedWords = config.blocked_words || [];
|
2018-07-30 01:44:03 +03:00
|
|
|
for (const word of blockedWords) {
|
|
|
|
const regex = new RegExp(`\\b${escapeStringRegexp(word)}\\b`, "i");
|
2018-11-24 17:12:36 +02:00
|
|
|
if (regex.test(savedMessage.data.content)) {
|
|
|
|
this.censorMessage(savedMessage, `blocked word (\`${word}\`) found`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter regex
|
2019-03-04 21:44:04 +02:00
|
|
|
const blockedRegex = config.blocked_regex || [];
|
2018-07-30 01:44:03 +03:00
|
|
|
for (const regexStr of blockedRegex) {
|
2019-02-16 14:13:19 +02:00
|
|
|
const regex = new RegExp(regexStr, "i");
|
2018-11-24 17:12:36 +02:00
|
|
|
if (regex.test(savedMessage.data.content)) {
|
|
|
|
this.censorMessage(savedMessage, `blocked regex (\`${regexStr}\`) found`);
|
2019-03-16 12:31:37 +02:00
|
|
|
return true;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|
2019-03-16 12:31:37 +02:00
|
|
|
|
|
|
|
return false;
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
async onMessageCreate(savedMessage: SavedMessage) {
|
2018-12-14 09:14:13 +02:00
|
|
|
if (savedMessage.is_bot) return;
|
2019-03-16 12:31:37 +02:00
|
|
|
const lock = await this.locks.acquire(`message-${savedMessage.id}`);
|
|
|
|
|
|
|
|
const wasDeleted = await this.applyFiltersToMsg(savedMessage);
|
|
|
|
|
|
|
|
if (wasDeleted) {
|
|
|
|
lock.interrupt();
|
|
|
|
} else {
|
|
|
|
lock.unlock();
|
|
|
|
}
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
|
2018-11-24 17:12:36 +02:00
|
|
|
async onMessageUpdate(savedMessage: SavedMessage) {
|
2018-12-14 09:14:13 +02:00
|
|
|
if (savedMessage.is_bot) return;
|
2019-03-16 12:31:37 +02:00
|
|
|
const lock = await this.locks.acquire(`message-${savedMessage.id}`);
|
|
|
|
|
|
|
|
const wasDeleted = await this.applyFiltersToMsg(savedMessage);
|
|
|
|
|
|
|
|
if (wasDeleted) {
|
|
|
|
lock.interrupt();
|
|
|
|
} else {
|
|
|
|
lock.unlock();
|
|
|
|
}
|
2018-07-30 01:44:03 +03:00
|
|
|
}
|
|
|
|
}
|