Skip to content

Commit

Permalink
Update likely format error detection logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-rifkin committed Dec 11, 2024
1 parent f06088a commit 77454da
Showing 1 changed file with 100 additions and 12 deletions.
112 changes: 100 additions & 12 deletions src/components/formatting-error-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,117 @@ import { BotComponent } from "../bot-component.js";
import { build_description, parse_out } from "../utils/strings.js";
import Code from "./code.js";
import { SelfClearingMap, SelfClearingSet } from "../utils/containers.js";
import * as dismark from "dismark";

const FAILED_CODE_BLOCK_RE = /^(?:"""?|'''?)(.+?)(?:"""?|'''?|$)/s;

type failed_code_block = {
type: "failed_code_block";
content: string;
};

class FailedCodeBlockRule extends dismark.Rule {
override match(remaining: string): dismark.match_result | null {
return remaining.match(FAILED_CODE_BLOCK_RE);
}

override parse(
match: dismark.match_result,
parser: dismark.MarkdownParser,
state: dismark.parser_state,
): dismark.parse_result {
return {
node: {
type: "failed_code_block",
content: match[1],
} as dismark.markdown_node | failed_code_block as dismark.markdown_node,
fragment_end: match[0].length,
};
}
}

export default class FormattingErrorDetection extends BotComponent {
messaged = new SelfClearingSet<string>(10 * MINUTE);
// trigger message -> reply
replies = new SelfClearingMap<string, Discord.Message>(10 * MINUTE);

async has_likely_format_errors(message: Discord.Message) {
const non_code_content = parse_out(message.content);
const has_wrong_triple_tick =
non_code_content.includes(`'''`) || non_code_content.includes(`"""`) || non_code_content.includes("```");
const is_beginner_and_didnt_highlight =
message.member &&
message.content.includes("```") &&
!message.content.match(/```\w/gi) &&
!this.wheatley.has_skill_roles_other_than_beginner(message.member);
return has_wrong_triple_tick || is_beginner_and_didnt_highlight;
static readonly markdown_parser = new dismark.MarkdownParser([
new dismark.EscapeRule(),
new dismark.BoldRule(),
new dismark.UnderlineRule(),
new dismark.ItalicsRule(),
new dismark.StrikethroughRule(),
new dismark.SpoilerRule(),
new FailedCodeBlockRule(),
new dismark.CodeBlockRule(),
new dismark.InlineCodeRule(),
new dismark.BlockquoteRule(),
new dismark.SubtextRule(),
new dismark.HeaderRule(),
new dismark.LinkRule(),
new dismark.ListRule(),
new dismark.TextRule(),
]);

static has_likely_format_errors(content: string, has_skill_roles_other_than_beginner: boolean) {
// early return
if (content.search(/['"`]{2}/) === -1) {
return false;
}
const ast = FormattingErrorDetection.markdown_parser.parse(content);
const scan_for_likely_mistakes = (node: dismark.markdown_node | failed_code_block): boolean => {
switch (node.type) {
case "doc":
for (const child of node.content) {
if (scan_for_likely_mistakes(child)) {
return true;
}
}
return false;
case "list":
for (const child of node.items) {
if (scan_for_likely_mistakes(child)) {
return true;
}
}
return false;
case "italics":
case "bold":
case "underline":
case "strikethrough":
case "spoiler":
case "header":
case "subtext":
case "masked_link":
case "blockquote":
return scan_for_likely_mistakes(node.content);
case "inline_code":
return node.content.includes("\n");
case "code_block":
return has_skill_roles_other_than_beginner && node.language === null;
case "failed_code_block":
return (node.content.match(/[()[\];]/g) ?? []).length >= 5;
case "plain":
return false;
default:
throw new Error(`Unknown ast node ${(node as dismark.markdown_node).type}`);
}
};
return scan_for_likely_mistakes(ast);
}

has_likely_format_errors(message: Discord.Message) {
const has_skill_roles_other_than_beginner = message.member
? this.wheatley.has_skill_roles_other_than_beginner(message.member)
: false;
return FormattingErrorDetection.has_likely_format_errors(message.content, has_skill_roles_other_than_beginner);
}

override async on_message_create(message: Discord.Message) {
if (message.author.bot || message.channel.isDMBased() || this.messaged.has(message.author.id)) {
return;
}
if (await this.has_likely_format_errors(message)) {
if (this.has_likely_format_errors(message)) {
const reply = await message.channel.send({
content: `<@${message.author.id}>`,
embeds: [
Expand Down Expand Up @@ -71,7 +159,7 @@ export default class FormattingErrorDetection extends BotComponent {
) {
if (this.replies.has(new_message.id)) {
const message = !new_message.partial ? new_message : await new_message.fetch();
if (!(await this.has_likely_format_errors(message))) {
if (!this.has_likely_format_errors(message)) {
await this.delete_reply(new_message.id);
}
}
Expand Down

0 comments on commit 77454da

Please sign in to comment.