Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

feat: change to markdown and map html to markdown #688

Draft
wants to merge 7 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,22 @@
"ajv": "^8.11.2",
"ajv-formats": "^2.1.1",
"axios": "^1.3.2",
"decimal.js": "^10.4.3",
"copyfiles": "^2.4.1",
"decimal.js": "^10.4.3",
"ethers": "^5.7.2",
"exponential-backoff": "^3.1.1",
"husky": "^8.0.2",
"jimp": "^0.22.4",
"js-yaml": "^4.1.0",
"libsodium-wrappers": "^0.7.11",
"lint-staged": "^13.1.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-gfm": "^3.0.0",
"micromark-extension-gfm": "^3.0.0",
"ms": "^2.1.3",
"node-html-parser": "^6.1.5",
"node-html-to-image": "^3.3.0",
"nodemon": "^2.0.19",
"parse5": "^7.1.2",
"prettier": "^2.7.1",
"probot": "^12.2.4",
"telegraf": "^4.11.2",
Expand Down
59 changes: 47 additions & 12 deletions src/handlers/payout/post.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getWalletAddress } from "../../adapters/supabase";
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers";
import { Incentives, MarkdownItem, Payload, StateReason, UserType } from "../../types";
import { Incentives, MarkdownItem, MarkdownItems, Payload, StateReason, UserType } from "../../types";
import { commentParser } from "../comment";
import Decimal from "decimal.js";
import { bountyInfo } from "../wildcard";
Expand Down Expand Up @@ -62,7 +62,7 @@ export const incentivizeComments = async () => {
return;
}

const issueComments = await getAllIssueComments(issue.number, "full");
const issueComments = await getAllIssueComments(issue.number);
logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`);
const issueCommentsByUser: Record<string, string[]> = {};
for (const issueComment of issueComments) {
Expand All @@ -73,14 +73,14 @@ export const incentivizeComments = async () => {
logger.info(`Skipping to parse the comment because it contains commands. comment: ${JSON.stringify(issueComment)}`);
continue;
}
if (!issueComment.body_html) {
logger.info(`Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(issueComment)}`);
if (!issueComment.body) {
logger.info(`Skipping to parse the comment because body is undefined. comment: ${JSON.stringify(issueComment)}`);
continue;
}
if (!issueCommentsByUser[user.login]) {
issueCommentsByUser[user.login] = [];
}
issueCommentsByUser[user.login].push(issueComment.body_html);
issueCommentsByUser[user.login].push(issueComment.body);
}
const tokenSymbol = await getTokenSymbol(paymentToken, rpc);
logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(issueCommentsByUser)}`);
Expand Down Expand Up @@ -172,7 +172,7 @@ export const incentivizeCreatorComment = async () => {
return;
}

