From efbdfefa04761cd6cc59cf34f82d317e497a0818 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Fri, 21 Jul 2023 11:46:11 +0200 Subject: [PATCH 01/13] feat: use html instead of markdown in comment incentives --- .github/ubiquibot-config.yml | 10 + package.json | 5 +- src/bindings/config.ts | 4 +- src/configs/price.ts | 11 +- src/handlers/payout/post.ts | 28 +- src/helpers/comment.ts | 56 ++-- src/helpers/issue.ts | 24 +- src/types/config.ts | 8 +- src/types/payload.ts | 2 + src/utils/helpers.ts | 15 +- src/utils/private.ts | 10 +- yarn.lock | 522 +---------------------------------- 12 files changed, 108 insertions(+), 587 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index 655095e6f..bdefdbd47 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -32,3 +32,13 @@ auto-pay-mode: true analytics-mode: true max-concurrent-bounties: 2 promotion-comment: "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
" +incentives: + comment: + code: 5 + img: 5 + h1: 1 + li: 0.5 + a: 0.5 + blockquote: 0 + totals: + word: 0.1 diff --git a/package.json b/package.json index 8d05c1033..5309ae7b5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@probot/adapter-github-actions": "^3.1.3", "@sinclair/typebox": "^0.25.9", "@supabase/supabase-js": "^2.4.0", - "@types/mdast": "^3.0.11", "@types/ms": "^0.7.31", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", @@ -49,13 +48,11 @@ "js-yaml": "^4.1.0", "libsodium-wrappers": "^0.7.11", "lint-staged": "^13.1.0", - "mdast-util-from-markdown": "^1.3.0", - "mdast-util-gfm": "^2.0.2", - "micromark-extension-gfm": "^2.0.3", "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", diff --git a/src/bindings/config.ts b/src/bindings/config.ts index 60e0eb5dd..7785aa9e3 100644 --- a/src/bindings/config.ts +++ b/src/bindings/config.ts @@ -13,7 +13,7 @@ export const loadConfig = async (context: Context): Promise => { baseMultiplier, timeLabels, priorityLabels, - commentElementPricing, + incentives, autoPayMode, analyticsMode, bountyHunterMax, @@ -36,7 +36,7 @@ export const loadConfig = async (context: Context): Promise => { issueCreatorMultiplier, timeLabels, priorityLabels, - commentElementPricing, + incentives, }, comments: { promotionComment: promotionComment, diff --git a/src/configs/price.ts b/src/configs/price.ts index d67c3fb5a..b925928f8 100644 --- a/src/configs/price.ts +++ b/src/configs/price.ts @@ -1,5 +1,4 @@ import { PriceConfig } from "../types"; -import { MarkdownItem } from "../types/markdown"; export const DefaultPriceConfig: PriceConfig = { baseMultiplier: 1000, @@ -53,11 +52,9 @@ export const DefaultPriceConfig: PriceConfig = { weight: 5, }, ], - commentElementPricing: { - [MarkdownItem.Text]: 0.1, - [MarkdownItem.Link]: 0.5, - [MarkdownItem.List]: 0.5, - [MarkdownItem.Code]: 5, - [MarkdownItem.Image]: 5, + incentives: { + comment: { + li: 0.1, + }, }, }; diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index c4d80b408..3076c6c8a 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -1,9 +1,9 @@ import { getWalletAddress } from "../../adapters/supabase"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers"; -import { MarkdownItem, Payload, UserType, CommentElementPricing } from "../../types"; +import { Incentives, Payload, UserType } from "../../types"; -const ItemsToExclude: string[] = [MarkdownItem.BlockQuote]; +const ItemsToExclude: string[] = ["blockquote"]; /** * Incentivize the contributors based on their contribution. * The default formula has been defined in https://github.com/ubiquity/ubiquibot/issues/272 @@ -12,7 +12,7 @@ export const incentivizeComments = async () => { const logger = getLogger(); const { mode: { incentiveMode }, - price: { baseMultiplier, commentElementPricing }, + price: { baseMultiplier, incentives }, payout: { paymentToken, rpc }, } = getBotConfig(); if (!incentiveMode) { @@ -34,13 +34,13 @@ export const incentivizeComments = async () => { return; } - const issueComments = await getAllIssueComments(issue.number); + const issueComments = await getAllIssueComments(issue.number, "html"); logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`); const issueCommentsByUser: Record = {}; for (const issueComment of issueComments) { const user = issueComment.user; if (user.type == UserType.Bot || user.login == assignee) continue; - issueCommentsByUser[user.login].push(issueComment.body); + issueCommentsByUser[user.login].push(issueComment.body_html); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(issueCommentsByUser)}`); @@ -54,7 +54,7 @@ export const incentivizeComments = async () => { for (const user of Object.keys(issueCommentsByUser)) { const comments = issueCommentsByUser[user]; const commentsByNode = await parseComments(comments, ItemsToExclude); - const rewardValue = calculateRewardValue(commentsByNode, commentElementPricing); + const rewardValue = calculateRewardValue(commentsByNode, incentives); logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = ((rewardValue * baseMultiplier) / 1000).toString(); @@ -77,7 +77,7 @@ export const incentivizeCreatorComment = async () => { const logger = getLogger(); const { mode: { incentiveMode }, - price: { commentElementPricing, issueCreatorMultiplier }, + price: { incentives, issueCreatorMultiplier }, payout: { paymentToken, rpc }, } = getBotConfig(); if (!incentiveMode) { @@ -99,7 +99,7 @@ export const incentivizeCreatorComment = async () => { return; } - const description = await getIssueDescription(issue.number); + const description = await getIssueDescription(issue.number, "html"); logger.info(`Getting the issue description done. description: ${description}`); const creator = issue.user; if (creator?.type === UserType.Bot || creator?.login === issue?.assignee) { @@ -108,7 +108,7 @@ export const incentivizeCreatorComment = async () => { } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); - const result = await generatePermitForComments(creator?.login, [description], issueCreatorMultiplier, commentElementPricing, tokenSymbol, issue.node_id); + const result = await generatePermitForComments(creator?.login, [description], issueCreatorMultiplier, incentives, tokenSymbol, issue.node_id); if (result.payoutUrl) { logger.info(`Permit url generated for creator. reward: ${result.payoutUrl}`); @@ -123,13 +123,13 @@ const generatePermitForComments = async ( user: string, comments: string[], multiplier: number, - commentElementPricing: Record, + incentives: Incentives, tokenSymbol: string, node_id: string ): Promise<{ comment: string; payoutUrl?: string; amountInETH?: string }> => { const logger = getLogger(); const commentsByNode = await parseComments(comments, ItemsToExclude); - const rewardValue = calculateRewardValue(commentsByNode, commentElementPricing); + const rewardValue = calculateRewardValue(commentsByNode, incentives); logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = ((rewardValue * multiplier) / 1000).toString(); @@ -149,12 +149,12 @@ const generatePermitForComments = async ( * @param commentElementPricing - The basic price table for reward calculation * @returns - The reward value */ -const calculateRewardValue = (comments: Record, commentElementPricing: CommentElementPricing): number => { +const calculateRewardValue = (comments: Record, incentives: Incentives): number => { let sum = 0; for (const key of Object.keys(comments)) { - const rewardValue = commentElementPricing[key]; + const rewardValue = incentives.comment[key]; const value = comments[key]; - if (key == MarkdownItem.Text || key == MarkdownItem.Paragraph) { + if (key == "#text") { sum += value.length * rewardValue; } else { sum += rewardValue; diff --git a/src/helpers/comment.ts b/src/helpers/comment.ts index 825958cc9..77340f058 100644 --- a/src/helpers/comment.ts +++ b/src/helpers/comment.ts @@ -1,44 +1,36 @@ -type MdastNode = { - type: string; - value: string; - children: MdastNode[]; +import parse5 from "parse5"; + +type Node = { + nodeName: string; + tagName?: string; + value?: string; + childNodes?: Node[]; }; -const cachedResult: Record = {}; -const traverse = (node: MdastNode, itemsToExclude: string[]): Record => { - if (!cachedResult[node.type]) { - cachedResult[node.type] = []; + +const traverse = (result: Record, node: Node, itemsToExclude: string[]): Record => { + if (itemsToExclude.includes(node.nodeName)) { + return result; } - if (!itemsToExclude.includes(node.type)) { - // skip pushing if the node type has been excluded - cachedResult[node.type].push(node.value); - } else if (node.children.length > 0) { - node.children.forEach((child) => traverse(child, itemsToExclude)); + if (!result[node.nodeName]) { + result[node.nodeName] = []; } - return cachedResult; -}; + result[node.nodeName].push(node.value ?? ""); + + if (node.childNodes && node.childNodes.length > 0) { + node.childNodes.forEach((child) => traverse(result, child, itemsToExclude)); + } -export const parseComments = async (comments: string[], itemsToExclude: string[]): Promise> => { - const { fromMarkdown } = await import("mdast-util-from-markdown"); - const { gfmFromMarkdown } = await import("mdast-util-gfm"); - const { gfm } = await import("micromark-extension-gfm"); + return result; +}; +export const parseComments = (comments: string[], itemsToExclude: string[]): Record => { const result: Record = {}; + for (const comment of comments) { - const tree = fromMarkdown(comment, { - extensions: [gfm()], - mdastExtensions: [gfmFromMarkdown()], - }); - - const parsedContent = traverse(tree as MdastNode, itemsToExclude); - for (const key of Object.keys(parsedContent)) { - if (Object.keys(result).includes(key)) { - result[key].push(...parsedContent[key]); - } else { - result[key] = parsedContent[key]; - } - } + const fragment = parse5.parseFragment(comment); + traverse(result, fragment as Node, itemsToExclude); } return result; diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index c815faab9..6fdbb4a4c 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -105,7 +105,7 @@ export const getCommentsOfIssue = async (issue_number: number): Promise => { +export const getIssueDescription = async (issue_number: number, format: "raw" | "html" | "text" = "raw"): Promise => { const context = getBotContext(); const logger = getLogger(); const payload = context.payload as Payload; @@ -116,17 +116,32 @@ export const getIssueDescription = async (issue_number: number): Promise owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: issue_number, + mediaType: { + format, + }, }); await checkRateLimitGit(response?.headers); - if (response.data.body) result = response.data.body; + if (response.data.body) { + switch (format) { + case "raw": + result = response.data.body; + break; + case "html": + result = response.data.body_html ?? ""; + break; + case "text": + result = response.data.body_text ?? ""; + break; + } + } } catch (e: unknown) { logger.debug(`Getting issue description failed!, reason: ${e}`); } return result; }; -export const getAllIssueComments = async (issue_number: number): Promise => { +export const getAllIssueComments = async (issue_number: number, format: "raw" | "html" | "text" | "full" = "raw"): Promise => { const context = getBotContext(); const payload = context.payload as Payload; @@ -141,6 +156,9 @@ export const getAllIssueComments = async (issue_number: number): Promise; -const CommentElementPricingSchema = Type.Record(Type.String(), Type.Number()); -export type CommentElementPricing = Static; +const IncentivesSchema = Type.Object({ + comment: Type.Record(Type.String(), Type.Number()), +}); +export type Incentives = Static; export const PriceConfigSchema = Type.Object({ baseMultiplier: Type.Number(), issueCreatorMultiplier: Type.Number(), timeLabels: Type.Array(LabelItemSchema), priorityLabels: Type.Array(LabelItemSchema), - commentElementPricing: CommentElementPricingSchema, + incentives: IncentivesSchema, }); export type PriceConfig = Static; diff --git a/src/types/payload.ts b/src/types/payload.ts index 1fadd187b..bf3f34df8 100644 --- a/src/types/payload.ts +++ b/src/types/payload.ts @@ -234,6 +234,8 @@ export const CommentSchema = Type.Object({ updated_at: Type.String({ format: "date-time" }), author_association: Type.String(), body: Type.String(), + body_html: Type.String(), + body_text: Type.String(), }); export type Comment = Static; diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index f0518ad1a..d30d8b2b2 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,5 @@ import { DEFAULT_NETWORK_ID, DefaultPriceConfig } from "../configs"; -import { CommentElementPricing } from "../types"; -import { WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; +import { Incentives, WideLabel, WideOrgConfig, WideRepoConfig } from "./private"; export const getNetworkId = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): number => { if (parsedRepo && parsedRepo["evm-network-id"] && !Number.isNaN(Number(parsedRepo["evm-network-id"]))) { @@ -52,13 +51,13 @@ export const getPriorityLabels = (parsedRepo: WideRepoConfig | undefined, parsed } }; -export const getCommentItemPrice = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): CommentElementPricing => { - if (parsedRepo && parsedRepo["comment-element-pricing"]) { - return parsedRepo["comment-element-pricing"]; - } else if (parsedOrg && parsedOrg["comment-element-pricing"]) { - return parsedOrg["comment-element-pricing"]; +export const getIncentives = (parsedRepo: WideRepoConfig | undefined, parsedOrg: WideOrgConfig | undefined): Incentives => { + if (parsedRepo && parsedRepo["incentives"]) { + return parsedRepo["incentives"]; + } else if (parsedOrg && parsedOrg["incentives"]) { + return parsedOrg["incentives"]; } else { - return DefaultPriceConfig["commentElementPricing"]; + return DefaultPriceConfig["incentives"]; } }; diff --git a/src/utils/private.ts b/src/utils/private.ts index ff3952cd4..e2c4a3342 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -12,8 +12,8 @@ import { getNetworkId, getPriorityLabels, getTimeLabels, - getCommentItemPrice, getPromotionComment, + getIncentives, } from "./helpers"; const CONFIG_REPO = "ubiquibot-config"; @@ -47,6 +47,10 @@ export interface WideLabel { value?: number | undefined; } +export interface Incentives { + comment: Record; +} + export interface WideConfig { "evm-network-id"?: number; "base-multiplier"?: number; @@ -58,7 +62,7 @@ export interface WideConfig { "analytics-mode"?: boolean; "incentive-mode"?: boolean; "max-concurrent-bounties"?: number; - "comment-element-pricing"?: Record; + incentives?: Incentives; } export type WideRepoConfig = WideConfig; @@ -138,7 +142,7 @@ export const getWideConfig = async (context: Context) => { analyticsMode: getAnalyticsMode(parsedRepo, parsedOrg), bountyHunterMax: getBountyHunterMax(parsedRepo, parsedOrg), incentiveMode: getIncentiveMode(parsedRepo, parsedOrg), - commentElementPricing: getCommentItemPrice(parsedRepo, parsedOrg), + incentives: getIncentives(parsedRepo, parsedOrg), promotionComment: getPromotionComment(parsedRepo, parsedOrg), }; diff --git a/yarn.lock b/yarn.lock index 671f8c351..dd970fcf2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2100,13 +2100,6 @@ dependencies: "@types/node" "*" -"@types/debug@^4.0.0": - version "4.1.8" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" - integrity sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ== - dependencies: - "@types/ms" "*" - "@types/eslint@^8.40.2": version "8.40.2" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.2.tgz#2833bc112d809677864a4b0e7d1de4f04d7dac2d" @@ -2213,13 +2206,6 @@ resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#a6ebde70d3b4af960fd802af8d0e3c7cfe281eb2" integrity sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA== -"@types/mdast@^3.0.0", "@types/mdast@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0" - integrity sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw== - dependencies: - "@types/unist" "*" - "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -2235,7 +2221,7 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/ms@*", "@types/ms@^0.7.31": +"@types/ms@^0.7.31": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== @@ -2345,11 +2331,6 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/unist@*", "@types/unist@^2.0.0": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" - integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== - "@types/websocket@^1.0.3": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" @@ -3152,11 +3133,6 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -ccount@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" - integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== - chalk@2.4.2, chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3184,11 +3160,6 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -character-entities@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" - integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== - chokidar@^3.5.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -3584,7 +3555,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3616,13 +3587,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decode-named-character-reference@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" - integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== - dependencies: - character-entities "^2.0.0" - decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -3680,11 +3644,6 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -dequal@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -3723,11 +3682,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -3858,7 +3812,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -entities@^4.2.0: +entities@^4.2.0, entities@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -3949,11 +3903,6 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -5974,11 +5923,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -kleur@^4.0.3: - version "4.1.5" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" - integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== - leven@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" @@ -6187,11 +6131,6 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -longest-streak@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" - integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== - lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -6262,126 +6201,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-table@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd" - integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== - -mdast-util-find-and-replace@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz#cc2b774f7f3630da4bd592f61966fecade8b99b1" - integrity sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw== - dependencies: - "@types/mdast" "^3.0.0" - escape-string-regexp "^5.0.0" - unist-util-is "^5.0.0" - unist-util-visit-parents "^5.0.0" - -mdast-util-from-markdown@^1.0.0, mdast-util-from-markdown@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0" - integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - decode-named-character-reference "^1.0.0" - mdast-util-to-string "^3.1.0" - micromark "^3.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-decode-string "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - unist-util-stringify-position "^3.0.0" - uvu "^0.5.0" - -mdast-util-gfm-autolink-literal@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz#67a13abe813d7eba350453a5333ae1bc0ec05c06" - integrity sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA== - dependencies: - "@types/mdast" "^3.0.0" - ccount "^2.0.0" - mdast-util-find-and-replace "^2.0.0" - micromark-util-character "^1.0.0" - -mdast-util-gfm-footnote@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz#ce5e49b639c44de68d5bf5399877a14d5020424e" - integrity sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-to-markdown "^1.3.0" - micromark-util-normalize-identifier "^1.0.0" - -mdast-util-gfm-strikethrough@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz#5470eb105b483f7746b8805b9b989342085795b7" - integrity sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-to-markdown "^1.3.0" - -mdast-util-gfm-table@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz#3552153a146379f0f9c4c1101b071d70bbed1a46" - integrity sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg== - dependencies: - "@types/mdast" "^3.0.0" - markdown-table "^3.0.0" - mdast-util-from-markdown "^1.0.0" - mdast-util-to-markdown "^1.3.0" - -mdast-util-gfm-task-list-item@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz#b280fcf3b7be6fd0cc012bbe67a59831eb34097b" - integrity sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-to-markdown "^1.3.0" - -mdast-util-gfm@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz#e92f4d8717d74bdba6de57ed21cc8b9552e2d0b6" - integrity sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg== - dependencies: - mdast-util-from-markdown "^1.0.0" - mdast-util-gfm-autolink-literal "^1.0.0" - mdast-util-gfm-footnote "^1.0.0" - mdast-util-gfm-strikethrough "^1.0.0" - mdast-util-gfm-table "^1.0.0" - mdast-util-gfm-task-list-item "^1.0.0" - mdast-util-to-markdown "^1.0.0" - -mdast-util-phrasing@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz#c7c21d0d435d7fb90956038f02e8702781f95463" - integrity sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg== - dependencies: - "@types/mdast" "^3.0.0" - unist-util-is "^5.0.0" - -mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6" - integrity sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - longest-streak "^3.0.0" - mdast-util-phrasing "^3.0.0" - mdast-util-to-string "^3.0.0" - micromark-util-decode-string "^1.0.0" - unist-util-visit "^4.0.0" - zwitch "^2.0.0" - -mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz#66f7bb6324756741c5f47a53557f0cbf16b6f789" - integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg== - dependencies: - "@types/mdast" "^3.0.0" - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -6424,279 +6243,6 @@ methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8" - integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw== - dependencies: - decode-named-character-reference "^1.0.0" - micromark-factory-destination "^1.0.0" - micromark-factory-label "^1.0.0" - micromark-factory-space "^1.0.0" - micromark-factory-title "^1.0.0" - micromark-factory-whitespace "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-chunked "^1.0.0" - micromark-util-classify-character "^1.0.0" - micromark-util-html-tag-name "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-resolve-all "^1.0.0" - micromark-util-subtokenize "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.1" - uvu "^0.5.0" - -micromark-extension-gfm-autolink-literal@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz#5853f0e579bbd8ef9e39a7c0f0f27c5a063a66e7" - integrity sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-sanitize-uri "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-extension-gfm-footnote@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz#05e13034d68f95ca53c99679040bc88a6f92fe2e" - integrity sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q== - dependencies: - micromark-core-commonmark "^1.0.0" - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-sanitize-uri "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-extension-gfm-strikethrough@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz#c8212c9a616fa3bf47cb5c711da77f4fdc2f80af" - integrity sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw== - dependencies: - micromark-util-chunked "^1.0.0" - micromark-util-classify-character "^1.0.0" - micromark-util-resolve-all "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-extension-gfm-table@^1.0.0: - version "1.0.7" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz#dcb46074b0c6254c3fc9cc1f6f5002c162968008" - integrity sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw== - dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-extension-gfm-tagfilter@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz#aa7c4dd92dabbcb80f313ebaaa8eb3dac05f13a7" - integrity sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g== - dependencies: - micromark-util-types "^1.0.0" - -micromark-extension-gfm-task-list-item@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz#b52ce498dc4c69b6a9975abafc18f275b9dde9f4" - integrity sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ== - dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-extension-gfm@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz#e517e8579949a5024a493e49204e884aa74f5acf" - integrity sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ== - dependencies: - micromark-extension-gfm-autolink-literal "^1.0.0" - micromark-extension-gfm-footnote "^1.0.0" - micromark-extension-gfm-strikethrough "^1.0.0" - micromark-extension-gfm-table "^1.0.0" - micromark-extension-gfm-tagfilter "^1.0.0" - micromark-extension-gfm-task-list-item "^1.0.0" - micromark-util-combine-extensions "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-factory-destination@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz#eb815957d83e6d44479b3df640f010edad667b9f" - integrity sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-factory-label@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz#cc95d5478269085cfa2a7282b3de26eb2e2dec68" - integrity sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-factory-space@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf" - integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-factory-title@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz#dd0fe951d7a0ac71bdc5ee13e5d1465ad7f50ea1" - integrity sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ== - dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-factory-whitespace@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz#798fb7489f4c8abafa7ca77eed6b5745853c9705" - integrity sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ== - dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-util-character@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc" - integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg== - dependencies: - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-util-chunked@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz#37a24d33333c8c69a74ba12a14651fd9ea8a368b" - integrity sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ== - dependencies: - micromark-util-symbol "^1.0.0" - -micromark-util-classify-character@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz#6a7f8c8838e8a120c8e3c4f2ae97a2bff9190e9d" - integrity sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-util-combine-extensions@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz#192e2b3d6567660a85f735e54d8ea6e3952dbe84" - integrity sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA== - dependencies: - micromark-util-chunked "^1.0.0" - micromark-util-types "^1.0.0" - -micromark-util-decode-numeric-character-reference@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz#b1e6e17009b1f20bc652a521309c5f22c85eb1c6" - integrity sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw== - dependencies: - micromark-util-symbol "^1.0.0" - -micromark-util-decode-string@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz#dc12b078cba7a3ff690d0203f95b5d5537f2809c" - integrity sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ== - dependencies: - decode-named-character-reference "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-symbol "^1.0.0" - -micromark-util-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz#92e4f565fd4ccb19e0dcae1afab9a173bbeb19a5" - integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw== - -micromark-util-html-tag-name@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz#48fd7a25826f29d2f71479d3b4e83e94829b3588" - integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q== - -micromark-util-normalize-identifier@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz#7a73f824eb9f10d442b4d7f120fecb9b38ebf8b7" - integrity sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q== - dependencies: - micromark-util-symbol "^1.0.0" - -micromark-util-resolve-all@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz#4652a591ee8c8fa06714c9b54cd6c8e693671188" - integrity sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA== - dependencies: - micromark-util-types "^1.0.0" - -micromark-util-sanitize-uri@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz#613f738e4400c6eedbc53590c67b197e30d7f90d" - integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A== - dependencies: - micromark-util-character "^1.0.0" - micromark-util-encode "^1.0.0" - micromark-util-symbol "^1.0.0" - -micromark-util-subtokenize@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz#941c74f93a93eaf687b9054aeb94642b0e92edb1" - integrity sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A== - dependencies: - micromark-util-chunked "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - -micromark-util-symbol@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142" - integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== - -micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283" - integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== - -micromark@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.2.0.tgz#1af9fef3f995ea1ea4ac9c7e2f19c48fd5c006e9" - integrity sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA== - dependencies: - "@types/debug" "^4.0.0" - debug "^4.0.0" - decode-named-character-reference "^1.0.0" - micromark-core-commonmark "^1.0.1" - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-chunked "^1.0.0" - micromark-util-combine-extensions "^1.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-encode "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-resolve-all "^1.0.0" - micromark-util-sanitize-uri "^1.0.0" - micromark-util-subtokenize "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.1" - uvu "^0.5.0" - micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -6865,7 +6411,7 @@ mri@1.1.4: resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== -mri@^1.1.0, mri@^1.2.0: +mri@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== @@ -7344,6 +6890,13 @@ parse5@6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -8040,13 +7593,6 @@ rxjs@^7.8.0: dependencies: tslib "^2.1.0" -sade@^1.7.3: - version "1.8.1" - resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== - dependencies: - mri "^1.1.0" - safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -9011,37 +8557,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -unist-util-is@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9" - integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw== - dependencies: - "@types/unist" "^2.0.0" - -unist-util-stringify-position@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d" - integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg== - dependencies: - "@types/unist" "^2.0.0" - -unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" - integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" - -unist-util-visit@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2" - integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" - unist-util-visit-parents "^5.1.1" - universal-github-app-jwt@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz#d57cee49020662a95ca750a057e758a1a7190e6e" @@ -9145,16 +8660,6 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uvu@^0.5.0: - version "0.5.6" - resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df" - integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA== - dependencies: - dequal "^2.0.0" - diff "^5.0.0" - kleur "^4.0.3" - sade "^1.7.3" - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -9497,8 +9002,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zwitch@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== From 3e6b040f6ed415dfe5844011cfed246ac1222227 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 29 Jul 2023 22:46:12 +0200 Subject: [PATCH 02/13] feat: change config, modify calculation --- .github/ubiquibot-config.yml | 13 +++++++------ src/configs/price.ts | 7 ++++++- src/handlers/payout/post.ts | 15 ++++++++++++--- src/types/config.ts | 10 +++++++++- src/utils/private.ts | 9 ++++++++- 5 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml index bdefdbd47..e1ce86d92 100644 --- a/.github/ubiquibot-config.yml +++ b/.github/ubiquibot-config.yml @@ -34,11 +34,12 @@ max-concurrent-bounties: 2 promotion-comment: "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
" incentives: comment: - code: 5 - img: 5 - h1: 1 - li: 0.5 - a: 0.5 - blockquote: 0 + elements: + code: 5 + img: 5 + h1: 1 + li: 0.5 + a: 0.5 + blockquote: 0 totals: word: 0.1 diff --git a/src/configs/price.ts b/src/configs/price.ts index b925928f8..5d08009fd 100644 --- a/src/configs/price.ts +++ b/src/configs/price.ts @@ -54,7 +54,12 @@ export const DefaultPriceConfig: PriceConfig = { ], incentives: { comment: { - li: 0.1, + elements: { + li: 1, + }, + totals: { + word: 0.1, + }, }, }, }; diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 3076c6c8a..1b49d8c6c 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -146,17 +146,26 @@ const generatePermitForComments = async ( * @dev Calculates the reward values for a given comments. We'll improve the formula whenever we get the better one. * * @param comments - The comments to calculate the reward for - * @param commentElementPricing - The basic price table for reward calculation + * @param incentives - The basic price table for reward calculation * @returns - The reward value */ const calculateRewardValue = (comments: Record, incentives: Incentives): number => { let sum = 0; for (const key of Object.keys(comments)) { - const rewardValue = incentives.comment[key]; const value = comments[key]; + + // if it's a text node calculate word count and multiply with the reward value if (key == "#text") { - sum += value.length * rewardValue; + const wordReward = incentives.comment.totals.word; + if (!wordReward) { + continue; + } + sum += value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0) * wordReward; } else { + const rewardValue = incentives.comment.elements[key]; + if (!rewardValue) { + continue; + } sum += rewardValue; } } diff --git a/src/types/config.ts b/src/types/config.ts index 5bc3b8aec..cb1a355ed 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -7,8 +7,16 @@ const LabelItemSchema = Type.Object({ }); export type LabelItem = Static; +const CommentIncentivesSchema = Type.Object({ + elements: Type.Record(Type.String(), Type.Number()), + totals: Type.Object({ + word: Type.Number(), + }), +}); +export type CommentIncentives = Static; + const IncentivesSchema = Type.Object({ - comment: Type.Record(Type.String(), Type.Number()), + comment: CommentIncentivesSchema, }); export type Incentives = Static; diff --git a/src/utils/private.ts b/src/utils/private.ts index e2c4a3342..1aa278327 100644 --- a/src/utils/private.ts +++ b/src/utils/private.ts @@ -47,8 +47,15 @@ export interface WideLabel { value?: number | undefined; } +export interface CommentIncentives { + elements: Record; + totals: { + word: number; + }; +} + export interface Incentives { - comment: Record; + comment: CommentIncentives; } export interface WideConfig { From d75f9205189d4d7fc3fe89ea31c7c45854dbf577 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 1 Aug 2023 10:43:36 +0200 Subject: [PATCH 03/13] fix: upsert wallet error checking --- src/adapters/supabase/helpers/client.ts | 32 +++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts index dbeb31ad1..4d3a20a60 100644 --- a/src/adapters/supabase/helpers/client.ts +++ b/src/adapters/supabase/helpers/client.ts @@ -158,14 +158,23 @@ export const upsertWalletAddress = async (username: string, address: string): Pr const logger = getLogger(); const { supabase } = getAdapters(); - const { data, error } = await supabase.from("wallets").select("user_name").eq("user_name", username).single(); - if (data) { - await supabase.from("wallets").upsert({ + const { data, error } = await supabase.from("wallets").select("user_name").eq("user_name", username); + if (error) { + logger.error(`Checking wallet address failed, error: ${JSON.stringify(error)}`); + throw new Error(`Checking wallet address failed, error: ${JSON.stringify(error)}`); + } + + if (data && data.length > 0) { + const { data: _data, error: _error } = await supabase.from("wallets").upsert({ user_name: username, wallet_address: address, updated_at: new Date().toUTCString(), }); - logger.info(`Upserting a wallet address done, { data: ${data}, error: ${error} }`); + if (_error) { + logger.error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`); + throw new Error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`); + } + logger.info(`Upserting a wallet address done, { data: ${JSON.stringify(_data)} }`); } else { const { data: _data, error: _error } = await supabase.from("wallets").insert({ user_name: username, @@ -173,7 +182,11 @@ export const upsertWalletAddress = async (username: string, address: string): Pr created_at: new Date().toUTCString(), updated_at: new Date().toUTCString(), }); - logger.info(`Creating a new wallet_table record done, { data: ${_data}, error: ${_error} }`); + if (_error) { + logger.error(`Creating a new wallet_table record failed, error: ${JSON.stringify(_error)}`); + throw new Error(`Creating a new wallet_table record failed, error: ${JSON.stringify(_error)}`); + } + logger.info(`Creating a new wallet_table record done, { data: ${JSON.stringify(_data)} }`); } }; @@ -300,12 +313,11 @@ export const getWalletMultiplier = async (username: string): Promise => export const getWalletInfo = async (username: string): Promise<{ multiplier: number | null; address: string | null } | number | undefined> => { const { supabase } = getAdapters(); - - const { data } = await supabase.from('wallets').select('multiplier, address').eq("user_name", username).single(); - if (data?.multiplier == null || data?.address == null) return 1 - else return {multiplier: data?.multiplier, address: data?.address} -}; + const { data } = await supabase.from("wallets").select("multiplier, address").eq("user_name", username).single(); + if (data?.multiplier == null || data?.address == null) return 1; + else return { multiplier: data?.multiplier, address: data?.address }; +}; export const getMultiplierReason = async (username: string): Promise => { const { supabase } = getAdapters(); From 75b019a4885c554cfa7c9465f6b11012d6aa4797 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 1 Aug 2023 11:19:52 +0200 Subject: [PATCH 04/13] fix: bugs and more error handling --- src/handlers/payout/action.ts | 13 +++++----- src/handlers/payout/post.ts | 45 +++++++++++++++++++++++++++-------- src/helpers/issue.ts | 24 +++++++++---------- src/types/payload.ts | 4 ++-- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 94b3e1243..9b2a0d39b 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -1,7 +1,7 @@ import { getWalletAddress, getWalletMultiplier } from "../../adapters/supabase"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addLabelToIssue, deleteLabel, generatePermit2Signature, getAllIssueComments, getTokenSymbol } from "../../helpers"; -import {UserType, Payload, StateReason } from "../../types"; +import { UserType, Payload, StateReason } from "../../types"; import { shortenEthAddress } from "../../utils"; import { bountyInfo } from "../wildcard"; @@ -45,6 +45,11 @@ export const handleIssueClosed = async () => { } const recipient = await getWalletAddress(assignee.login); + if (!recipient || recipient?.trim() === "") { + logger.info(`Recipient address is missing`); + return; + } + const multiplier = await getWalletMultiplier(assignee.login); if (multiplier === 0) { @@ -55,10 +60,6 @@ export const handleIssueClosed = async () => { // TODO: add multiplier to the priceInEth const priceInEth = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * multiplier).toString(); - if (!recipient || recipient?.trim() === "") { - logger.info(`Recipient address is missing`); - return; - } const payoutUrl = await generatePermit2Signature(recipient, priceInEth, issue.node_id); const tokenSymbol = await getTokenSymbol(paymentToken, rpc); @@ -67,7 +68,7 @@ export const handleIssueClosed = async () => { const comment = `### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```"; const comments = await getAllIssueComments(issue.number); const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); - if (permitComments.length > 0) { + if (permitComments.length > 0) { logger.info(`Skip to generate a permit url because it has been already posted`); return `Permit generation skipped because it was already posted to this issue.`; } diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 1b49d8c6c..c89888383 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -2,6 +2,7 @@ import { getWalletAddress } from "../../adapters/supabase"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers"; import { Incentives, Payload, UserType } from "../../types"; +import { commentParser } from "../comment"; const ItemsToExclude: string[] = ["blockquote"]; /** @@ -21,12 +22,12 @@ export const incentivizeComments = async () => { } const context = getBotContext(); const payload = context.payload as Payload; - const org = payload.organization?.login; const issue = payload.issue; - if (!issue || !org) { - logger.info(`Incomplete payload. issue: ${issue}, org: ${org}`); + if (!issue) { + logger.info(`Incomplete payload. issue: ${issue}`); return; } + const assignees = issue?.assignees ?? []; const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) { @@ -34,12 +35,24 @@ export const incentivizeComments = async () => { return; } - const issueComments = await getAllIssueComments(issue.number, "html"); + const issueComments = await getAllIssueComments(issue.number, "full"); logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`); const issueCommentsByUser: Record = {}; for (const issueComment of issueComments) { const user = issueComment.user; if (user.type == UserType.Bot || user.login == assignee) continue; + const commands = commentParser(issueComment.body); + if (commands.length > 0) { + 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)}`); + continue; + } + if (!issueCommentsByUser[user.login]) { + issueCommentsByUser[user.login] = []; + } issueCommentsByUser[user.login].push(issueComment.body_html); } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); @@ -55,6 +68,10 @@ export const incentivizeComments = async () => { const comments = issueCommentsByUser[user]; const commentsByNode = await parseComments(comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); + if (rewardValue === 0) { + logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`); + continue; + } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = ((rewardValue * baseMultiplier) / 1000).toString(); @@ -86,13 +103,13 @@ export const incentivizeCreatorComment = async () => { } const context = getBotContext(); const payload = context.payload as Payload; - const org = payload.organization?.login; const issue = payload.issue; - if (!issue || !org) { - logger.info(`Incomplete payload. issue: ${issue}, org: ${org}`); + if (!issue) { + logger.info(`Incomplete payload. issue: ${issue}`); return; } - const assignees = issue?.assignees ?? []; + + const assignees = issue.assignees ?? []; const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) { logger.info("Skipping payment permit generation because `assignee` is `undefined`."); @@ -100,15 +117,19 @@ export const incentivizeCreatorComment = async () => { } const description = await getIssueDescription(issue.number, "html"); + if (!description) { + logger.info(`Skipping to generate a permit url because issue description is empty. description: ${description}`); + return; + } logger.info(`Getting the issue description done. description: ${description}`); const creator = issue.user; - if (creator?.type === UserType.Bot || creator?.login === issue?.assignee) { + if (creator.type === UserType.Bot || creator.login === issue.assignee) { logger.info("Issue creator assigneed himself or Bot created this issue."); return; } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); - const result = await generatePermitForComments(creator?.login, [description], issueCreatorMultiplier, incentives, tokenSymbol, issue.node_id); + const result = await generatePermitForComments(creator.login, [description], issueCreatorMultiplier, incentives, tokenSymbol, issue.node_id); if (result.payoutUrl) { logger.info(`Permit url generated for creator. reward: ${result.payoutUrl}`); @@ -130,6 +151,10 @@ const generatePermitForComments = async ( const logger = getLogger(); const commentsByNode = await parseComments(comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); + if (rewardValue === 0) { + logger.info(`No reward for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); + return { comment: "" }; + } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = ((rewardValue * multiplier) / 1000).toString(); diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index d7142ca6c..885a86b27 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -192,18 +192,16 @@ export const getIssueDescription = async (issue_number: number, format: "raw" | }); await checkRateLimitGit(response?.headers); - if (response.data.body) { - switch (format) { - case "raw": - result = response.data.body; - break; - case "html": - result = response.data.body_html ?? ""; - break; - case "text": - result = response.data.body_text ?? ""; - break; - } + switch (format) { + case "raw": + result = response.data.body ?? ""; + break; + case "html": + result = response.data.body_html ?? ""; + break; + case "text": + result = response.data.body_text ?? ""; + break; } } catch (e: unknown) { logger.debug(`Getting issue description failed!, reason: ${e}`); @@ -525,4 +523,4 @@ export const getAvailableOpenedPullRequests = async (username: string) => { } } return result; -}; \ No newline at end of file +}; diff --git a/src/types/payload.ts b/src/types/payload.ts index bf3f34df8..cf26fe3b6 100644 --- a/src/types/payload.ts +++ b/src/types/payload.ts @@ -234,8 +234,8 @@ export const CommentSchema = Type.Object({ updated_at: Type.String({ format: "date-time" }), author_association: Type.String(), body: Type.String(), - body_html: Type.String(), - body_text: Type.String(), + body_html: Type.Optional(Type.String()), + body_text: Type.Optional(Type.String()), }); export type Comment = Static; From 144bf97913c2bc6dcce7eab2081c832504458eae Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 1 Aug 2023 11:23:11 +0200 Subject: [PATCH 05/13] fix: import parse5 --- src/helpers/comment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/comment.ts b/src/helpers/comment.ts index 77340f058..5c1029eaf 100644 --- a/src/helpers/comment.ts +++ b/src/helpers/comment.ts @@ -1,4 +1,4 @@ -import parse5 from "parse5"; +import * as parse5 from "parse5"; type Node = { nodeName: string; From feffe5fd882aeeb14aa0765d00ae32b871d3f008 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 1 Aug 2023 11:56:39 +0200 Subject: [PATCH 06/13] feat: remove empty text values --- src/helpers/comment.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/helpers/comment.ts b/src/helpers/comment.ts index 5c1029eaf..cba6c165f 100644 --- a/src/helpers/comment.ts +++ b/src/helpers/comment.ts @@ -16,7 +16,7 @@ const traverse = (result: Record, node: Node, itemsToExclude: result[node.nodeName] = []; } - result[node.nodeName].push(node.value ?? ""); + result[node.nodeName].push(node.value?.trim() ?? ""); if (node.childNodes && node.childNodes.length > 0) { node.childNodes.forEach((child) => traverse(result, child, itemsToExclude)); @@ -33,5 +33,10 @@ export const parseComments = (comments: string[], itemsToExclude: string[]): Rec traverse(result, fragment as Node, itemsToExclude); } + // remove empty values + if (result["#text"]) { + result["#text"] = result["#text"].filter((str) => str.length > 0); + } + return result; }; From b33662e62688ca7af334eb3dfe93e1f9b88818f0 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 5 Aug 2023 10:38:44 +0200 Subject: [PATCH 07/13] feat: round to 2 decimal points --- src/handlers/payout/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index c89888383..b6367a78e 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -74,7 +74,7 @@ export const incentivizeComments = async () => { } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); - const amountInETH = ((rewardValue * baseMultiplier) / 1000).toString(); + const amountInETH = ((rewardValue * baseMultiplier) / 1000).toFixed(2); if (account) { const payoutUrl = await generatePermit2Signature(account, amountInETH, issue.node_id); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; @@ -157,7 +157,7 @@ const generatePermitForComments = async ( } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); - const amountInETH = ((rewardValue * multiplier) / 1000).toString(); + const amountInETH = ((rewardValue * multiplier) / 1000).toFixed(2); let comment = ""; if (account) { const payoutUrl = await generatePermit2Signature(account, amountInETH, node_id); From 5cc126fc929cde5a37110872f4f89afc024b084c Mon Sep 17 00:00:00 2001 From: whilefoo Date: Fri, 11 Aug 2023 10:51:28 +0200 Subject: [PATCH 08/13] feat: use decimal.js --- package.json | 1 + src/handlers/payout/action.ts | 12 ++++++------ src/handlers/payout/post.ts | 31 +++++++++++++++++-------------- src/helpers/permit.ts | 5 +++-- yarn.lock | 2 +- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 5309ae7b5..f5877c4c6 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "axios": "^1.3.2", + "decimal.js": "^10.4.3", "ethers": "^5.7.2", "husky": "^8.0.2", "jimp": "^0.22.4", diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index 7cf120302..aa526b50e 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -13,6 +13,7 @@ import { import { UserType, Payload, StateReason } from "../../types"; import { shortenEthAddress } from "../../utils"; import { bountyInfo } from "../wildcard"; +import Decimal from "decimal.js"; export const handleIssueClosed = async () => { const context = getBotContext(); @@ -115,22 +116,21 @@ export const handleIssueClosed = async () => { return; } - const { value } = await getWalletMultiplier(assignee.login, id?.toString()); + const { value: multiplier } = await getWalletMultiplier(assignee.login, id?.toString()); - if (value === 0) { + if (multiplier === 0) { const errMsg = "Refusing to generate the payment permit because " + `@${assignee.login}` + "'s payment `multiplier` is `0`"; logger.info(errMsg); return errMsg; } - // TODO: add multiplier to the priceInEth - let priceInEth = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString(); + let priceInEth = new Decimal(issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4)).mul(multiplier); // if bounty hunter has any penalty then deduct it from the bounty const penaltyAmount = await getPenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString()); if (penaltyAmount.gt(0)) { logger.info(`Deducting penalty from bounty`); - const bountyAmount = ethers.utils.parseUnits(priceInEth, 18); + const bountyAmount = ethers.utils.parseUnits(priceInEth.toString(), 18); const bountyAmountAfterPenalty = bountyAmount.sub(penaltyAmount); if (bountyAmountAfterPenalty.lte(0)) { await removePenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString(), bountyAmount); @@ -138,7 +138,7 @@ export const handleIssueClosed = async () => { logger.info(msg); return msg; } - priceInEth = ethers.utils.formatUnits(bountyAmountAfterPenalty, 18); + priceInEth = new Decimal(ethers.utils.formatUnits(bountyAmountAfterPenalty, 18)); } const payoutUrl = await generatePermit2Signature(recipient, priceInEth, issue.node_id); diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index b6367a78e..e7679e8f4 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -3,6 +3,7 @@ import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers"; import { Incentives, Payload, UserType } from "../../types"; import { commentParser } from "../comment"; +import Decimal from "decimal.js"; const ItemsToExclude: string[] = ["blockquote"]; /** @@ -62,19 +63,19 @@ export const incentivizeComments = async () => { const reward: Record = {}; // The mapping between gh handle and amount in ETH - const fallbackReward: Record = {}; + const fallbackReward: Record = {}; let comment = ""; for (const user of Object.keys(issueCommentsByUser)) { const comments = issueCommentsByUser[user]; const commentsByNode = await parseComments(comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); - if (rewardValue === 0) { + if (rewardValue.equals(0)) { logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`); continue; } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); - const amountInETH = ((rewardValue * baseMultiplier) / 1000).toFixed(2); + const amountInETH = rewardValue.mul(baseMultiplier).div(1000); if (account) { const payoutUrl = await generatePermit2Signature(account, amountInETH, issue.node_id); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; @@ -147,17 +148,17 @@ const generatePermitForComments = async ( incentives: Incentives, tokenSymbol: string, node_id: string -): Promise<{ comment: string; payoutUrl?: string; amountInETH?: string }> => { +): Promise<{ comment: string; payoutUrl?: string; amountInETH?: Decimal }> => { const logger = getLogger(); const commentsByNode = await parseComments(comments, ItemsToExclude); const rewardValue = calculateRewardValue(commentsByNode, incentives); - if (rewardValue === 0) { + if (rewardValue.equals(0)) { logger.info(`No reward for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); return { comment: "" }; } logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); - const amountInETH = ((rewardValue * multiplier) / 1000).toFixed(2); + const amountInETH = rewardValue.mul(multiplier).div(1000); let comment = ""; if (account) { const payoutUrl = await generatePermit2Signature(account, amountInETH, node_id); @@ -174,24 +175,26 @@ const generatePermitForComments = async ( * @param incentives - The basic price table for reward calculation * @returns - The reward value */ -const calculateRewardValue = (comments: Record, incentives: Incentives): number => { - let sum = 0; +const calculateRewardValue = (comments: Record, incentives: Incentives): Decimal => { + let sum = new Decimal(0); for (const key of Object.keys(comments)) { const value = comments[key]; // if it's a text node calculate word count and multiply with the reward value if (key == "#text") { - const wordReward = incentives.comment.totals.word; - if (!wordReward) { + if (!incentives.comment.totals.word) { continue; } - sum += value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0) * wordReward; + 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 { - const rewardValue = incentives.comment.elements[key]; - if (!rewardValue) { + if (!incentives.comment.elements[key]) { continue; } - sum += rewardValue; + const rewardValue = new Decimal(incentives.comment.elements[key]); + const reward = rewardValue.mul(value.length); + sum = sum.add(reward); } } diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts index de17b98eb..4cc029004 100644 --- a/src/helpers/permit.ts +++ b/src/helpers/permit.ts @@ -2,6 +2,7 @@ import { MaxUint256, PermitTransferFrom, SignatureTransfer } from "@uniswap/perm import { BigNumber, ethers } from "ethers"; import { getBotConfig, getLogger } from "../bindings"; import { keccak256, toUtf8Bytes } from "ethers/lib/utils"; +import Decimal from "decimal.js"; const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all networks @@ -13,7 +14,7 @@ const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on * * @returns Permit2 url including base64 encocded data */ -export const generatePermit2Signature = async (spender: string, amountInEth: string, identifier: string): Promise => { +export const generatePermit2Signature = async (spender: string, amountInEth: Decimal, identifier: string): Promise => { const { payout: { networkId, privateKey, permitBaseUrl, rpc, paymentToken }, } = getBotConfig(); @@ -26,7 +27,7 @@ export const generatePermit2Signature = async (spender: string, amountInEth: str // token we are permitting to be transferred token: paymentToken, // amount we are permitting to be transferred - amount: ethers.utils.parseUnits(amountInEth, 18), + amount: ethers.utils.parseUnits(amountInEth.toString(), 18), }, // who can transfer the tokens spender: spender, diff --git a/yarn.lock b/yarn.lock index dd970fcf2..1fc4fce2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3582,7 +3582,7 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.2.1: +decimal.js@^10.2.1, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== From d8dff6799a3c543f37abb0eabe80ad6abec807fa Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 12 Aug 2023 11:02:23 +0200 Subject: [PATCH 09/13] feat: add description for permits --- src/handlers/payout/action.ts | 3 ++- src/handlers/payout/post.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts index c0a057579..94e0edfee 100644 --- a/src/handlers/payout/action.ts +++ b/src/handlers/payout/action.ts @@ -176,7 +176,8 @@ export const handleIssueClosed = async () => { const tokenSymbol = await getTokenSymbol(paymentToken, rpc); const shortenRecipient = shortenEthAddress(recipient, `[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]`.length); logger.info(`Posting a payout url to the issue, url: ${payoutUrl}`); - const comment = `### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```"; + const comment = + `#### Task Assignee Reward\n### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```"; const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); if (permitComments.length > 0) { logger.info(`Skip to generate a permit url because it has been already posted`); diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 0fccb3a1b..42c75102c 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -64,7 +64,7 @@ export const incentivizeComments = async () => { // The mapping between gh handle and amount in ETH const fallbackReward: Record = {}; - let comment = ""; + let comment = `#### Conversation Rewards\n`; for (const user of Object.keys(issueCommentsByUser)) { const comments = issueCommentsByUser[user]; const commentsByNode = await parseComments(comments, ItemsToExclude); @@ -159,7 +159,7 @@ const generatePermitForComments = async ( logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = rewardValue.mul(multiplier).div(1000); - let comment = ""; + let comment = `#### Task Creator Reward\n`; if (account) { const { payoutUrl } = await generatePermit2Signature(account, amountInETH, node_id); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; From 7c6f399b039a874e93ed99c56925d3f7d998de75 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 13 Aug 2023 17:20:23 +0200 Subject: [PATCH 10/13] feat: update default config and README --- README.md | 7 ++++++- ubiquibot-config-default.json | 13 +++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0311933d0..f1d45640c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,12 @@ To test the bot, you can: `issue-creator-multiplier` is a number that defines a base multiplier for calculating incentive reward for the creator of the issue. -`comment-element-pricing` defines how much is a part of the comment worth. For example `text: 0.1` means that any text in the comment will be multiplied by 0.1 +`incentives` defines incentive rewards: + +- `comment` defines comment rewards: + - `elements` defines reward value for HTML elements such as `p`, `img`, `a`. + - `totals`: + - `word` defines reward for each word in the comment `max-concurrent-bounties` is the maximum number of bounties that can be assigned to a bounty hunter at once. This excludes bounties with pending pull request reviews. diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json index 57eb6df89..05ac29931 100644 --- a/ubiquibot-config-default.json +++ b/ubiquibot-config-default.json @@ -91,12 +91,13 @@ "payment-permit-max-price": 1000, "comment-incentives": false, "max-concurrent-assigns": 2, - "comment-element-pricing": { - "text": 0.1, - "link": 0.5, - "list": 0.5, - "code": 5, - "image": 5 + "incentives": { + "comment": { + "elements": {}, + "totals": { + "word": 0 + } + } }, "promotion-comment": "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot!
", "register-wallet-with-verification": false From c7282336a068b3c8fba69a58449028603b7c4f56 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Tue, 15 Aug 2023 12:34:26 +0200 Subject: [PATCH 11/13] feat: added checks --- src/handlers/payout/post.ts | 57 +++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 42c75102c..c93e2bd79 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -1,9 +1,10 @@ import { getWalletAddress } from "../../adapters/supabase"; import { getBotConfig, getBotContext, getLogger } from "../../bindings"; import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers"; -import { Incentives, Payload, UserType } from "../../types"; +import { Incentives, Payload, StateReason, UserType } from "../../types"; import { commentParser } from "../comment"; import Decimal from "decimal.js"; +import { bountyInfo } from "../wildcard"; const ItemsToExclude: string[] = ["blockquote"]; /** @@ -13,7 +14,7 @@ const ItemsToExclude: string[] = ["blockquote"]; export const incentivizeComments = async () => { const logger = getLogger(); const { - mode: { incentiveMode }, + mode: { incentiveMode, paymentPermitMaxPrice }, price: { baseMultiplier, incentives }, payout: { paymentToken, rpc }, } = getBotConfig(); @@ -29,10 +30,33 @@ export const incentivizeComments = async () => { return; } + if (issue.state_reason !== StateReason.COMPLETED) { + logger.info("incentivizeComments: comment incentives skipped because the issue was not closed as completed"); + return; + } + + if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { + logger.info(`incentivizeComments: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); + return; + } + + const issueDetailed = bountyInfo(issue); + if (!issueDetailed.isBounty) { + logger.info(`incentivizeComments: its not a bounty`); + return; + } + + const comments = await getAllIssueComments(issue.number); + const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); + if (permitComments.length > 0) { + logger.info(`incentivizeComments: skip to generate a permit url because it has been already posted`); + return; + } + const assignees = issue?.assignees ?? []; const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) { - logger.info("Skipping payment permit generation because `assignee` is `undefined`."); + logger.info("incentivizeComments: skipping payment permit generation because `assignee` is `undefined`."); return; } @@ -94,7 +118,7 @@ export const incentivizeComments = async () => { export const incentivizeCreatorComment = async () => { const logger = getLogger(); const { - mode: { incentiveMode }, + mode: { incentiveMode, paymentPermitMaxPrice }, price: { incentives, issueCreatorMultiplier }, payout: { paymentToken, rpc }, } = getBotConfig(); @@ -110,10 +134,33 @@ export const incentivizeCreatorComment = async () => { return; } + if (issue.state_reason !== StateReason.COMPLETED) { + logger.info("incentivizeCreatorComment: comment incentives skipped because the issue was not closed as completed"); + return; + } + + if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) { + logger.info(`incentivizeCreatorComment: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`); + return; + } + + const issueDetailed = bountyInfo(issue); + if (!issueDetailed.isBounty) { + logger.info(`incentivizeCreatorComment: its not a bounty`); + return; + } + + const comments = await getAllIssueComments(issue.number); + const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); + if (permitComments.length > 0) { + logger.info(`incentivizeCreatorComment: skip to generate a permit url because it has been already posted`); + return; + } + const assignees = issue.assignees ?? []; const assignee = assignees.length > 0 ? assignees[0] : undefined; if (!assignee) { - logger.info("Skipping payment permit generation because `assignee` is `undefined`."); + logger.info("incentivizeCreatorComment: skipping payment permit generation because `assignee` is `undefined`."); return; } From 9013519d69fc18c6fbffeead36320ed8fffc5619 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 17 Aug 2023 11:10:25 +0200 Subject: [PATCH 12/13] fix: match text --- src/handlers/payout/post.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index c93e2bd79..293f64169 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -47,7 +47,9 @@ export const incentivizeComments = async () => { } const comments = await getAllIssueComments(issue.number); - const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); + const permitComments = comments.filter( + (content) => content.body.includes("Conversation Rewards") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot + ); if (permitComments.length > 0) { logger.info(`incentivizeComments: skip to generate a permit url because it has been already posted`); return; @@ -151,7 +153,9 @@ export const incentivizeCreatorComment = async () => { } const comments = await getAllIssueComments(issue.number); - const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot); + const permitComments = comments.filter( + (content) => content.body.includes("Task Creator Reward") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot + ); if (permitComments.length > 0) { logger.info(`incentivizeCreatorComment: skip to generate a permit url because it has been already posted`); return; From 0a2d848d9e0cbb0b2c7447626f3b70feae8417b8 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 17 Aug 2023 22:47:42 +0200 Subject: [PATCH 13/13] feat: add check for max permit price --- src/handlers/payout/post.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts index 293f64169..17c8eb4ad 100644 --- a/src/handlers/payout/post.ts +++ b/src/handlers/payout/post.ts @@ -102,6 +102,10 @@ export const incentivizeComments = async () => { logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = rewardValue.mul(baseMultiplier).div(1000); + if (amountInETH.gt(paymentPermitMaxPrice)) { + logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`); + continue; + } if (account) { const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id); comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`; @@ -181,7 +185,15 @@ export const incentivizeCreatorComment = async () => { } const tokenSymbol = await getTokenSymbol(paymentToken, rpc); - const result = await generatePermitForComments(creator.login, [description], issueCreatorMultiplier, incentives, tokenSymbol, issue.node_id); + const result = await generatePermitForComments( + creator.login, + [description], + issueCreatorMultiplier, + incentives, + tokenSymbol, + issue.node_id, + paymentPermitMaxPrice + ); if (result.payoutUrl) { logger.info(`Permit url generated for creator. reward: ${result.payoutUrl}`); @@ -198,7 +210,8 @@ const generatePermitForComments = async ( multiplier: number, incentives: Incentives, tokenSymbol: string, - node_id: string + node_id: string, + paymentPermitMaxPrice: number ): Promise<{ comment: string; payoutUrl?: string; amountInETH?: Decimal }> => { const logger = getLogger(); const commentsByNode = await parseComments(comments, ItemsToExclude); @@ -210,6 +223,10 @@ const generatePermitForComments = async ( logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`); const account = await getWalletAddress(user); const amountInETH = rewardValue.mul(multiplier).div(1000); + if (amountInETH.gt(paymentPermitMaxPrice)) { + logger.info(`Skipping issue creator reward for user ${user} because reward is higher than payment permit max price`); + return { comment: "" }; + } let comment = `#### Task Creator Reward\n`; if (account) { const { payoutUrl } = await generatePermit2Signature(account, amountInETH, node_id);