diff --git a/.env b/.env deleted file mode 100644 index 9fbd201..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -GH_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6258244..78c31e9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ yarn-debug.log* yarn-error.log* # Private +.env # misc .DS_Store diff --git a/README.md b/README.md index 8e0569a..7d29d05 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ English | [简体中文](./README.zh-CN.md) A GitHub Action to help you manage issues +Online documentation | [Changelog](./changelog.md) + ## 😎 Why use GitHub Action? 1. Complete free. @@ -26,12 +28,14 @@ A GitHub Action to help you manage issues - [`lock-issue`](#lock-issue) - [`open-issue`](#open-issue) - [`remove-assignees`](#remove-assignees) + - [`remove-labels`](#remove-labels) - [`set-labels`](#set-labels) - [`unlock-issue`](#unlock-issue) - [`update-comment`](#update-comment) - [`update-issue`](#update-issue) - ⭐ Advanced - [`check-inactive`](#check-inactive) + - [`check-issue`](#check-issue) - [`close-issues`](#close-issues) - [`find-comments`](#find-comments) - [`lock-issues`](#lock-issues) @@ -325,6 +329,31 @@ Remove the person designated by issue. ⏫ [Back to list](#List) +#### `remove-labels` + +Remove the specified labels. + +```yml +- name: Remove labels + uses: actions-cool/issues-helper@v1.2 + with: + actions: 'remove-labels' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.issue.number }} + labels: 'xx' +``` + +| Param | Desc | Type | Required | Version | +| -- | -- | -- | -- | -- | +| actions | Action type | string | ✔ | v1.2 | +| token | [Token explain](#token) | string | ✔ | v1.2 | +| issue-number | The number of issue | number | ✔ | v1.2 | +| labels | The removed labels. When it is a blank character, do not remove | string | ✔ | v1.2 | + +- `labels` supports multiple, such as `x1,x2,x3`, only the labels added by the issue will be removed + +⏫ [Back to list](#List) + #### `set-labels` Replace the labels of issue. @@ -449,7 +478,7 @@ Update the specified issue according to the `issue-number`. ### ⭐ Advanced -It is not recommended to use multiple actions for advanced usage. +Advanced usage is not recommended to use multiple actions at the same time. #### `check-inactive` @@ -495,6 +524,60 @@ jobs: - `inactive-day`: When entering, it will filter the issue update time earlier than the current time minus the number of inactive days. If not entered, all - `inactive-label`: The default is `inactive`, others can be customized. When the project does not contain the label, it will be created automatically +⏫ [Back to list](#List) + +#### `check-issue` + +Check whether the issue meets the conditions according to the passed parameters and `issue-number`, and return a boolean value. + +The effect of the following example is: when an issue is newly opened, verify whether the current issue designator contains `x1` or `x2`. If one designated person is satisfied, the verification will pass, and at the same time, verify whether the title meets the conditions. The conditions are as follows: + +``` +x1 + y1 +x2 + y1 +x1 + y2 +x2 + y2 + +"x1y3y2" true +"1x2y" false +"y2 x1" true +``` + +```yml +name: Check Issue + +on: + issues: + types: [edited] + +jobs: + check-issue: + runs-on: ubuntu-latest + steps: + - name: check-issue + uses: actions-cool/issues-helper@v1 + with: + actions: 'check-issue' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.issue.number }} + assignee-includes: 'x1,x2' + title-includes: 'x1,x2/y1,y2' +``` + +| Param | Desc | Type | Required | Version | +| -- | -- | -- | -- | -- | +| actions | Action type | string | ✔ | v1.2 | +| token | [Token explain](#token) | string | ✔ | v1.2 | +| issue-number | The number of issue | number | ✔ | v1.2 | +| assignee-includes | Assignees contains check | string | ✔ | v1.2 | +| title-includes | Title contains check | string | ✔ | v1.2 | +| body-includes | Body contains check | string | ✔ | v1.2 | + +- `title-includes` `body-includes` supports the format `x1,x2` or `x1,x2/y1,y2`. Only supports two levels +- Return `check-result` + +⏫ [Back to list](#List) + #### `close-issues` Every 7 days at UTC 0, close the issues that have been filled with the `need info` label and have not been active for more than 7 days. diff --git a/README.zh-CN.md b/README.zh-CN.md index e8adf20..ae3aac8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,6 +6,8 @@ 一个帮你管理 issues 的 GitHub Action +在线文档 | [更新日志](./changelog.zh-CN.md) + ## 😎 为什么用 GitHub Action? 1. 完全免费。 @@ -26,12 +28,14 @@ - [`lock-issue`](#lock-issue) - [`open-issue`](#open-issue) - [`remove-assignees`](#remove-assignees) + - [`remove-labels`](#remove-labels) - [`set-labels`](#set-labels) - [`unlock-issue`](#unlock-issue) - [`update-comment`](#update-comment) - [`update-issue`](#update-issue) - ⭐ 进 阶 - [`check-inactive`](#check-inactive) + - [`check-issue`](#check-issue) - [`close-issues`](#close-issues) - [`find-comments`](#find-comments) - [`lock-issues`](#lock-issues) @@ -325,6 +329,31 @@ jobs: ⏫ [返回列表](#列-表) +#### `remove-labels` + +移除指定 labels。 + +```yml +- name: Remove labels + uses: actions-cool/issues-helper@v1.2 + with: + actions: 'remove-labels' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.issue.number }} + labels: 'xx' +``` + +| 参数 | 描述 | 类型 | 必填 | 版本 | +| -- | -- | -- | -- | -- | +| actions | 操作类型 | string | ✔ | v1.2 | +| token | [token 说明](#token) | string | ✔ | v1.2 | +| issue-number | 指定的 issue | number | ✔ | v1.2 | +| labels | 移除的 labels。当为空字符时,不进行移除 | string | ✔ | v1.2 | + +- `labels` 支持多个,如 `x1,x2,x3`,只会移除 issue 已添加的 labels + +⏫ [返回列表](#列-表) + #### `set-labels` 替换 issue 的 labels。 @@ -449,7 +478,7 @@ jobs: ### ⭐ 进 阶 -进阶用法不建议 actions 多重使用。 +进阶用法不建议 actions 多个一次同时使用。 #### `check-inactive` @@ -495,6 +524,60 @@ jobs: - `inactive-day`:当输入时,会筛选 issue 更新时间早于当前时间减去非活跃天数。不填时,会查询所有 - `inactive-label`:默认为 `inactive`,可自定义其他。当项目未包含该 label 时,会自动新建 +⏫ [返回列表](#列-表) + +#### `check-issue` + +根据传入的参数和 `issue-number` 来检查该 issue 是否满足条件,返回一个布尔值。 + +下面的例子效果是:当 issue 新开时,校验当前 issue 指定人是否包含 `x1` 或者 `x2`,满足一个指定人即可校验通过,同时校验标题是否满足条件。条件如下: + +``` +x1 + y1 +x2 + y1 +x1 + y2 +x2 + y2 + +"x1y3y2" true +"1x2y" false +"y2 x1" true +``` + +```yml +name: Check Issue + +on: + issues: + types: [edited] + +jobs: + check-issue: + runs-on: ubuntu-latest + steps: + - name: check-issue + uses: actions-cool/issues-helper@v1 + with: + actions: 'check-issue' + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.issue.number }} + assignee-includes: 'x1,x2' + title-includes: 'x1,x2/y1,y2' +``` + +| 参数 | 描述 | 类型 | 必填 | 版本 | +| -- | -- | -- | -- | -- | +| actions | 操作类型 | string | ✔ | v1.2 | +| token | [token 说明](#token) | string | ✔ | v1.2 | +| issue-number | 指定的 issue | number | ✔ | v1.2 | +| assignee-includes | 是否包含指定人 | string | ✔ | v1.2 | +| title-includes | 标题包含校验 | string | ✔ | v1.2 | +| body-includes | 内容包含校验 | string | ✔ | v1.2 | + +- `title-includes` `body-includes` 支持格式 `x1,x2` 或者 `x1,x2/y1,y2`。只支持两个层级 +- 返回 `check-result` + +⏫ [返回列表](#列-表) + #### `close-issues` 每 7 天 UTC 0 时,关闭已填加 `need info` label 且 7 天以上未活跃的 issues。 diff --git a/action.yml b/action.yml index 42a0185..12b946e 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,9 @@ name: 'Issues Helper' -description: 'Some operations on issue' +description: 'A GitHub Action to help you manage issues' author: 'xrkffgg' branding: icon: 'message-square' - color: 'blue' + color: 'black' inputs: actions: description: 'Action name' @@ -32,6 +32,8 @@ inputs: description: 'Find comments direction' comment-auth: description: 'Find comments query auth' + assignee-includes: + description: 'Check use' body-includes: description: 'Query use' title-includes: @@ -55,6 +57,8 @@ outputs: description: 'Create comment ID' comments: description: 'Find comments' + check-result: + description: 'Check issue' runs: using: node12 main: 'dist/index.js' diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..579ee5b --- /dev/null +++ b/changelog.md @@ -0,0 +1,35 @@ +# ✨ Changelog + +## Version rules + +- Use two-level semantic version, such as v1, v1.1, v2, v2.1 +- v1 represents the initial version +- The fixes and additions to the v1 version will be released to the v1.1 version +- When the released v1.x runs stable for a certain period of time, release the advanced v2 version + +## Version selection + +- It is recommended to use the latest releases version. It can be seen in [releases](https://github.com/actions-cool/issues-helper/releases) + +- You can also refer to the update log below to select the version + +- It also supports the direct use of branch versions. Such as: + +```yml +- name: Issues Helper + uses: actions-cool/issues-helper@main +``` + +## Change Log + +### v1.1 + +`2020.12.24` + +- fix: yml not support array. [#11](https://github.com/actions-cool/issues-helper/pull/11) + +### v1 + +`2020.12.23` + +🎉 First release. diff --git a/changelog.zh-CN.md b/changelog.zh-CN.md new file mode 100644 index 0000000..a7bd90d --- /dev/null +++ b/changelog.zh-CN.md @@ -0,0 +1,35 @@ +# ✨ 更新日志 + +## 版本规则 + +- 采用两级语义化版本,如v1、v1.1、v2、v2.1 +- v1 表示初始版本 +- 对 v1 版本的修复和新增会发布到 v1.1 版本 +- 当发布的 v1.x 运行一定时间稳定后,发布进阶 v2 版本 + +## 版本选择 + +- 建议采用最新 releases 版本。可在 [releases](https://github.com/actions-cool/issues-helper/releases) 看到 + +- 同时也可参照下面的更新日志来选择版本 + +- 也支持直接使用分支版本。如: + +```yml +- name: Issues Helper + uses: actions-cool/issues-helper@main +``` + +## 更新日志 + +### v1.1 + +`2020.12.24` + +- fix: yml not support array. [#11](https://github.com/actions-cool/issues-helper/pull/11) + +### v1 + +`2020.12.23` + +🎉 First release. diff --git a/dist/index.js b/dist/index.js index b4b02c3..3a86fb1 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5996,6 +5996,8 @@ const { doLockIssue } = __webpack_require__(9932); +const { dealInput, matchKeyword } = __webpack_require__(6254); + const token = core.getInput('token'); const octokit = new Octokit({ auth: `token ${token}` }); @@ -6005,6 +6007,7 @@ direction = direction === 'desc' ? 'desc' : 'asc'; const commentAuth = core.getInput("comment-auth"); const bodyIncludes = core.getInput('body-includes'); const titleIncludes = core.getInput('title-includes'); +const assigneeIncludes = core.getInput('assignee-includes'); const issueCreator = core.getInput("issue-creator"); const issueAssignee = core.getInput('issue-assignee'); @@ -6038,6 +6041,55 @@ async function doCheckInactive (owner, repo, labels) { } }; +/** + * 检查 issue 是否满足条件,满足返回 true + * 当前 issue 的指定人是否有一个满足 assigneeIncludes 里的某个 + * 关键字匹配,是否包含前一个某个+后一个某个 '官网,网站/挂了,无法访问' + */ +async function doCheckIssue (owner, repo, issueNumber) { + var checkResult = true; + const issue = await octokit.issues.get({ + owner, + repo, + issue_number: issueNumber + }); + + if (!!checkResult && assigneeIncludes) { + let assigneesCheck = dealInput(assigneeIncludes); + let checkAssignee = false; + issue.data.assignees.forEach(it => { + if (checkResult && !checkAssignee && assigneesCheck.includes(it.login)) { + checkResult = true; + checkAssignee = true; + } + }) + !checkAssignee ? checkResult = false : null; + } + + if (!!checkResult && titleIncludes) { + const titleArr = titleIncludes.split('/'); + const keyword1 = dealInput(titleArr[0]); + const keyword2 = dealInput(titleArr[1]); + checkResult = + keyword2.length ? + matchKeyword(issue.data.title, keyword1) && matchKeyword(issue.data.title, keyword2) : + matchKeyword(issue.data.title, keyword1); + } + + if (!!checkResult && bodyIncludes) { + const bodyArr = bodyIncludes.split('/'); + const keyword1 = dealInput(bodyArr[0]); + const keyword2 = dealInput(bodyArr[1]); + checkResult = + keyword2.length ? + matchKeyword(issue.data.body, keyword1) && matchKeyword(issue.data.body, keyword2) : + matchKeyword(issue.data.body, keyword1); + console.log(!!checkResult) + } + core.info(`Actions: [check-issue][${!!checkResult}] success!`); + core.setOutput("check-result", !!checkResult); +}; + async function doCloseIssues (owner, repo, labels) { const issues = await doQueryIssues(owner, repo, labels, 'open'); @@ -6115,7 +6167,12 @@ async function doQueryIssues (owner, repo, labels, state) { res.data.forEach(iss => { const a = bodyIncludes ? iss.body.includes(bodyIncludes) : true; const b = titleIncludes ? iss.title.includes(titleIncludes) : true; - if (a && b) { + /** + * Note: GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. + * For this reason, "Issues" endpoints may return both issues and pull requests in the response. + * You can identify pull requests by the pull_request key. + */ + if (a && b && iss.pull_request === undefined) { if (inactiveDay && typeof(inactiveDay) === 'number') { let lastTime = dayjs.utc().subtract(inactiveDay, 'day'); let updateTime = dayjs.utc(iss.updated_at); @@ -6133,6 +6190,7 @@ async function doQueryIssues (owner, repo, labels, state) { module.exports = { doCheckInactive, + doCheckIssue, doCloseIssues, doFindComments, doLockIssues, @@ -6300,6 +6358,28 @@ async function doRemoveAssignees (owner, repo, issueNumber, assignees) { core.info(`Actions: [remove-assignees][${assignees}] success!`); }; +async function doRemoveLabels (owner, repo, issueNumber, labels) { + const issue = await octokit.issues.get({ + owner, + repo, + issue_number: issueNumber + }); + const dealLabels = dealInput(labels); + let addLables = []; + if (dealLabels.length) { + issue.data.labels.forEach(item => { + !dealLabels.includes(item.name) ? addLables.push(item.name) : ''; + }) + await octokit.issues.setLabels({ + owner, + repo, + issue_number: issueNumber, + labels: addLables + }); + core.info(`Actions: [remove-labels][${labels}] success!`); + } +}; + async function doSetLabels (owner, repo, issueNumber, labels) { await octokit.issues.setLabels({ owner, @@ -6348,7 +6428,7 @@ async function doUpdateComment ( await octokit.issues.updateComment(params); core.info(`Actions: [update-comment][${commentId}] success!`); - } + } if (contents) { await doCreateCommentContent(owner, repo, commentId, dealInput(contents)); @@ -6443,6 +6523,7 @@ module.exports = { doLockIssue, doOpenIssue, doRemoveAssignees, + doRemoveLabels, doSetLabels, doUnlockIssue, doUpdateComment, @@ -6470,6 +6551,7 @@ const { doLockIssue, doOpenIssue, doRemoveAssignees, + doRemoveLabels, doSetLabels, doUnlockIssue, doUpdateComment, @@ -6478,6 +6560,7 @@ const { const { doCheckInactive, + doCheckIssue, doCloseIssues, doFindComments, doLockIssues, @@ -6494,6 +6577,7 @@ const ALLACTIONS = [ 'lock-issue', 'open-issue', 'remove-assignees', + 'remove-labels', 'set-labels', 'unlock-issue', 'update-comment', @@ -6501,6 +6585,7 @@ const ALLACTIONS = [ // advanced 'check-inactive', + 'check-issue', 'close-issues', 'find-comments', 'lock-issues', @@ -6574,6 +6659,9 @@ async function main() { case 'remove-assignees': await doRemoveAssignees(owner, repo, issueNumber, assignees); break; + case 'remove-labels': + await doRemoveLabels(owner, repo, issueNumber, labels); + break; case 'set-labels': await doSetLabels(owner, repo, issueNumber, labels); break; @@ -6611,6 +6699,13 @@ async function main() { labels ) break; + case 'check-issue': + await doCheckIssue( + owner, + repo, + issueNumber + ); + break; case 'close-issues': await doCloseIssues( owner, @@ -6664,8 +6759,13 @@ function dealInput (para) { return arr; }; +function matchKeyword(content, keywords) { + return keywords.find(item => content.toLowerCase().includes(item)); +}; + module.exports = { dealInput, + matchKeyword, }; diff --git a/src/advanced.js b/src/advanced.js index f6f1f5e..fc00f47 100644 --- a/src/advanced.js +++ b/src/advanced.js @@ -15,6 +15,8 @@ const { doLockIssue } = require('./base.js'); +const { dealInput, matchKeyword } = require('./util.js'); + const token = core.getInput('token'); const octokit = new Octokit({ auth: `token ${token}` }); @@ -24,6 +26,7 @@ direction = direction === 'desc' ? 'desc' : 'asc'; const commentAuth = core.getInput("comment-auth"); const bodyIncludes = core.getInput('body-includes'); const titleIncludes = core.getInput('title-includes'); +const assigneeIncludes = core.getInput('assignee-includes'); const issueCreator = core.getInput("issue-creator"); const issueAssignee = core.getInput('issue-assignee'); @@ -57,6 +60,55 @@ async function doCheckInactive (owner, repo, labels) { } }; +/** + * 检查 issue 是否满足条件,满足返回 true + * 当前 issue 的指定人是否有一个满足 assigneeIncludes 里的某个 + * 关键字匹配,是否包含前一个某个+后一个某个 '官网,网站/挂了,无法访问' + */ +async function doCheckIssue (owner, repo, issueNumber) { + var checkResult = true; + const issue = await octokit.issues.get({ + owner, + repo, + issue_number: issueNumber + }); + + if (!!checkResult && assigneeIncludes) { + let assigneesCheck = dealInput(assigneeIncludes); + let checkAssignee = false; + issue.data.assignees.forEach(it => { + if (checkResult && !checkAssignee && assigneesCheck.includes(it.login)) { + checkResult = true; + checkAssignee = true; + } + }) + !checkAssignee ? checkResult = false : null; + } + + if (!!checkResult && titleIncludes) { + const titleArr = titleIncludes.split('/'); + const keyword1 = dealInput(titleArr[0]); + const keyword2 = dealInput(titleArr[1]); + checkResult = + keyword2.length ? + matchKeyword(issue.data.title, keyword1) && matchKeyword(issue.data.title, keyword2) : + matchKeyword(issue.data.title, keyword1); + } + + if (!!checkResult && bodyIncludes) { + const bodyArr = bodyIncludes.split('/'); + const keyword1 = dealInput(bodyArr[0]); + const keyword2 = dealInput(bodyArr[1]); + checkResult = + keyword2.length ? + matchKeyword(issue.data.body, keyword1) && matchKeyword(issue.data.body, keyword2) : + matchKeyword(issue.data.body, keyword1); + console.log(!!checkResult) + } + core.info(`Actions: [check-issue][${!!checkResult}] success!`); + core.setOutput("check-result", !!checkResult); +}; + async function doCloseIssues (owner, repo, labels) { const issues = await doQueryIssues(owner, repo, labels, 'open'); @@ -134,7 +186,12 @@ async function doQueryIssues (owner, repo, labels, state) { res.data.forEach(iss => { const a = bodyIncludes ? iss.body.includes(bodyIncludes) : true; const b = titleIncludes ? iss.title.includes(titleIncludes) : true; - if (a && b) { + /** + * Note: GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. + * For this reason, "Issues" endpoints may return both issues and pull requests in the response. + * You can identify pull requests by the pull_request key. + */ + if (a && b && iss.pull_request === undefined) { if (inactiveDay && typeof(inactiveDay) === 'number') { let lastTime = dayjs.utc().subtract(inactiveDay, 'day'); let updateTime = dayjs.utc(iss.updated_at); @@ -152,6 +209,7 @@ async function doQueryIssues (owner, repo, labels, state) { module.exports = { doCheckInactive, + doCheckIssue, doCloseIssues, doFindComments, doLockIssues, diff --git a/src/base.js b/src/base.js index acfcca0..2517e17 100644 --- a/src/base.js +++ b/src/base.js @@ -154,6 +154,28 @@ async function doRemoveAssignees (owner, repo, issueNumber, assignees) { core.info(`Actions: [remove-assignees][${assignees}] success!`); }; +async function doRemoveLabels (owner, repo, issueNumber, labels) { + const issue = await octokit.issues.get({ + owner, + repo, + issue_number: issueNumber + }); + const dealLabels = dealInput(labels); + let addLables = []; + if (dealLabels.length) { + issue.data.labels.forEach(item => { + !dealLabels.includes(item.name) ? addLables.push(item.name) : ''; + }) + await octokit.issues.setLabels({ + owner, + repo, + issue_number: issueNumber, + labels: addLables + }); + core.info(`Actions: [remove-labels][${labels}] success!`); + } +}; + async function doSetLabels (owner, repo, issueNumber, labels) { await octokit.issues.setLabels({ owner, @@ -202,7 +224,7 @@ async function doUpdateComment ( await octokit.issues.updateComment(params); core.info(`Actions: [update-comment][${commentId}] success!`); - } + } if (contents) { await doCreateCommentContent(owner, repo, commentId, dealInput(contents)); @@ -297,6 +319,7 @@ module.exports = { doLockIssue, doOpenIssue, doRemoveAssignees, + doRemoveLabels, doSetLabels, doUnlockIssue, doUpdateComment, diff --git a/src/main.js b/src/main.js index aef9fc3..0336c36 100644 --- a/src/main.js +++ b/src/main.js @@ -13,6 +13,7 @@ const { doLockIssue, doOpenIssue, doRemoveAssignees, + doRemoveLabels, doSetLabels, doUnlockIssue, doUpdateComment, @@ -21,6 +22,7 @@ const { const { doCheckInactive, + doCheckIssue, doCloseIssues, doFindComments, doLockIssues, @@ -37,6 +39,7 @@ const ALLACTIONS = [ 'lock-issue', 'open-issue', 'remove-assignees', + 'remove-labels', 'set-labels', 'unlock-issue', 'update-comment', @@ -44,6 +47,7 @@ const ALLACTIONS = [ // advanced 'check-inactive', + 'check-issue', 'close-issues', 'find-comments', 'lock-issues', @@ -117,6 +121,9 @@ async function main() { case 'remove-assignees': await doRemoveAssignees(owner, repo, issueNumber, assignees); break; + case 'remove-labels': + await doRemoveLabels(owner, repo, issueNumber, labels); + break; case 'set-labels': await doSetLabels(owner, repo, issueNumber, labels); break; @@ -154,6 +161,13 @@ async function main() { labels ) break; + case 'check-issue': + await doCheckIssue( + owner, + repo, + issueNumber + ); + break; case 'close-issues': await doCloseIssues( owner, diff --git a/src/util.js b/src/util.js index e9824d4..8600c56 100644 --- a/src/util.js +++ b/src/util.js @@ -11,6 +11,11 @@ function dealInput (para) { return arr; }; +function matchKeyword(content, keywords) { + return keywords.find(item => content.toLowerCase().includes(item)); +}; + module.exports = { dealInput, + matchKeyword, };