const description = await getIssueDescription(issue.number, "html");
const description = await getIssueDescription(issue.number);
if (!description) {
logger.info(`Skipping to generate a permit url because issue description is empty. description: ${description}`);
return;
Expand Down Expand Up @@ -243,28 +243,63 @@ const generatePermitForComments = async (
* @param incentives - The basic price table for reward calculation
* @returns - The reward value
*/
const calculateRewardValue = (comments: Record<string, string[]>, incentives: Incentives): Decimal => {
const calculateRewardValue = (comments: Record<MarkdownItem, string[]>, incentives: Incentives): Decimal => {
let sum = new Decimal(0);
for (const key of Object.keys(comments)) {
const value = comments[key];
for (const item of MarkdownItems) {
const value = comments[item];

// if it's a text node calculate word count and multiply with the reward value
if (key == "#text") {
if (item === MarkdownItem.Text) {
if (!incentives.comment.totals.word) {
continue;
}
const wordReward = new Decimal(incentives.comment.totals.word);
const reward = wordReward.mul(value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0));
sum = sum.add(reward);
} else {
if (!incentives.comment.elements[key]) {
const htmlTag = MarkdownItemToHTMLTag[item];
if (!htmlTag || !incentives.comment.elements[htmlTag]) {
continue;
}
const rewardValue = new Decimal(incentives.comment.elements[key]);
const rewardValue = new Decimal(incentives.comment.elements[htmlTag]);
const reward = rewardValue.mul(value.length);
sum = sum.add(reward);
}
}

return sum;
};

const MarkdownItemToHTMLTag: Record<MarkdownItem, string> = {
[MarkdownItem.Text]: "p",
[MarkdownItem.Paragraph]: "p",
[MarkdownItem.Heading]: "h1",
[MarkdownItem.Heading1]: "h1",
[MarkdownItem.Heading2]: "h2",
[MarkdownItem.Heading3]: "h3",
[MarkdownItem.Heading4]: "h4",
[MarkdownItem.Heading5]: "h5",
[MarkdownItem.Heading6]: "h6",
[MarkdownItem.ListItem]: "li",
[MarkdownItem.List]: "ul",
[MarkdownItem.Link]: "a",
[MarkdownItem.Image]: "img",
[MarkdownItem.BlockQuote]: "blockquote",
[MarkdownItem.Code]: "code",
[MarkdownItem.Emphasis]: "em",
[MarkdownItem.Strong]: "strong",
[MarkdownItem.Delete]: "del",
[MarkdownItem.HTML]: "html",
[MarkdownItem.InlineCode]: "code",
[MarkdownItem.LinkReference]: "a",
[MarkdownItem.ImageReference]: "img",
[MarkdownItem.FootnoteReference]: "sup",
[MarkdownItem.FootnoteDefinition]: "li",
[MarkdownItem.Table]: "table",
[MarkdownItem.TableCell]: "td",
[MarkdownItem.TableRow]: "tr",
[MarkdownItem.ThematicBreak]: "hr",
[MarkdownItem.Break]: "br",
[MarkdownItem.Root]: "div",
[MarkdownItem.Definition]: "dl",
};
87 changes: 68 additions & 19 deletions src/helpers/comment.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,91 @@
import * as parse5 from "parse5";
import { MarkdownItem } from "../types";

type Node = {
nodeName: string;
tagName?: string;
value?: string;
childNodes?: Node[];
type: MarkdownItem;
value: string;
depth?: number;
children: Node[];
};

const traverse = (result: Record<string, string[]>, node: Node, itemsToExclude: string[]): Record<string, string[]> => {
if (itemsToExclude.includes(node.nodeName)) {
return result;
const traverse = (result: Record<MarkdownItem, string[]>, node: Node, itemsToExclude: string[]): Record<MarkdownItem, string[]> => {
if (!result[node.type]) {
result[node.type] = [];
}

if (!result[node.nodeName]) {
result[node.nodeName] = [];
if (node.type === MarkdownItem.Heading) {
node.type = `heading${node.depth}` as MarkdownItem;
}

result[node.nodeName].push(node.value?.trim() ?? "");
result[node.type].push(node.value?.trim() ?? "");

if (itemsToExclude.includes(node.type)) {
return result;
}

if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach((child) => traverse(result, child, itemsToExclude));
if (node.children && node.children.length > 0) {
node.children.forEach((child) => traverse(result, child, itemsToExclude));
}

return result;
};

export const parseComments = (comments: string[], itemsToExclude: string[]): Record<string, string[]> => {
const result: Record<string, string[]> = {};
export const parseComments = async (comments: string[], itemsToExclude: string[]): Promise<Record<MarkdownItem, string[]>> => {
// dynamic import of mdast
const { fromMarkdown } = await import("mdast-util-from-markdown");
const { gfmFromMarkdown } = await import("mdast-util-gfm");
const { gfm } = await import("micromark-extension-gfm");

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried importing normally at the top but I got the error:

Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/whilefoo/ubiquity/ubiquibot/node_modules/mdast-util-from-markdown/index.js from /Users/whilefoo/ubiquity/ubiquibot/lib/src/helpers/comment.js not supported.
    Instead change the require of index.js in /Users/whilefoo/ubiquity/ubiquibot/lib/src/helpers/comment.js to a dynamic import() which is available in all CommonJS modules.

After that I changed to dynamic import in the function but the error stays the same. I checked the compiled javascript and it's still require() - it seems every import is compiled to require.

@rndquu any ideas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a related issue #378

And this is a related PR #377 (which had been reverted because we got rid of mdast packages)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems #377 was supposed to fix the issue but it doesn't - instead of throwing the error at startup, it throws the error when the function runs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it throws the same error both when modules are imported at the top of the comment.ts file and in the parseComments() function, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it throws the same error both when modules are imported at the top of the comment.ts file and in the parseComments() function, right?

yes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried changing Typescript settings to transpile to ESM and added "type": "module" to package.json, but I get a different error because Probot is using require() internally

Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/whilefoo/ubiquity/ubiquibot/lib/src/index.js from /Users/whilefoo/ubiquity/ubiquibot/node_modules/probot/lib/helpers/resolve-app-function.js not supported.
Instead change the require of index.js in /Users/whilefoo/ubiquity/ubiquibot/node_modules/probot/lib/helpers/resolve-app-function.js to a dynamic import() which is available in all CommonJS modules.

node_modules/probot/lib/helpers/resolve-app-function.js is using require()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could downgrade all 3 packages (mdast-util-from-markdown, mdast-util-gfm, micromark-extension-gfm) to their "pre ESM" versions

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried downgrading and I have an issue with mdast-util-gfm because that version didn't have any types so typescript is complaining. I will see if there's anything I can do

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/ubiquity/ubiquibot/blob/incentive-based-on-comment/src/helpers/comment.ts#L23

Please take a look at the incentive-based-on-comment branch @whilefoo. It has been done with mdast library.
The blocker on that branch was the function timeout: 10s as of now.

I hope you can get reusable/helpful methods from that branch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please take a look at the incentive-based-on-comment branch @whilefoo.

That is exactly how I've done it but it's not working, you can check the whole conversation for context

const result: Record<MarkdownItem, string[]> = {
[MarkdownItem.Text]: [],
[MarkdownItem.Paragraph]: [],
[MarkdownItem.Heading]: [],
[MarkdownItem.Heading1]: [],
[MarkdownItem.Heading2]: [],
[MarkdownItem.Heading3]: [],
[MarkdownItem.Heading4]: [],
[MarkdownItem.Heading5]: [],
[MarkdownItem.Heading6]: [],
[MarkdownItem.ListItem]: [],
[MarkdownItem.List]: [],
[MarkdownItem.Link]: [],
[MarkdownItem.Image]: [],
[MarkdownItem.BlockQuote]: [],
[MarkdownItem.Code]: [],
[MarkdownItem.Emphasis]: [],
[MarkdownItem.Strong]: [],
[MarkdownItem.Delete]: [],
[MarkdownItem.HTML]: [],
[MarkdownItem.InlineCode]: [],
[MarkdownItem.LinkReference]: [],
[MarkdownItem.ImageReference]: [],
[MarkdownItem.FootnoteReference]: [],
[MarkdownItem.FootnoteDefinition]: [],
[MarkdownItem.Table]: [],
[MarkdownItem.TableCell]: [],
[MarkdownItem.TableRow]: [],
[MarkdownItem.ThematicBreak]: [],
[MarkdownItem.Break]: [],
[MarkdownItem.Root]: [],
[MarkdownItem.Definition]: [],
};

for (const comment of comments) {
const fragment = parse5.parseFragment(comment);
traverse(result, fragment as Node, itemsToExclude);
const tree = fromMarkdown(comment, {
extensions: [gfm()],
mdastExtensions: [gfmFromMarkdown()],
});
console.log(`Comment Mdast Tree: ${JSON.stringify(tree, null, 2)}`);
traverse(result, tree as Node, itemsToExclude);
}

console.log(`Comment Parsed: ${JSON.stringify(result, null, 2)}`);

// remove empty values
if (result["#text"]) {
result["#text"] = result["#text"].filter((str) => str.length > 0);
if (result[MarkdownItem.Text]) {
result[MarkdownItem.Text] = result[MarkdownItem.Text].filter((str) => str.length > 0);
}

console.log(`Comment Parsed Cleaned: ${JSON.stringify(result, null, 2)}`);

return result;
};
73 changes: 63 additions & 10 deletions src/types/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,63 @@
export const MarkdownItem = {
Paragraph: "paragraph",
List: "list",
Link: "link",
Text: "text",
Code: "code",
Image: "image",
BlockQuote: "blockquote",
} as const;
export type MarkdownItem = (typeof MarkdownItem)[keyof typeof MarkdownItem];
// All types supported by standard Markdown and GFM
export enum MarkdownItem {
// Standard Markdown
BlockQuote = "blockquote",
Break = "break",
Code = "code",
Definition = "definition",
Emphasis = "emphasis",
Heading = "heading",
Heading1 = "heading1",
Heading2 = "heading2",
Heading3 = "heading3",
Heading4 = "heading4",
Heading5 = "heading5",
Heading6 = "heading6",
HTML = "html",
Image = "image",
ImageReference = "imageReference",
InlineCode = "inlineCode",
Link = "link",
LinkReference = "linkReference",
List = "list",
ListItem = "listItem",
Paragraph = "paragraph",
Root = "root",
Strong = "strong",
Text = "text",
ThematicBreak = "thematicBreak",
// GFM
Delete = "delete",
FootnoteDefinition = "footnoteDefinition",
FootnoteReference = "footnoteReference",
Table = "table",
TableCell = "tableCell",
TableRow = "tableRow",
}
export const MarkdownItems = [
MarkdownItem.BlockQuote,
MarkdownItem.Break,
MarkdownItem.Code,
MarkdownItem.Definition,
MarkdownItem.Emphasis,
MarkdownItem.Heading,
MarkdownItem.HTML,
MarkdownItem.Image,
MarkdownItem.ImageReference,
MarkdownItem.InlineCode,
MarkdownItem.Link,
MarkdownItem.LinkReference,
MarkdownItem.List,
MarkdownItem.ListItem,
MarkdownItem.Paragraph,
MarkdownItem.Root,
MarkdownItem.Strong,
MarkdownItem.Text,
MarkdownItem.ThematicBreak,
MarkdownItem.Delete,
MarkdownItem.FootnoteDefinition,
MarkdownItem.FootnoteReference,
MarkdownItem.Table,
MarkdownItem.TableCell,
MarkdownItem.TableRow,
] as const;
Loading
Loading