├── src ├── helper │ ├── index.ts │ ├── types.ts │ ├── base.ts │ ├── helper.ts │ └── advanced.ts ├── issue │ ├── index.ts │ ├── types.ts │ └── issue.ts ├── core │ ├── README.md │ └── index.ts ├── shared.ts ├── main.ts ├── util │ └── index.ts └── types.ts ├── .prettierignore ├── .eslintignore ├── web ├── docs │ ├── base.md │ ├── advanced.md │ ├── base.zh-CN.md │ ├── guide │ │ ├── ref.md │ │ ├── ref.zh-CN.md │ │ ├── index.zh-CN.md │ │ ├── index.md │ │ ├── note.zh-CN.md │ │ ├── note.md │ │ ├── faq.zh-CN.md │ │ ├── start.zh-CN.md │ │ ├── start.md │ │ └── faq.md │ ├── advanced.zh-CN.md │ ├── changelog.zh-CN.md │ ├── changelog.md │ ├── index.zh-CN.md │ └── index.md ├── .dumirc.ts ├── remark-plugins │ └── index.ts └── .dumi │ └── global.less ├── .prettierrc.js ├── scripts ├── pub.sh ├── check-commit.js ├── release.js ├── update-version.js ├── tag.js └── update-users.js ├── CONTRIBUTING.md ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── resource.md │ ├── question.md │ ├── bug_report.md │ └── feature_request.md ├── workflows │ ├── ci.yml │ ├── gh-pages.yml │ ├── ci-notice.yml │ ├── preview-start.yml │ ├── preview-build.yml │ └── preview-deploy.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── tests └── index.test.ts ├── package.json ├── action.yml ├── USERS.js ├── tsconfig.json ├── CHANGELOG.md └── README.zh-CN.md /src/helper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './helper'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/issue/index.ts: -------------------------------------------------------------------------------- 1 | export * from './issue'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | docs-dist/ 4 | node_modules/ 5 | src/.umi/ 6 | src/main.ts 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | es/**/* 2 | lib/**/* 3 | node_modules 4 | _site 5 | dist 6 | coverage 7 | **/*.d.ts 8 | .eslintrc.js 9 | -------------------------------------------------------------------------------- /web/docs/base.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⭐ Base 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/docs/advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🌟 Advanced 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/docs/base.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⭐ 基 础 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/docs/guide/ref.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🎁 Reference 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/docs/advanced.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🌟 进 阶 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/helper/types.ts: -------------------------------------------------------------------------------- 1 | import type { TAction } from '../types'; 2 | 3 | export interface IIssueHelperEngine { 4 | doExeAction: (action: TAction) => Promise; 5 | } 6 | -------------------------------------------------------------------------------- /web/docs/guide/ref.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🎁 参 考 3 | --- 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/docs/changelog.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | toc: menu 3 | --- 4 | 5 | # ✨ 更新日志 6 | 7 | - v2 [升级参考](/zh-CN/guide/faq) 8 | - v3 [变更](/zh-CN/guide/faq/#v3-变更) 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/core/README.md: -------------------------------------------------------------------------------- 1 | # `@actions/core` 2 | 3 | ## Web 4 | 5 | - https://github.com/actions/toolkit/tree/main/packages/core 6 | 7 | ![](https://github.com/actions-cool/resources/blob/main/image/annotations.png?raw=true) 8 | -------------------------------------------------------------------------------- /web/docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | toc: menu 3 | --- 4 | 5 | # ✨ Changelog 6 | 7 | - v2 [upgrade reference](/guide/faq) 8 | - v3 [changelog](/guide/faq#v3-changelog) 9 | 10 | 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | arrowParens: 'avoid', 6 | importOrder: ['^@formily/(.*)', '^@(.*)$', '^[./]'], 7 | importOrderSeparation: true, 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/pub.sh: -------------------------------------------------------------------------------- 1 | echo "[TEST] check format" 2 | npm run format-check 3 | 4 | echo "[TEST] test package" 5 | npm run package 6 | 7 | echo "[TEST] test commit" 8 | npm run check-commit 9 | 10 | echo "[Action] do tag" 11 | npm run tag 12 | 13 | echo "[Action] do release" 14 | npm run release 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Dev 2 | 3 | ### Code 4 | 5 | All code is in `/src`. 6 | The online code is in `/web`. 7 | 8 | ## release 9 | 10 | direct `npm run pub` 11 | 12 | ### 手动 13 | - 删除 v2 tag 14 | - git push origin :refs/tags/v2 15 | - 把最新的 v2.1.0 推送到 远端 v2 tag 16 | - git push origin v2.1.0:v2 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@umijs/fabric/dist/eslint")], 3 | plugins: ["simple-import-sort"], 4 | rules: { 5 | "simple-import-sort/imports": "error", 6 | "simple-import-sort/exports": "error", 7 | "@typescript-eslint/no-parameter-properties": 0, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /src/shared.ts: -------------------------------------------------------------------------------- 1 | export const EEmoji = { 2 | '+1': '+1', 3 | '-1': '-1', 4 | laugh: 'laugh', 5 | confused: 'confused', 6 | heart: 'heart', 7 | hooray: 'hooray', 8 | rocket: 'rocket', 9 | eyes: 'eyes', 10 | }; 11 | 12 | export const ELockReasons = { 13 | 'off-topic': 'off-topic', 14 | 'too heated': 'too heated', 15 | resolved: 'resolved', 16 | spam: 'spam', 17 | }; 18 | 19 | export const EConst = { 20 | ExcludeEmpty: '$exclude-empty', 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/resource.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '💎 资源参考' 3 | about: Reference resources 4 | title: '[Resource] some' 5 | labels: 'resource' 6 | assignees: '' 7 | --- 8 | 9 | ### 😎 资源类别 Resource category 10 | 11 | 12 | 13 | 14 | ### 🎨 资源内容 Resource content 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | setup: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [20.x] 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@main 14 | 15 | - name: install 16 | run: yarn install 17 | 18 | - name: format 19 | run: yarn format-check 20 | 21 | - name: test 22 | run: yarn test 23 | 24 | - name: package 25 | run: yarn package 26 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [16.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - run: yarn 17 | - run: npm run docs:build 18 | - name: Deploy 19 | uses: peaceiris/actions-gh-pages@v3 20 | with: 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | publish_dir: ./docs-dist 23 | -------------------------------------------------------------------------------- /.github/workflows/ci-notice.yml: -------------------------------------------------------------------------------- 1 | name: CI Notice 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | setup: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [14.x] 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@main 17 | 18 | - uses: actions-cool/ci-notice@main 19 | with: 20 | ci: | 21 | yarn 22 | yarn run package 23 | yarn run docs:build 24 | notice-types: 'issue' 25 | issue-assignees: 'xrkffgg' 26 | -------------------------------------------------------------------------------- /scripts/check-commit.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const simpleGit = require('simple-git/promise'); 3 | 4 | const cwd = process.cwd(); 5 | const git = simpleGit(cwd); 6 | 7 | async function checkCommit({ files }) { 8 | if (files.length) { 9 | console.log(chalk.yellow('🙄 You forgot something to commit.')); 10 | files.forEach(({ path: filePath }) => { 11 | console.log(' -', chalk.red(filePath)); 12 | }); 13 | console.log(''); 14 | process.exit(1); 15 | } 16 | } 17 | 18 | async function run() { 19 | const status = await git.status(); 20 | await checkCommit(status); 21 | } 22 | 23 | run(); 24 | -------------------------------------------------------------------------------- /.github/workflows/preview-start.yml: -------------------------------------------------------------------------------- 1 | name: Preview Start 2 | 3 | on: pull_request_target 4 | 5 | jobs: 6 | preview: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: create 10 | uses: actions-cool/maintain-one-comment@v1.1.0 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | body: | 14 | ⚡️ Deploying PR Preview... 15 | 16 | 17 | 18 | 19 | body-include: '' 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '❓ 疑问或需要帮助' 3 | about: Question or need help 4 | title: '[Question] Help' 5 | labels: 'question' 6 | assignees: '' 7 | --- 8 | 9 | ### 🧐 问题描述 Problem Description 10 | 11 | 12 | 13 | 14 | ### 💻 示例代码 Sample code 15 | 16 | 17 | 18 | 19 | ### 🚑 其他信息 Other information 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 报告 bug 3 | about: Report Bug. 4 | title: '[BUG] Report bug' 5 | labels: 'bug' 6 | assignees: 7 | --- 8 | 9 | ### 🐛 Bug 描述 Bug description 10 | 11 | 12 | 13 | 14 | ### 🏞 期望结果 Desired result 15 | 16 | 17 | 18 | 19 | ### 🚑 其他信息 Other information 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🌟 需求、功能、建议 3 | about: Needs, functions, suggestions. 4 | title: '[Feature] New function' 5 | labels: 'feature' 6 | assignees: 7 | --- 8 | 9 | ### 🥰 需求描述 Description of Requirement 10 | 11 | 12 | 13 | 14 | ### 🧐 解决方案 Solution 15 | 16 | 17 | 18 | 19 | ### 🚑 其他信息 Other information 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github'; 2 | import { dealStringToArr, THANKS } from 'actions-util'; 3 | 4 | import * as core from './core'; 5 | import { IssueHelperEngine } from './helper'; 6 | import type { TAction } from './types'; 7 | 8 | async function main() { 9 | try { 10 | const actions = core.getInput('actions', { required: true }); 11 | const showThanks = core.getBooleanInput('show-thanks'); 12 | const IHE = new IssueHelperEngine(github.context); 13 | for (const action of dealStringToArr(actions)) { 14 | await IHE.doExeAction(action as TAction); 15 | } 16 | if (showThanks) { 17 | core.baseInfo(`\n${THANKS}`); 18 | } 19 | } catch (err: any) { 20 | core.setFailed(err.message); 21 | } 22 | } 23 | 24 | main(); 25 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | 3 | export const baseInfo = (mess: string) => { 4 | core.info(mess); 5 | }; 6 | 7 | export const info = (mess: string) => { 8 | core.info(`[📝 AC] ${mess}`); 9 | }; 10 | 11 | export const error = (mess: string) => { 12 | core.error(`[💥 AC] ${mess}`); 13 | }; 14 | 15 | export const notice = (mess: string) => { 16 | core.notice(`[🏷 AC] ${mess}`); 17 | }; 18 | 19 | export const warning = (mess: string) => { 20 | core.warning(`[🎃 AC] ${mess}`); 21 | }; 22 | 23 | export const getInput = core.getInput; 24 | export const getBooleanInput = core.getBooleanInput; 25 | 26 | export const setOutput = core.setOutput; 27 | 28 | export const setFailed = (mess: string) => { 29 | core.setFailed(`[🚨 AC] ${mess}`); 30 | }; 31 | -------------------------------------------------------------------------------- /web/docs/guide/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🍭 介 绍 3 | --- 4 | 5 | Issues 助手是一个轻松帮你自动管理 issues 的 GitHub Action。 6 | 7 | ### GitHub Actions 是什么? 8 | 9 | GitHub Actions 是由 GitHub 官方提供在存储库中自动化、自定义和执行软件开发工作流程。您可以发现,创建和共享操作以执行所需的任何工作(包括CI / CD),并在完全定制的工作流程中组合操作。[更多介绍](https://docs.github.com/en/free-pro-team@latest/actions)。 10 | 11 | `issues-helper` 就是以此为基础,利用 GitHub Actions 来帮你处理各种关于 issue 方面的操作。 12 | 13 | ### ✨ 特性 14 | 15 | - 😎 完全免费 16 | - 🚀 全自动操作 17 | - 🏖 托管于 GitHub 服务器,只要 GitHub 不宕机,它就不受影响 18 | 19 | ### ⚡ 反馈 20 | 21 | 非常欢迎你来尝试使用,并提出意见,你可以通过以下方式: 22 | 23 | - 通过 [Issue](https://github.com/actions-cool/issues-helper/issues) 报告 bug 或进行咨询 24 | - 通过 [Discussions](https://github.com/actions-cool/issues-helper/discussions) 进行讨论 25 | - 提交 [Pull Request](https://github.com/actions-cool/issues-helper/pulls) 改进 `issues-helper` 的代码 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # @source: https://github.com/xrkffgg/gitignore/blob/master/.gitignore 2 | 3 | # production 4 | # /dist 5 | /docs-dist 6 | 7 | # Log file 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Private 14 | .env 15 | 16 | # misc 17 | .DS_Store 18 | 19 | # dependencies 20 | node_modules 21 | # yarn.lock 22 | package-lock.json 23 | 24 | # local env files 25 | .env.local 26 | .env.*.local 27 | 28 | # Compiled file 29 | *.class 30 | *.css.map 31 | *.sass.map 32 | *.scss.map 33 | 34 | # Editor directories and files 35 | .idea 36 | .vscode 37 | *.suo 38 | *.ntvs* 39 | *.njsproj 40 | *.sln 41 | *.sw? 42 | ~$*.* 43 | 44 | # umi 45 | .umi 46 | .umi-production 47 | .umi-test 48 | .env.local 49 | 50 | # dumi 51 | web/.dumi/tmp 52 | web/.dumi/tmp-test 53 | web/.dumi/tmp-production 54 | 55 | # cache 56 | .sass-cache/ 57 | 58 | # test 59 | coverage 60 | -------------------------------------------------------------------------------- /.github/workflows/preview-build.yml: -------------------------------------------------------------------------------- 1 | name: Preview Build 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | build-preview: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | ref: ${{ github.event.pull_request.head.sha }} 15 | 16 | - name: build 17 | run: | 18 | yarn 19 | yarn docs:preview 20 | 21 | - run: | 22 | zip -r dist.zip docs-dist 23 | 24 | - name: upload dist artifact 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: dist 28 | path: dist.zip 29 | retention-days: 5 30 | 31 | - name: Save PR number 32 | if: ${{ always() }} 33 | run: echo ${{ github.event.number }} > ./pr-id.txt 34 | 35 | - name: Upload PR number 36 | if: ${{ always() }} 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: pr 40 | path: ./pr-id.txt 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present xrkffgg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/docs/guide/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🍭 Guide 3 | --- 4 | 5 | The Issues Helper is a GitHub Action that easily helps you automatically manage issues. 6 | 7 | ### What are GitHub Actions? 8 | 9 | Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you'd like, including CI/CD, and combine actions in a completely customized workflow. [More](https://docs.github.com/en/free-pro-team@latest/actions). 10 | 11 | `issues-helper` is based on this, using GitHub Actions to help you deal with various operations on issues. 12 | 13 | ### ✨ Feature 14 | 15 | - 😎 Complete free 16 | - 🚀 Fully automatic 17 | - 🏖 Hosted on the GitHub server, as long as GitHub is not down, it is not affected 18 | 19 | ### ⚡ Feedback 20 | 21 | You are very welcome to try it out and put forward your comments. You can use the following methods: 22 | 23 | - Report bugs or consult with [Issue](https://github.com/actions-cool/issues-helper/issues) 24 | - Discuss via [Discussions](https://github.com/actions-cool/issues-helper/discussions) 25 | - Submit [Pull Request](https://github.com/actions-cool/issues-helper/pulls) to improve the code of `issues-helper` 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ### 🤔 这个变动的性质是?/ What is the nature of this change? 7 | 8 | - [ ] 新特性提交 / New feature 9 | - [ ] bug 修复 / Fix bug 10 | - [ ] 样式优化 / Style optimization 11 | - [ ] 代码风格优化 / Code style optimization 12 | - [ ] 性能优化 / Performance optimization 13 | - [ ] 构建优化 / Build optimization 14 | - [ ] 网站、文档、Demo 改进 / Website, documentation, demo improvements 15 | - [ ] 重构代码或样式 / Refactor code or style 16 | - [ ] 测试相关 / Test related 17 | - [ ] 其他 / Other 18 | 19 | ### 🔗 相关 Issue / Related Issue 20 | 21 | 25 | 26 | ### 💡 需求背景和解决方案 / Background or solution 27 | 28 | 32 | 33 | ### 📝 更新日志 / Changelog 34 | 35 | 39 | 40 | | Language | Changelog | 41 | | ---------- | --------- | 42 | | 🇺🇸 English | | 43 | | 🇨🇳 Chinese | | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | import { dealStringToArr } from 'actions-util'; 2 | import sampleSize from 'lodash/sampleSize'; 3 | 4 | export const dealRandomAssignees = (assignees: string, randomTo: string | void): string[] => { 5 | let arr = dealStringToArr(assignees); 6 | if (randomTo && Number(randomTo) > 0 && Number(randomTo) < arr.length) { 7 | arr = sampleSize(arr, Number(randomTo)); 8 | } 9 | return arr; 10 | }; 11 | 12 | export const matchKeyword = (content: string = '', keywords: string[]): boolean => { 13 | return !!keywords.find(item => content?.toLowerCase().includes(item)); 14 | }; 15 | 16 | export const checkDuplicate = (body: string | void): boolean => { 17 | if (!body || !body.startsWith('Duplicate of')) { 18 | return false; 19 | } 20 | const arr = body.split(' '); 21 | return arr[0] == 'Duplicate' && arr[1] == 'of'; 22 | }; 23 | 24 | export const getPreMonth = (m: number): number => { 25 | return m == 1 ? 12 : m - 1; 26 | }; 27 | 28 | // replace some & split & cull empty 29 | export const replaceStr2Arr = (str: string, replace: string, split: string): string[] => { 30 | return str 31 | .replace(replace, '') 32 | .trim() 33 | .split(split) 34 | .reduce((result: string[], it) => (it ? [...result, it.trim()] : result), []); 35 | }; 36 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | import { replaceStr2Arr } from '../src/util'; 2 | 3 | describe('Test', () => { 4 | it('test doQueryIssues', () => { 5 | const issues = [ 6 | { 7 | id: 0, 8 | labels: [{ name: '0' }, { name: '1' }], 9 | }, 10 | { 11 | id: 1, 12 | labels: [{ name: '1' }, { name: '2' }], 13 | }, 14 | { 15 | id: 2, 16 | labels: [{ name: '2' }, { name: '3' }], 17 | }, 18 | { 19 | id: 3, 20 | labels: [{ name: '1' }, { name: '4' }], 21 | }, 22 | { 23 | id: 4, 24 | labels: [{ name: '1' }, { name: '3' }], 25 | }, 26 | { 27 | id: 5, 28 | labels: [{ name: '1' }, { name: '5' }], 29 | }, 30 | ]; 31 | 32 | let ex = ['2', '4']; 33 | let r = []; 34 | 35 | issues.forEach(iss => { 36 | for (let i = 0; i < iss.labels.length; i += 1) { 37 | if (ex.includes(iss.labels[i].name)) return; 38 | } 39 | r.push(iss); 40 | }); 41 | 42 | expect(r[0].id).toEqual(0); 43 | expect(r[1].id).toEqual(4); 44 | expect(r[2].id).toEqual(5); 45 | expect(r.length).toEqual(3); 46 | }); 47 | 48 | it('test replaceStr2Arr', () => { 49 | const st = '/assign @1 @2 @3@a 3 @s @1_2 2'; 50 | const re = '/assign'; 51 | const sp = '@'; 52 | 53 | expect(replaceStr2Arr(st, re, sp)).toEqual(['1', '2', '3', 'a 3', 's', '1_2 2']); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const open = require('open'); 3 | const newGithubReleaseUrl = require('new-github-release-url'); 4 | const { readFileSync } = require('fs'); 5 | const path = require('path'); 6 | 7 | let tag = ''; 8 | 9 | const CHANGELOG_NAME = 'CHANGELOG.md'; 10 | const user = 'actions-cool'; 11 | const repo = 'issues-helper'; 12 | 13 | function getChangelog(content) { 14 | const lines = content.split('\n'); 15 | const changeLog = []; 16 | const pin = /^## /; 17 | let begin = false; 18 | for (let i = 0; i < lines.length; i += 1) { 19 | const line = lines[i]; 20 | if (begin && pin.test(line)) { 21 | break; 22 | } 23 | if (begin && line) { 24 | changeLog.push(line); 25 | } 26 | if (!begin) { 27 | begin = pin.test(line); 28 | if (begin) { 29 | tag = line.substring(3, line.length).trim(); 30 | } 31 | } 32 | } 33 | return changeLog.join('\n\n'); 34 | } 35 | 36 | const changelogPath = path.join(__dirname, '..', CHANGELOG_NAME); 37 | const changelog = readFileSync(changelogPath, 'utf-8'); 38 | 39 | const body = getChangelog(changelog); 40 | 41 | async function run() { 42 | const url = newGithubReleaseUrl({ 43 | user, 44 | repo, 45 | tag, 46 | body: body, 47 | }); 48 | 49 | await open(url); 50 | 51 | console.log(chalk.yellow('🚀 Please check tag and changelog in a auto new webview tab. Then click publish!')); 52 | } 53 | 54 | run(); 55 | -------------------------------------------------------------------------------- /web/docs/guide/note.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🎗 记 录 3 | --- 4 | 5 | :::success{title="😊"} 6 | 这里记录自己在使用中总结的一些东西,希望可以帮助到你。 7 | ::: 8 | 9 | ## `yml` 中包含判断 10 | 11 | ```yml 12 | if: contains(github.event.issue.body, 'ie') == false 13 | ``` 14 | 15 | - 当 issue body 不包含 `ie` 触发 16 | - 测试 yml 中不支持 js `includes()` 语法 17 | - 大小写不校验,`IE` 还有同时类似 `kiekk` 也可满足 18 | 19 | 更多[查看](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#functions)。 20 | 21 | ## `yml` 中传值和输出 22 | 23 | ``` 24 | with: 25 | actions: 'month-statistics' 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | count-labels: 'true' 28 | ``` 29 | 30 | - `count-labels`:不管设置 `true` 还是 `'ture'`,在程序里接收到的都是字符串格式 31 | 32 | 同时输出的也是字符串格式。[参看](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs)。 33 | 34 | - `check-result`:判断条件为 `if: steps.xxid.outputs.check-result == 'true'` 35 | 36 | ## `GitHub Actions bot` 触发 37 | 38 | 当设置了一个 Actions,如为给一个 issue 新增 label `x1` 时,Actions 自动为该 issue 增加 `x2` label。 39 | 40 | 但如果这个是由 `GitHub Actions bot` 完成的(即 actions 中 token 不传,或使用默认 `token: ${{ secrets.GITHUB_TOKEN }}`),则不会触发 label `x2` 的 Actions。 41 | 42 | ref: [GitHub docs](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token) 43 | 44 | ## `assignees` 范围 45 | 46 | - 仓库的所有者或协作者,若有组织,包括成员 47 | - issue 的参与者,包括创建者、评论者 48 | - 最多支持 10 个 49 | 50 | ## 运行基准 51 | 52 | 比如:我用 Tag 触发一个 Action,触发基准的代码就会走这个 Tag 对应代码的 Action 定义,而非主分支代码。 53 | -------------------------------------------------------------------------------- /web/.dumirc.ts: -------------------------------------------------------------------------------- 1 | // more config: https://d.umijs.org/config 2 | import { defineConfig } from 'dumi'; 3 | import path from 'path'; 4 | import remarkPlugin from './remark-plugins'; 5 | 6 | const name = 'issues-helper'; 7 | 8 | const isProdSite = 9 | // 不是预览模式 同时是生产环境 10 | process.env.PREVIEW !== 'true' && process.env.NODE_ENV === 'production'; 11 | 12 | const logo = 13 | 'https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*8xDgSL-O6O4AAAAAAAAAAAAAARQnAQ'; 14 | 15 | export default defineConfig({ 16 | title: 'Issues Helper', 17 | outputPath: '../docs-dist', 18 | base: isProdSite ? `/${name}/` : '/', 19 | publicPath: isProdSite ? `/${name}/` : '/', 20 | locales: [ 21 | { id: 'en-US', name: 'English', }, 22 | { id: 'zh-CN', name: '中文' }, 23 | ], 24 | favicons: [logo], 25 | extraRemarkPlugins: [remarkPlugin], 26 | themeConfig: { 27 | logo, 28 | nav: { 29 | 'zh-CN': [ 30 | { title: '指 南', link: '/zh-CN/guide' }, 31 | { title: '基 础', link: '/zh-CN/base' }, 32 | { title: '进 阶', link: '/zh-CN/advanced' }, 33 | { title: '更新日志', link: '/zh-CN/changelog' } 34 | ], 35 | 'en-US': [ 36 | { title: 'Guide', link: '/guide' }, 37 | { title: 'Base', link: '/base' }, 38 | { title: 'Advanced', link: '/advanced' }, 39 | { title: 'Changelog', link: '/changelog' }, 40 | ], 41 | }, 42 | socialLinks: { 43 | github: 'https://github.com/actions-cool/issues-helper' 44 | }, 45 | footer: 'Open-source MIT Licensed | Copyright © 2020-present
Powered by xrkffgg' 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { TPermissionType } from 'actions-util'; 2 | 3 | export { Context } from '@actions/github/lib/context'; 4 | 5 | export type TEmoji = '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes'; 6 | 7 | export type TLockReasons = 'off-topic' | 'too heated' | 'resolved' | 'spam' | undefined; 8 | 9 | export type TIssueState = 'open' | 'closed'; 10 | 11 | export type TUpdateMode = 'append' | 'replace'; 12 | 13 | export type TUserPermission = TPermissionType; 14 | 15 | export type TCloseReason = 'completed' | 'not_planned'; 16 | 17 | export type TOutInfo = { 18 | auth: string; 19 | id?: number; 20 | number?: number; 21 | title?: string; 22 | body?: string; 23 | state?: TIssueState; 24 | created: string; 25 | updated: string; 26 | }; 27 | 28 | export type TOutList = TOutInfo[]; 29 | 30 | export type TAction = 31 | // [ Base Begin ] 32 | | 'add-assignees' 33 | | 'add-labels' 34 | | 'close-issue' 35 | | 'create-comment' 36 | | 'create-issue' 37 | | 'create-label' 38 | | 'delete-comment' 39 | | 'lock-issue' 40 | | 'open-issue' 41 | | 'remove-assignees' 42 | | 'remove-labels' 43 | | 'set-labels' 44 | | 'unlock-issue' 45 | | 'update-comment' 46 | | 'update-issue' 47 | // [ Base End ] 48 | // ^_^ ========== ^_^ 49 | // [ Advanced Begin ] 50 | | 'check-inactive' 51 | | 'check-issue' 52 | | 'close-issues' 53 | | 'find-comments' 54 | | 'find-issues' 55 | | 'get-issue' 56 | | 'lock-issues' 57 | | 'mark-assignees' 58 | | 'mark-duplicate' 59 | | 'toggle-labels' 60 | | 'welcome'; 61 | //// [ Advanced End ] 62 | -------------------------------------------------------------------------------- /scripts/update-version.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, writeFileSync } = require('fs'); 2 | 3 | const last = /@v2/g; 4 | const now = `@v3`; 5 | 6 | let readme = readFileSync('./README.md', 'utf-8'); 7 | readme = readme.replace(last, now); 8 | writeFileSync('./README.md', readme); 9 | console.log('readme done!'); 10 | 11 | let readmeen = readFileSync('./README.en-US.md', 'utf-8'); 12 | readmeen = readmeen.replace(last, now); 13 | writeFileSync('./README.en-US.md', readmeen); 14 | console.log('readmeen done!'); 15 | 16 | let index = readFileSync('./web/docs/index.md', 'utf-8'); 17 | index = index.replace(last, now); 18 | writeFileSync('./web/docs/index.md', index); 19 | console.log('index done!'); 20 | 21 | let indexen = readFileSync('./web/docs/index.en-US.md', 'utf-8'); 22 | indexen = indexen.replace(last, now); 23 | writeFileSync('./web/docs/index.en-US.md', indexen); 24 | console.log('indexen done!'); 25 | 26 | let base = readFileSync('./web/docs/base.md', 'utf-8'); 27 | base = base.replace(last, now); 28 | writeFileSync('./web/docs/base.md', base); 29 | console.log('base done!'); 30 | 31 | let baseen = readFileSync('./web/docs/base.en-US.md', 'utf-8'); 32 | baseen = baseen.replace(last, now); 33 | writeFileSync('./web/docs/base.en-US.md', baseen); 34 | console.log('baseen done!'); 35 | 36 | let adv = readFileSync('./web/docs/advanced.md', 'utf-8'); 37 | adv = adv.replace(last, now); 38 | writeFileSync('./web/docs/advanced.md', adv); 39 | console.log('adv done!'); 40 | 41 | let adven = readFileSync('./web/docs/advanced.en-US.md', 'utf-8'); 42 | adven = adven.replace(last, now); 43 | writeFileSync('./web/docs/advanced.en-US.md', adven); 44 | console.log('adven done!'); 45 | -------------------------------------------------------------------------------- /web/docs/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Issues 助手 3 | order: 1 4 | hero: 5 | title: Issues 助手 6 | description: 🤖 一个轻松帮你自动管理 issues 的 GitHub Action 7 | actions: 8 | - text: 快速开始 9 | link: /zh-CN/guide/start 10 | features: 11 | - emoji: 🎁 12 | title: 完全免费 13 | description: 使用 GitHub 自带提供的 Actions 服务 14 | - emoji: 👌 15 | title: 简单易用 16 | description: 教程详细,模版丰富 17 | - emoji: 🌍 18 | title: 轻松托管 19 | description: 只要 GitHub 不宕机,它就不受影响 20 | --- 21 | 22 | ## 🍭 快速上手 23 | 24 | 这里列举一个非常简单以及常用的例子。对应场景为:当一个 issue 新增 `help wanted` 标签时,系统会自动进行评论。 25 | 26 | ```yml 27 | name: Issue Reply 28 | 29 | on: 30 | issues: 31 | types: [labeled] 32 | 33 | jobs: 34 | reply-helper: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: help wanted 38 | if: github.event.label.name == 'help wanted' 39 | uses: actions-cool/issues-helper@v3 40 | with: 41 | actions: 'create-comment' 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | issue-number: ${{ github.event.issue.number }} 44 | body: | 45 | Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome PR。 46 | 47 | 你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎PR。 48 | ``` 49 | 50 | ## 💖 谁在使用? 51 | 52 | 53 | 54 | ## ⚡ 反馈 55 | 56 | 非常欢迎你来尝试使用,并提出意见,你可以通过以下方式: 57 | 58 | - 通过 [Issue](https://github.com/actions-cool/issues-helper/issues) 报告 bug 或进行咨询 59 | - 通过 [Discussions](https://github.com/actions-cool/issues-helper/discussions) 进行讨论 60 | - 提交 [Pull Request](https://github.com/actions-cool/issues-helper/pulls) 改进 `issues-helper` 的代码 61 | 62 | 也欢迎加入 钉钉交流群 63 | 64 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*-iuDSpF7QAQAAAAAAAAAAAAAARQnAQ) 65 | -------------------------------------------------------------------------------- /web/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Issues Helper 3 | order: 1 4 | hero: 5 | title: Issues Helper 6 | description: 🤖 A GitHub Action that easily helps you automatically manage issues 7 | actions: 8 | - text: Quick start 9 | link: /guide/start 10 | features: 11 | - emoji: 🎁 12 | title: Completely free 13 | description: Use the Actions service provided by GitHub 14 | - emoji: 👌 15 | title: Easy to use 16 | description: Detailed tutorials and rich templates 17 | - emoji: 🌍 18 | title: Easy hosting 19 | description: As long as GitHub is not down, it will not be affected 20 | --- 21 | 22 | ## 🍭 Get started quickly 23 | 24 | Here is a very simple and commonly used example. The corresponding scenario is: when an issue adds the `help wanted` tag, the system will automatically comment. 25 | 26 | ```yml 27 | name: Issue Reply 28 | 29 | on: 30 | issues: 31 | types: [labeled] 32 | 33 | jobs: 34 | reply-helper: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: help wanted 38 | if: github.event.label.name == 'help wanted' 39 | uses: actions-cool/issues-helper@v3 40 | with: 41 | actions: 'create-comment' 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | issue-number: ${{ github.event.issue.number }} 44 | body: | 45 | Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome PR。 46 | 47 | 你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎PR。 48 | ``` 49 | 50 | ## 💖 Who is using? 51 | 52 | 53 | 54 | ## ⚡ Feedback 55 | 56 | You are very welcome to try it out and put forward your comments. You can use the following methods: 57 | 58 | - Report bugs or consult with [Issue](https://github.com/actions-cool/issues-helper/issues) 59 | - Discuss via [Discussions](https://github.com/actions-cool/issues-helper/discussions) 60 | - Submit [Pull Request](https://github.com/actions-cool/issues-helper/pulls) to improve the code of `issues-helper` 61 | -------------------------------------------------------------------------------- /web/docs/guide/note.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🎗 Note 3 | --- 4 | 5 | :::success{title="😊"} 6 | Here are some things I summarized in my use, I hope it can help you. 7 | ::: 8 | 9 | ## Include judgment in `yml` 10 | 11 | ```yml 12 | if: contains(github.event.issue.body, 'ie') == false 13 | ``` 14 | - Triggered when the issue body does not contain `ie` 15 | - The js `includes()` syntax is not supported in the yml 16 | - Case is not checked, `IE` and also similar to `kiekk` can also be satisfied 17 | 18 | [More](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#functions). 19 | 20 | ## Pass value and output in `yml` 21 | 22 | ``` 23 | with: 24 | actions: 'month-statistics' 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | count-labels: 'true' 27 | ``` 28 | 29 | - `count-labels`: Regardless of setting `true` or `'ture'`, all received in the program is in string format 30 | 31 | At the same time, the output is also in string format. [See](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs). 32 | 33 | - `check-result`: The judgment condition is `if: steps.xxid.outputs.check-result =='true'` 34 | 35 | ## `GitHub Actions bot` trigger 36 | 37 | When an action is set, such as adding a label `x1` to an issue, Actions will automatically add a label `x2` to the issue. 38 | 39 | But if this is done by `GitHub Actions bot` (that is, the token in the actions is not passed, or the default `token: ${{ secrets.GITHUB_TOKEN }}` is used), the actions of label `x2` will not be triggered. 40 | 41 | ref: [GitHub docs](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token) 42 | 43 | ## `assignees` scope 44 | 45 | - The owner or collaborator of the warehouse, if there is an organization, including members 46 | - Participants of the issue, including creators and commenters 47 | - Max 10 48 | 49 | ## Benchmark 50 | 51 | For example: I use a Tag to trigger an Action, and the code that triggers the benchmark will follow the Action definition of the code corresponding to this Tag instead of the main branch code. 52 | -------------------------------------------------------------------------------- /scripts/tag.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const simpleGit = require('simple-git/promise'); 3 | const { execSync } = require('child_process'); 4 | const { readFileSync } = require('fs'); 5 | const path = require('path'); 6 | 7 | const CHANGELOG_NAME = 'CHANGELOG.md'; 8 | const CHANGELOG_PATH = path.join(__dirname, '..', CHANGELOG_NAME); 9 | const CHANGELOG = readFileSync(CHANGELOG_PATH, 'utf-8'); 10 | 11 | const cwd = process.cwd(); 12 | const git = simpleGit(cwd); 13 | 14 | async function run() { 15 | execSync(`git pull`); 16 | 17 | const data = await git.tags(); 18 | const tags = data.all; 19 | let tag = tags.reverse()[0]; 20 | console.log(chalk.green(`[Git Query] tag: ${tag}`)); 21 | 22 | const tagChangelog = getChangelogTag(CHANGELOG); 23 | if (tagChangelog && tag != tagChangelog) { 24 | console.log(chalk.yellow(`[Git Action] Push new ${tagChangelog} tag!`)); 25 | execSync(`git tag ${tagChangelog}`); 26 | execSync(`git push origin ${tagChangelog}:${tagChangelog}`); 27 | execSync(`git pull`); 28 | tag = tagChangelog; 29 | } else { 30 | console.log(chalk.yellow('🙄 Please add new release changelog first.')); 31 | console.log(''); 32 | process.exit(1); 33 | } 34 | 35 | const tagSimple = tag.startsWith('v') ? tag.substring(0, 2) : tag.substring(0, 1); 36 | console.log(chalk.green(`[Git Query] tagSimple: ${tagSimple}`)); 37 | 38 | if (tags.includes(tagSimple)) { 39 | console.log(chalk.yellow(`[Git Action] Delete ${tagSimple} tag`)); 40 | execSync(`git push origin :refs/tags/${tagSimple}`); 41 | console.log(chalk.green(`[Git Action] Delete ${tagSimple} tag success`)); 42 | } 43 | 44 | console.log(chalk.yellow(`[Git Action] Add new simple ${tagSimple} tag`)); 45 | execSync(`git push origin ${tag}:${tagSimple}`); 46 | console.log(chalk.green('🎉 Done!')); 47 | } 48 | 49 | function getChangelogTag(content) { 50 | const lines = content.split('\n'); 51 | const pin = /^## /; 52 | let begin = false; 53 | let tag = ''; 54 | 55 | for (let i = 0; i < lines.length; i += 1) { 56 | const line = lines[i]; 57 | if (begin && pin.test(line)) { 58 | break; 59 | } 60 | if (!begin) { 61 | begin = pin.test(line); 62 | if (begin) { 63 | tag = line.substring(3, line.length); 64 | } 65 | } 66 | } 67 | 68 | return tag.trim(); 69 | } 70 | 71 | run(); 72 | -------------------------------------------------------------------------------- /web/docs/guide/faq.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 💬 FAQ 3 | --- 4 | 5 | ## 该功能是否收费? 6 | 7 | GitHub Actions 是由 GitHub 免费提供的。其中 `Private` 项目每月有 2000 次的限制,[具体查看](https://github.com/settings/billing)。`Public` 项目无限制。 8 | 9 | ### 有没有速率的限制? 10 | 11 | 有的。Action 底层使用的是 GitHub REST API。一般情况是每小时 5000 次。原则上基本是够用的,同时也要求在 Action 定义时,尽量避免无效的请求。[具体查看](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting)。 12 | 13 | ## 有没有现成的模板可以参考? 14 | 15 | 有的。 16 | 17 | 1. 你可以使用这个 [GitHub Actions workflow template](https://github.com/actions-cool/.github) 仓库的模板 18 | 2. 个人练习和测试 [Actions](https://github.com/actions-cool/test-issues-helper) 的仓库 19 | 3. 也可以来 [线上使用者](/zh-CN/#-谁在使用?) 的仓库参照 20 | 21 | ## 我想暂停 Actions,有没有简单的办法? 22 | 23 | 有的,你可以将直接修改 `actions`。例如:`actions: 'create-comment'` 修改为 `actions: '#create-comment'`。同时也方便恢复。 24 | 25 | ## 这么多版本,如何选择? 26 | 27 | 你可以查看详细的 [更新日志](/zh-CN/changelog)。推荐采用最新 releases 版本。 28 | 29 | - 版本规则 30 | - 采用两级语义化版本,如v1、v1.1、v2、v2.1 31 | - v1 表示初始版本 32 | - 对 v1 版本的修复和新增会发布到 v1.1 版本 33 | - 当发布的 v1.x 运行一定时间稳定或进行重构时,发布进阶 v2.x 版本 34 | - v2 版本后会严格按照三级语义来发布版本,如 v2.0.0、v2.1.0 35 | 36 | - 版本选择 37 | - 建议采用最新 releases 版本。可在 [releases](https://github.com/actions-cool/issues-helper/releases) 看到 38 | - 同时也可参照下面的更新日志来选择版本 39 | - 最新的 v1.x release 代码会合并到 1.x 分支中 40 | - v2 版本后支持使用 v2 tag,将同步最新 2.x 代码 41 | - 支持直接使用分支版本。如: 42 | 43 | ```yml 44 | - name: Issues Helper 45 | uses: actions-cool/issues-helper@main 46 | 47 | # or 48 | 49 | - name: Issues Helper 50 | uses: actions-cool/issues-helper@1.x 51 | 52 | # or 53 | 54 | - name: Issues Helper 55 | uses: actions-cool/issues-helper@v3 56 | ``` 57 | 58 | ## 从 v1.x 升级到 v2,有什么注意的地方吗? 59 | 60 | v1.12 和 v2.0.0 版本的差别只有一处。即 `mark-duplicate` 中的 `require-permission` 增加了默认值 `write`。 61 | 62 | ## v3 变更 63 | 64 | 🚀 v3 版本重构完成,主要变更内容: 65 | 66 | 1. JS to TS 67 | 2. 将 issue 核心功能封装成为类供 helper 使用 68 | 3. 提示信息统一 69 | 4. 增加自动发布脚本 70 | 71 | 功能变更参考: 72 | 73 | - 🚀 New Feature 74 | - `mark-assignees`: 评论快捷设置 assignees 75 | - `find-issues`: 条件查询当前仓库 issues 76 | - 🐞 Bug Fix 77 | - 修复 `find-comments` 返回结果 direction 未起作用 78 | - 修复 `lock-issues` lock 与 comment 的顺序问题 79 | - 🛠 Refactor 80 | - contents 更名为容易理解的 emoji 81 | - `issue-emojis` 更名为 `issue-emoji` 82 | - deleteComment updateComment 不再支持 `out-comments`,保持纯粹功能 83 | - 移除 title body 默认值 84 | - `month-statistics` 移除 85 | 86 | ## 如果这里没有我想要的功能,该怎么办? 87 | 88 | 你可以在 issues 中提出。 89 | -------------------------------------------------------------------------------- /web/docs/guide/start.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⚡️ 快速开始 3 | --- 4 | 5 | ### 1. 新建 Action 6 | 7 | 点击仓库的 Actions,若已增加过 Actions,会显示如下界面。 8 | 9 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*D5dMQLk2pI0AAAAAAAAAAAAAARQnAQ) 10 | 11 | 点击 `New workflow` 新增。 12 | 13 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*cClPRIW6HKcAAAAAAAAAAAAAARQnAQ) 14 | 15 | :::success{title="提示"} 16 | 你可以点击 set up a workflow yourself 新增一个自定义 action,也可以根据模板来套用新增一个 action。模板使用。 17 | ::: 18 | 19 | ### 2. 编写 Action 20 | 21 | Actions 存放地址是固定的,统一为 `/.github/workflows/xx.yml`。 22 | 23 | 下面拿首页的例子详细说明下。对应场景为:当一个 issue 新增 `help wanted` 标签时,系统会自动进行评论。 24 | 25 | ```yml 26 | name: Issue Reply 27 | 28 | on: 29 | issues: 30 | types: [labeled] 31 | 32 | jobs: 33 | reply-helper: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: help wanted 37 | if: github.event.label.name == 'help wanted' 38 | uses: actions-cool/issues-helper@v3 39 | with: 40 | actions: 'create-comment' 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | issue-number: ${{ github.event.issue.number }} 43 | body: | 44 | Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome PR。 45 | 46 | 你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎PR。 47 | ``` 48 | - `YML` 语法参考 49 | - [GitHub Actions 语法](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows) 50 | - `name`:workflow 名称 51 | - Actions 流程名称,可根据实际情况自定义 52 | - `on`:action 触发条件 53 | - 参考 [工作流触发机制](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows) 54 | - `uses`:使用 actions 名称 55 | - `uses: actions-cool/issues-helper@v2.0.0`。版本选择请 [参考](/zh-CN/changelog) 56 | - `issues-hepler` 参数 57 | - `actions`:使用功能的名称,**必填**。支持多个,需用逗号隔开,如 `create-comment,close-issue` 表示评论和关闭 issue 58 | - `token`:需拥有 push 权限的人员 token 59 | - 更多 [参考](/zh-CN/guide/ref#-token-说明) 60 | - `issue-number`:传入参数,这里表示当前 issue 的编号。如果你对写法疑惑,可 [查看](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) 61 | - `body`:传入参数,这里表示当前进行评论的内容 62 | 63 | ### 3. 启用 Action 64 | 65 | 当你完成编写完成提交到主分支后,即可自动启用该 workflow,触发条件遵循 `on` 的定义。 66 | 67 | 😏 相信到这里你已经对 `如何使用` 有了大概的了解,是不是想快点尝试一下。 68 | 69 | 下面请在 [基 础](/zh-CN/base) 和 [进 阶](/zh-CN/advanced) 查看你需要的功能,灵活参考。 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "description": "A GitHub Action easily helps you automatically manage issues.", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/actions-cool/issues-helper.git", 7 | "branch": "main" 8 | }, 9 | "license": "MIT", 10 | "author": "xrkffgg", 11 | "scripts": { 12 | "dev": "APP_ROOT=web dumi dev", 13 | "start": "APP_ROOT=web dumi dev", 14 | "docs:build": "APP_ROOT=web dumi build", 15 | "docs-dev:build": "APP_ROOT=web UMI_ENV=dev dumi build", 16 | "docs:deploy": "gh-pages -d docs-dist", 17 | "docs:preview": "PREVIEW=true npm run docs:build", 18 | "gh-pages": "npm run docs:build && npm run docs:deploy", 19 | "format": "prettier --write **/*.ts **/*/*.ts", 20 | "format-check": "prettier --check **/*.ts **/*/*.ts", 21 | "lint": "eslint src/*.ts src/*/*.ts", 22 | "lint-fix": "eslint src/*.ts src/*/*.ts --fix", 23 | "lint-up": "npm run format && npm run lint-fix", 24 | "ci:fix": "npm run lint-up", 25 | "lint-all": "npm run format-check && npm run lint", 26 | "check-commit": "node ./scripts/check-commit.js", 27 | "tag": "node ./scripts/tag.js", 28 | "release": "node ./scripts/release", 29 | "test": "father test", 30 | "package": "ncc build src/main.ts -o dist", 31 | "users": "node ./scripts/update-users.js", 32 | "version": "node ./scripts/update-version.js", 33 | "pub": "sh -e ./scripts/pub.sh", 34 | "all": "npm run lint-all && npm run test && npm run package" 35 | }, 36 | "dependencies": { 37 | "@actions/core": "^1.10.0", 38 | "@actions/github": "^4.0.0", 39 | "@octokit/rest": "^18.0.12", 40 | "actions-util": "^1.1.3", 41 | "dayjs": "^1.9.7", 42 | "lodash": "^4.17.20" 43 | }, 44 | "devDependencies": { 45 | "@trivago/prettier-plugin-sort-imports": "^2.0.4", 46 | "@types/lodash": "^4.14.175", 47 | "@typescript-eslint/parser": "^4.15.2", 48 | "@umijs/fabric": "^2.5.6", 49 | "@vercel/ncc": "0.34.0", 50 | "chalk": "^4.1.2", 51 | "common-tags": "^1.8.2", 52 | "dumi": "^2.1.21", 53 | "eslint": "^7.18.0", 54 | "eslint-plugin-github": "^4.1.1", 55 | "eslint-plugin-simple-import-sort": "^7.0.0", 56 | "father": "^2.30.7", 57 | "gh-pages": "^3.1.0", 58 | "new-github-release-url": "^1.0.0", 59 | "open": "^7.3.0", 60 | "prettier": "^2.2.1", 61 | "simple-git": "^2.46.0", 62 | "typescript": "^4.1.3" 63 | }, 64 | "resolutions": { 65 | "types-ramda": "0.29.4" 66 | } 67 | } -------------------------------------------------------------------------------- /web/docs/guide/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ⚡️ Quick start 3 | --- 4 | 5 | ### 1. New Action 6 | 7 | Click Actions in the warehouse, if Actions have been added, the following interface will be displayed. 8 | 9 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*D5dMQLk2pI0AAAAAAAAAAAAAARQnAQ) 10 | 11 | Click `New workflow` to add. 12 | 13 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*cClPRIW6HKcAAAAAAAAAAAAAARQnAQ) 14 | 15 | :::success{title="Tips"} 16 | You can click set up a workflow yourself to add a custom action, or you can apply a new action based on a template. Templates. 17 | ::: 18 | 19 | ### 2. Edit Action 20 | 21 | Actions storage address is fixed, unified as `/.github/workflows/xx.yml`. 22 | 23 | Let's take the example of the home page and explain it in detail. The corresponding scenario is: when an issue adds the `help wanted` tag, the system will automatically comment. 24 | 25 | ```yml 26 | name: Issue Reply 27 | 28 | on: 29 | issues: 30 | types: [labeled] 31 | 32 | jobs: 33 | reply-helper: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: help wanted 37 | if: github.event.label.name == 'help wanted' 38 | uses: actions-cool/issues-helper@v3 39 | with: 40 | actions: 'create-comment' 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | issue-number: ${{ github.event.issue.number }} 43 | body: | 44 | Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome PR。 45 | 46 | 你好 @${{ github.event.issue.user.login }},我们完全同意你的提议/反馈,欢迎PR。 47 | ``` 48 | - `YML` syntax reference 49 | - [Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows) 50 | - `name`: The workflow name 51 | - Actions workflow name, can be customized according to actual situation 52 | - `on`: The action trigger condition 53 | - Reference [Events that trigger workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows) 54 | - `uses`: Use actions name 55 | - `uses: actions-cool/issues-helper@v2.0.0`。Please [refer](/changelog) to version selection 56 | - `issues-hepler` parameter 57 | - `actions`: The name of the function used, **required**. Support multiple, separated by commas, such as `create-comment,close-issue` means comment and close issue 58 | - `token`: A person who needs to have push permission token 59 | - [More view](/guide/ref#-token) 60 | - `issue-number`: Incoming parameter, here means the number of the current issue. If you are confused about the writing, you can [view](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) 61 | - `body`: Incoming parameters, here means the content of the current comment 62 | 63 | ### 3. Enable Action 64 | 65 | When you finish writing and submit to the master branch, you can automatically enable the workflow, and the trigger conditions follow the definition of `on`. 66 | 67 | 😏 I believe that you have a general understanding of `how to use`, do you want to try it quickly? 68 | 69 | Please check the functions you need in [Basic](/base) and [Advanced](/advanced) for flexible reference. 70 | -------------------------------------------------------------------------------- /src/issue/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | TCloseReason, 3 | TEmoji, 4 | TIssueState, 5 | TLockReasons, 6 | TUpdateMode, 7 | TUserPermission, 8 | } from '../types'; 9 | 10 | export interface IIssueBaseInfo { 11 | owner: string; 12 | repo: string; 13 | issueNumber: number; 14 | token: string; 15 | } 16 | 17 | export interface IListIssuesParams { 18 | state: TIssueState | 'all'; 19 | creator?: string; 20 | assignee?: string; 21 | mentioned?: string; 22 | labels?: string; 23 | } 24 | 25 | export type TIssueInfo = { 26 | number: number; 27 | title: string; 28 | body: string; 29 | user: { 30 | login: string; 31 | }; 32 | assignees: { 33 | login: string; 34 | }[]; 35 | labels: { 36 | name: string; 37 | }[]; 38 | state: TIssueState; 39 | created_at: string; 40 | updated_at: string; 41 | pull_request?: any; 42 | locked?: boolean; 43 | }; 44 | 45 | export type TIssueList = TIssueInfo[]; 46 | 47 | export type TCommentInfo = { 48 | id: number; 49 | body: string; 50 | user: { 51 | login: string; 52 | }; 53 | created_at: string; 54 | updated_at: string; 55 | }; 56 | 57 | export type TCommentList = TCommentInfo[]; 58 | 59 | export interface IIssueCoreEngine { 60 | setIssueNumber: (newIssueNumber: number) => void; 61 | addAssignees: (assignees: string[]) => Promise; 62 | addLabels: (labels: string[]) => Promise; 63 | 64 | closeIssue: (reason: TCloseReason) => Promise; 65 | /** 66 | * @param body The comment body. 67 | * @returns The create new comment id. 68 | */ 69 | createComment: (body: string) => Promise; 70 | createCommentEmoji: (commentId: number, emoji: TEmoji[]) => Promise; 71 | /** 72 | * @param title 73 | * @param body 74 | * @param labels 75 | * @param assignees 76 | * @returns The create new issue number. 77 | */ 78 | createIssue: ( 79 | title: string, 80 | body: string, 81 | labels?: string[], 82 | assignees?: string[], 83 | ) => Promise; 84 | createIssueEmoji: (emoji: TEmoji[]) => Promise; 85 | createLabel: ( 86 | labelName: string, 87 | labelColor: string | undefined, 88 | labelDescription: string | undefined, 89 | ) => Promise; 90 | 91 | deleteComment: (commentId: number) => Promise; 92 | 93 | getIssue: () => Promise; 94 | getUserPermission: (username: string) => Promise; 95 | 96 | listComments: () => Promise; 97 | listIssues: (params: IListIssuesParams) => Promise; 98 | lockIssue: (lockReason: TLockReasons) => Promise; 99 | 100 | openIssue: () => Promise; 101 | 102 | removeAssignees: (assignees: string[]) => Promise; 103 | removeLabels: (labels: string[]) => Promise; 104 | 105 | setLabels: (labels: string[]) => Promise; 106 | 107 | unlockIssue: () => Promise; 108 | 109 | updateComment: (commentId: number, body: string, mode: TUpdateMode) => Promise; 110 | updateIssue: ( 111 | state: TIssueState, 112 | title: string | void, 113 | body: string | void, 114 | mode: TUpdateMode, 115 | labels?: string[] | void, 116 | assignees?: string[] | void, 117 | ) => Promise; 118 | } 119 | -------------------------------------------------------------------------------- /web/remark-plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { unistUtilVisit } from 'dumi'; 2 | 3 | const WRAPPER_OPEN_TAG = ' { 8 | unistUtilVisit.visit(tree, 'html', (node, index, parent) => { 9 | if (node.value.startsWith(WRAPPER_OPEN_TAG)) { 10 | // get attributes 11 | const attrMatch = node.value.match(/(\w+)=['"]([^'"]+)['"]/g); 12 | 13 | // convert attributes to object 14 | const attrObj = attrMatch.reduce((acc, cur) => { 15 | const split = cur.split('='); 16 | const key = split[0].trim().replace(/['"]/g, ''); 17 | const value = split[1].trim().replace(/['"]/g, ''); 18 | 19 | if (value.match(/^-?\d+$/)) { 20 | acc[key] = Number(value); 21 | } else { 22 | acc[key] = value; 23 | } 24 | 25 | return acc; 26 | }, {}); 27 | 28 | // transform headings 29 | const depth = Number.isInteger(attrObj['depth']) ? attrObj['depth'] : 0; 30 | unistUtilVisit.visit(tree, 'heading', (node: any) => { 31 | node.depth = node.depth - depth; 32 | }); 33 | 34 | // transform links 35 | unistUtilVisit.visit(tree, 'link', (node, index, parent) => { 36 | /** 37 | * remove blacktop link 38 | * zh-CN: url="#列-表", en-US: url="#List" 39 | */ 40 | if (['#列-表', '#List'].includes(node.url)) { 41 | if (parent && typeof index === 'number') { 42 | parent.children.splice( 43 | index, 44 | 1, 45 | // ...('children' in node ? node.children : []) // 不需要保留子节点 46 | ); 47 | } 48 | return unistUtilVisit.CONTINUE; 49 | } 50 | 51 | /** 52 | * redirect: 53 | * 1. token 说明 54 | */ 55 | if (node.url === '#token') { 56 | node.url = `./guide/ref${node.url}`; 57 | return unistUtilVisit.CONTINUE; 58 | } 59 | 60 | if (['#emoji-类型', '#emoji-types'].includes(node.url)) { 61 | node.url = `./guide/ref${node.url}`; 62 | return unistUtilVisit.CONTINUE; 63 | } 64 | 65 | if (node.url === '#github-docs') { 66 | node.url = `./guide/ref${node.url}`; 67 | return unistUtilVisit.CONTINUE; 68 | } 69 | 70 | if (['#outputs-使用', '#outputs-use'].includes(node.url)) { 71 | node.url = `./guide/ref${node.url}`; 72 | return unistUtilVisit.CONTINUE; 73 | } 74 | 75 | if (['#校验规则', '#check-rules'].includes(node.url)) { 76 | node.url = `./guide/ref${node.url}`; 77 | return unistUtilVisit.CONTINUE; 78 | } 79 | 80 | if (node.url === '#comment-id') { 81 | node.url = `./guide/ref${node.url}`; 82 | return unistUtilVisit.CONTINUE; 83 | } 84 | }); 85 | } 86 | 87 | if ( 88 | (node.value === WRAPPER_CLOSE_TAG || node.value.startsWith(WRAPPER_OPEN_TAG)) && 89 | parent && 90 | typeof index === 'number' 91 | ) { 92 | if (parent && typeof index === 'number') { 93 | parent.children.splice(index, 1); 94 | } 95 | } 96 | }); 97 | }; 98 | } 99 | 100 | export default remarkPlugin; 101 | -------------------------------------------------------------------------------- /.github/workflows/preview-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Preview Deploy 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Preview Build"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | success: 11 | runs-on: ubuntu-latest 12 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' 13 | steps: 14 | - name: download pr artifact 15 | uses: dawidd6/action-download-artifact@v6 16 | with: 17 | workflow: ${{ github.event.workflow_run.workflow_id }} 18 | name: pr 19 | 20 | - name: save PR id 21 | id: pr 22 | run: echo "::set-output name=id::$( 48 | 49 | 50 | body-include: '' 51 | number: ${{ steps.pr.outputs.id }} 52 | 53 | - name: The job failed 54 | if: ${{ failure() }} 55 | uses: actions-cool/maintain-one-comment@v1.1.0 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | body: | 59 | 😭 Deploy PR Preview failed. 60 | 61 | 62 | 63 | 64 | body-include: '' 65 | number: ${{ steps.pr.outputs.id }} 66 | 67 | failed: 68 | runs-on: ubuntu-latest 69 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' 70 | steps: 71 | - name: download pr artifact 72 | uses: dawidd6/action-download-artifact@v6 73 | with: 74 | workflow: ${{ github.event.workflow_run.workflow_id }} 75 | name: pr 76 | 77 | - name: save PR id 78 | id: pr 79 | run: echo "::set-output name=id::$( 89 | 90 | 91 | body-include: '' 92 | number: ${{ steps.pr.outputs.id }} 93 | -------------------------------------------------------------------------------- /scripts/update-users.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, writeFileSync } = require('fs'); 2 | const { stripIndent } = require('common-tags'); 3 | 4 | // ************************************************************************** 5 | 6 | let { users } = require('../USERS.js'); 7 | 8 | users.sort((a, b) => getCurrentName(a).localeCompare(getCurrentName(b))); 9 | 10 | // ************************************************************************** 11 | const DEFAULT_WIDTH = 46; 12 | 13 | // ************************************************************************** 14 | let table = ''; 15 | let row = users.length / 4; 16 | let lastNo = users.length % 4; 17 | if (lastNo != 0) row += 1; 18 | for (let j = 1; j <= row; j++) { 19 | let data = ''; 20 | data = stripIndent` 21 | 22 | ${getImg(users[(j - 1) * 4])} 23 | ${getImg(users[(j - 1) * 4 + 1])} 24 | ${getImg(users[(j - 1) * 4 + 2])} 25 | ${getImg(users[(j - 1) * 4 + 3])} 26 | 27 | 28 | ${getName(users[(j - 1) * 4])} 29 | ${getName(users[(j - 1) * 4 + 1])} 30 | ${getName(users[(j - 1) * 4 + 2])} 31 | ${getName(users[(j - 1) * 4 + 3])} 32 | 33 | `; 34 | table += data; 35 | } 36 | 37 | table = ` 38 | ${table} 39 |
40 | 41 | `; 42 | 43 | // ************************************************************************** 44 | 45 | const point = ''; 46 | const cnPoint = `## 图标`; 47 | const enPoint = `## Badge`; 48 | const ReadmeCN = './README.zh-CN.md'; 49 | const ReadmeEN = './README.md'; 50 | 51 | // ************************************************************************** 52 | 53 | const cn = readFileSync(ReadmeCN, 'utf8'); 54 | const cnIn = cn.indexOf(point); 55 | const cnAfterIn = cn.indexOf(cnPoint); 56 | const cnBefore = cn.substring(0, cnIn); 57 | const cnAfter = cn.substring(cnAfterIn, cn.length); 58 | const newcn = cnBefore + table + cnAfter; 59 | writeFileSync(ReadmeCN, newcn); 60 | console.log(`🎉 Done cn`); 61 | 62 | // ************************************************************************** 63 | 64 | const en = readFileSync(ReadmeEN, 'utf8'); 65 | const enIn = en.indexOf(point); 66 | const enAfterIn = en.indexOf(enPoint); 67 | const enBefore = en.substring(0, enIn); 68 | const enAfter = en.substring(enAfterIn, en.length); 69 | const newen = enBefore + table + enAfter; 70 | writeFileSync(ReadmeEN, newen); 71 | console.log(`🎉 Done en`); 72 | 73 | // ************************************************************************** 74 | 75 | function getImg(o) { 76 | if (o) { 77 | return ` 78 | 79 | 80 | 81 | `; 82 | } 83 | return ``; 84 | } 85 | 86 | function getImgWidth(o) { 87 | if (o) { 88 | let width = o.width; 89 | if (width === 'auto') { 90 | width = ''; 91 | } else { 92 | width = width ? width : DEFAULT_WIDTH; 93 | } 94 | return ` width="${width}"`; 95 | } 96 | return ''; 97 | } 98 | 99 | function getName(o) { 100 | if (o) { 101 | return `${o.url.split('/').slice(-1)[0]}`; 102 | } 103 | return ``; 104 | } 105 | 106 | function getCurrentName(o) { 107 | if (o) { 108 | return o.url.split('/').slice(-1)[0]; 109 | } 110 | return ``; 111 | } 112 | 113 | // ************************************************************************** 114 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Issues Helper' 2 | description: 'A GitHub Action that easily helps you automatically manage issues' 3 | author: 'xrkffgg' 4 | 5 | # https://actions-cool.github.io/github-action-branding/ 6 | branding: 7 | icon: 'message-square' 8 | color: 'black' 9 | 10 | inputs: 11 | actions: 12 | description: 'Action name' 13 | token: 14 | description: 'Github_token' 15 | default: ${{ github.token }} 16 | repo: 17 | description: 'The repositorie' 18 | 19 | issue-number: 20 | description: 'Issue-number' 21 | comment-id: 22 | description: 'Comment-id' 23 | body: 24 | description: 'Issue body' 25 | title: 26 | description: 'Issue title' 27 | assignees: 28 | description: 'Issue assignees' 29 | random-to: 30 | description: 'Issue assignees random to' 31 | close-reason: 32 | description: 'Issue close reason' 33 | 34 | # label 35 | labels: 36 | description: 'Issue labels' 37 | label-name: 38 | description: 'Create label name' 39 | label-color: 40 | description: 'Create label color, default #ededed' 41 | label-desc: 42 | description: 'Create label description' 43 | 44 | state: 45 | description: 'Issue state' 46 | update-mode: 47 | description: 'Body update mode' 48 | emoji: 49 | description: 'Issue reactions emoji' 50 | direction: 51 | description: 'Find direction' 52 | 53 | # comments 54 | comment-auth: 55 | description: 'Find comments query auth' 56 | assignee-includes: 57 | description: 'Check use' 58 | body-includes: 59 | description: 'Query use' 60 | 61 | # check 62 | title-excludes: 63 | description: 'Remove some to check title whether empty.' 64 | title-includes: 65 | description: 'Query use' 66 | issue-creator: 67 | description: 'Query use' 68 | issue-assignee: 69 | description: 'Query use' 70 | issue-mentioned: 71 | description: 'Query use' 72 | issue-emoji: 73 | description: 'For welcome' 74 | issue-state: 75 | description: 'Query use' 76 | inactive-day: 77 | description: 'Query use' 78 | inactive-mode: 79 | description: 'Inactive mode' 80 | lock-reason: 81 | description: 'The reason lock issue' 82 | inactive-label: 83 | description: 'Issue label set use' 84 | exclude-labels: 85 | description: 'Query issues exclude labels' 86 | assign-command: 87 | description: 'For mark-assigness' 88 | duplicate-command: 89 | description: 'For mark-duplicate' 90 | duplicate-labels: 91 | description: 'For mark-duplicate add labels' 92 | require-permission: 93 | description: 'Only the allow can do. Possible admin, write, read, and none.' 94 | remove-labels: 95 | description: 'For remove labels' 96 | close-issue: 97 | description: 'For mark-duplicate' 98 | show-thanks: 99 | description: 'Whether to print the thank-you banner' 100 | required: false 101 | default: 'true' 102 | create-issue-if-not-exist: 103 | description: 'Create a new issue if not exist when find-issues' 104 | exclude-issue-numbers: 105 | description: 'Exclude the specified issues' 106 | 107 | outputs: 108 | issue-number: 109 | description: 'Issue Number' 110 | issue-title: 111 | description: 'Issue Title' 112 | issue-body: 113 | description: 'Issue Body' 114 | issue-labels: 115 | description: 'Issue labels' 116 | issue-assignees: 117 | description: 'Issue assignees' 118 | issue-state: 119 | description: 'Issue state' 120 | comment-id: 121 | description: 'Create comment ID' 122 | comments: 123 | description: 'Find comments' 124 | issues: 125 | description: 'Find issues' 126 | check-result: 127 | description: 'Check issue' 128 | 129 | runs: 130 | using: node20 131 | main: 'dist/index.js' 132 | -------------------------------------------------------------------------------- /web/docs/guide/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 💬 FAQ 3 | --- 4 | 5 | ## Is there a charge for this feature? 6 | 7 | GitHub Actions is provided free of charge by GitHub. Among them, the `Private` project has a monthly limit of 2000 times, [see details](https://github.com/settings/billing). The `Public` project is unlimited. 8 | 9 | ### Is there a rate limit? 10 | 11 | Yes. The bottom layer of Action uses GitHub REST API. The general situation is 5000 times per hour. It is basically sufficient in principle, and it is also required to avoid invalid requests when defining Action. [Detailed view](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). 12 | 13 | ## Are there any ready-made templates for reference? 14 | 15 | Yes. 16 | 17 | 1. You can use this [GitHub Actions workflow template](https://github.com/actions-cool/.github) repository template 18 | 2. Personal exercises and tests [Actions](https://github.com/actions-cool/test-issues-helper) repository 19 | 3. You can also refer to the warehouse of [online users](#-who-is-using) 20 | 21 | ## I want to pause Actions, is there an easy way? 22 | 23 | Yes, you can directly modify `actions`. For example: `actions:'create-comment'` is changed to `actions:'#create-comment'`. It is also convenient for recovery. 24 | 25 | ## So many versions, how to choose? 26 | 27 | You can view the detailed [changelog](/changelog). The latest releases version is recommended. 28 | 29 | - Version rules 30 | - Use two-level semantic version, such as v1, v1.1, v2, v2.1 31 | - v1 represents the initial version 32 | - The fixes and additions to the v1 version will be released to the v1.1 version 33 | - When the released v1.x runs stable for a certain period of time or undergoes refactoring, release the advanced v2.x version 34 | - After the v2 version, the version will be released strictly according to the three-level semantics, such as v2.0.0, v2.1.0 35 | 36 | - Version selection 37 | - It is recommended to use the latest releases version. It can be seen in [releases](https://github.com/actions-cool/issues-helper/releases) 38 | - You can also refer to the update log below to select the version 39 | - The latest v1.x release code will be merged into the 1.x branch 40 | - After the v2 version, the v2 tag is supported, and the latest 2.x code will be synchronized 41 | - It also supports the direct use of branch versions. Such as: 42 | 43 | ```yml 44 | - name: Issues Helper 45 | uses: actions-cool/issues-helper@main 46 | 47 | # or 48 | 49 | - name: Issues Helper 50 | uses: actions-cool/issues-helper@1.x 51 | 52 | # or 53 | 54 | - name: Issues Helper 55 | uses: actions-cool/issues-helper@v2 56 | ``` 57 | 58 | ## What should I pay attention to when upgrading from v1.x to v2? 59 | 60 | There is only one difference between v1.12 and v2.0.0. That is, `require-permission` in `mark-duplicate` has added the default value `write`. 61 | 62 | ## v3 changelog 63 | 64 | 🚀 The refactoring of the v3 version is completed. The main changes are: 65 | 66 | 1. JS to TS 67 | 2. Encapsulate the core functions of the issue into classes for helpers to use 68 | 3. Unified prompt information 69 | 4. Added automatic release script 70 | 71 | Reference for functional changes: 72 | 73 | - 🚀 New Feature 74 | - `mark-assignees`: Comment quick settings assignees 75 | - `find-issues`: Conditional query current warehouse issues 76 | - 🐞 Bug Fix 77 | - Fixed `find-comments` return result direction not working 78 | - Fix `lock-issues` lock and comment order issue 79 | - 🛠 Refactor 80 | - `contents` renamed to easy-to-understand `emoji` 81 | - `issue-emojis` renamed to `issue-emoji` 82 | - deleteComment updateComment no longer supports `out-comments`, keeping pure functionality 83 | - Remove title body default 84 | - `month-statistics` removed 85 | 86 | ## What should I do if there is no function I want here? 87 | 88 | You can submit it in [What do you want?](https://github.com/actions-cool/issues-helper/discussions/18). 89 | -------------------------------------------------------------------------------- /USERS.js: -------------------------------------------------------------------------------- 1 | // ************************************************************************** 2 | // step1: add to end 3 | // step2: npm run users 4 | // step3: push 3 files & open a new PR 5 | 6 | /** 7 | * @param {string} url github repo 8 | * @param {string} logo logo url 9 | * @param {string} width auto use rectangle logo 10 | */ 11 | 12 | const users = [ 13 | { 14 | url: 'https://github.com/ant-design/ant-design', 15 | logo: 'https://avatars1.githubusercontent.com/u/12101536?s=200&v=4' 16 | }, 17 | { 18 | url: 'https://github.com/vueComponent/ant-design-vue', 19 | logo: 'https://avatars1.githubusercontent.com/u/32120805?s=200&v=4' 20 | }, 21 | { 22 | url: 'https://github.com/umijs/dumi', 23 | logo: 'https://avatars1.githubusercontent.com/u/33895495?s=200&v=4' 24 | }, 25 | { 26 | url: 'https://github.com/umijs/umi', 27 | logo: 'https://avatars1.githubusercontent.com/u/33895495?s=200&v=4' 28 | }, 29 | { 30 | url: 'https://github.com/AttoJS/vue-request', 31 | logo: 'https://user-images.githubusercontent.com/29775873/129506134-55044c85-24cd-47d3-81ef-dba842214d71.png' 32 | }, 33 | { 34 | url: 'https://github.com/mui/material-ui', 35 | logo: 'https://avatars.githubusercontent.com/u/33663932?s=200&v=4' 36 | }, 37 | { 38 | url: 'https://github.com/lijinke666/react-music-player', 39 | logo: 'https://user-images.githubusercontent.com/29775873/129506058-b0d8c741-f73a-496c-98de-7db2fb586db7.png' 40 | }, 41 | { 42 | url: 'https://github.com/ant-design-blazor/ant-design-blazor', 43 | logo: 'https://user-images.githubusercontent.com/29775873/129505619-5abddb68-8663-4c71-b7d2-049c716aab26.png' 44 | }, 45 | { 46 | url: 'https://github.com/zoo-js/zoo', 47 | logo: 'https://avatars1.githubusercontent.com/u/70757173?s=200&v=4' 48 | }, 49 | { 50 | url: 'https://github.com/react-component', 51 | logo: 'https://avatars3.githubusercontent.com/u/9441414?s=200&v=4' 52 | }, 53 | { 54 | url: 'https://github.com/prettier/prettier', 55 | logo: 'https://user-images.githubusercontent.com/29775873/129505900-ca248179-2435-429d-9fd3-779206bcd899.png', 56 | }, 57 | { 58 | url: 'https://github.com/vuejs/jsx-next', 59 | logo: 'https://avatars.githubusercontent.com/u/6128107?s=200&v=4', 60 | }, 61 | { 62 | url: 'https://github.com/vitejs/vite', 63 | logo: 'https://avatars.githubusercontent.com/u/65625612?s=200&v=4', 64 | }, 65 | { 66 | url: 'https://github.com/jdf2e/nutui', 67 | logo: 'https://img14.360buyimg.com/imagetools/jfs/t1/167902/2/8762/791358/603742d7E9b4275e3/e09d8f9a8bf4c0ef.png', 68 | }, 69 | { 70 | width: 'auto', 71 | url: 'https://github.com/alibaba/formily', 72 | logo: 'https://img.alicdn.com/imgextra/i2/O1CN01Kq3OHU1fph6LGqjIz_!!6000000004056-55-tps-1141-150.svg', 73 | }, 74 | { 75 | url: 'https://github.com/vuepress/vuepress-next', 76 | logo: 'https://v2.vuepress.vuejs.org/images/hero.png', 77 | }, 78 | { 79 | url: 'https://github.com/element-plus/element-plus', 80 | logo: 'https://avatars.githubusercontent.com/u/68583457', 81 | }, 82 | { 83 | url: 'https://github.com/antvis/S2', 84 | logo: 'https://gw.alipayobjects.com/mdn/rms_56cbb2/afts/img/A*TI8XSK3W0EkAAAAAAAAAAAAAARQnAQ', 85 | }, 86 | { 87 | url: 'https://github.com/twbs/bootstrap', 88 | logo: 'https://getbootstrap.com/docs/5.1/assets/brand/bootstrap-logo-shadow.png', 89 | }, 90 | { 91 | url: 'https://github.com/nolimits4web/swiper', 92 | logo: 'https://user-images.githubusercontent.com/29775873/156721728-3b0021ea-6932-4a77-a104-2e0bad97346e.png', 93 | }, 94 | { 95 | url: 'https://github.com/vitest-dev/vitest', 96 | logo: 'https://user-images.githubusercontent.com/11247099/145112184-a9ff6727-661c-439d-9ada-963124a281f7.png', 97 | }, 98 | { 99 | url: 'https://github.com/electron/electron', 100 | logo: 'https://avatars.githubusercontent.com/u/13409222?s=200&v=4', 101 | }, 102 | { 103 | url: 'https://github.com/ant-design/ant-design-mobile', 104 | logo: 'https://gw.alipayobjects.com/zos/bmw-prod/b874caa9-4458-412a-9ac6-a61486180a62.svg', 105 | }, 106 | { 107 | url: 'https://github.com/vuejs/core', 108 | logo: 'https://avatars.githubusercontent.com/u/6128107?s=48&v=4', 109 | }, 110 | ]; 111 | 112 | // ************************************************************************** 113 | module.exports = { 114 | users 115 | }; 116 | 117 | // ************************************************************************** 118 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", 6 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 7 | "module": "commonjs", 8 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./lib", 17 | /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", 19 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, 30 | /* Enable all strict type-checking options. */ 31 | "noImplicitAny": true, 32 | /* Raise error on expressions and declarations with an implied 'any' type. */ 33 | // "strictNullChecks": true, /* Enable strict null checks. */ 34 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 35 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 36 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 37 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 38 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 39 | 40 | /* Additional Checks */ 41 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 42 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 43 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 44 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true 55 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 56 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 57 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 58 | 59 | /* Source Map Options */ 60 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 61 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 63 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 64 | 65 | /* Experimental Options */ 66 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 67 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 68 | }, 69 | "exclude": [ 70 | "node_modules", 71 | "lib", 72 | "dist", 73 | "docs-dist", 74 | "web", 75 | "**/*.test.ts" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /src/helper/base.ts: -------------------------------------------------------------------------------- 1 | import { dealStringToArr } from 'actions-util'; 2 | 3 | import * as core from '../core'; 4 | import type { IIssueCoreEngine } from '../issue'; 5 | import { ELockReasons } from '../shared'; 6 | import type { TCloseReason, TEmoji, TIssueState, TLockReasons, TUpdateMode } from '../types'; 7 | 8 | let ICE: IIssueCoreEngine; 9 | export function initBaseICE(_ICE: IIssueCoreEngine) { 10 | ICE = _ICE; 11 | } 12 | 13 | export async function doAddAssignees(assignees: string[]) { 14 | await ICE.addAssignees(assignees); 15 | core.info(`[doAddAssignees] [${assignees}] success!`); 16 | } 17 | 18 | export async function doAddLabels(labels: string[], issueNumber?: number) { 19 | if (issueNumber) ICE.setIssueNumber(issueNumber); 20 | await ICE.addLabels(labels); 21 | core.info(`[doAddLabels] [${labels}] success!`); 22 | } 23 | 24 | export async function doCloseIssue(reason: TCloseReason, issueNumber?: number) { 25 | if (issueNumber) ICE.setIssueNumber(issueNumber); 26 | await ICE.closeIssue(reason); 27 | core.info(`[doCloseIssue] success!`); 28 | } 29 | 30 | export async function doCreateComment(body: string, emoji?: string, issueNumber?: number) { 31 | if (body) { 32 | if (issueNumber) ICE.setIssueNumber(issueNumber); 33 | const commentId = await ICE.createComment(body); 34 | core.info(`[doCreateComment] [${body}] success!`); 35 | core.setOutput('comment-id', commentId); 36 | if (emoji) { 37 | await doCreateCommentEmoji(commentId, emoji); 38 | } 39 | } else { 40 | core.warning(`[doCreateComment] body is empty!`); 41 | } 42 | } 43 | 44 | export async function doCreateCommentEmoji(_commentId: number | void, emoji: string) { 45 | const commentId = _commentId || core.getInput('comment-id'); 46 | if (emoji && commentId) { 47 | await ICE.createCommentEmoji(+commentId, dealStringToArr(emoji) as TEmoji[]); 48 | core.info(`[doCreateCommentEmoji] [${emoji}] success!`); 49 | } else { 50 | core.warning(`[doCreateCommentEmoji] emoji or commentId is empty!`); 51 | } 52 | } 53 | 54 | export async function doCreateIssue( 55 | title: string, 56 | body: string, 57 | labels?: string[], 58 | assignees?: string[], 59 | emoji?: string | void, 60 | ) { 61 | if (title) { 62 | const issueNumber = await ICE.createIssue(title, body, labels, assignees); 63 | core.info(`[doCreateIssue] [${title}] success!`); 64 | core.setOutput('issue-number', issueNumber); 65 | if (emoji) { 66 | ICE.setIssueNumber(issueNumber); 67 | await ICE.createIssueEmoji(dealStringToArr(emoji) as TEmoji[]); 68 | core.info(`[createIssueEmoji] [${emoji}] success!`); 69 | } 70 | } else { 71 | core.warning(`[doCreateIssue] title is empty!`); 72 | } 73 | } 74 | 75 | export async function doCreateLabel() { 76 | const name = core.getInput('label-name'); 77 | const color = core.getInput('label-color'); 78 | const description = core.getInput('label-desc'); 79 | 80 | if (name) { 81 | await ICE.createLabel(name, color, description); 82 | core.info(`[doCreateLabel] [${name}] success!`); 83 | } else { 84 | core.warning(`[doCreateLabel] label-name is empty!`); 85 | } 86 | } 87 | 88 | export async function doDeleteComment(_commentId: number | void) { 89 | const commentId = _commentId || core.getInput('comment-id'); 90 | if (commentId) { 91 | await ICE.deleteComment(+commentId); 92 | core.info(`[doDeleteComment] [${commentId}] success!`); 93 | } else { 94 | core.warning(`[doDeleteComment] commentId is empty!`); 95 | } 96 | } 97 | 98 | export async function doGetIssue() { 99 | const { number, title, body, state, labels, assignees } = await ICE.getIssue(); 100 | core.setOutput('issue-number', number); 101 | core.setOutput('issue-title', title || ''); 102 | core.setOutput('issue-body', body || ''); 103 | core.setOutput('issue-state', state); 104 | const labelsString = labels.length ? labels.map(({ name }) => name).join(',') : ''; 105 | core.setOutput('issue-labels', labelsString); 106 | const assigneesString = assignees.length ? assignees.map(({ login }) => login).join(',') : ''; 107 | core.setOutput('issue-assignees', assigneesString); 108 | } 109 | 110 | export async function doLockIssue(issueNumber?: number) { 111 | if (issueNumber) ICE.setIssueNumber(issueNumber); 112 | const lockReason = (core.getInput('lock-reason') || '') as TLockReasons; 113 | if (lockReason && !ELockReasons[lockReason]) { 114 | core.warning(`[doLockIssue] lock-reason is illegal!`); 115 | return; 116 | } 117 | await ICE.lockIssue(lockReason as TLockReasons); 118 | core.info(`[doLockIssue] success!`); 119 | } 120 | 121 | export async function doOpenIssue() { 122 | await ICE.openIssue(); 123 | core.info(`[doOpenIssue] success!`); 124 | } 125 | 126 | export async function doRemoveAssignees(assignees: string[]) { 127 | await ICE.removeAssignees(assignees); 128 | core.info(`[doRemoveAssignees] [${assignees}] success!`); 129 | } 130 | 131 | export async function doRemoveLabels(labels: string[]) { 132 | await ICE.removeLabels(labels); 133 | core.info(`[doRemoveLabels] [${labels}] success!`); 134 | } 135 | 136 | export async function doSetLabels(labels: string[]) { 137 | await ICE.setLabels(labels); 138 | core.info(`[doSetLabels] [${labels}] success!`); 139 | } 140 | 141 | export async function doUnlockIssue() { 142 | await ICE.unlockIssue(); 143 | core.info(`[doUnlockIssue] success!`); 144 | } 145 | 146 | export async function doUpdateComment( 147 | _commentId: number | void, 148 | body: string, 149 | updateMode: TUpdateMode, 150 | emoji: string | void, 151 | ) { 152 | const commentId = _commentId || core.getInput('comment-id'); 153 | if (commentId) { 154 | await ICE.updateComment(+commentId, body, updateMode); 155 | core.info(`[doUpdateComment] [${commentId}] success!`); 156 | if (emoji) { 157 | await doCreateCommentEmoji(+commentId, emoji); 158 | } 159 | } else { 160 | core.warning(`[doUpdateComment] commentId is empty!`); 161 | } 162 | } 163 | 164 | export async function doUpdateIssue( 165 | issueNumber: number, 166 | state: TIssueState, 167 | title: string | void, 168 | body: string | void, 169 | updateMode: TUpdateMode, 170 | labels?: string[] | void, 171 | assignees?: string[] | void, 172 | ) { 173 | if (issueNumber) ICE.setIssueNumber(issueNumber); 174 | await ICE.updateIssue(state, title, body, updateMode, labels, assignees); 175 | core.info(`[doUpdateIssue] success!`); 176 | } 177 | -------------------------------------------------------------------------------- /src/helper/helper.ts: -------------------------------------------------------------------------------- 1 | import { dealStringToArr } from 'actions-util'; 2 | 3 | import * as core from '../core'; 4 | import type { IIssueCoreEngine, TCommentInfo } from '../issue'; 5 | import { IssueCoreEngine } from '../issue'; 6 | import type { Context, TAction, TCloseReason, TIssueState, TUpdateMode } from '../types'; 7 | import { dealRandomAssignees } from '../util'; 8 | import { 9 | doCheckInactive, 10 | doCheckIssue, 11 | doCloseIssues, 12 | doFindComments, 13 | doFindIssues, 14 | doLockIssues, 15 | doMarkAssignees, 16 | doMarkDuplicate, 17 | doToggleLabels, 18 | doWelcome, 19 | initAdvancedICE, 20 | } from './advanced'; 21 | import { 22 | doAddAssignees, 23 | doAddLabels, 24 | doCloseIssue, 25 | doCreateComment, 26 | doCreateIssue, 27 | doCreateLabel, 28 | doDeleteComment, 29 | doGetIssue, 30 | doLockIssue, 31 | doOpenIssue, 32 | doRemoveAssignees, 33 | doRemoveLabels, 34 | doSetLabels, 35 | doUnlockIssue, 36 | doUpdateComment, 37 | doUpdateIssue, 38 | initBaseICE, 39 | } from './base'; 40 | import type { IIssueHelperEngine } from './types'; 41 | 42 | export class IssueHelperEngine implements IIssueHelperEngine { 43 | private ICE!: IIssueCoreEngine; 44 | 45 | private owner!: string; 46 | private repo!: string; 47 | private issueNumber!: number; 48 | 49 | private emoji?: string; 50 | private labels?: string[]; 51 | private assignees?: string[]; 52 | private title: string = ''; 53 | private body: string = ''; 54 | private state: TIssueState = 'open'; 55 | private updateMode: TUpdateMode = 'replace'; 56 | private closeReason: TCloseReason = 'not_planned'; 57 | 58 | public constructor(readonly ctx: Context) { 59 | this.initInput(ctx); 60 | this.initIssueCore(); 61 | initBaseICE(this.ICE); 62 | initAdvancedICE(this.ICE); 63 | } 64 | 65 | private initInput(ctx: Context) { 66 | // No display to outside 67 | const repoInput = core.getInput('repo'); 68 | if (repoInput) { 69 | this.owner = repoInput.split('/')[0]; 70 | this.repo = repoInput.split('/')[1]; 71 | } else { 72 | this.owner = ctx.repo.owner; 73 | this.repo = ctx.repo.repo; 74 | } 75 | 76 | let defaultCtxNumber: number | undefined; 77 | if (ctx.eventName === 'issues' || ctx.eventName === 'issue_comment') { 78 | defaultCtxNumber = ctx.payload.issue?.number; 79 | } 80 | const issueNumber = core.getInput('issue-number') || defaultCtxNumber; 81 | if (issueNumber) { 82 | this.issueNumber = +issueNumber; 83 | } 84 | 85 | this.emoji = core.getInput('emoji') || ''; 86 | this.labels = dealStringToArr(core.getInput('labels') || ''); 87 | 88 | const assigneesInput = core.getInput('assignees') || ''; 89 | const randomTo = core.getInput('random-to'); 90 | this.assignees = dealRandomAssignees(assigneesInput, randomTo); 91 | 92 | this.title = core.getInput('title') || ''; 93 | this.body = core.getInput('body') || ''; 94 | this.state = core.getInput('state') === 'closed' ? 'closed' : 'open'; 95 | this.updateMode = core.getInput('update-mode') === 'append' ? 'append' : 'replace'; 96 | this.closeReason = core.getInput('close-reason') === 'completed' ? 'completed' : 'not_planned'; 97 | } 98 | 99 | private initIssueCore() { 100 | const { owner, repo, issueNumber } = this; 101 | const token = core.getInput('token'); 102 | this.ICE = new IssueCoreEngine({ 103 | owner, 104 | repo, 105 | issueNumber, 106 | token, 107 | }); 108 | core.info(`[Init] [${owner}/${repo} => ${issueNumber}]`); 109 | } 110 | 111 | public async doExeAction(action: TAction) { 112 | const { 113 | issueNumber, 114 | emoji, 115 | labels, 116 | assignees, 117 | title, 118 | body, 119 | updateMode, 120 | state, 121 | ctx, 122 | closeReason, 123 | } = this; 124 | switch (action) { 125 | // ---[ Base Begin ]--->>> 126 | case 'add-assignees': { 127 | if (assignees && assignees.length) { 128 | await doAddAssignees(assignees); 129 | } else { 130 | core.warning(`[doAddAssignees] assignees is empty!`); 131 | } 132 | break; 133 | } 134 | case 'add-labels': { 135 | if (labels && labels.length) { 136 | await doAddLabels(labels); 137 | } else { 138 | core.warning(`[doAddLabels] labels is empty!`); 139 | } 140 | break; 141 | } 142 | case 'close-issue': { 143 | await doCloseIssue(closeReason); 144 | break; 145 | } 146 | case 'create-comment': { 147 | await doCreateComment(body, emoji); 148 | break; 149 | } 150 | case 'create-issue': { 151 | await doCreateIssue(title, body, labels, assignees, emoji); 152 | break; 153 | } 154 | case 'create-label': { 155 | await doCreateLabel(); 156 | break; 157 | } 158 | case 'delete-comment': { 159 | await doDeleteComment(); 160 | break; 161 | } 162 | case 'get-issue': { 163 | await doGetIssue(); 164 | break; 165 | } 166 | case 'lock-issue': { 167 | await doLockIssue(); 168 | break; 169 | } 170 | case 'open-issue': { 171 | await doOpenIssue(); 172 | break; 173 | } 174 | case 'remove-assignees': { 175 | if (assignees && assignees.length) { 176 | await doRemoveAssignees(assignees); 177 | } else { 178 | core.warning(`[doRemoveAssignees] assignees is empty!`); 179 | } 180 | break; 181 | } 182 | case 'remove-labels': { 183 | if (labels && labels.length) { 184 | await doRemoveLabels(labels); 185 | } else { 186 | core.warning(`[doRemoveLabels] labels is empty!`); 187 | } 188 | break; 189 | } 190 | case 'set-labels': { 191 | if (labels && labels.length) { 192 | await doSetLabels(labels); 193 | } else { 194 | core.warning(`[doSetLabels] labels is empty!`); 195 | } 196 | break; 197 | } 198 | case 'unlock-issue': { 199 | await doUnlockIssue(); 200 | break; 201 | } 202 | case 'update-comment': { 203 | await doUpdateComment(0, body, updateMode, emoji); 204 | break; 205 | } 206 | case 'update-issue': { 207 | await doUpdateIssue(0, state, title, body, updateMode, labels, assignees); 208 | break; 209 | } 210 | // ---[ Base End ]--->>> 211 | // ^_^ ============= ^_^ 212 | // -[ Advanced Begin ]-> 213 | case 'check-inactive': { 214 | await doCheckInactive(body, emoji); 215 | break; 216 | } 217 | case 'check-issue': { 218 | await doCheckIssue(); 219 | break; 220 | } 221 | case 'close-issues': { 222 | await doCloseIssues(body, closeReason, emoji); 223 | break; 224 | } 225 | case 'find-comments': { 226 | await doFindComments(); 227 | break; 228 | } 229 | case 'find-issues': { 230 | await doFindIssues(); 231 | break; 232 | } 233 | case 'lock-issues': { 234 | await doLockIssues(body, emoji); 235 | break; 236 | } 237 | case 'mark-assignees': { 238 | if (this.checkEvent4Mark()) { 239 | core.warning(`[mark-assignees] only support event '[issue_comment: created/edited]'!`); 240 | return; 241 | } 242 | await doMarkAssignees(ctx.payload.comment as TCommentInfo); 243 | break; 244 | } 245 | case 'mark-duplicate': { 246 | if (this.checkEvent4Mark()) { 247 | core.warning(`[mark-duplicate] only support event '[issue_comment: created/edited]'!`); 248 | return; 249 | } 250 | await doMarkDuplicate(ctx.payload.comment as TCommentInfo, closeReason, labels, emoji); 251 | break; 252 | } 253 | case 'toggle-labels': { 254 | await doToggleLabels(labels); 255 | break; 256 | } 257 | case 'welcome': { 258 | if (ctx.eventName === 'issues' && ctx.payload.action === 'opened') { 259 | await doWelcome(ctx.actor, issueNumber, body, labels, assignees, emoji); 260 | } else { 261 | core.warning('[welcome] only support issue opened!'); 262 | } 263 | break; 264 | } 265 | // -[ Advanced End ]-> 266 | default: { 267 | core.warning(`The ${action} is not allowed.`); 268 | break; 269 | } 270 | } 271 | } 272 | 273 | private checkEvent4Mark() { 274 | const { ctx } = this; 275 | return ( 276 | ctx.eventName !== 'issue_comment' && 277 | (ctx.payload.action === 'created' || ctx.payload.action === 'edited') 278 | ); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | ## v3.7.4 11 | 12 | `2025.12.16` 13 | 14 | - 🛠 refactor: `check-inactive` action ignores already tagged issues. [#220](https://github.com/actions-cool/issues-helper/pull/220) [@btea](https://github.com/btea) 15 | 16 | ## v3.7.3 17 | 18 | `2025.12.08` 19 | 20 | - 🛠 refactor: lockIssues filter locked issue. [#218](https://github.com/actions-cool/issues-helper/pull/218) [@btea](https://github.com/btea) 21 | 22 | ## v3.7.2 23 | 24 | `2025.11.07` 25 | 26 | - 🚀 feat: doQueryIssues title/body-includes support multiple value. [#215](https://github.com/actions-cool/issues-helper/pull/215) [@btea](https://github.com/btea) 27 | - 🛠 refactor: avoid calling dealStringToArr inside loop. [#216](https://github.com/actions-cool/issues-helper/pull/216) [@btea](https://github.com/btea) 28 | 29 | ## v3.7.1 30 | 31 | `2025.11.03` 32 | 33 | - 🚀 feat: find-comments action body-includes param support multiple value. [#213](https://github.com/actions-cool/issues-helper/pull/213) [@btea](https://github.com/btea) 34 | 35 | ## v3.7.0 36 | 37 | `2025.10.31` 38 | 39 | - 🚀 feat: `check-inactive` action add `exclude-issue-numbers` param. [#211](https://github.com/actions-cool/issues-helper/pull/211) [@btea](https://github.com/btea) 40 | 41 | ## v3.6.3 42 | 43 | `2025.08.26` 44 | 45 | - 🐞 fix: lock-issues only process unlocked. [#207](https://github.com/actions-cool/issues-helper/pull/207) 46 | 47 | ## v3.6.2 48 | 49 | `2025.07.25` 50 | 51 | - 🚀 feat: add `create-issue-if-not-exist` to find-issues. [#204](https://github.com/actions-cool/issues-helper/pull/204) 52 | 53 | ## v3.6.1 54 | 55 | `2025.07.25` 56 | 57 | - 🚀 feat: allow user to disable the thankyou message. [#201](https://github.com/actions-cool/issues-helper/pull/201) [@KingBain](https://github.com/KingBain) 58 | 59 | ## v3.6.0 60 | 61 | `2024.02.18` 62 | 63 | - 🚀 feat: add `assignees` to find-issues. [#192](https://github.com/actions-cool/issues-helper/pull/192) 64 | - 💄 chore: Bump runtime to node20. [#190](https://github.com/actions-cool/issues-helper/pull/190) [@danielcompton](https://github.com/danielcompton) 65 | 66 | ## v3.5.2 67 | 68 | `2023.08.16` 69 | 70 | - 🐞 fix: return `issue-assignees` in the correct output field for `get-issue`. [#163](https://github.com/actions-cool/issues-helper/pull/163) [@misund](https://github.com/misund) 71 | 72 | ## v3.5.1 73 | 74 | `2023.07.27` 75 | 76 | - 💄 perf: `inactive-mode` support `issue-created` `comment-created`. 77 | 78 | ## v3.5.0 79 | 80 | `2023.07.19` 81 | 82 | - 🚀 feat: support `inactive-mode`. Optional `comment`, which will check the last comment update time. [#158](https://github.com/actions-cool/issues-helper/pull/158) 83 | 84 | ## v3.4.0 85 | 86 | `2023.02.06` 87 | 88 | - 🚀 feat: support `toggle-labels`. [#132](https://github.com/actions-cool/issues-helper/pull/132) [@Wxh16144](https://github.com/Wxh16144) 89 | 90 | ## v3.3.3 91 | 92 | `2022.11.13` 93 | 94 | - 🐞 fix: body null case. [#129](https://github.com/actions-cool/issues-helper/pull/129) [@madmansn0w](https://github.com/madmansn0w) 95 | 96 | ## v3.3.2 97 | 98 | `2022.10.21` 99 | 100 | - 💄 chore: update `@actions/core` version. [#125](https://github.com/actions-cool/issues-helper/pull/125) [@wjz304](https://github.com/wjz304) 101 | 102 | ## v3.3.1 103 | 104 | `2022.10.19` 105 | 106 | - 🐞 fix: body null case. [#123](https://github.com/actions-cool/issues-helper/pull/123) 107 | 108 | ## v3.3.0 109 | 110 | `2022.09.02` 111 | 112 | - 🚀 feat: add `get-issue`. [#114](https://github.com/actions-cool/issues-helper/pull/114) 113 | 114 | ## v3.2.1 115 | 116 | `2022.08.31` 117 | 118 | - 🐞 fix: mark-duplicate labels invalid. [#116](https://github.com/actions-cool/issues-helper/pull/116) 119 | 120 | ## v3.2.0 121 | 122 | `2022.08.26` 123 | 124 | - 🚀 feat: add `$exclude-empty` for `exclude-labels`. [#112](https://github.com/actions-cool/issues-helper/pull/112) 125 | - When set to include `$exclude-empty`, no label issue can be excluded 126 | 127 | ## v3.1.0 128 | 129 | `2022.08.09` 130 | 131 | - 🚀 feat: add reason for closing issue. [#110](https://github.com/actions-cool/issues-helper/pull/110) [@Xhofe](https://github.com/Xhofe) 132 | 133 | ## v3.0.1 134 | 135 | `2022.08.01` 136 | 137 | - 🐞 fix: check will undefined. 138 | 139 | ## v3.0.0 140 | 141 | `2022.02.15` 142 | 143 | - 🚀 New Feature 144 | - `mark-assignees`: Comment quick settings assignees 145 | - `find-issues`: Conditional query current warehouse issues 146 | - 🐞 Bug Fix 147 | - Fixed `find-comments` return result direction not working 148 | - Fix `lock-issues` lock and comment order issue 149 | - 🛠 Refactor 150 | - `contents` renamed to easy-to-understand `emoji` 151 | - `issue-emojis` renamed to `issue-emoji` 152 | - deleteComment updateComment no longer supports `out-comments`, keeping pure functionality 153 | - Remove title body default 154 | - `month-statistics` is removed 155 | 156 | ## v2.5.0 157 | 158 | `2021.10.19` 159 | 160 | - 🚀 feat: add thanks. 161 | 162 | > 🐣 This will be the last version of 2.x, 3.x is under development. 163 | 164 | ## v2.4.3 165 | 166 | `2021.09.13` 167 | 168 | - 🚀 feat: default number support `issue-comment`. [#90](https://github.com/actions-cool/issues-helper/pull/90) 169 | 170 | ## v2.4.2 171 | 172 | `2021.09.06` 173 | 174 | - 🐞 fix: `doRemoveLabels` when has no label. [#88](https://github.com/actions-cool/issues-helper/pull/88) 175 | 176 | ## v2.4.1 177 | 178 | `2021.09.05` 179 | 180 | - 🐞 fix: remove labels define error. [#86](https://github.com/actions-cool/issues-helper/pull/86) 181 | 182 | ## v2.4.0 183 | 184 | `2021.08.15` 185 | 186 | - 🚀 feat: support custom repo. [#83](https://github.com/actions-cool/issues-helper/pull/83) 187 | - 🚀 feat: support default issueNumber get from context. [#81](https://github.com/actions-cool/issues-helper/pull/81) 188 | - 🐞 fix: action run async. [#79](https://github.com/actions-cool/issues-helper/pull/79) 189 | 190 | ## v2.3.1 191 | 192 | `2021.08.09` 193 | 194 | - ⚡️ refactor: extract exclude-labels array to outer scope. [#75](https://github.com/actions-cool/issues-helper/pull/75) [@meteorlxy](https://github.com/meteorlxy) 195 | 196 | ## v2.3.0 197 | 198 | `2021.08.09` 199 | 200 | - 🚀 feat: add exclude-labels for `check-inactive` `close-issues` `lock-issues`. [#74](https://github.com/actions-cool/issues-helper/pull/74) 201 | 202 | ## v2.2.1 203 | 204 | `2021.03.21` 205 | 206 | - fix: list comment page lost in `find-comments`. [#66](https://github.com/actions-cool/issues-helper/pull/66) 207 | 208 | ## v2.2.0 209 | 210 | `2021.03.21` 211 | 212 | - feat: Added `title-excludes` parameter to `check-issue`. [#65](https://github.com/actions-cool/issues-helper/pull/65) 213 | 214 | ## v2.1.2 215 | 216 | `2021.02.19` 217 | 218 | - feat: update/delete comment support find-comments out. [#63](https://github.com/actions-cool/issues-helper/pull/63) 219 | 220 | ## v2.1.1 221 | 222 | `2021.02.03` 223 | 224 | - fix: api request limit. [#57](https://github.com/actions-cool/issues-helper/pull/57) 225 | - chore: add catch. [#59](https://github.com/actions-cool/issues-helper/pull/59) 226 | 227 | ## v2.1.0 228 | 229 | `2021.02.02` 230 | 231 | - feat: add create-label. [#54](https://github.com/actions-cool/issues-helper/pull/54) 232 | 233 | ## v2.0.0 234 | 235 | `2021.01.26` 236 | 237 | - refactor: add require-permission default. [#51](https://github.com/actions-cool/issues-helper/pull/51) 238 | 239 | ## v1.12 240 | 241 | > It will be the last version of 1.x 242 | 243 | `2021.01.26` 244 | 245 | - feat: add require-permission. [#46](https://github.com/actions-cool/issues-helper/pull/46) [#48](https://github.com/actions-cool/issues-helper/pull/48) 246 | - feat: add lock-reason. [#49](https://github.com/actions-cool/issues-helper/pull/49) 247 | 248 | ## v1.11 249 | 250 | `2021.01.14` 251 | 252 | - feat: add question mark duplicate. [#38](https://github.com/actions-cool/issues-helper/pull/38) 253 | - perf: expand duplicate action. [#40](https://github.com/actions-cool/issues-helper/pull/40) 254 | 255 | ## v1.10 256 | 257 | `2021.01.12` 258 | 259 | - fix: duplicate labels set. [#36](https://github.com/actions-cool/issues-helper/pull/36) 260 | 261 | ## v1.9 262 | 263 | `2021.01.11` 264 | 265 | - feat: add random to. [#35](https://github.com/actions-cool/issues-helper/pull/35) 266 | 267 | ## v1.8 268 | 269 | `2021.01.07` 270 | 271 | - [#31](https://github.com/actions-cool/issues-helper/pull/31) 272 | - refactor: split content 273 | - feat: add `remove-labels` for duplicate 274 | - docs: optimize website 275 | 276 | ## v1.7 277 | 278 | `2021.01.02` 279 | 280 | - [#27](https://github.com/actions-cool/issues-helper/pull/27) 281 | - feat: add `month-statistics` 282 | - fix: query issues less because pages max 100 283 | - fix: js nested `require` 284 | 285 | ## v1.6 286 | 287 | `2020.12.30` 288 | 289 | - perf: optimize duplicate. [#24](https://github.com/actions-cool/issues-helper/pull/24) 290 | 291 | ## v1.5 292 | 293 | `2020.12.30` 294 | 295 | - feat: add `mark-duplicate`. [#23](https://github.com/actions-cool/issues-helper/pull/23) 296 | 297 | ## v1.4 298 | 299 | `2020.12.29` 300 | 301 | - fix: perfect `inactive-day` check. [#22](https://github.com/actions-cool/issues-helper/pull/22) 302 | 303 | ## v1.3 304 | 305 | `2020.12.28` 306 | 307 | - feat: add welcome. [#19](https://github.com/actions-cool/issues-helper/pull/19) 308 | 309 | ## v1.2 310 | 311 | `2020.12.25` 312 | 313 | - feat: add check-issue & remove labels. [#12](https://github.com/actions-cool/issues-helper/pull/12) 314 | 315 | ## v1.1 316 | 317 | `2020.12.24` 318 | 319 | - fix: yml not support array. [#11](https://github.com/actions-cool/issues-helper/pull/11) 320 | 321 | ## v1 322 | 323 | `2020.12.23` 324 | 325 | 🎉 First release. 326 | -------------------------------------------------------------------------------- /src/issue/issue.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/rest'; 2 | 3 | import { EEmoji } from '../shared'; 4 | import type { 5 | TCloseReason, 6 | TEmoji, 7 | TIssueState, 8 | TLockReasons, 9 | TUpdateMode, 10 | TUserPermission, 11 | } from '../types'; 12 | import type { 13 | IIssueBaseInfo, 14 | IIssueCoreEngine, 15 | IListIssuesParams, 16 | TCommentList, 17 | TIssueInfo, 18 | TIssueList, 19 | } from './types'; 20 | 21 | export class IssueCoreEngine implements IIssueCoreEngine { 22 | private owner!: string; 23 | private repo!: string; 24 | private issueNumber!: number; 25 | private octokit!: Octokit; 26 | 27 | public constructor(_info: IIssueBaseInfo) { 28 | if (_info.owner && _info.repo) { 29 | this.owner = _info.owner; 30 | this.repo = _info.repo; 31 | this.issueNumber = _info.issueNumber; 32 | this.octokit = new Octokit({ auth: `token ${_info.token}` }); 33 | } else { 34 | console.error(`Init failed, need owner、repo!`); 35 | } 36 | } 37 | 38 | // Allow modify issue number in this way 39 | public setIssueNumber(newIssueNumber: number) { 40 | this.issueNumber = newIssueNumber; 41 | } 42 | 43 | public async addAssignees(assignees: string[]) { 44 | const { owner, repo, octokit, issueNumber } = this; 45 | await octokit.issues.addAssignees({ 46 | owner, 47 | repo, 48 | issue_number: issueNumber, 49 | assignees, 50 | }); 51 | } 52 | 53 | public async addLabels(labels: string[]) { 54 | const { owner, repo, octokit, issueNumber } = this; 55 | await octokit.issues.addLabels({ 56 | owner, 57 | repo, 58 | issue_number: issueNumber, 59 | labels, 60 | }); 61 | } 62 | 63 | public async closeIssue(reason: TCloseReason) { 64 | const { owner, repo, octokit, issueNumber } = this; 65 | await octokit.issues.update({ 66 | owner, 67 | repo, 68 | issue_number: issueNumber, 69 | state: 'closed', 70 | state_reason: reason, 71 | }); 72 | } 73 | 74 | public async createComment(body: string): Promise { 75 | const { owner, repo, octokit, issueNumber } = this; 76 | const { data } = await octokit.issues.createComment({ 77 | owner, 78 | repo, 79 | issue_number: issueNumber, 80 | body, 81 | }); 82 | return data.id; 83 | } 84 | 85 | public async createCommentEmoji(commentId: number, emoji: TEmoji[]) { 86 | const { owner, repo, octokit } = this; 87 | for (const content of emoji) { 88 | if (content && EEmoji[content]) { 89 | await octokit.reactions.createForIssueComment({ 90 | owner, 91 | repo, 92 | comment_id: commentId, 93 | content, 94 | }); 95 | } 96 | } 97 | } 98 | 99 | public async createIssue( 100 | title: string, 101 | body: string, 102 | labels?: string[], 103 | assignees?: string[], 104 | ): Promise { 105 | const { owner, repo, octokit } = this; 106 | const { data } = await octokit.issues.create({ 107 | owner, 108 | repo, 109 | title, 110 | body, 111 | labels, 112 | assignees, 113 | }); 114 | return data.number; 115 | } 116 | 117 | public async createIssueEmoji(emoji: TEmoji[]) { 118 | const { owner, repo, octokit, issueNumber } = this; 119 | for (const content of emoji) { 120 | if (content && EEmoji[content]) { 121 | await octokit.reactions.createForIssue({ 122 | owner, 123 | repo, 124 | issue_number: issueNumber, 125 | content, 126 | }); 127 | } 128 | } 129 | } 130 | 131 | public async createLabel( 132 | labelName: string, 133 | labelColor: string = 'ededed', 134 | labelDescription: string = '', 135 | ) { 136 | const { owner, repo, octokit } = this; 137 | await octokit.issues.createLabel({ 138 | owner, 139 | repo, 140 | name: labelName, 141 | color: labelColor, 142 | description: labelDescription, 143 | }); 144 | } 145 | 146 | public async deleteComment(commentId: number) { 147 | const { owner, repo, octokit } = this; 148 | await octokit.issues.deleteComment({ 149 | owner, 150 | repo, 151 | comment_id: commentId, 152 | }); 153 | } 154 | 155 | public async getIssue() { 156 | const { owner, repo, octokit, issueNumber } = this; 157 | const issue = await octokit.issues.get({ 158 | owner, 159 | repo, 160 | issue_number: issueNumber, 161 | }); 162 | return issue.data as unknown as TIssueInfo; 163 | } 164 | 165 | public async getUserPermission(username: string) { 166 | const { owner, repo, octokit } = this; 167 | const { data } = await octokit.repos.getCollaboratorPermissionLevel({ 168 | owner, 169 | repo, 170 | username, 171 | }); 172 | return data.permission as TUserPermission; 173 | } 174 | 175 | public async listComments(page = 1) { 176 | const { octokit, owner, repo, issueNumber } = this; 177 | const { data } = await octokit.issues.listComments({ 178 | owner, 179 | repo, 180 | issue_number: issueNumber, 181 | per_page: 100, 182 | page, 183 | }); 184 | let comments = [...data] as unknown as TCommentList; 185 | if (comments.length >= 100) { 186 | comments = comments.concat(await this.listComments(page + 1)); 187 | } 188 | return comments; 189 | } 190 | 191 | public async listIssues(params: IListIssuesParams, page = 1) { 192 | const { octokit, owner, repo } = this; 193 | const { data } = await octokit.issues.listForRepo({ 194 | ...params, 195 | owner, 196 | repo, 197 | per_page: 100, 198 | page, 199 | }); 200 | let issues = [...data] as unknown as TIssueList; 201 | if (issues.length >= 100) { 202 | issues = issues.concat(await this.listIssues(params, page + 1)); 203 | } 204 | return issues; 205 | } 206 | 207 | public async lockIssue(lockReason: TLockReasons) { 208 | const { owner, repo, octokit, issueNumber } = this; 209 | const params: any = { 210 | owner, 211 | repo, 212 | issue_number: issueNumber, 213 | }; 214 | if (lockReason) { 215 | params.lock_reason = lockReason; 216 | } 217 | await octokit.issues.lock(params); 218 | } 219 | 220 | public async openIssue() { 221 | const { owner, repo, octokit, issueNumber } = this; 222 | await octokit.issues.update({ 223 | owner, 224 | repo, 225 | issue_number: issueNumber, 226 | state: 'open', 227 | }); 228 | } 229 | 230 | public async removeAssignees(assignees: string[]) { 231 | const { owner, repo, octokit, issueNumber } = this; 232 | await octokit.issues.removeAssignees({ 233 | owner, 234 | repo, 235 | issue_number: issueNumber, 236 | assignees, 237 | }); 238 | } 239 | 240 | public async removeLabels(labels: string[]) { 241 | const { owner, repo, octokit, issueNumber } = this; 242 | const issue = await this.getIssue(); 243 | 244 | const baseLabels: string[] = issue.labels.map(({ name }) => name); 245 | const removeLabels = baseLabels.filter(name => labels.includes(name)); 246 | 247 | for (const label of removeLabels) { 248 | await octokit.issues.removeLabel({ 249 | owner, 250 | repo, 251 | issue_number: issueNumber, 252 | name: label, 253 | }); 254 | } 255 | } 256 | 257 | public async setLabels(labels: string[]) { 258 | // https://github.com/octokit/rest.js/issues/34 259 | // - Probability to appear 260 | // - avoid use setLabels 261 | const issue = await this.getIssue(); 262 | 263 | const baseLabels: string[] = issue.labels.map(({ name }: any) => name); 264 | const removeLabels = baseLabels.filter(name => !labels.includes(name)); 265 | const addLabels = labels.filter(name => !baseLabels.includes(name)); 266 | 267 | if (removeLabels.length) { 268 | await this.removeLabels(removeLabels); 269 | } 270 | 271 | if (addLabels.length) { 272 | await this.addLabels(addLabels); 273 | } 274 | } 275 | 276 | public async unlockIssue() { 277 | const { owner, repo, octokit, issueNumber } = this; 278 | await octokit.issues.unlock({ 279 | owner, 280 | repo, 281 | issue_number: issueNumber, 282 | }); 283 | } 284 | 285 | public async updateComment(commentId: number, body: string, mode: TUpdateMode) { 286 | const { owner, repo, octokit } = this; 287 | const comment = await octokit.issues.getComment({ 288 | owner, 289 | repo, 290 | comment_id: commentId, 291 | }); 292 | const baseBody = comment.data.body; 293 | const newBody = body ? (mode === 'append' ? `${baseBody}\n${body}` : body) : baseBody; 294 | 295 | await octokit.issues.updateComment({ 296 | owner, 297 | repo, 298 | comment_id: commentId, 299 | body: newBody || '', 300 | }); 301 | } 302 | 303 | public async updateIssue( 304 | state: TIssueState, 305 | title: string | void, 306 | body: string | void, 307 | mode: TUpdateMode, 308 | labels?: string[] | void, 309 | assignees?: string[] | void, 310 | ) { 311 | const { owner, repo, octokit, issueNumber } = this; 312 | const issue = await this.getIssue(); 313 | const { 314 | body: baseBody, 315 | title: baseTitle, 316 | labels: baseLabels, 317 | assignees: baseAssigness, 318 | state: baseState, 319 | } = issue; 320 | 321 | const baseLabelsName = baseLabels.map(({ name }) => name); 322 | const baseAssignessName = baseAssigness?.map(({ login }) => login); 323 | 324 | const newBody = body ? (mode === 'append' ? `${baseBody}\n${body}` : body) : baseBody; 325 | 326 | if (labels && labels.length) { 327 | for (const label of labels) { 328 | if (baseLabelsName && baseLabelsName.length && baseLabelsName.indexOf(label) < 0) { 329 | await this.createLabel(label); 330 | } 331 | } 332 | } 333 | 334 | await octokit.issues.update({ 335 | owner, 336 | repo, 337 | issue_number: issueNumber, 338 | state: state || baseState, 339 | title: title || baseTitle, 340 | body: newBody, 341 | labels: labels?.length ? labels : baseLabelsName, 342 | assignees: assignees?.length ? assignees : baseAssignessName, 343 | }); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/helper/advanced.ts: -------------------------------------------------------------------------------- 1 | import type { TPermissionType } from 'actions-util'; 2 | import { checkPermission, dealStringToArr } from 'actions-util'; 3 | import dayjs from 'dayjs'; 4 | import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; 5 | import utc from 'dayjs/plugin/utc'; 6 | 7 | import * as core from '../core'; 8 | import type { IIssueCoreEngine, IListIssuesParams, TCommentInfo, TIssueList } from '../issue'; 9 | import { EConst } from '../shared'; 10 | import type { TCloseReason, TEmoji, TIssueState, TOutList } from '../types'; 11 | import { checkDuplicate, matchKeyword, replaceStr2Arr } from '../util'; 12 | import { 13 | doAddAssignees, 14 | doAddLabels, 15 | doCloseIssue, 16 | doCreateComment, 17 | doCreateCommentEmoji, 18 | doCreateIssue, 19 | doLockIssue, 20 | doRemoveLabels, 21 | doSetLabels, 22 | doUpdateComment, 23 | } from './base'; 24 | 25 | let ICE: IIssueCoreEngine; 26 | export function initAdvancedICE(_ICE: IIssueCoreEngine) { 27 | ICE = _ICE; 28 | } 29 | 30 | export async function doQueryIssues( 31 | state: TIssueState | 'all', 32 | creator?: string, 33 | ignoreLabels?: boolean, 34 | ): Promise { 35 | const params = { 36 | state, 37 | } as IListIssuesParams; 38 | 39 | const issueCreator = core.getInput('issue-creator'); 40 | const issueAssignee = core.getInput('issue-assignee'); 41 | const issueMentioned = core.getInput('issue-mentioned'); 42 | 43 | if (issueCreator) params.creator = issueCreator; 44 | if (issueAssignee) params.assignee = issueAssignee; 45 | if (issueMentioned) params.mentioned = issueMentioned; 46 | 47 | const labels = core.getInput('labels'); 48 | 49 | if (labels && !ignoreLabels) params.labels = labels; 50 | 51 | if (creator) params.creator = creator; 52 | 53 | const issuesList = await ICE.listIssues(params); 54 | const issues: TIssueList = []; 55 | const issueNumbers: number[] = []; 56 | 57 | if (issuesList.length) { 58 | const excludeLabels = core.getInput('exclude-labels') || ''; 59 | const bodyIncludes = core.getInput('body-includes'); 60 | const titleIncludes = core.getInput('title-includes'); 61 | 62 | const excludeLabelsArr = dealStringToArr(excludeLabels); 63 | const bodyIncludesArr = dealStringToArr(bodyIncludes); 64 | const titleIncludesArr = dealStringToArr(titleIncludes); 65 | issuesList.forEach(async issue => { 66 | const bodyCheck = 67 | bodyIncludesArr.length > 0 ? bodyIncludesArr.some(body => issue.body.includes(body)) : true; 68 | const titleCheck = 69 | titleIncludesArr.length > 0 70 | ? titleIncludesArr.some(title => issue.title.includes(title)) 71 | : true; 72 | /** 73 | * Note: GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. 74 | * For this reason, "Issues" endpoints may return both issues and pull requests in the response. 75 | * You can identify pull requests by the pull_request key. 76 | */ 77 | if (bodyCheck && titleCheck && issue.pull_request === undefined) { 78 | if (excludeLabelsArr.length) { 79 | if (issue.labels.length) { 80 | for (let i = 0; i < issue.labels.length; i += 1) { 81 | if (excludeLabelsArr.includes(issue.labels[i].name)) return; 82 | } 83 | } else { 84 | if (excludeLabelsArr.includes(EConst.ExcludeEmpty)) return; 85 | } 86 | } 87 | 88 | const inactiveDay = core.getInput('inactive-day'); 89 | if (inactiveDay) { 90 | dayjs.extend(utc); 91 | dayjs.extend(isSameOrBefore); 92 | 93 | const lastTime = dayjs.utc().subtract(+inactiveDay, 'day'); 94 | 95 | const inactiveMode = dealStringToArr(core.getInput('inactive-mode')); 96 | let checkTime: dayjs.Dayjs | null = null; 97 | 98 | for (const mode of inactiveMode) { 99 | if (checkTime) { 100 | break; 101 | } 102 | if (mode === 'comment' || mode === 'comment-created') { 103 | ICE.setIssueNumber(issue.number); 104 | const comments = await ICE.listComments(); 105 | if (comments.length) { 106 | checkTime = dayjs.utc( 107 | comments[comments.length - 1][mode === 'comment' ? 'updated_at' : 'created_at'], 108 | ); 109 | } 110 | } 111 | if (mode === 'issue-created') { 112 | checkTime = dayjs.utc(issue.created_at); 113 | } 114 | } 115 | 116 | if (!checkTime) { 117 | checkTime = dayjs.utc(issue.updated_at); 118 | } 119 | 120 | if (checkTime && checkTime.isSameOrBefore(lastTime)) { 121 | issues.push(issue); 122 | issueNumbers.push(issue.number); 123 | } 124 | } else { 125 | issues.push(issue); 126 | issueNumbers.push(issue.number); 127 | } 128 | } 129 | }); 130 | } 131 | 132 | core.info(`[doQueryIssues] issueNumbers is ---> ${JSON.stringify(issueNumbers)}`); 133 | return issues; 134 | } 135 | 136 | export async function doCheckInactive(body: string, emoji?: string) { 137 | let issueState = core.getInput('issue-state'); 138 | if (issueState !== 'all' && issueState !== 'closed') { 139 | issueState = 'open'; 140 | } 141 | const issues = await doQueryIssues(issueState as TIssueState | 'all'); 142 | if (issues.length) { 143 | const inactiveLabel = core.getInput('inactive-label') || 'inactive'; 144 | const excludeIssueNumbers = dealStringToArr(core.getInput('exclude-issue-numbers') || '').map( 145 | Number, 146 | ); 147 | const hasInactiveLabelIssueNumbers: number[] = []; 148 | for (const issue of issues) { 149 | const { labels, number } = issue; 150 | if (excludeIssueNumbers?.includes(number)) continue; 151 | const labelNames = labels.map(({ name }) => name); 152 | if (!labelNames.includes(inactiveLabel)) { 153 | core.info(`[doCheckInactive] Doing ---> ${number}`); 154 | await doAddLabels([inactiveLabel], number); 155 | if (body) await doCreateComment(body, emoji, number); 156 | } else { 157 | hasInactiveLabelIssueNumbers.push(number); 158 | } 159 | } 160 | if (hasInactiveLabelIssueNumbers.length) { 161 | core.info( 162 | `[doCheckInactive] These issues already has ${inactiveLabel} label! ` + 163 | JSON.stringify(hasInactiveLabelIssueNumbers) + 164 | ' total ${hasInactiveLabelIssueNumbers.length}', 165 | ); 166 | } 167 | } else { 168 | core.info(`[doCheckInactive] Query issues empty!`); 169 | } 170 | } 171 | 172 | /** 173 | * 检查 issue 是否满足条件,满足返回 true 174 | * 当前 issue 的指定人是否有一个满足 assigneeIncludes 里的某个 175 | * 关键字匹配,是否包含前一个某个+后一个某个 '官网,网站/挂了,无法访问' 176 | */ 177 | export async function doCheckIssue() { 178 | let checkResult = true; 179 | 180 | const issue = await ICE.getIssue(); 181 | const assigneeIncludes = core.getInput('assignee-includes'); 182 | 183 | if (assigneeIncludes) { 184 | const assigneesCheck = dealStringToArr(assigneeIncludes); 185 | let checkAssignee = false; 186 | issue.assignees.forEach(it => { 187 | if (checkResult && !checkAssignee && assigneesCheck.includes(it.login)) { 188 | checkResult = true; 189 | checkAssignee = true; 190 | } 191 | }); 192 | if (!checkAssignee) checkResult = false; 193 | } 194 | 195 | const titleRemove = core.getInput('title-excludes'); 196 | if (!!checkResult && titleRemove) { 197 | const removes = dealStringToArr(titleRemove); 198 | let t = issue.title; 199 | removes.forEach(re => { 200 | t = t.replace(re, ''); 201 | }); 202 | if (t.trim().length == 0) { 203 | checkResult = false; 204 | } 205 | } 206 | 207 | const titleIncludes = core.getInput('title-includes'); 208 | if (!!checkResult && titleIncludes) { 209 | const titleArr = titleIncludes.split('/'); 210 | const keyword1 = dealStringToArr(titleArr[0]); 211 | const keyword2 = dealStringToArr(titleArr[1]); 212 | checkResult = keyword2.length 213 | ? matchKeyword(issue.title, keyword1) && matchKeyword(issue.title, keyword2) 214 | : matchKeyword(issue.title, keyword1); 215 | } 216 | 217 | const bodyIncludes = core.getInput('body-includes'); 218 | if (!!checkResult && bodyIncludes) { 219 | const bodyArr = bodyIncludes.split('/'); 220 | const keyword1 = dealStringToArr(bodyArr[0]); 221 | const keyword2 = dealStringToArr(bodyArr[1]); 222 | checkResult = 223 | keyword2 && keyword2.length 224 | ? matchKeyword(issue.body, keyword1) && matchKeyword(issue.body, keyword2) 225 | : matchKeyword(issue.body, keyword1); 226 | } 227 | 228 | core.info(`[doCheckIssue] result is [${checkResult}]`); 229 | core.setOutput('check-result', checkResult); 230 | } 231 | 232 | export async function doCloseIssues(body: string, closeReason: TCloseReason, emoji?: string) { 233 | const issues = await doQueryIssues('open'); 234 | if (issues.length) { 235 | for (const { number } of issues) { 236 | core.info(`[doCloseIssues] Doing ---> ${number}`); 237 | if (body) await doCreateComment(body, emoji, number); 238 | await doCloseIssue(closeReason, number); 239 | } 240 | } else { 241 | core.info(`[doCloseIssues] Query issues empty!`); 242 | } 243 | } 244 | 245 | export async function doFindComments() { 246 | const commentList = await ICE.listComments(); 247 | core.info(`[doFindComments] success!`); 248 | 249 | const comments: TOutList = []; 250 | 251 | if (commentList.length) { 252 | const commentAuth = core.getInput('comment-auth'); 253 | const bodyIncludes = core.getInput('body-includes'); 254 | const direction = core.getInput('direction') === 'desc' ? 'desc' : 'asc'; 255 | const bodyIncludesArr = dealStringToArr(bodyIncludes); 256 | for (const comment of commentList) { 257 | const checkUser = commentAuth ? comment.user.login === commentAuth : true; 258 | const checkBody = 259 | bodyIncludesArr.length > 0 260 | ? bodyIncludesArr.some(text => comment.body.includes(text)) 261 | : true; 262 | if (checkUser && checkBody) { 263 | comments.push({ 264 | id: comment.id, 265 | auth: comment.user.login, 266 | body: comment.body, 267 | created: comment.created_at, 268 | updated: comment.updated_at, 269 | }); 270 | } 271 | } 272 | if (direction === 'desc') { 273 | comments.reverse(); 274 | } 275 | core.setOutput('comments', JSON.stringify(comments)); 276 | core.info(`[doFindComments] comments --> ${JSON.stringify(comments)}`); 277 | } else { 278 | core.info(`[doFindComments] Query comments empty!`); 279 | } 280 | } 281 | 282 | export async function doFindIssues() { 283 | let issueState = core.getInput('issue-state'); 284 | if (issueState !== 'all' && issueState !== 'closed') { 285 | issueState = 'open'; 286 | } 287 | const issueList = await doQueryIssues(issueState as TIssueState | 'all'); 288 | let issues: TOutList = []; 289 | if (issueList.length) { 290 | const direction = core.getInput('direction') === 'desc' ? 'desc' : 'asc'; 291 | issues = issueList.map(issue => { 292 | return { 293 | auth: issue.user.login, 294 | number: issue.number, 295 | title: issue.title, 296 | body: issue.body, 297 | state: issue.state, 298 | assignees: issue.assignees.map(val => val.login), 299 | created: issue.created_at, 300 | updated: issue.updated_at, 301 | }; 302 | }); 303 | if (direction === 'desc') { 304 | issues.reverse(); 305 | } 306 | core.info(`[doFindIssues] issues --> ${JSON.stringify(issues)}`); 307 | } else { 308 | core.info(`[doFindIssues] Query issues empty!`); 309 | const ifCreate = core.getInput('create-issue-if-not-exist'); 310 | if (ifCreate) { 311 | const titleIncludes = core.getInput('title-includes') || 'New issue by AC find-issues'; 312 | const bodyIncludes = core.getInput('body-includes') || ''; 313 | const labels = dealStringToArr(core.getInput('labels') || ''); 314 | doCreateIssue(titleIncludes, bodyIncludes, labels); 315 | } 316 | } 317 | core.setOutput('issues', JSON.stringify(issues)); 318 | } 319 | 320 | export async function doLockIssues(body: string, emoji?: string) { 321 | let issueState = core.getInput('issue-state'); 322 | if (issueState !== 'all' && issueState !== 'closed') { 323 | issueState = 'open'; 324 | } 325 | const issues = await doQueryIssues(issueState as TIssueState | 'all'); 326 | 327 | if (issues.length) { 328 | const hasLockedIssueNumbers: number[] = []; 329 | for (const { number, locked } of issues) { 330 | if (!locked) { 331 | core.info(`[doLockIssues] Doing ---> ${number}`); 332 | if (body) await doCreateComment(body, emoji, number); 333 | await doLockIssue(number); 334 | } else { 335 | hasLockedIssueNumbers.push(number); 336 | } 337 | } 338 | if (hasLockedIssueNumbers.length) { 339 | core.info( 340 | `[doLockIssues] Locked issues ---> ${JSON.stringify(hasLockedIssueNumbers)} total ${ 341 | hasLockedIssueNumbers.length 342 | }`, 343 | ); 344 | } 345 | } else { 346 | core.info(`[doLockIssues] Query issues empty!`); 347 | } 348 | } 349 | 350 | export async function doMarkAssignees(comment: TCommentInfo) { 351 | const assignCommand = core.getInput('assign-command') || '/assign'; 352 | if (comment.body.startsWith(assignCommand)) { 353 | const { body, user } = comment; 354 | const assigns = replaceStr2Arr(body, assignCommand, '@'); 355 | const requirePermission = core.getInput('require-permission') || 'write'; 356 | const permission = await ICE.getUserPermission(user.login); 357 | if (!checkPermission(requirePermission as TPermissionType, permission)) { 358 | core.info(`[doMarkAssignees] The user ${user.login} is not allow!`); 359 | return; 360 | } 361 | await doAddAssignees(assigns); 362 | core.info(`[doMarkAssignees] Done!`); 363 | } else { 364 | core.info(`[doMarkAssignees] The issues ignore!`); 365 | } 366 | } 367 | 368 | export async function doMarkDuplicate( 369 | comment: TCommentInfo, 370 | closeReason: TCloseReason, 371 | labels?: string[] | void, 372 | emoji?: string, 373 | ) { 374 | const duplicateCommand = core.getInput('duplicate-command'); 375 | const duplicateLabels = core.getInput('duplicate-labels'); 376 | const removeLables = core.getInput('remove-labels') || ''; 377 | const closeIssue = core.getInput('close-issue'); 378 | const requirePermission = core.getInput('require-permission') || 'write'; 379 | 380 | const commentId = comment.id; 381 | const commentBody = comment.body; 382 | const commentUser = comment.user.login; 383 | 384 | const ifCommandInput = !!duplicateCommand; 385 | 386 | if ( 387 | !commentBody.includes('?') && 388 | ((ifCommandInput && 389 | commentBody.startsWith(duplicateCommand) && 390 | commentBody.split(' ')[0] == duplicateCommand) || 391 | checkDuplicate(commentBody)) 392 | ) { 393 | const permission = await ICE.getUserPermission(commentUser); 394 | if (!checkPermission(requirePermission as TPermissionType, permission)) { 395 | core.info(`[doMarkDuplicate] The user ${commentUser} is not allow!`); 396 | return; 397 | } 398 | 399 | if (ifCommandInput) { 400 | const nextBody = commentBody.replace(duplicateCommand, 'Duplicate of'); 401 | await doUpdateComment(commentId, nextBody, 'replace', emoji); 402 | } else if (emoji) { 403 | await doCreateCommentEmoji(commentId, emoji); 404 | } 405 | 406 | const issue = await ICE.getIssue(); 407 | let newLabels: string[] = []; 408 | if (issue.labels.length > 0) { 409 | newLabels = issue.labels 410 | .map(({ name }) => name) 411 | .filter(name => !dealStringToArr(removeLables).includes(name)); 412 | } 413 | if (duplicateLabels) { 414 | newLabels = [...newLabels, ...dealStringToArr(duplicateLabels)]; 415 | } 416 | if (labels?.length) { 417 | newLabels = [...labels]; 418 | } 419 | if (newLabels.length > 0) { 420 | await doSetLabels(newLabels); 421 | } 422 | if (closeIssue === 'true') { 423 | await doCloseIssue(closeReason); 424 | } 425 | core.info(`[doMarkDuplicate] Done!`); 426 | } else { 427 | core.warning( 428 | `This comment body should start with 'duplicate-command' or 'Duplicate of' and not include '?'`, 429 | ); 430 | } 431 | } 432 | 433 | export async function doToggleLabels(labels: string[] = []) { 434 | const issue = await ICE.getIssue(); 435 | const baseLabels: string[] = issue.labels.map(({ name }: any) => name); 436 | 437 | const addLabels = []; 438 | const removeLabels = []; 439 | 440 | for (const label of labels) { 441 | if (baseLabels.includes(label)) { 442 | removeLabels.push(label); 443 | } else { 444 | addLabels.push(label); 445 | } 446 | } 447 | 448 | if (removeLabels.length) { 449 | await doRemoveLabels(removeLabels); 450 | } 451 | 452 | if (addLabels.length) { 453 | await doAddLabels(addLabels); 454 | } 455 | 456 | core.info(`[doToggleLabels] Done!`); 457 | } 458 | 459 | export async function doWelcome( 460 | auth: string, 461 | issueNumber: number, 462 | body: string, 463 | labels?: string[] | void, 464 | assignees?: string[] | void, 465 | emoji?: string, 466 | ) { 467 | core.info(`[doWelcome] [${auth}]`); 468 | const issues = await doQueryIssues('all', auth, true); 469 | if (issues.length == 0 || (issues.length == 1 && issues[0].number == issueNumber)) { 470 | if (body) { 471 | await doCreateComment(body, emoji); 472 | } 473 | 474 | if (assignees?.length) { 475 | await doAddAssignees(assignees); 476 | } 477 | 478 | if (labels?.length) { 479 | await doAddLabels(labels); 480 | } 481 | 482 | const issueEmoji = core.getInput('issue-emoji'); 483 | if (issueEmoji) { 484 | await ICE.createIssueEmoji(dealStringToArr(issueEmoji) as TEmoji[]); 485 | } 486 | } else { 487 | core.info(`[doWelcome] ${auth} is not first time!`); 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /web/.dumi/global.less: -------------------------------------------------------------------------------- 1 | @import (reference) 'dumi/theme-default/styles/variables.less'; 2 | 3 | @dumi-theme-default-prefix: @prefix; 4 | @font-family: -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; 5 | 6 | html { 7 | scroll-behavior: smooth; 8 | -webkit-overflow-scrolling: touch; 9 | } 10 | 11 | // dumi 12 | .@{dumi-theme-default-prefix}-source-code { 13 | background-color: rgba(0, 0, 0, 0.04) !important; 14 | border-radius: 4px; 15 | box-shadow: inset 0 0 10px 2px rgba(0, 0, 0, .2); 16 | 17 | @{dark-selector} & { 18 | box-shadow: inset 0 0 10px 2px rgba(0, 0, 0, .8); 19 | } 20 | 21 | // https://github.com/actions-cool/issues-helper/pull/152#issuecomment-1504394017 22 | & + & { 23 | margin-top: 16px; 24 | } 25 | } 26 | 27 | 28 | @{dark-selector} .markdown .@{prefix}-source-code pre { 29 | background-color: transparent; 30 | } 31 | 32 | 33 | .@{dumi-theme-default-prefix}-hero { 34 | max-width: 100% !important; 35 | 36 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='560' viewBox='0 0 360 360'%3E%3Cpath fill='%23187dce' fill-opacity='0.26' d='M0 85.02l4.62-4.27a49.09 49.09 0 0 0 7.33 3.74l-1.2 10.24 2.66.87 5.05-9c2.62.65 5.34 1.08 8.12 1.28L28.6 98h2.8l2.02-10.12c2.74-.2 5.46-.62 8.12-1.28l5.05 8.99 2.66-.86-1.2-10.24c2.55-1.03 5-2.29 7.33-3.74l7.58 7 2.26-1.65-4.3-9.38a48.3 48.3 0 0 0 5.8-5.8l9.38 4.3 1.65-2.26-7-7.58a49.09 49.09 0 0 0 3.74-7.33l10.24 1.2.87-2.66-9-5.05a48.07 48.07 0 0 0 1.28-8.12L88 41.4v-2.8l-10.12-2.02c-.2-2.74-.62-5.46-1.28-8.12l8.99-5.05-.86-2.66-10.24 1.2c-1.03-2.55-2.29-5-3.74-7.33l7-7.58-1.65-2.26-9.38 4.3a48.3 48.3 0 0 0-5.8-5.8L62.42 0h2.16l-1.25 2.72a50.31 50.31 0 0 1 3.95 3.95l9.5-4.36 3.52 4.85-7.08 7.68c.94 1.6 1.79 3.27 2.54 4.98l10.38-1.21 1.85 5.7-9.11 5.12c.39 1.8.68 3.65.87 5.52L90 37v6l-10.25 2.05a49.9 49.9 0 0 1-.87 5.52l9.11 5.12-1.85 5.7-10.38-1.21c-.75 1.7-1.6 3.37-2.54 4.98l7.08 7.68-3.52 4.85-9.5-4.36a50.31 50.31 0 0 1-3.95 3.95l4.36 9.5-4.85 3.52-7.68-7.08c-1.6.94-3.27 1.79-4.98 2.54l1.21 10.38-5.7 1.85-5.12-9.11c-1.8.39-3.65.68-5.52.87L33 100h-6l-2.05-10.25a49.9 49.9 0 0 1-5.52-.87l-5.12 9.11-5.7-1.85 1.21-10.38c-1.7-.75-3.37-1.6-4.98-2.54L0 87.68v-2.66zM0 52.7V27.3l8.38 4.84a22.96 22.96 0 0 0 0 15.72L0 52.7zm0-39.16A39.91 39.91 0 0 1 26 .2v17.15a22.98 22.98 0 0 0-13.62 7.86L0 18.06v-4.52zm0 52.92v-4.52l12.38-7.15A22.98 22.98 0 0 0 26 62.65V79.8A39.91 39.91 0 0 1 0 66.46zM34 79.8V62.65a22.98 22.98 0 0 0 13.62-7.86l14.85 8.58A39.97 39.97 0 0 1 34 79.8zm32.48-23.36l-14.86-8.58a22.96 22.96 0 0 0 0-15.72l14.86-8.58A39.86 39.86 0 0 1 70 40a39.9 39.9 0 0 1-3.52 16.44zm-4.01-39.8L47.62 25.2A22.98 22.98 0 0 0 34 17.35V.2a39.97 39.97 0 0 1 28.47 16.43v.01zM0 50.38l5.98-3.45a25.01 25.01 0 0 1 0-13.88L0 29.6v20.78zm.5-34.35l11.48 6.63c3.27-3.4 7.44-5.8 12.02-6.94V2.47A37.96 37.96 0 0 0 .5 16.04v-.01zm0 47.92A37.96 37.96 0 0 0 24 77.53V64.28a24.97 24.97 0 0 1-12.02-6.95L.5 63.96v-.01zM36 77.53a37.96 37.96 0 0 0 23.5-13.57l-11.48-6.63A24.97 24.97 0 0 1 36 64.28v13.25zm29.5-23.96a37.91 37.91 0 0 0 0-27.14l-11.48 6.63a25.01 25.01 0 0 1 0 13.88l11.49 6.63h-.01zm-6-37.53A37.96 37.96 0 0 0 36 2.47v13.25c4.66 1.15 8.8 3.6 12.02 6.95l11.48-6.63zM30 54a14 14 0 1 1 0-28 14 14 0 0 1 0 28zm0-2a12 12 0 1 0 0-24 12 12 0 0 0 0 24zm0-2a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm77.47 45.17l-1.62-5.97 5.67-2.06 2.61 5.64c1.09-.25 2.2-.44 3.33-.58l.52-6.2h6.04l.52 6.2c1.13.14 2.24.33 3.33.58l2.6-5.64 5.68 2.06-1.62 5.97c1.02.51 2 1.07 2.95 1.69l4.35-4.38 4.62 3.88-3.53 5c.8.84 1.53 1.71 2.23 2.62l5.52-2.6 3.02 5.23-4.98 3.46c.46 1.06.86 2.14 1.2 3.25l6.02-.54 1.05 5.94-5.84 1.54c.07 1.16.07 2.32 0 3.48l5.84 1.54-1.05 5.94-6.02-.54c-.34 1.1-.74 2.2-1.2 3.25l4.98 3.46-3.02 5.22-5.52-2.6c-.7.92-1.44 1.8-2.23 2.62l3.53 5-4.62 3.89-4.35-4.38a30.2 30.2 0 0 1-2.95 1.69l1.62 5.97-5.67 2.06-2.61-5.64c-1.09.25-2.2.44-3.33.58l-.52 6.2h-6.04l-.52-6.2a30.27 30.27 0 0 1-3.33-.58l-2.6 5.64-5.68-2.06 1.62-5.97c-1.01-.5-2-1.07-2.95-1.69l-4.35 4.38-4.62-3.88 3.53-5a32.5 32.5 0 0 1-2.23-2.62l-5.52 2.6-3.02-5.23 4.98-3.46a29.66 29.66 0 0 1-1.2-3.25l-6.02.54-1.05-5.94 5.84-1.54a30.28 30.28 0 0 1 0-3.48l-5.84-1.54 1.05-5.94 6.02.54c.34-1.1.74-2.2 1.2-3.25l-4.98-3.46 3.02-5.22 5.52 2.6c.7-.92 1.44-1.8 2.23-2.62l-3.53-5 4.62-3.89 4.35 4.38a30.2 30.2 0 0 1 2.95-1.69zm15.2-1.12l-.5-6.05h-2.34l-.5 6.05c-2.18.13-4.3.5-6.32 1.1l-2.54-5.5-2.2.8 1.6 5.85a27.97 27.97 0 0 0-5.56 3.21l-4.27-4.3-1.79 1.5 3.5 4.95a28.14 28.14 0 0 0-4.12 4.92l-5.5-2.59-1.16 2.02 4.98 3.46a27.8 27.8 0 0 0-2.2 6.03l-6.03-.55-.4 2.3 5.86 1.54a28.3 28.3 0 0 0 0 6.42l-5.87 1.55.4 2.3 6.05-.56a27.8 27.8 0 0 0 2.2 6.03l-5 3.47 1.17 2.02 5.49-2.59a28.14 28.14 0 0 0 4.12 4.92l-3.5 4.96 1.79 1.5 4.27-4.31a27.97 27.97 0 0 0 5.56 3.21l-1.6 5.85 2.2.8 2.54-5.5c2.02.6 4.14.97 6.32 1.1l.5 6.05h2.34l.5-6.05c2.18-.13 4.3-.5 6.32-1.1l2.54 5.5 2.2-.8-1.6-5.85a27.97 27.97 0 0 0 5.56-3.21l4.27 4.3 1.79-1.5-3.5-4.95a28.14 28.14 0 0 0 4.12-4.92l5.5 2.59 1.16-2.02-4.98-3.46a27.8 27.8 0 0 0 2.2-6.03l6.03.55.4-2.3-5.86-1.54a28.3 28.3 0 0 0 0-6.42l5.87-1.55-.4-2.3-6.05.56a27.8 27.8 0 0 0-2.2-6.03l4.99-3.46-1.17-2.02-5.49 2.59a28.14 28.14 0 0 0-4.12-4.92l3.5-4.96-1.79-1.5-4.27 4.31a27.97 27.97 0 0 0-5.56-3.21l1.6-5.85-2.2-.8-2.54 5.5c-2.02-.6-4.14-.97-6.32-1.1l.01-.01zM121 128a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-2a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-18a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm8.49 3.51a5 5 0 1 1 6.95-7.2 5 5 0 0 1-6.95 7.2zM133 120a5 5 0 1 1 10 0 5 5 0 0 1-10 0zm-3.51 8.49a5 5 0 1 1 7.2 6.95 5 5 0 0 1-7.2-6.95zM121 132a5 5 0 1 1 0 10 5 5 0 0 1 0-10zm-8.49-3.51a5 5 0 1 1-6.95 7.2 5 5 0 0 1 6.95-7.2zM109 120a5 5 0 1 1-10 0 5 5 0 0 1 10 0zm3.51-8.49a5 5 0 1 1-7.2-6.95 5 5 0 0 1 7.2 6.95zM121 106a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm9.9 4.1a3 3 0 1 0 4.39-4.09 3 3 0 0 0-4.39 4.09zm4.1 9.9a3 3 0 1 0 6 0 3 3 0 0 0-6 0zm-4.1 9.9a3 3 0 1 0 4.09 4.39 3 3 0 0 0-4.09-4.39zM121 134a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm-9.9-4.1a3 3 0 1 0-4.39 4.09 3 3 0 0 0 4.39-4.09zM107 120a3 3 0 1 0-6 0 3 3 0 0 0 6 0zm4.1-9.9a3 3 0 1 0-4.09-4.39 3 3 0 0 0 4.09 4.39zm129.42-6.95v.01c.87.07 1.74.17 2.6.3l1.5-3.91 1.94-3.64 3.89.97v4.13l-.5 4.13c.83.28 1.64.59 2.44.93l2.42-3.43 2.76-3.07 3.54 1.88-1 4-1.49 3.89c.73.47 1.45.97 2.15 1.49l3.19-2.76 3.42-2.3 2.97 2.67-1.93 3.65-2.38 3.4c.6.64 1.2 1.3 1.76 1.99l3.68-1.94 3.85-1.48 2.29 3.28-2.7 3.11-3.12 2.82c.43.76.84 1.53 1.22 2.32l4.04-1 4.1-.5 1.43 3.73-3.37 2.37-3.7 1.98c.23.84.44 1.68.62 2.54l4.17.01 4.1.5.48 3.97-3.85 1.48-4.06 1.02c.03.87.03 1.75 0 2.62l4.06 1.02 3.85 1.48-.48 3.97-4.1.51h-4.17c-.18.86-.39 1.71-.63 2.54l3.7 1.98 3.38 2.37-1.43 3.73-4.1-.5-4.04-1c-.38.79-.79 1.56-1.22 2.32l3.13 2.82 2.7 3.11-2.3 3.28-3.85-1.48-3.68-1.95a37 37 0 0 1-1.76 2l2.38 3.41 1.93 3.64-2.97 2.67-3.42-2.3-3.19-2.76a40.1 40.1 0 0 1-2.15 1.48l1.48 3.9 1 4-3.53 1.88-2.76-3.07-2.42-3.43c-.8.33-1.61.65-2.45.93l.5 4.13v4.13l-3.88.97-1.94-3.65-1.5-3.9c-.86.13-1.73.23-2.6.31L240 187l-1 4h-4l-1-4-.52-4.16a37.6 37.6 0 0 1-2.6-.3l-1.5 3.91-1.94 3.64-3.89-.97v-4.13l.5-4.13c-.83-.28-1.64-.59-2.44-.93l-2.42 3.43-2.76 3.07-3.54-1.88 1-4 1.49-3.89c-.74-.47-1.45-.97-2.15-1.49l-3.19 2.76-3.42 2.3-2.97-2.67 1.93-3.65 2.38-3.4c-.61-.65-1.2-1.31-1.76-1.99l-3.68 1.94-3.85 1.48-2.29-3.28 2.7-3.11 3.12-2.82c-.43-.76-.84-1.53-1.22-2.32l-4.04 1-4.1.5-1.43-3.73 3.37-2.37 3.7-1.98c-.23-.84-.44-1.68-.62-2.54l-4.17-.01-4.1-.5-.48-3.97 3.85-1.48 4.06-1.02c-.03-.87-.03-1.75 0-2.62l-4.06-1.02-3.85-1.48.48-3.97 4.1-.51h4.17c.18-.86.39-1.71.63-2.54l-3.7-1.98-3.38-2.37 1.43-3.73 4.1.5 4.04 1c.38-.79.79-1.56 1.22-2.32l-3.13-2.82-2.7-3.11 2.3-3.28 3.85 1.48 3.68 1.95a37 37 0 0 1 1.76-2l-2.38-3.41-1.93-3.64 2.97-2.67 3.42 2.3 3.19 2.76c.7-.52 1.41-1.02 2.15-1.48l-1.48-3.9-1-4 3.53-1.88 2.76 3.07 2.42 3.43c.8-.33 1.61-.65 2.45-.93l-.5-4.13v-4.13l3.88-.97 1.94 3.65 1.5 3.9c.86-.13 1.73-.23 2.6-.31L234 99l1-4h4l1 4 .52 4.15zm-14.3 3.4c-1.83.54-3.6 1.21-5.3 2l-3.5-4.97-1.38-1.53-.88.47.5 2 2.16 5.67a38.09 38.09 0 0 0-4.66 3.22l-4.61-4-1.71-1.15-.75.67.97 1.82 3.47 4.98a38.22 38.22 0 0 0-3.79 4.28l-5.37-2.84-1.92-.74-.57.82 1.35 1.56 4.52 4.09a37.9 37.9 0 0 0-2.64 5l-5.89-1.45-2.04-.25-.36.94 1.69 1.18 5.36 2.87a37.74 37.74 0 0 0-1.35 5.5l-6.08.01-2.04.25-.12 1 1.92.73 5.9 1.5a38.54 38.54 0 0 0 0 5.65l-5.9 1.49-1.92.74.12.99 2.04.25 6.08.01c.31 1.86.77 3.7 1.35 5.5l-5.36 2.87-1.7 1.18.37.94 2.04-.25 5.9-1.46a37.9 37.9 0 0 0 2.63 5.01l-4.52 4.1-1.35 1.55.57.82 1.92-.74 5.37-2.84a38.22 38.22 0 0 0 3.8 4.28l-3.48 4.98-.97 1.82.75.67 1.7-1.15 4.62-4a38.09 38.09 0 0 0 4.66 3.22l-2.17 5.67-.5 2 .89.47 1.38-1.53 3.5-4.98c1.7.8 3.47 1.47 5.3 2l-.73 6.04v2.06l.97.24.97-1.82 2.2-5.68c1.83.36 3.7.6 5.62.68L236 187l.5 2h1l.5-2 .75-6.04a38.2 38.2 0 0 0 5.62-.68l2.2 5.68.97 1.82.97-.24v-2.06l-.73-6.03c1.83-.54 3.6-1.21 5.3-2l3.5 4.97 1.38 1.53.88-.47-.5-2-2.16-5.67a38.09 38.09 0 0 0 4.66-3.22l4.61 4 1.71 1.15.75-.67-.97-1.82-3.47-4.98a38.22 38.22 0 0 0 3.79-4.28l5.37 2.84 1.92.74.57-.82-1.35-1.56-4.52-4.09c1-1.6 1.88-3.27 2.64-5l5.89 1.45 2.04.25.36-.94-1.69-1.18-5.36-2.87a37.4 37.4 0 0 0 1.35-5.5l6.08-.01 2.04-.25.12-1-1.92-.73-5.9-1.5c.14-1.88.14-3.77 0-5.65l5.9-1.49 1.92-.74-.12-.99-2.04-.25-6.08-.01a37.4 37.4 0 0 0-1.35-5.5l5.36-2.87 1.7-1.18-.37-.94-2.04.25-5.9 1.46a37.9 37.9 0 0 0-2.63-5.01l4.52-4.1 1.35-1.55-.57-.82-1.92.74-5.37 2.84a38.22 38.22 0 0 0-3.8-4.28l3.48-4.98.97-1.82-.75-.67-1.7 1.15-4.62 4a38.09 38.09 0 0 0-4.66-3.22l2.17-5.67.5-2-.89-.47-1.38 1.53-3.5 4.98c-1.7-.8-3.47-1.47-5.3-2l.73-6.04v-2.06l-.97-.24-.97 1.82-2.2 5.68c-1.83-.36-3.7-.6-5.62-.68L238 99l-.5-2h-1l-.5 2-.75 6.04c-1.92.09-3.8.32-5.62.68l-2.2-5.68-.97-1.82-.97.24v2.06l.73 6.03zm-5.85 5.65A34.82 34.82 0 0 1 236 108v6a28.8 28.8 0 0 0-12.63 3.39l-3-5.2v.01zm2.8.83l1 1.74a30.8 30.8 0 0 1 9.83-2.63v-2.01a32.8 32.8 0 0 0-10.83 2.9zm-4.53.17l3 5.2a29.12 29.12 0 0 0-9.24 9.24l-5.2-3a35.18 35.18 0 0 1 11.44-11.44zm-.67 2.84a33.19 33.19 0 0 0-7.93 7.93l1.74 1a31.18 31.18 0 0 1 7.2-7.2l-1.01-1.73zm-11.77 10.33h-.01l5.2 3A28.8 28.8 0 0 0 208 142h-6a34.82 34.82 0 0 1 4.2-15.63zm.83 2.8a32.8 32.8 0 0 0-2.9 10.83h2.01a30.8 30.8 0 0 1 2.63-9.83l-1.74-1zM202.01 144h6.01c.15 4.41 1.3 8.73 3.38 12.63l-5.2 3a34.82 34.82 0 0 1-4.19-15.63zm2.12 2a32.8 32.8 0 0 0 2.9 10.84l1.74-1a30.8 30.8 0 0 1-2.63-9.84h-2.01zm3.07 15.36l5.2-3c2.34 3.74 5.5 6.9 9.24 9.24l-3 5.2a35.18 35.18 0 0 1-11.44-11.44zm2.84.67a33.19 33.19 0 0 0 7.93 7.93l1-1.74a31.18 31.18 0 0 1-7.2-7.2l-1.73 1.01zm10.33 11.77v.01l3-5.2A28.85 28.85 0 0 0 236 172v6a34.82 34.82 0 0 1-15.63-4.2zm2.8-.83a32.8 32.8 0 0 0 10.83 2.9v-2.01a30.8 30.8 0 0 1-9.83-2.63l-1 1.74zm14.83 5.02v-6.01c4.41-.15 8.73-1.3 12.63-3.38l3 5.2a34.82 34.82 0 0 1-15.63 4.19zm2-2.12a32.8 32.8 0 0 0 10.84-2.9l-1-1.74a30.8 30.8 0 0 1-9.84 2.63v2.01zm15.36-3.07l-3-5.2c3.74-2.34 6.9-5.5 9.24-9.24l5.2 3a35.18 35.18 0 0 1-11.44 11.44zm.67-2.84a33.19 33.19 0 0 0 7.93-7.93l-1.74-1a31.18 31.18 0 0 1-7.2 7.2l1.01 1.73zm11.77-10.33h.01l-5.2-3A28.85 28.85 0 0 0 266 144h6a34.82 34.82 0 0 1-4.2 15.63zm-.83-2.8a32.8 32.8 0 0 0 2.9-10.83h-2.01a30.8 30.8 0 0 1-2.63 9.83l1.74 1zm5.02-14.83h-6.01a28.85 28.85 0 0 0-3.38-12.63l5.2-3a34.82 34.82 0 0 1 4.19 15.63zm-2.12-2a32.8 32.8 0 0 0-2.9-10.84l-1.74 1a30.8 30.8 0 0 1 2.63 9.84h2.01zm-3.07-15.36l-5.2 3a29.12 29.12 0 0 0-9.24-9.24l3-5.2a35.18 35.18 0 0 1 11.44 11.44zm-2.84-.67a33.19 33.19 0 0 0-7.93-7.93l-1 1.74a31.18 31.18 0 0 1 7.2 7.2l1.73-1.01zM238 108a34.82 34.82 0 0 1 15.63 4.19l-3 5.2a28.85 28.85 0 0 0-12.63-3.38V108zm12.84 5.02a32.8 32.8 0 0 0-10.84-2.9v2.01a30.8 30.8 0 0 1 9.83 2.63l1-1.74h.01zM237 156a13 13 0 1 1 0-26 13 13 0 0 1 0 26zm0-2a11 11 0 1 0 0-22 11 11 0 0 0 0 22zM137.54 0h56.92l-.74 1.03c.57.7 1.12 1.4 1.64 2.14l7.75-2.9 2 3.46-6.38 5.25c.37.82.72 1.65 1.03 2.5l8.22-.8 1.04 3.86-7.52 3.43c.15.88.26 1.77.35 2.67L210 22v4l-8.15 1.36c-.09.9-.2 1.8-.35 2.67l7.52 3.43-1.04 3.86-8.22-.8c-.31.85-.66 1.68-1.03 2.5l6.38 5.25-2 3.46-7.75-2.9c-.52.74-1.07 1.45-1.64 2.14l4.8 6.73-2.82 2.83-6.73-4.8c-.7.56-1.4 1.11-2.14 1.63l2.9 7.75-3.46 2-5.25-6.38c-.82.37-1.65.72-2.5 1.03l.8 8.22-3.86 1.04-3.43-7.52c-.88.15-1.77.26-2.67.35L168 68h-4l-1.36-8.15c-.9-.09-1.8-.2-2.67-.35l-3.43 7.52-3.86-1.04.8-8.22c-.85-.31-1.68-.66-2.5-1.03l-5.25 6.38-3.46-2 2.9-7.75a36.15 36.15 0 0 1-2.14-1.64l-6.73 4.8-2.83-2.82 4.8-6.73c-.56-.7-1.11-1.4-1.63-2.14l-7.75 2.9-2-3.46 6.38-5.25c-.37-.82-.72-1.65-1.03-2.5l-8.22.8-1.04-3.86 7.52-3.43c-.15-.88-.26-1.77-.35-2.67L122 26v-4l8.15-1.36c.09-.9.2-1.8.35-2.67l-7.52-3.43 1.04-3.86 8.22.8c.31-.85.66-1.68 1.03-2.5l-6.38-5.25 2-3.46 7.75 2.9c.52-.74 1.07-1.45 1.64-2.14L137.54 0zm2.43 0l.83 1.17a34.14 34.14 0 0 0-3.38 4.4l-7.63-2.86-.33.58 6.29 5.18a33.79 33.79 0 0 0-2.13 5.12l-8.1-.78-.18.64 7.42 3.37a34.02 34.02 0 0 0-.72 5.5L124 23.68v.66l8.04 1.34c.1 1.88.33 3.72.72 5.5l-7.42 3.38.18.64 8.1-.78a33.88 33.88 0 0 0 2.13 5.12l-6.29 5.18.33.58 7.63-2.86c1 1.56 2.14 3.03 3.38 4.4l-4.73 6.63.47.47 6.63-4.73a34.14 34.14 0 0 0 4.4 3.38l-2.86 7.63.58.33 5.18-6.29c1.63.84 3.35 1.56 5.12 2.13l-.78 8.1.64.18 3.37-7.42c1.79.39 3.63.63 5.5.72l1.35 8.04h.66l1.34-8.04c1.88-.1 3.72-.33 5.5-.72l3.38 7.42.64-.18-.78-8.1a33.88 33.88 0 0 0 5.12-2.13l5.18 6.29.58-.33-2.86-7.63c1.56-1 3.03-2.14 4.4-3.38l6.63 4.73.47-.47-4.73-6.63a34.14 34.14 0 0 0 3.38-4.4l7.63 2.86.33-.58-6.29-5.18a33.79 33.79 0 0 0 2.13-5.12l8.1.78.18-.64-7.42-3.37c.39-1.79.63-3.63.72-5.5l8.04-1.35v-.66l-8.04-1.34c-.1-1.88-.33-3.72-.72-5.5l7.42-3.38-.18-.64-8.1.78a33.79 33.79 0 0 0-2.13-5.12l6.29-5.18-.33-.58-7.63 2.86c-1-1.56-2.14-3.03-3.38-4.4l.83-1.17h-52.06V0zm-2.82 27h14.15A15.02 15.02 0 0 0 163 38.7v14.15A29.01 29.01 0 0 1 137.15 27zm12.57-27H163v9.3A15.02 15.02 0 0 0 151.3 21h-14.15a28.99 28.99 0 0 1 12.57-21zM169 52.85V38.7A15.02 15.02 0 0 0 180.7 27h14.15A29.01 29.01 0 0 1 169 52.85zM182.28 0a28.99 28.99 0 0 1 12.57 21H180.7A15.02 15.02 0 0 0 169 9.3V0h13.28zm-42.82 29A27.03 27.03 0 0 0 161 50.54V40.25A17.04 17.04 0 0 1 149.75 29h-10.29zm14.16-29a27.04 27.04 0 0 0-14.16 19h10.29A17.04 17.04 0 0 1 161 7.75V0h-7.38zM171 50.54A27.03 27.03 0 0 0 192.54 29h-10.29A17.04 17.04 0 0 1 171 40.25v10.29zM178.38 0H171v7.75A17.04 17.04 0 0 1 182.25 19h10.29a27.04 27.04 0 0 0-14.16-19zM166 34a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm-39.51 176.15l-10.67-7.95 6-10.4 12.23 5.27a23.97 23.97 0 0 1 8.4-4.86L144 177h12l1.55 13.21a23.97 23.97 0 0 1 8.4 4.86l12.23-5.27 6 10.4-10.67 7.95a24 24 0 0 1 0 9.7l10.67 7.95-6 10.4-12.23-5.27a23.97 23.97 0 0 1-8.4 4.86L156 249h-12l-1.55-13.21a23.97 23.97 0 0 1-8.4-4.86l-12.23 5.27-6-10.4 10.67-7.95a24.1 24.1 0 0 1 0-9.7zm29.25-16.4l-1.5-12.75h-8.48l-1.5 12.76c-3.75 1-7.1 2.99-9.79 5.65l-11.8-5.08-4.23 7.34 10.3 7.68c-.98 3.7-.98 7.6 0 11.3l-10.3 7.68 4.23 7.34 11.8-5.08a22.1 22.1 0 0 0 9.8 5.65l1.5 12.76h8.47l1.5-12.76c3.75-1 7.1-2.99 9.79-5.65l11.8 5.08 4.23-7.34-10.3-7.68c.98-3.7.98-7.6 0-11.3l10.3-7.68-4.23-7.34-11.8 5.08a21.98 21.98 0 0 0-9.8-5.65l.01-.01zM150 225a12 12 0 1 1 0-24 12 12 0 0 1 0 24zm0-2a10 10 0 1 0 0-20 10 10 0 0 0 0 20zm3.53 67.72l4.26.07.51 1.93-3.65 2.19c.11.63.2 1.27.25 1.92L159 298v2l-4.1 1.17c-.05.65-.14 1.29-.25 1.92l3.65 2.2-.51 1.92-4.26.07c-.22.61-.47 1.21-.74 1.8l2.96 3.05-1 1.74-4.13-1.04a24.1 24.1 0 0 1-1.18 1.54l2.07 3.72-1.42 1.42-3.72-2.07c-.5.41-1.01.8-1.54 1.18l1.04 4.13-1.74 1-3.05-2.96c-.59.27-1.19.52-1.8.74l-.07 4.26-1.93.51-2.19-3.65c-.63.11-1.27.2-1.92.25L132 327h-2l-1.17-4.1c-.65-.05-1.29-.14-1.92-.25l-2.2 3.65-1.92-.51-.07-4.26c-.61-.22-1.21-.47-1.8-.74l-3.05 2.96-1.74-1 1.04-4.13a24.1 24.1 0 0 1-1.54-1.18l-3.72 2.07-1.42-1.42 2.07-3.72c-.41-.5-.8-1.01-1.18-1.54l-4.13 1.04-1-1.74 2.96-3.05c-.27-.59-.52-1.19-.74-1.8l-4.26-.07-.51-1.93 3.65-2.19c-.11-.63-.2-1.27-.25-1.92L103 300v-2l4.1-1.17c.05-.65.14-1.29.25-1.92l-3.65-2.2.51-1.92 4.26-.07c.22-.61.47-1.21.74-1.8l-2.96-3.05 1-1.74 4.13 1.04c.38-.53.77-1.04 1.18-1.54l-2.07-3.72 1.42-1.42 3.72 2.07c.5-.41 1.01-.8 1.54-1.18l-1.04-4.13 1.74-1 3.05 2.96c.59-.27 1.19-.52 1.8-.74l.07-4.26 1.93-.51 2.19 3.65c.63-.11 1.27-.2 1.92-.25L130 271h2l1.17 4.1c.65.05 1.29.14 1.92.25l2.2-3.65 1.92.51.07 4.26c.61.22 1.21.47 1.8.74l3.05-2.96 1.74 1-1.04 4.13c.53.38 1.04.77 1.54 1.18l3.72-2.07 1.42 1.42-2.07 3.72c.41.5.8 1.01 1.18 1.54l4.13-1.04 1 1.74-2.96 3.05c.27.59.52 1.19.74 1.8zM109 299a22 22 0 1 0 44 0 22 22 0 0 0-44 0zm27.11-10.86l-3 5.22a6 6 0 0 0-4.21 0l-3.01-5.22a11.95 11.95 0 0 1 10.22 0zm1.74 1a12 12 0 0 1 5.1 8.86h-6.01a6.01 6.01 0 0 0-2.1-3.64l3-5.22h.01zm-13.7 0l3.02 5.22a6.01 6.01 0 0 0-2.1 3.64h-6.03a12 12 0 0 1 5.11-8.86zm-5.1 10.86h6.01a6.01 6.01 0 0 0 2.1 3.64l-3 5.22a12 12 0 0 1-5.12-8.86h.01zm6.84 9.86l3-5.22a6 6 0 0 0 4.21 0l3.01 5.22a11.95 11.95 0 0 1-10.22 0zm11.96-1l-3.02-5.22a6.01 6.01 0 0 0 2.1-3.64h6.03a12 12 0 0 1-5.11 8.86zm-4.68-19.62a10.04 10.04 0 0 0-4.34 0l1.05 1.82c.74-.1 1.5-.1 2.24 0l1.05-1.82zm5.2 3l-1.05 1.82c.46.59.84 1.24 1.12 1.94h2.1a9.99 9.99 0 0 0-2.17-3.76zm-14.74 0a9.99 9.99 0 0 0-2.17 3.76h2.1c.28-.7.66-1.35 1.12-1.94l-1.05-1.82zm-2.17 9.76a9.99 9.99 0 0 0 2.17 3.76l1.05-1.82a8.01 8.01 0 0 1-1.12-1.94h-2.1zm7.37 6.76c1.43.32 2.91.32 4.34 0l-1.05-1.82c-.74.1-1.5.1-2.24 0l-1.05 1.82zm9.54-3a9.99 9.99 0 0 0 2.17-3.76h-2.1c-.28.7-.66 1.35-1.12 1.94l1.05 1.82zM127 299a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm2 0a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm15 0a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm-6.5 11.26a4 4 0 1 1 4 6.93 4 4 0 0 1-4-6.93zm-13 0a4 4 0 1 1-4 6.93 4 4 0 0 1 4-6.93zM118 299a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm6.5-11.26a4 4 0 1 1-4-6.93 4 4 0 0 1 4 6.93zm13 0a4 4 0 1 1 4-6.93 4 4 0 0 1-4 6.93zM146 299a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm-7.5 12.99a2 2 0 1 0 1.66 3.64 2 2 0 0 0-1.66-3.64zm-15 0a2 2 0 1 0-2.15 3.38 2 2 0 0 0 2.15-3.38zM116 299a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm7.5-12.99a2 2 0 1 0-1.66-3.64 2 2 0 0 0 1.66 3.64zm15 0a2 2 0 1 0 2.15-3.38 2 2 0 0 0-2.15 3.38zm103.8-61.7l-.8-8.22 5.8-1.55 3.42 7.52c2.26-.43 4.57-.74 6.92-.9L259 213h6l1.36 8.16c2.35.16 4.66.47 6.92.9l3.42-7.52 5.8 1.55-.8 8.22c2.21.77 4.37 1.66 6.45 2.68l5.25-6.38 5.2 3-2.9 7.74a60.25 60.25 0 0 1 5.53 4.25l6.73-4.8 4.24 4.24-4.8 6.73a60.25 60.25 0 0 1 4.25 5.53l7.74-2.9 3 5.2-6.38 5.25a59.62 59.62 0 0 1 2.68 6.45l8.22-.8 1.55 5.8-7.52 3.42c.43 2.26.74 4.57.9 6.92L330 278v6l-8.16 1.36a60.03 60.03 0 0 1-.9 6.92l7.52 3.42-1.55 5.8-8.22-.8a59.62 59.62 0 0 1-2.68 6.45l6.38 5.25-3 5.2-7.74-2.9a60.25 60.25 0 0 1-4.25 5.53l4.8 6.73-4.24 4.24-6.73-4.8a60.25 60.25 0 0 1-5.53 4.25l2.9 7.74-5.2 3-5.25-6.38a59.62 59.62 0 0 1-6.45 2.68l.8 8.22-5.8 1.55-3.42-7.52c-2.26.43-4.57.74-6.92.9L265 349h-6l-1.36-8.16a60.03 60.03 0 0 1-6.92-.9l-3.42 7.52-5.8-1.55.8-8.22a59.62 59.62 0 0 1-6.45-2.68l-5.25 6.38-5.2-3 2.9-7.74a60.25 60.25 0 0 1-5.53-4.25l-6.73 4.8-4.24-4.24 4.8-6.73a60.25 60.25 0 0 1-4.25-5.53l-7.74 2.9-3-5.2 6.38-5.25a59.62 59.62 0 0 1-2.68-6.45l-8.22.8-1.55-5.8 7.52-3.42c-.43-2.29-.73-4.6-.9-6.92L194 284v-6l8.16-1.36c.16-2.35.47-4.66.9-6.92l-7.52-3.42 1.55-5.8 8.22.8c.77-2.2 1.66-4.35 2.68-6.45l-6.38-5.25 3-5.2 7.74 2.9a60.25 60.25 0 0 1 4.25-5.53l-4.8-6.73 4.24-4.24 6.73 4.8a60.25 60.25 0 0 1 5.53-4.25l-2.9-7.74 5.2-3 5.25 6.38a59.62 59.62 0 0 1 6.45-2.68zm2.12 1.4c-3.15 1-6.19 2.27-9.08 3.77l-5.19-6.3-2.3 1.33 2.86 7.65a58.24 58.24 0 0 0-7.79 5.98l-6.65-4.75-1.88 1.88 4.75 6.65a58.24 58.24 0 0 0-5.98 7.79l-7.65-2.86-1.33 2.3 6.3 5.2a57.64 57.64 0 0 0-3.77 9.07l-8.12-.79-.69 2.58 7.43 3.38a58 58 0 0 0-1.27 9.73l-8.06 1.35v2.66l8.06 1.35c.15 3.32.58 6.58 1.27 9.73l-7.43 3.38.7 2.58 8.11-.79c1 3.15 2.27 6.19 3.77 9.08l-6.3 5.19 1.33 2.3 7.65-2.86a58.24 58.24 0 0 0 5.98 7.79l-4.75 6.65 1.88 1.88 6.65-4.75a60.3 60.3 0 0 0 7.79 5.98l-2.86 7.65 2.3 1.33 5.2-6.3a56.99 56.99 0 0 0 9.07 3.77l-.79 8.12 2.58.69 3.38-7.43c3.15.69 6.4 1.12 9.73 1.27l1.35 8.06h2.66l1.35-8.06c3.32-.15 6.58-.58 9.73-1.27l3.38 7.43 2.58-.7-.79-8.11c3.15-1 6.19-2.27 9.08-3.77l5.19 6.3 2.3-1.33-2.86-7.65a58.24 58.24 0 0 0 7.79-5.98l6.65 4.75 1.88-1.88-4.75-6.65a60.3 60.3 0 0 0 5.98-7.79l7.65 2.86 1.33-2.3-6.3-5.2a56.99 56.99 0 0 0 3.77-9.07l8.12.79.69-2.58-7.43-3.38a58 58 0 0 0 1.27-9.73l8.06-1.35v-2.66l-8.06-1.35a58.04 58.04 0 0 0-1.27-9.73l7.43-3.38-.7-2.58-8.11.79c-1-3.15-2.27-6.19-3.77-9.08l6.3-5.19-1.33-2.3-7.65 2.86a58.24 58.24 0 0 0-5.98-7.79l4.75-6.65-1.88-1.88-6.65 4.75a58.24 58.24 0 0 0-7.79-5.98l2.86-7.65-2.3-1.33-5.2 6.3a57.64 57.64 0 0 0-9.07-3.77l.79-8.12-2.58-.69-3.38 7.43a58 58 0 0 0-9.73-1.27l-1.35-8.06h-2.66l-1.35 8.06c-3.32.15-6.58.58-9.73 1.27l-3.38-7.43-2.58.7.79 8.11zm4.58 50.1a13.96 13.96 0 0 0 0 10.39l-33.88 19.55A52.77 52.77 0 0 1 209 281c0-8.94 2.21-17.37 6.12-24.75L249 275.8v.01zm2-3.47l-33.87-19.56A52.97 52.97 0 0 1 260 228.04v39.1a13.99 13.99 0 0 0-9 5.2zm0 17.32a13.99 13.99 0 0 0 9 5.2v39.1a52.97 52.97 0 0 1-42.87-24.74L251 289.66zm13 5.2a13.99 13.99 0 0 0 9-5.2l33.87 19.56A52.97 52.97 0 0 1 264 333.96v-39.1zm11-8.66a13.96 13.96 0 0 0 0-10.4l33.88-19.55A52.77 52.77 0 0 1 315 281c0 8.94-2.21 17.37-6.12 24.75L275 286.2zm-2-13.86a13.99 13.99 0 0 0-9-5.2v-39.1a52.97 52.97 0 0 1 42.87 24.74L273 272.34zm-57.04-13.3A50.8 50.8 0 0 0 211 281a50.8 50.8 0 0 0 4.96 21.96l30.62-17.68c-.78-2.8-.78-5.76 0-8.56l-30.62-17.68zm4-6.93l30.62 17.68a16.08 16.08 0 0 1 7.42-4.29v-35.35a50.96 50.96 0 0 0-38.04 21.96zm0 57.78A50.96 50.96 0 0 0 258 331.85V296.5a15.98 15.98 0 0 1-7.42-4.29l-30.62 17.68zM266 331.85a50.96 50.96 0 0 0 38.04-21.96l-30.62-17.68a16.08 16.08 0 0 1-7.42 4.29v35.35zm42.04-28.89A50.8 50.8 0 0 0 313 281a50.8 50.8 0 0 0-4.96-21.96l-30.62 17.68c.78 2.8.78 5.76 0 8.56l30.62 17.68zm-4-50.85A50.96 50.96 0 0 0 266 230.15v35.35c2.86.74 5.41 2.25 7.42 4.29l30.62-17.68zM262 290a9 9 0 1 1 0-18 9 9 0 0 1 0 18zm0-2a7 7 0 1 0 0-14 7 7 0 0 0 0 14zM0 242.64l2.76.4 4.75 2.27a38.2 38.2 0 0 1 2.85-3.4l-3.06-4.28-1.69-5.11 3.07-2.58 4.74 2.55 3.69 3.76a37.96 37.96 0 0 1 3.84-2.22l-1.42-5.07.17-5.38 3.76-1.37 3.6 4.02 2.17 4.79c1.42-.34 2.88-.6 4.37-.77L34 225l2-5h4l2 5 .4 5.25c1.49.17 2.95.43 4.37.77l2.18-4.8 3.59-4 3.76 1.36.17 5.38-1.42 5.07c1.33.67 2.6 1.41 3.84 2.22l3.69-3.76 4.74-2.55 3.07 2.58-1.69 5.11-3.06 4.29a38.2 38.2 0 0 1 2.85 3.4l4.75-2.28 5.33-.77 2 3.46-3.33 4.23-4.34 2.98c.59 1.36 1.1 2.75 1.52 4.17l5.23-.52 5.27 1.1.7 3.94-4.58 2.84-5.1 1.31a38.6 38.6 0 0 1 0 4.44l5.1 1.3 4.58 2.85-.7 3.93-5.27 1.1-5.23-.5a36.3 36.3 0 0 1-1.52 4.16l4.34 2.98 3.33 4.23-2 3.46-5.33-.77-4.75-2.27a38.2 38.2 0 0 1-2.85 3.4l3.06 4.28 1.69 5.11-3.07 2.58-4.74-2.55-3.69-3.76a37.96 37.96 0 0 1-3.84 2.22l1.42 5.07-.17 5.38-3.76 1.37-3.6-4.02-2.17-4.79c-1.42.34-2.88.6-4.37.77L42 311l-2 5h-4l-2-5-.4-5.25a37.87 37.87 0 0 1-4.37-.77l-2.18 4.8-3.59 4-3.76-1.36-.17-5.38 1.42-5.07c-1.32-.66-2.6-1.4-3.84-2.22l-3.69 3.76-4.74 2.55-3.07-2.58 1.69-5.11 3.06-4.29a38.2 38.2 0 0 1-2.85-3.4l-4.75 2.28-2.76.4v-8.17l3.1-2.13a37.72 37.72 0 0 1-1.52-4.17l-1.58.16v-8.82l.06-.01a38.6 38.6 0 0 1 0-4.44l-.06-.01v-8.82l1.58.16c.43-1.43.94-2.82 1.52-4.17L0 250.8v-8.17.01zm0 1.87v3.89l5.62 3.84a35.74 35.74 0 0 0-2.55 7.02l-3.07-.3v4.75l2.2.56a36.42 36.42 0 0 0 0 7.46l-2.2.56v4.75l3.07-.3a35.2 35.2 0 0 0 2.55 7.02L0 287.6v3.89l1.76-.26 6.41-3.07c1.4 2.06 3 3.98 4.8 5.71l-4.14 5.78-1.01 3.07 1.22 1.03 2.85-1.52 4.98-5.08c2 1.45 4.16 2.7 6.45 3.73l-1.9 6.84.1 3.23 1.5.55 2.15-2.4 2.94-6.48a35.9 35.9 0 0 0 7.34 1.3L36 311l1.2 3h1.6l1.2-3 .55-7.09a35.9 35.9 0 0 0 7.34-1.29l2.94 6.47 2.15 2.4 1.5-.54.1-3.23-1.9-6.84a35.96 35.96 0 0 0 6.45-3.73l4.98 5.08 2.85 1.52 1.22-1.03-1-3.07-4.15-5.78a35.8 35.8 0 0 0 4.8-5.7l6.4 3.06 3.2.46.8-1.38-2-2.54-5.85-4.01c1.1-2.24 1.95-4.6 2.55-7.02l7.07.7 3.16-.66.28-1.58-2.75-1.7-6.88-1.77c.26-2.48.26-4.98 0-7.46l6.88-1.77 2.75-1.7-.28-1.58-3.16-.66-7.07.7a35.74 35.74 0 0 0-2.55-7.02l5.86-4 2-2.55-.8-1.38-3.2.46-6.41 3.07c-1.4-2.06-3-3.98-4.8-5.71l4.14-5.78 1.01-3.07-1.22-1.03-2.85 1.52-4.98 5.08c-2-1.45-4.16-2.7-6.45-3.73l1.9-6.84-.1-3.23-1.5-.55-2.15 2.4-2.94 6.48a35.9 35.9 0 0 0-7.34-1.3L40 225l-1.2-3h-1.6l-1.2 3-.55 7.09c-2.48.17-4.94.6-7.34 1.29l-2.94-6.47-2.15-2.4-1.5.54-.1 3.23 1.9 6.84a35.96 35.96 0 0 0-6.45 3.73l-4.98-5.08-2.85-1.52-1.22 1.03 1 3.07 4.15 5.78a36.18 36.18 0 0 0-4.8 5.7l-6.4-3.06L0 244.5v.01zM38 272a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0-26a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm24 24a4 4 0 1 1 8 0 4 4 0 0 1-8 0zm-24 24a4 4 0 1 1 0 8 4 4 0 0 1 0-8zm-24-24a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm24-26a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm26 26a2 2 0 1 0 4 0 2 2 0 0 0-4 0zm-26 26a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-26-26a2 2 0 1 0-4 0 2 2 0 0 0 4 0zm3.37 22.63a12 12 0 1 1 16.17-17.74 12 12 0 0 1-16.17 17.74zm0-45.26a12 12 0 1 1 17.74 16.17 12 12 0 0 1-17.74-16.17zm45.26 0a12 12 0 1 1-16.17 17.74 12 12 0 0 1 16.17-17.74zm0 45.26a12 12 0 1 1-17.74-16.17 12 12 0 0 1 17.74 16.17zm-15.56-29.7a10 10 0 1 0 14.39-13.9 10 10 0 0 0-14.39 13.9zm0 14.14a10 10 0 1 0 13.9 14.39 10 10 0 0 0-13.9-14.39zm-14.14 0a10 10 0 1 0-14.39 13.9 10 10 0 0 0 14.39-13.9zm0-14.14a10 10 0 1 0-13.9-14.39 10 10 0 0 0 13.9 14.39zm230.9-245.4l-.08-4.18 1.93-.52 2.04 3.67c1.07-.2 2.16-.35 3.26-.43L270 10h2l1.02 4.07c1.1.08 2.2.22 3.26.43l2.04-3.67 1.93.52-.07 4.19a27 27 0 0 1 3.04 1.26l2.91-3.01 1.74 1-1.16 4.03c.91.62 1.78 1.29 2.61 2l3.6-2.15 1.41 1.41-2.16 3.6c.72.83 1.4 1.7 2 2.6l4.04-1.15 1 1.74-3.01 2.91c.48.98.9 2 1.26 3.04l4.2-.07.5 1.93-3.66 2.04c.2 1.07.35 2.16.43 3.26L303 41v2l-4.07 1.02a26.9 26.9 0 0 1-.43 3.26l3.67 2.04-.52 1.93-4.19-.07a27.82 27.82 0 0 1-1.26 3.04l3.01 2.91-1 1.74-4.03-1.16c-.62.91-1.29 1.78-2 2.61l2.15 3.6-1.41 1.41-3.6-2.16c-.83.72-1.7 1.4-2.6 2l1.15 4.04-1.74 1-2.91-3.01a27 27 0 0 1-3.04 1.26l.07 4.2-1.93.5-2.04-3.66c-1.07.2-2.16.35-3.26.43L272 74h-2l-1.02-4.07a26.9 26.9 0 0 1-3.26-.43l-2.04 3.67-1.93-.52.07-4.19a27.82 27.82 0 0 1-3.04-1.26l-2.91 3.01-1.74-1 1.16-4.03c-.9-.62-1.78-1.29-2.61-2l-3.6 2.15-1.41-1.41 2.16-3.6c-.72-.83-1.4-1.7-2-2.6l-4.04 1.15-1-1.74 3.01-2.91a27 27 0 0 1-1.26-3.04l-4.2.07-.5-1.93 3.66-2.04c-.2-1.07-.35-2.16-.43-3.26L239 43v-2l4.07-1.02c.08-1.1.22-2.2.43-3.26l-3.67-2.04.52-1.93 4.19.07a27 27 0 0 1 1.26-3.04l-3.01-2.91 1-1.74 4.03 1.16c.62-.91 1.29-1.78 2-2.61l-2.15-3.6 1.41-1.41 3.6 2.16c.83-.72 1.7-1.4 2.6-2l-1.15-4.04 1.74-1 2.91 3.01a27 27 0 0 1 3.04-1.26l.01-.01zM271 68a26 26 0 1 0 0-52 26 26 0 0 0 0 52zm0-9a17 17 0 1 1 0-34 17 17 0 0 1 0 34zm0-2a15 15 0 1 0 0-30 15 15 0 0 0 0 30zm0-8a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-2a5 5 0 1 0 0-10 5 5 0 0 0 0 10zm0-14a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm9 9a2 2 0 1 1 4 0 2 2 0 0 1-4 0zm-9 9a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm-9-9a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm47.93 53.79l-1.8-3.91 1.63-1.18 3.15 2.92c.4-.17.82-.3 1.25-.4L315 89h2l.84 4.21c.43.1.85.24 1.25.4l3.15-2.9 1.62 1.17-1.8 3.9c.3.33.55.69.78 1.06l4.26-.5.62 1.9-3.75 2.1c.04.44.04.87 0 1.31l3.75 2.1-.62 1.9-4.26-.5c-.23.38-.49.74-.77 1.06l1.8 3.91-1.63 1.18-3.15-2.92c-.4.17-.82.3-1.25.4L317 113h-2l-.84-4.21c-.43-.1-.85-.24-1.25-.4l-3.15 2.9-1.62-1.17 1.8-3.9a8.03 8.03 0 0 1-.78-1.06l-4.26.5-.62-1.9 3.75-2.1a8.1 8.1 0 0 1 0-1.31l-3.75-2.1.62-1.9 4.26.5c.23-.38.49-.74.77-1.06zM316 106a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM75.73 179.2l-.6-2.1 1.74-1 1.51 1.57a9.93 9.93 0 0 1 2.1-.55L81 175h2l.53 2.12c.72.1 1.42.3 2.09.55l1.51-1.56 1.74 1-.6 2.1c.56.45 1.07.96 1.52 1.52l2.1-.6 1 1.74-1.56 1.51c.25.67.44 1.37.55 2.1L94 186v2l-2.12.53a9.9 9.9 0 0 1-.55 2.09l1.56 1.51-1 1.74-2.1-.6a9.93 9.93 0 0 1-1.52 1.52l.6 2.1-1.74 1-1.51-1.56c-.67.25-1.37.44-2.1.55L83 199h-2l-.53-2.12c-.71-.1-1.42-.3-2.09-.55l-1.51 1.56-1.74-1 .6-2.1a9.93 9.93 0 0 1-1.52-1.52l-2.1.6-1-1.74 1.56-1.51a9.93 9.93 0 0 1-.55-2.1L70 188v-2l2.12-.53c.1-.72.3-1.42.55-2.09l-1.56-1.51 1-1.74 2.1.6c.45-.56.96-1.07 1.52-1.52v-.01zm2.15.94a8.04 8.04 0 0 0-2.74 2.74l-.14.25a7.96 7.96 0 0 0 0 7.74l.14.25a8.04 8.04 0 0 0 2.74 2.74l.25.14a7.96 7.96 0 0 0 7.74 0l.25-.14a8.04 8.04 0 0 0 2.74-2.74l.14-.25a7.96 7.96 0 0 0 0-7.74l-.14-.25a8.04 8.04 0 0 0-2.74-2.74l-.25-.14a7.96 7.96 0 0 0-7.74 0l-.25.14zM82 193a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm278 3.18l-3.8 5.6-7.18-3.51 2.6-8.07a32.15 32.15 0 0 1-3.07-2.46l-7.27 4.35-5.04-6.22 5.82-6.26c-.64-1.13-1.2-2.3-1.7-3.52l-8.45.73-1.8-7.8 7.95-3.07a32.5 32.5 0 0 1 0-3.9l-7.95-3.07 1.8-7.8 8.45.73a31.7 31.7 0 0 1 1.7-3.52l-5.82-6.26 5.04-6.22 7.27 4.35c.97-.88 2-1.7 3.07-2.46l-2.6-8.07 7.19-3.5 3.79 5.59v64.36zm0-3.53v-57.3l-4.46-6.58-4.1 2 2.53 7.87a30.14 30.14 0 0 0-5.13 4.1l-7.08-4.24-2.88 3.55 5.65 6.09a29.87 29.87 0 0 0-2.82 5.86l-8.24-.7-1.03 4.46 7.73 2.99a30.34 30.34 0 0 0 0 6.5l-7.73 3 1.03 4.45 8.24-.7a29.87 29.87 0 0 0 2.82 5.86l-5.65 6.1 2.88 3.54 7.08-4.23a30.14 30.14 0 0 0 5.13 4.09l-2.54 7.86 4.11 2 4.46-6.57zm0-51.57v5.71l-3.56-3.8a24.94 24.94 0 0 1 3.56-1.91zm0 22.68l-14.17 6.64c-2.5-9.5.77-19.57 8.38-25.78l5.79 10.5v8.64zm0 23.16a25.08 25.08 0 0 1-13.32-13.9l13.32-2.55v16.45zm0-43.64l-.39.2.39.4v-.6zm0 18.29v-2.35l-6.3-11.44a22.93 22.93 0 0 0-6.43 19.76l12.73-5.97zm0 23.15v-12.23l-10.47 2.01A23.1 23.1 0 0 0 360 182.72zM0 129.82l1 1.46a31.8 31.8 0 0 1 3.8-.86L6 122h8l1.2 8.42c1.3.21 2.57.5 3.8.86l4.8-7.06 7.18 3.51-2.6 8.07c1.07.76 2.1 1.58 3.07 2.46l7.27-4.35 5.04 6.22-5.82 6.26c.64 1.13 1.2 2.3 1.7 3.52l8.45-.73 1.8 7.8-7.95 3.07c.08 1.3.08 2.6 0 3.9l7.95 3.07-1.8 7.8-8.45-.73a33.5 33.5 0 0 1-1.7 3.52l5.82 6.26-5.04 6.22-7.27-4.35c-.97.88-2 1.7-3.07 2.46l2.6 8.07-7.19 3.5-4.78-7.05c-1.24.36-2.51.65-3.8.86L14 202H6l-1.2-8.42a31.8 31.8 0 0 1-3.8-.86l-1 1.46v-64.36zm0 3.53v57.3l.2-.29c2.02.7 4.15 1.2 6.34 1.44l1.17 8.2h4.58l1.17-8.2c2.2-.25 4.32-.74 6.35-1.44l4.65 6.87 4.1-2-2.53-7.87a30.14 30.14 0 0 0 5.13-4.1l7.08 4.24 2.88-3.55-5.65-6.09c1.14-1.83 2.1-3.8 2.82-5.86l8.24.7 1.03-4.46-7.73-2.99a30.7 30.7 0 0 0 0-6.5l7.73-3-1.03-4.45-8.24.7a29.87 29.87 0 0 0-2.82-5.86l5.65-6.1-2.88-3.54-7.08 4.23a30.14 30.14 0 0 0-5.13-4.09l2.54-7.86-4.11-2-4.65 6.86a29.82 29.82 0 0 0-6.35-1.44l-1.17-8.2H7.7l-1.17 8.2c-2.2.25-4.32.74-6.35 1.44l-.19-.29H0zm34.17 35.05l-16.26-7.62a7.94 7.94 0 0 0-.8-2.44l8.68-15.72a24.95 24.95 0 0 1 8.38 25.78zm-.85 2.63a25.01 25.01 0 0 1-21.94 15.93l2.23-17.82a8.3 8.3 0 0 0 2.07-1.5l17.64 3.39zM0 139.08A24.92 24.92 0 0 1 10 137c5 0 9.65 1.47 13.56 4l-12.28 13.1a8.06 8.06 0 0 0-2.56 0L0 144.8v-5.72zm0 22.68v-8.65l2.88 5.23c-.4.77-.66 1.59-.79 2.44l-2.09.98zm0 23.16v-16.45l4.32-.83c.6.6 1.3 1.11 2.07 1.5l2.23 17.82c-2.97-.16-5.9-.85-8.62-2.04zM10 156a6 6 0 1 1 0 12 6 6 0 0 1 0-12zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM0 141.28v.6l9.48 10.13c.35-.02.7-.02 1.04 0l9.87-10.54A22.9 22.9 0 0 0 10 139c-3.58 0-6.98.82-10 2.28zm0 18.29l.34-.16c.09-.34.2-.67.32-.99l-.66-1.2v2.35zm0 23.15c1.97.95 4.1 1.63 6.34 1.99l-1.8-14.33a11.6 11.6 0 0 1-.83-.6l-3.71.7v12.24zm13.66 1.99a23.03 23.03 0 0 0 16.8-12.21l-14.17-2.72c-.27.21-.55.42-.84.6l-1.79 14.33zm19.07-19.17a22.93 22.93 0 0 0-6.42-19.75l-6.97 12.63c.12.32.23.65.32.99l13.07 6.13zM137.54 360l-4.07-5.7 2.83-2.83 6.73 4.8c.7-.56 1.4-1.11 2.14-1.63l-2.9-7.75 3.46-2 5.25 6.38c.82-.37 1.65-.72 2.5-1.03l-.8-8.22 3.86-1.04 3.43 7.52c.88-.15 1.77-.26 2.67-.35L164 340h4l1.36 8.15c.9.09 1.8.2 2.67.35l3.43-7.52 3.86 1.04-.8 8.22c.85.31 1.68.66 2.5 1.03l5.25-6.38 3.46 2-2.9 7.75c.74.52 1.45 1.07 2.14 1.64l6.73-4.8 2.83 2.82-4.07 5.7h-56.92zm2.43 0h52.06l3.9-5.46-.47-.47-6.63 4.73a34.14 34.14 0 0 0-4.4-3.38l2.86-7.63-.58-.33-5.18 6.29a33.79 33.79 0 0 0-5.12-2.13l.78-8.1-.64-.18-3.37 7.42a34.02 34.02 0 0 0-5.5-.72l-1.35-8.04h-.66l-1.34 8.04c-1.88.1-3.72.33-5.5.72l-3.38-7.42-.64.18.78 8.1a33.88 33.88 0 0 0-5.12 2.13l-5.18-6.29-.58.33 2.86 7.63c-1.56 1-3.03 2.14-4.4 3.38l-6.63-4.73-.47.47 3.9 5.46zm9.75 0a28.83 28.83 0 0 1 13.28-4.85V360h-13.28zm32.56 0H169v-4.85c4.9.5 9.42 2.22 13.28 4.85zm-28.66 0H161v-2.54a26.8 26.8 0 0 0-7.38 2.54zm24.76 0a26.8 26.8 0 0 0-7.38-2.54V360h7.38zM358.79 0h-1.21l1.5 3.28a48.3 48.3 0 0 0-5.8 5.8l-9.38-4.3-1.65 2.26 7 7.58a47.84 47.84 0 0 0-3.74 7.33l-10.24-1.2-.86 2.66 8.99 5.05a47.91 47.91 0 0 0-1.28 8.12L332 38.6v2.8l10.12 2.02c.2 2.78.63 5.5 1.28 8.12l-9 5.05.87 2.66 10.24-1.2c1.04 2.54 2.29 5 3.74 7.33l-7 7.58 1.65 2.26 9.38-4.3a48.3 48.3 0 0 0 5.8 5.8l-4.3 9.38 2.26 1.65 2.96-2.73v2.66l-2.84 2.62-4.85-3.52 4.36-9.5a50.31 50.31 0 0 1-3.95-3.95l-9.5 4.36-3.52-4.85 7.08-7.68a49.83 49.83 0 0 1-2.54-4.98l-10.38 1.21-1.85-5.7 9.11-5.12a49.9 49.9 0 0 1-.87-5.52L330 43v-6l10.25-2.05c.19-1.87.48-3.72.87-5.52l-9.11-5.12 1.85-5.7 10.38 1.21c.75-1.71 1.6-3.37 2.54-4.98l-7.08-7.68 3.52-4.85 9.5 4.36a50.31 50.31 0 0 1 3.95-3.95L355.42 0h3.37zM360 52.7l-6.48 3.74A39.86 39.86 0 0 1 350 40a39.9 39.9 0 0 1 3.52-16.44L360 27.3v25.4zm0-39.16v4.52l-2.47-1.43c.77-1.07 1.6-2.1 2.47-3.09zm0 52.92c-.87-.99-1.7-2.02-2.47-3.1l2.47-1.42v4.52zm0-16.07V29.61l-5.5-3.18a37.91 37.91 0 0 0 0 27.14l5.5-3.18zM62.42 360h2.16l3.11-6.78-4.85-3.52-7.68 7.08a49.83 49.83 0 0 0-4.98-2.54l1.21-10.38-5.7-1.85-5.12 9.11a49.9 49.9 0 0 0-5.52-.87L33 340h-6l-2.05 10.25c-1.85.19-3.7.48-5.52.87l-5.12-9.11-5.7 1.85 1.21 10.38c-1.71.75-3.37 1.6-4.98 2.54L0 352.32v5.17-2.5l4.62 4.26a47.84 47.84 0 0 1 7.33-3.74l-1.2-10.24 2.66-.86 5.05 8.99a47.91 47.91 0 0 1 8.12-1.28L28.6 342h2.8l2.02 10.12c2.78.2 5.5.63 8.12 1.28l5.05-9 2.66.87-1.2 10.24c2.54 1.04 5 2.29 7.33 3.74l7.58-7 2.26 1.65-2.8 6.1zM360 244.51l-1.44-.2-.8 1.38 2 2.54.24.17v-3.89zm0 14.45l-4-.4-3.16.66-.28 1.58 2.75 1.7 4.69 1.2v-4.74zm0 13.33l-4.7 1.2-2.74 1.71.28 1.58 3.16.66 4-.4v-4.75zm0 15.31l-.24.17-2 2.54.8 1.38 1.44-.2v-3.89zm0 5.76l-2.57.37-2-3.46 3.33-4.23 1.24-.85v8.17zm0-14.31l-3.65.36-5.27-1.1-.7-3.94 4.58-2.84 5.04-1.3v8.82zm0-13.28l-5.04-1.3-4.58-2.84.7-3.93 5.27-1.1 3.65.35v8.82zm0-14.96l-1.24-.85-3.33-4.23 2-3.46 2.57.37v8.17zm0 101.5V360h-4.58l-3.11-6.78 4.85-3.52 2.84 2.62v-.01zm0 2.67l-2.96-2.73-2.26 1.65 2.8 6.1H360v-5.02z'%3E%3C/path%3E%3C/svg%3E"); 37 | 38 | & &-title>span { 39 | font-size: 120px; 40 | font-family: @font-family 41 | } 42 | } 43 | 44 | 45 | .@{dumi-theme-default-prefix}-header { 46 | background: rgba(255, 255, 255, 0.8) !important; 47 | backdrop-filter: blur(20px); 48 | 49 | @{dark-selector} & { 50 | background: rgba(0, 0, 0, 0.8) !important; 51 | } 52 | } 53 | 54 | code { 55 | padding: 1px 5px !important; 56 | border-radius: 4px; 57 | color: #ff7875 !important; 58 | background: rgba(0, 0, 0, 0.06) !important; 59 | box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.1); 60 | border: 1px solid rgba(0, 0, 0, 0.1); 61 | } 62 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

🤖 Issues 助手

8 | 9 |
10 | 11 | 一个轻松帮你自动管理 issues 的 GitHub Action 12 | 13 | [![](https://img.shields.io/github/actions/workflow/status/actions-cool/issues-helper/ci.yml?branch=main&style=flat-square)](https://github.com/actions-cool/issues-helper/actions) 14 | [![](https://img.shields.io/badge/marketplace-issues--helper-red?style=flat-square)](https://github.com/marketplace/actions/issues-helper) 15 | ![](https://img.shields.io/github/languages/top/actions-cool/issues-helper?color=%2308979c&style=flat-square) 16 | [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) 17 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 18 | 19 | [![](https://img.shields.io/github/stars/actions-cool/issues-helper?style=flat-square)](https://github.com/actions-cool/issues-helper/stargazers) 20 | [![](https://img.shields.io/github/v/release/actions-cool/issues-helper?style=flat-square&color=orange)](https://github.com/actions-cool/issues-helper/releases) 21 | [![](https://img.shields.io/badge/discussions-on%20github-blue?style=flat-square&color=%23ff7875)](https://github.com/actions-cool/issues-helper/discussions) 22 | [![](https://img.shields.io/github/license/actions-cool/issues-helper?style=flat-square)](https://github.com/actions-cool/issues-helper/blob/main/LICENSE) 23 | 24 |
25 | 26 | [English](README.md) | 简体中文 27 | 28 | ## 🔗 链接 29 | 30 | - [在线文档](https://actions-cool.github.io/issues-helper/) 31 | - [在线文档 v2 版本](https://actions-cool.github.io/issues-helper-2.x/) 32 | - [更新日志](./CHANGELOG.md) 33 | 34 | ## 😎 为什么用 GitHub Action? 35 | 36 | 1. 完全免费 37 | 2. 全自动操作 38 | 3. 托管于 GitHub 服务器,只要 GitHub 不宕机,它就不受影响 39 | 40 | > Private 项目每月有 2000 次的限制,[具体查看](https://github.com/settings/billing)。Public 项目无限制。 41 | 42 | ## 谁在使用? 43 | 44 | 欢迎在 [**这里**](https://github.com/actions-cool/issues-helper/issues/6) 留言。 45 | 46 |
47 | 48 | 53 | 58 | 63 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 80 | 85 | 90 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 107 | 112 | 117 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 134 | 139 | 144 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 161 | 166 | 171 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 188 | 193 | 198 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 |
ant-designant-design-blazorant-design-mobileant-design-vue
76 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 91 | 92 | 93 | 94 |
bootstrapcoredumielectron
103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | 121 |
element-plusformilyjsx-nextmaterial-ui
130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 140 | 141 | 142 | 143 | 145 | 146 | 147 | 148 |
nutuiprettierreact-componentreact-music-player
157 | 158 | 159 | 160 | 162 | 163 | 164 | 165 | 167 | 168 | 169 | 170 | 172 | 173 | 174 | 175 |
S2swiperumivite
184 | 185 | 186 | 187 | 189 | 190 | 191 | 192 | 194 | 195 | 196 | 197 | 199 | 200 | 201 | 202 |
vitestvue-requestvuepress-nextzoo
211 | 212 | ## 图标 213 | 214 | 如果觉得 actions-cool 能帮到你,欢迎复制到 README 中支持,推广给更多的项目:[![actions-cool](https://img.shields.io/badge/using-actions--cool-blue?style=flat-square)](https://github.com/actions-cool),[更多样式](https://github.com/actions-cool/issues-helper/issues/92)。 215 | 216 | ``` 217 | [![actions-cool](https://img.shields.io/badge/using-actions--cool-blue?style=flat-square)](https://github.com/actions-cool) 218 | ``` 219 | 220 | ## ⚡ 反馈 221 | 222 | 非常欢迎你来尝试使用,并提出意见,你可以通过以下方式: 223 | 224 | - 通过 [Issue](https://github.com/actions-cool/issues-helper/issues) 报告 bug 或进行咨询 225 | - 通过 [Discussions](https://github.com/actions-cool/issues-helper/discussions) 进行讨论 226 | - 提交 [Pull Request](https://github.com/actions-cool/issues-helper/pulls) 改进 `issues-helper` 的代码 227 | 228 | 也欢迎加入 钉钉交流群 229 | 230 | ![](https://gw.alipayobjects.com/mdn/rms_f97235/afts/img/A*-iuDSpF7QAQAAAAAAAAAAAAAARQnAQ) 231 | 232 | ## 列 表 233 | 234 | 当以下列表没有你想要的功能时,可以在 [issues](https://github.com/actions-cool/issues-helper/issues) 中提出。 235 | 236 | - ⭐ 基 础 237 | - [`add-assignees`](#add-assignees) 238 | - [`add-labels`](#add-labels) 239 | - [`close-issue`](#close-issue) 240 | - [`create-comment`](#create-comment) 241 | - [`create-issue`](#create-issue) 242 | - [`create-label`](#create-label) 243 | - [`delete-comment`](#delete-comment) 244 | - [`get-issue`](#get-issue) 245 | - [`lock-issue`](#lock-issue) 246 | - [`open-issue`](#open-issue) 247 | - [`remove-assignees`](#remove-assignees) 248 | - [`remove-labels`](#remove-labels) 249 | - [`set-labels`](#set-labels) 250 | - [`unlock-issue`](#unlock-issue) 251 | - [`update-comment`](#update-comment) 252 | - [`update-issue`](#update-issue) 253 | - 🌟 进 阶 254 | - [`check-inactive`](#check-inactive) 255 | - [`check-issue`](#check-issue) 256 | - [`close-issues`](#close-issues) 257 | - [`find-comments`](#find-comments) 258 | - [`find-issues`](#find-issues) 259 | - [`lock-issues`](#lock-issues) 260 | - [`mark-assignees`](#mark-assignees) 261 | - [`mark-duplicate`](#mark-duplicate) 262 | - [`toggle-labels`](#toggle-labels) 263 | - [`welcome`](#welcome) 264 | 265 | ## 🚀 使 用 266 | 267 | ### ⭐ 基 础 268 | 269 | 270 | 271 | 为了更好的展示功能,下面以实际场景举例,请灵活参考。 272 | 273 | #### `add-assignees` 274 | 275 | 当一个 issue 新增或修改时,将这个 issue 指定某人或多人。 276 | 277 | ```yml 278 | name: Add Assigness 279 | 280 | on: 281 | issues: 282 | types: [opened, edited] 283 | 284 | jobs: 285 | add-assigness: 286 | runs-on: ubuntu-latest 287 | steps: 288 | - name: Add assigness 289 | uses: actions-cool/issues-helper@v3 290 | with: 291 | actions: 'add-assignees' 292 | token: ${{ secrets.GITHUB_TOKEN }} 293 | issue-number: ${{ github.event.issue.number }} 294 | assignees: 'xxx' or 'xx1,xx2' 295 | random-to: 1 296 | ``` 297 | 298 | | 参数 | 描述 | 类型 | 必填 | 299 | | -- | -- | -- | -- | 300 | | actions | 操作类型 | string | ✔ | 301 | | token | [token 说明](#token) | string | ✖ | 302 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 303 | | assignees | 指定人。当不填或者为空字符时,不操作 | string | ✖ | 304 | | random-to | 当设置时,会在 assignees 中随机选择 | number | ✖ | 305 | 306 | - `actions` 支持多个,需用逗号隔开。如:`add-assignees, add-labels` 307 | - 其中的 `name` 可根据自行根据实际情况修改 308 | - [on 参考](#github-docs) 309 | - `${{ github.event.issue.number }}` 表示当前 issue 编号,[更多参考](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events) 310 | - `assignees` 支持多个,需用逗号隔开 311 | - assign 最多只能设定 10 个 312 | 313 | [⏫ 返回列表](#列-表) 314 | 315 | #### `add-labels` 316 | 317 | 当一个新增的 issue 内容不包含指定格式时,为这个 issue 添加 labels。 318 | 319 | ```yml 320 | name: Add Labels 321 | 322 | on: 323 | issues: 324 | types: [opened] 325 | 326 | jobs: 327 | add-labels: 328 | runs-on: ubuntu-latest 329 | if: contains(github.event.issue.body, 'xxx') == false 330 | steps: 331 | - name: Add labels 332 | uses: actions-cool/issues-helper@v3 333 | with: 334 | actions: 'add-labels' 335 | token: ${{ secrets.GITHUB_TOKEN }} 336 | issue-number: ${{ github.event.issue.number }} 337 | labels: 'bug' or 'bug1, bug2' 338 | ``` 339 | 340 | | 参数 | 描述 | 类型 | 必填 | 341 | | -- | -- | -- | -- | 342 | | actions | 操作类型 | string | ✔ | 343 | | token | [token 说明](#token) | string | ✖ | 344 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 345 | | labels | 新增的 labels。当不填或者为空字符时,不新增 | string | ✖ | 346 | 347 | - `labels` 支持多个,需用逗号隔开 348 | 349 | [⏫ 返回列表](#列-表) 350 | 351 | #### `close-issue` 352 | 353 | 关闭指定 issue。 354 | 355 | ```yml 356 | - name: Close issue 357 | uses: actions-cool/issues-helper@v3 358 | with: 359 | actions: 'close-issue' 360 | token: ${{ secrets.GITHUB_TOKEN }} 361 | issue-number: xxx 362 | ``` 363 | 364 | | 参数 | 描述 | 类型 | 必填 | 365 | | -- | -- | -- | -- | 366 | | actions | 操作类型 | string | ✔ | 367 | | token | [token 说明](#token) | string | ✖ | 368 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 369 | | close-reason | 关闭原因。默认`not_planned`未计划,`completed`完成 | string | ✖ | 370 | 371 | [⏫ 返回列表](#列-表) 372 | 373 | #### `create-comment` 374 | 375 | 当新增一个指定 label 时,对该 issue 进行评论。 376 | 377 | ```yml 378 | name: Create Comment 379 | 380 | on: 381 | issues: 382 | types: [labeled] 383 | 384 | jobs: 385 | create-comment: 386 | runs-on: ubuntu-latest 387 | if: github.event.label.name == 'xxx' 388 | steps: 389 | - name: Create comment 390 | uses: actions-cool/issues-helper@v3 391 | with: 392 | actions: 'create-comment' 393 | token: ${{ secrets.GITHUB_TOKEN }} 394 | issue-number: ${{ github.event.issue.number }} 395 | body: | 396 | Hello ${{ github.event.issue.user.login }}. Add some comments. 397 | 398 | 你好 ${{ github.event.issue.user.login }}。巴拉巴拉。 399 | emoji: '+1' or '+1,heart' 400 | ``` 401 | 402 | | 参数 | 描述 | 类型 | 必填 | 403 | | -- | -- | -- | -- | 404 | | actions | 操作类型 | string | ✔ | 405 | | token | [token 说明](#token) | string | ✖ | 406 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 407 | | body | 新增评论的内容 | string | ✔ | 408 | | emoji | 为新增评论的增加 [emoji](#emoji-类型) | string | ✖ | 409 | 410 | - `body` 为空时,无操作 411 | - 返回 `comment-id`,可用于之后操作。[用法参考](#outputs-使用) 412 | - `${{ github.event.issue.user.login }}` 表示该 issue 的创建者 413 | - `emoji` 支持多个,需用逗号隔开 414 | 415 | [⏫ 返回列表](#列-表) 416 | 417 | #### `create-issue` 418 | 419 | 感觉新增 issue 使用场景不多。这里举例,每月 1 号 UTC 00:00 新增一个 issue。 420 | 421 | ```yml 422 | name: Create Issue 423 | 424 | on: 425 | schedule: 426 | - cron: "0 0 1 * *" 427 | 428 | jobs: 429 | create-issue: 430 | runs-on: ubuntu-latest 431 | steps: 432 | - name: Create issue 433 | uses: actions-cool/issues-helper@v3 434 | with: 435 | actions: 'create-issue' 436 | token: ${{ secrets.GITHUB_TOKEN }} 437 | title: 'xxxx' 438 | body: 'xxxx' 439 | labels: 'xx' 440 | assignees: 'xxx' 441 | emoji: '+1' 442 | ``` 443 | 444 | | 参数 | 描述 | 类型 | 必填 | 445 | | -- | -- | -- | -- | 446 | | actions | 操作类型 | string | ✔ | 447 | | token | [token 说明](#token) | string | ✖ | 448 | | title | 新增 issue 的标题 | string | ✔ | 449 | | body | 新增 issue 的内容 | string | ✖ | 450 | | labels | 为新增 issue 添加 labels | string | ✖ | 451 | | assignees | 为新增 issue 添加 assignees | string | ✖ | 452 | | random-to | 当设置时,会在 assignees 中随机选择 | number | ✖ | 453 | | emoji | 为新增 issue 增加 [emoji](#emoji-types) | string | ✖ | 454 | 455 | - `title` 为空时,无操作 456 | - 返回 `issue-number`,[用法参考](#outputs-使用) 457 | 458 | [⏫ 返回列表](#列-表) 459 | 460 | #### `create-label` 461 | 462 | 新增 label。若想批量维护 labels,[可查看](https://github.com/actions-cool/labels-helper)。 463 | 464 | ```yml 465 | - name: Create label 466 | uses: actions-cool/issues-helper@v3 467 | with: 468 | actions: 'create-label' 469 | token: ${{ secrets.GITHUB_TOKEN }} 470 | label-name: 'xx' 471 | label-color: '0095b3' 472 | label-desc: 'xx' 473 | ``` 474 | 475 | | 参数 | 描述 | 类型 | 必填 | 476 | | -- | -- | -- | -- | 477 | | actions | 操作类型 | string | ✔ | 478 | | token | [token 说明](#token) | string | ✖ | 479 | | label-name | 标签名称,支持 emoji | string | ✔ | 480 | | label-color | 标签颜色,格式为 16 进制色码,不加 `#` | string | ✖ | 481 | | label-desc | 标签描述 | string | ✖ | 482 | 483 | - `label-name`:若已存在,则无操作 484 | - `label-color`:默认为 `ededed` 485 | 486 | [⏫ 返回列表](#列-表) 487 | 488 | #### `delete-comment` 489 | 490 | 根据 [`comment-id`](#comment-id) 删除指定评论。 491 | 492 | ```yml 493 | - name: Delete comment 494 | uses: actions-cool/issues-helper@v3 495 | with: 496 | actions: 'delete-comment' 497 | token: ${{ secrets.GITHUB_TOKEN }} 498 | comment-id: xxx 499 | ``` 500 | 501 | | 参数 | 描述 | 类型 | 必填 | 502 | | -- | -- | -- | -- | 503 | | actions | 操作类型 | string | ✔ | 504 | | token | [token 说明](#token) | string | ✖ | 505 | | comment-id | 指定的 comment | number | ✔ | 506 | 507 | [⏫ 返回列表](#列-表) 508 | 509 | #### `get-issue` 510 | 511 | 查询 issue 信息。 512 | 513 | ```yml 514 | - name: Get Issue 515 | uses: actions-cool/issues-helper@v3 516 | with: 517 | actions: 'get-issue' 518 | token: ${{ secrets.GITHUB_TOKEN }} 519 | ``` 520 | 521 | | 参数 | 描述 | 类型 | 必填 | 522 | | -- | -- | -- | -- | 523 | | actions | 操作类型 | string | ✔ | 524 | | token | [token 说明](#token) | string | ✖ | 525 | 526 | - 返回 `issue-number` `issue-title` `issue-body` `issue-labels` `issue-assignees` `issue-state`,[用法参考](#outputs-使用) 527 | 528 | [⏫ 返回列表](#列-表) 529 | 530 | #### `lock-issue` 531 | 532 | 当新增 `invalid` label 时,对该 issue 进行锁定。 533 | 534 | ```yml 535 | name: Lock Issue 536 | 537 | on: 538 | issues: 539 | types: [labeled] 540 | 541 | jobs: 542 | lock-issue: 543 | runs-on: ubuntu-latest 544 | if: github.event.label.name == 'invalid' 545 | steps: 546 | - name: Lock issue 547 | uses: actions-cool/issues-helper@v3 548 | with: 549 | actions: 'lock-issue' 550 | token: ${{ secrets.GITHUB_TOKEN }} 551 | issue-number: ${{ github.event.issue.number }} 552 | ``` 553 | 554 | | 参数 | 描述 | 类型 | 必填 | 555 | | -- | -- | -- | -- | 556 | | actions | 操作类型 | string | ✔ | 557 | | token | [token 说明](#token) | string | ✖ | 558 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 559 | | lock-reason | 锁定 issue 的原因 | string | ✖ | 560 | 561 | - `lock-reason`:可选值有 `off-topic` `too heated` `resolved` `spam` 562 | 563 | [⏫ 返回列表](#列-表) 564 | 565 | #### `open-issue` 566 | 567 | 打开指定 issue。 568 | 569 | ```yml 570 | - name: Open issue 571 | uses: actions-cool/issues-helper@v3 572 | with: 573 | actions: 'open-issue' 574 | token: ${{ secrets.GITHUB_TOKEN }} 575 | issue-number: xxx 576 | ``` 577 | 578 | | 参数 | 描述 | 类型 | 必填 | 579 | | -- | -- | -- | -- | 580 | | actions | 操作类型 | string | ✔ | 581 | | token | [token 说明](#token) | string | ✖ | 582 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 583 | 584 | [⏫ 返回列表](#列-表) 585 | 586 | #### `remove-assignees` 587 | 588 | 移除 issue 指定人员。 589 | 590 | ```yml 591 | - name: Remove assignees 592 | uses: actions-cool/issues-helper@v3 593 | with: 594 | actions: 'remove-assignees' 595 | token: ${{ secrets.GITHUB_TOKEN }} 596 | issue-number: ${{ github.event.issue.number }} 597 | assignees: 'xx' 598 | ``` 599 | 600 | | 参数 | 描述 | 类型 | 必填 | 601 | | -- | -- | -- | -- | 602 | | actions | 操作类型 | string | ✔ | 603 | | token | [token 说明](#token) | string | ✖ | 604 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 605 | | assignees | 移除的指定人。当为空字符时,不进行移除 | string | ✔ | 606 | 607 | [⏫ 返回列表](#列-表) 608 | 609 | #### `remove-labels` 610 | 611 | 移除指定 labels。 612 | 613 | ```yml 614 | - name: Remove labels 615 | uses: actions-cool/issues-helper@v3 616 | with: 617 | actions: 'remove-labels' 618 | token: ${{ secrets.GITHUB_TOKEN }} 619 | issue-number: ${{ github.event.issue.number }} 620 | labels: 'xx' 621 | ``` 622 | 623 | | 参数 | 描述 | 类型 | 必填 | 624 | | -- | -- | -- | -- | 625 | | actions | 操作类型 | string | ✔ | 626 | | token | [token 说明](#token) | string | ✖ | 627 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 628 | | labels | 移除的 labels。当为空字符时,不进行移除 | string | ✔ | 629 | 630 | - `labels` 支持多个,如 `x1,x2,x3`,只会移除 issue 已添加的 labels 631 | 632 | [⏫ 返回列表](#列-表) 633 | 634 | #### `set-labels` 635 | 636 | 替换 issue 的 labels。 637 | 638 | ```yml 639 | - name: Set labels 640 | uses: actions-cool/issues-helper@v3 641 | with: 642 | actions: 'set-labels' 643 | token: ${{ secrets.GITHUB_TOKEN }} 644 | issue-number: ${{ github.event.issue.number }} 645 | labels: 'xx' 646 | ``` 647 | 648 | | 参数 | 描述 | 类型 | 必填 | 649 | | -- | -- | -- | -- | 650 | | actions | 操作类型 | string | ✔ | 651 | | token | [token 说明](#token) | string | ✖ | 652 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 653 | | labels | labels 设置。当空字符时,会移除所有 | string | ✔ | 654 | 655 | [⏫ 返回列表](#列-表) 656 | 657 | #### `unlock-issue` 658 | 659 | 解锁指定 issue。 660 | 661 | ```yml 662 | - name: Unlock issue 663 | uses: actions-cool/issues-helper@v3 664 | with: 665 | actions: 'unlock-issue' 666 | token: ${{ secrets.GITHUB_TOKEN }} 667 | issue-number: ${{ github.event.issue.number }} 668 | ``` 669 | 670 | | 参数 | 描述 | 类型 | 必填 | 671 | | -- | -- | -- | -- | 672 | | actions | 操作类型 | string | ✔ | 673 | | token | [token 说明](#token) | string | ✖ | 674 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 675 | 676 | [⏫ 返回列表](#列-表) 677 | 678 | #### `update-comment` 679 | 680 | 根据 [`comment-id`](#comment-id) 更新指定评论。 681 | 682 | 下面的例子展示的是,为每个新增的 comment 增加 👀 。 683 | 684 | ```yml 685 | name: Add eyes to each comment 686 | 687 | on: 688 | issue_comment: 689 | types: [created] 690 | 691 | jobs: 692 | update-comment: 693 | runs-on: ubuntu-latest 694 | steps: 695 | - name: Update comment 696 | uses: actions-cool/issues-helper@v3 697 | with: 698 | actions: 'update-comment' 699 | token: ${{ secrets.GITHUB_TOKEN }} 700 | comment-id: ${{ github.event.comment.id }} 701 | emoji: 'eyes' 702 | ``` 703 | 704 | | 参数 | 描述 | 类型 | 必填 | 705 | | -- | -- | -- | -- | 706 | | actions | 操作类型 | string | ✔ | 707 | | token | [token 说明](#token) | string | ✖ | 708 | | comment-id | 指定的 comment | number | ✔ | 709 | | body | 更新 comment 的内容 | string | ✖ | 710 | | update-mode | 更新模式。默认 `replace` 替换,`append` 附加 | string | ✖ | 711 | | emoji | 增加 [emoji](#emoji-types) | string | ✖ | 712 | 713 | - `body` 不填时,会保持原有 714 | - `update-mode` 为 `append` 时,会进行附加操作。非 `append` 都会进行替换。仅对 `body` 生效 715 | 716 | [⏫ 返回列表](#列-表) 717 | 718 | #### `update-issue` 719 | 720 | 根据 `issue-number` 更新指定 issue。 721 | 722 | ```yml 723 | - name: Update issue 724 | uses: actions-cool/issues-helper@v3 725 | with: 726 | actions: 'update-issue' 727 | token: ${{ secrets.GITHUB_TOKEN }} 728 | issue-number: ${{ github.event.issue.number }} 729 | state: 'open' 730 | title: 'xxx' 731 | body: 'xxxx' 732 | update-mode: 'replace' 733 | labels: 'xx' 734 | assignees: 'xxx' 735 | emoji: '+1' 736 | ``` 737 | 738 | | 参数 | 描述 | 类型 | 必填 | 739 | | -- | -- | -- | -- | 740 | | actions | 操作类型 | string | ✔ | 741 | | token | [token 说明](#token) | string | ✖ | 742 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 743 | | state | 修改 issue 的状态,可选值 `open` `closed` | string | ✖ | 744 | | title | 修改 issue 的标题 | string | ✖ | 745 | | body | 修改 issue 的内容 | string | ✖ | 746 | | update-mode | 更新模式。默认 `replace` 替换,`append` 附加 | string | ✖ | 747 | | labels | 替换 issue 的 labels | string | ✖ | 748 | | assignees | 替换 issue 的 assignees | string | ✖ | 749 | | emoji | 增加 [emoji](#emoji-types) | string | ✖ | 750 | 751 | - `state` 默认为 `open` 752 | - 当可选项不填时,会保持原有 753 | 754 | [⏫ 返回列表](#列-表) 755 | 756 | 757 | ### 🌟 进 阶 758 | 759 | 760 | 进阶用法不建议 actions 多个一次同时使用。 761 | 762 | #### `check-inactive` 763 | 764 | 每月 1 号 UTC 0 时,对所有 30 天以上未活跃的 issues 增加 `inactive` 标签。 765 | 766 | ```yml 767 | name: Check inactive 768 | 769 | on: 770 | schedule: 771 | - cron: "0 0 1 * *" 772 | 773 | jobs: 774 | check-inactive: 775 | runs-on: ubuntu-latest 776 | steps: 777 | - name: check-inactive 778 | uses: actions-cool/issues-helper@v3 779 | with: 780 | actions: 'check-inactive' 781 | token: ${{ secrets.GITHUB_TOKEN }} 782 | inactive-day: 30 783 | ``` 784 | 785 | | 参数 | 描述 | 类型 | 必填 | 786 | | -- | -- | -- | -- | 787 | | actions | 操作类型 | string | ✔ | 788 | | token | [token 说明](#token) | string | ✖ | 789 | | body | 操作 issue 时,可进行评论。不填时,不评论 | string | ✖ | 790 | | emoji | 为该评论增加 [emoji](#emoji-types) | string | ✖ | 791 | | labels | 标签筛选 | string | ✖ | 792 | | issue-state | 状态筛选 | string | ✖ | 793 | | issue-assignee | 指定人筛选 | string | ✖ | 794 | | issue-creator | 创建人筛选 | string | ✖ | 795 | | issue-mentioned | 提及人筛选 | string | ✖ | 796 | | body-includes | 包含内容筛选,支持多个字符串过滤 | string | ✖ | 797 | | title-includes | 包含标题筛选,支持多个字符串过滤 | string | ✖ | 798 | | inactive-day | 非活跃天数筛选 | number | ✖ | 799 | | inactive-mode | 检测不活跃的模式 | string | ✖ | 800 | | inactive-label | 新增标签名称 | string | ✖ | 801 | | exclude-labels | 排除标签筛选 | string | ✖ | 802 | | exclude-issue-numbers | 排除指定 issue | string | ✖ | 803 | 804 | - `labels`:为多个时,会查询同时拥有多个。不填时,会查询所有 805 | - `issue-state`:默认为 `open`。可选值 `all` `closed`,非这 2 项时,均为 `open` 806 | - `issue-assignee`:不支持多人。不填或输入 * 时,查询所有。输入 `none` 会查询未添加指定人的 issues 807 | - `inactive-day`:当输入时,会筛选 issue 更新时间早于当前时间减去非活跃天数。不填时,会查询所有 808 | - `inactive-label`:默认为 `inactive`,可自定义其他。当项目未包含该 label 时,会自动新建 809 | - `exclude-labels`:设置包含 `$exclude-empty` 时,可排除无 label issue 810 | - `inactive-mode`: 811 | - 默认 `issue`,检查 issue 的更新时间 812 | - 可选 `comment`,检查最后一个评论的更新时间 813 | - 可选 `issue-created`,检查 issue 的创建时间 814 | - 可选 `comment-created`,最后一个评论的创建时间 815 | - 你也可以设置多个如:`comment, issue-created` 816 | - 将会以优先级检测,先检测最后一条评论更新时间,如无评论,则使用 issue 的创建时间 817 | 818 | [⏫ 返回列表](#列-表) 819 | 820 | #### `check-issue` 821 | 822 | 根据传入的参数和 `issue-number` 来检查该 issue 是否满足条件,返回一个布尔值。 823 | 824 | 下面的例子效果是:当 issue 新开时,校验当前 issue 指定人是否包含 `x1` 或者 `x2`,满足一个指定人即可校验通过,同时校验标题是否满足条件,[校验规则](#校验规则)。 825 | 826 | ```yml 827 | name: Check Issue 828 | 829 | on: 830 | issues: 831 | types: [edited] 832 | 833 | jobs: 834 | check-issue: 835 | runs-on: ubuntu-latest 836 | steps: 837 | - name: check-issue 838 | uses: actions-cool/issues-helper@v3 839 | with: 840 | actions: 'check-issue' 841 | token: ${{ secrets.GITHUB_TOKEN }} 842 | issue-number: ${{ github.event.issue.number }} 843 | assignee-includes: 'x1,x2' 844 | title-includes: 'x1,x2/y1,y2' 845 | ``` 846 | 847 | | 参数 | 描述 | 类型 | 必填 | 848 | | -- | -- | -- | -- | 849 | | actions | 操作类型 | string | ✔ | 850 | | token | [token 说明](#token) | string | ✖ | 851 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 852 | | assignee-includes | 是否包含指定人 | string | ✖ | 853 | | title-includes | 标题包含校验 | string | ✖ | 854 | | title-excludes | 检测标题移除默认 title 后是否为空 | string | ✖ | 855 | | body-includes | 内容包含校验 | string | ✖ | 856 | 857 | - `title-includes` `body-includes` 支持格式 `x1,x2` 或者 `x1,x2/y1,y2`。只支持两个层级 858 | - 返回 `check-result`,由于 yml 原因,判断条件为 `if: steps.xxid.outputs.check-result == 'true'` 859 | 860 | [⏫ 返回列表](#列-表) 861 | 862 | #### `close-issues` 863 | 864 | 每 7 天 UTC 0 时,关闭已填加 `need info` label 且 7 天以上未活跃的 issues。 865 | 866 | ```yml 867 | name: Check need info 868 | 869 | on: 870 | schedule: 871 | - cron: "0 0 */7 * *" 872 | 873 | jobs: 874 | check-need-info: 875 | runs-on: ubuntu-latest 876 | steps: 877 | - name: close-issues 878 | uses: actions-cool/issues-helper@v3 879 | with: 880 | actions: 'close-issues' 881 | token: ${{ secrets.GITHUB_TOKEN }} 882 | labels: 'need info' 883 | inactive-day: 7 884 | ``` 885 | 886 | | 参数 | 描述 | 类型 | 必填 | 887 | | -- | -- | -- | -- | 888 | | actions | 操作类型 | string | ✔ | 889 | | token | [token 说明](#token) | string | ✖ | 890 | | body | 操作 issue 时,可进行评论。不填时,不评论 | string | ✖ | 891 | | emoji | 为该评论增加 [emoji](#emoji-types) | string | ✖ | 892 | | labels | 标签筛选 | string | ✖ | 893 | | issue-assignee | 指定人筛选 | string | ✖ | 894 | | issue-creator | 创建人筛选 | string | ✖ | 895 | | issue-mentioned | 提及人筛选 | string | ✖ | 896 | | body-includes | 包含内容筛选,支持多个字符串过滤 | string | ✖ | 897 | | title-includes | 包含标题筛选,支持多个字符串过滤 | string | ✖ | 898 | | inactive-day | 非活跃天数筛选 | number | ✖ | 899 | | exclude-labels | 排除标签筛选 | string | ✖ | 900 | | close-reason | 关闭原因。默认`not_planned`未计划,`completed`完成 | string | ✖ | 901 | 902 | - `labels`:为多个时,会查询同时拥有多个。不填时,会查询所有 903 | - `issue-assignee`:不支持多人。不填或输入 * 时,查询所有。输入 `none` 会查询未添加指定人的 issues 904 | - `inactive-day`:当输入时,会筛选 issue 更新时间早于当前时间减去非活跃天数。不填时,会查询所有 905 | - `exclude-labels`:设置包含 `$exclude-empty` 时,可排除无 label issue 906 | 907 | [⏫ 返回列表](#列-表) 908 | 909 | #### `find-comments` 910 | 911 | 查找当前仓库 1 号 issue 中,创建者是 k ,内容包含 `this` 的评论列表。 912 | 913 | ```yml 914 | - name: Find comments 915 | uses: actions-cool/issues-helper@v3 916 | with: 917 | actions: 'find-comments' 918 | token: ${{ secrets.GITHUB_TOKEN }} 919 | issue-number: 1 920 | comment-auth: 'k' 921 | body-includes: 'this,that' 922 | ``` 923 | 924 | | 参数 | 描述 | 类型 | 必填 | 925 | | -- | -- | -- | -- | 926 | | actions | 操作类型 | string | ✔ | 927 | | token | [token 说明](#token) | string | ✖ | 928 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 929 | | comment-auth | 评论创建者,不填时会查询所有 | string | ✖ | 930 | | body-includes | 评论内容包含过滤,支持多个字符串过滤,不填时无校验 | string | ✖ | 931 | | direction | 返回 `comments` 排序 | string | ✖ | 932 | 933 | - 返回 `comments`,格式如下: 934 | 935 | ```js 936 | [ 937 | {id: 1, auth: 'x', body: 'xxx', created: '', updated: ''}, 938 | {id: 2, auth: 'x', body: 'xxx', created: '', updated: ''}, 939 | ] 940 | ``` 941 | 942 | - `direction` 默认为升序,只有设置 `desc` 时,会返回降序 943 | - 返回数组中 `created` `updated`,由所处环境决定,会是 UTC +0 944 | 945 | [⏫ 返回列表](#列-表) 946 | 947 | #### `find-issues` 948 | 949 | 查找当前仓库,创建者是 k ,title 包含 `this` ,body 包含 `that`,打开状态的 issues 列表。 950 | 951 | ```yml 952 | - name: Find issues 953 | uses: actions-cool/issues-helper@v3 954 | with: 955 | actions: 'find-issues' 956 | token: ${{ secrets.GITHUB_TOKEN }} 957 | issue-creator: 'k' 958 | issue-state: 'open' 959 | title-includes: 'this,that' 960 | body-includes: 'that,this' 961 | labels: 'documentation' 962 | ``` 963 | 964 | | 参数 | 描述 | 类型 | 必填 | 965 | | -- | -- | -- | -- | 966 | | actions | 操作类型 | string | ✔ | 967 | | token | [token 说明](#token) | string | ✖ | 968 | | issue-state | 状态筛选 | string | ✖ | 969 | | issue-creator | 创建者筛选 | string | ✖ | 970 | | title-includes | 标题包含过滤,支持多个字符串过滤,不填时无校验 | string | ✖ | 971 | | body-includes | 内容包含过滤,支持多个字符串过滤,不填时无校验 | string | ✖ | 972 | | labels | 标签筛选 | string | ✖ | 973 | | exclude-labels | 排除标签筛选 | string | ✖ | 974 | | inactive-day | 非活跃天数筛选 | number | ✖ | 975 | | direction | 返回 `issues` 排序 | string | ✖ | 976 | | create-issue-if-not-exist | 如果没找到新建一个 | boolean | ✖ | 977 | 978 | - 返回 `issues`,格式如下: 979 | 980 | ```js 981 | [ 982 | {number: 1, auth: 'x', body: 'xxx', body: 'xxx', state: 'open', assignees: ['x1', 'x2'], created: '', updated: ''}, 983 | {number: 2, auth: 'x', body: 'xxx', body: 'xxx', state: 'closed', assignees: ['x1', 'x2'], created: '', updated: ''}, 984 | ] 985 | ``` 986 | 987 | - `direction` 默认为升序,只有设置 `desc` 时,会返回降序 988 | - 返回数组中 `created` `updated`,由所处环境决定,会是 UTC +0 989 | - `exclude-labels`:设置包含 `$exclude-empty` 时,可排除无 label issue 990 | 991 | [⏫ 返回列表](#列-表) 992 | 993 | #### `lock-issues` 994 | 995 | 每 3 个月 1 号 UTC 0 时,锁定已填加 `inactive` label 且 128 天以上未活跃的所有 issues。 996 | 997 | ```yml 998 | name: Lock inactive issues 999 | 1000 | on: 1001 | schedule: 1002 | - cron: "0 0 1 */3 *" 1003 | 1004 | jobs: 1005 | lock-issues: 1006 | runs-on: ubuntu-latest 1007 | steps: 1008 | - name: lock-issues 1009 | uses: actions-cool/issues-helper@v3 1010 | with: 1011 | actions: 'lock-issues' 1012 | token: ${{ secrets.GITHUB_TOKEN }} 1013 | labels: 'inactive' 1014 | inactive-day: 128 1015 | ``` 1016 | 1017 | | 参数 | 描述 | 类型 | 必填 | 1018 | | -- | -- | -- | -- | 1019 | | actions | 操作类型 | string | ✔ | 1020 | | token | [token 说明](#token) | string | ✖ | 1021 | | body | 操作 issue 时,可进行评论。不填时,不评论 | string | ✖ | 1022 | | emoji | 为该评论增加 [emoji](#emoji-types) | string | ✖ | 1023 | | labels | 标签筛选 | string | ✖ | 1024 | | issue-state | 状态筛选 | string | ✖ | 1025 | | issue-assignee | 指定人筛选 | string | ✖ | 1026 | | issue-creator | 创建人筛选 | string | ✖ | 1027 | | issue-mentioned | 提及人筛选 | string | ✖ | 1028 | | body-includes | 包含内容筛选,支持多个字符串过滤 | string | ✖ | 1029 | | title-includes | 包含标题筛选,支持多个字符串过滤 | string | ✖ | 1030 | | inactive-day | 非活跃天数筛选 | number | ✖ | 1031 | | lock-reason | 锁定 issue 的原因 | string | ✖ | 1032 | | exclude-labels | 排除标签筛选 | string | ✖ | 1033 | 1034 | - `labels`:为多个时,会查询同时拥有多个。不填时,会查询所有 1035 | - `issue-state`:默认为 `open`。可选值 `all` `closed`,非这 2 项时,均为 `open` 1036 | - `issue-assignee`:不支持多人。不填或输入 * 时,查询所有。输入 `none` 会查询未添加指定人的 issues 1037 | - `inactive-day`:当输入时,会筛选 issue 更新时间早于当前时间减去非活跃天数。不填时,会查询所有 1038 | - `exclude-labels`:设置包含 `$exclude-empty` 时,可排除无 label issue 1039 | 1040 | [⏫ 返回列表](#列-表) 1041 | 1042 | #### `mark-assignees` 1043 | 1044 | 快捷加指定人,仅作用于 issue 新增编辑评论。 1045 | 1046 | ```yml 1047 | name: Issue Mark Assignees 1048 | 1049 | on: 1050 | issue_comment: 1051 | types: [created, edited] 1052 | 1053 | jobs: 1054 | mark-assignees: 1055 | runs-on: ubuntu-latest 1056 | steps: 1057 | - name: mark-assignees 1058 | uses: actions-cool/issues-helper@v3 1059 | with: 1060 | actions: 'mark-assignees' 1061 | token: ${{ secrets.GITHUB_TOKEN }} 1062 | ``` 1063 | 1064 | | 参数 | 描述 | 类型 | 必填 | 1065 | | -- | -- | -- | -- | 1066 | | actions | 操作类型 | string | ✔ | 1067 | | token | [token 说明](#token) | string | ✖ | 1068 | | assign-command | 可设置简洁命令,如:`/a` | string | ✖ | 1069 | | require-permission | 要求权限,默认为 `write` | string | ✖ | 1070 | 1071 | - `assign-command`:可设置简洁命令。默认:`/assign` 1072 | - `require-permission`:可选值有 `admin`,`write`,`read`,`none` 1073 | - 团队成员若设置 `read` 权限,则为 `read` 1074 | - 外部 Collaborator 若设置 `read` 权限,则为 `read` 1075 | - 普通用户为 `read` 权限 1076 | - 当设置 `write` 后,`admin` 和 `write` 满足条件 1077 | 1078 | [⏫ 返回列表](#列-表) 1079 | 1080 | #### `mark-duplicate` 1081 | 1082 | 快捷标记重复问题,仅作用于 issue 新增编辑评论。 1083 | 1084 | ```yml 1085 | name: Issue Mark Duplicate 1086 | 1087 | on: 1088 | issue_comment: 1089 | types: [created, edited] 1090 | 1091 | jobs: 1092 | mark-duplicate: 1093 | runs-on: ubuntu-latest 1094 | steps: 1095 | - name: mark-duplicate 1096 | uses: actions-cool/issues-helper@v3 1097 | with: 1098 | actions: 'mark-duplicate' 1099 | token: ${{ secrets.GITHUB_TOKEN }} 1100 | ``` 1101 | 1102 | | 参数 | 描述 | 类型 | 必填 | 1103 | | -- | -- | -- | -- | 1104 | | actions | 操作类型 | string | ✔ | 1105 | | token | [token 说明](#token) | string | ✖ | 1106 | | duplicate-command | 可设置简洁命令,如:`/d` | string | ✖ | 1107 | | duplicate-labels | 为该 issue 额外增加 labels | string | ✖ | 1108 | | remove-labels | 设置可移除的 labels | string | ✖ | 1109 | | labels | 替换该 issue 的 labels | string | ✖ | 1110 | | emoji | 为该评论的增加 [emoji](#emoji-types) | string | ✖ | 1111 | | close-issue | 是否同时关闭该 issue | string | ✖ | 1112 | | require-permission | 要求权限,默认为 `write` | string | ✖ | 1113 | | close-reason | 关闭原因。默认`not_planned`未计划,`completed`完成 | string | ✖ | 1114 | 1115 | - `duplicate-command`:当设置简洁命令时,同时仍支持原有 `Duplicate of`。屏蔽内容包含 `?` 1116 | - `labels`:优先级最高 1117 | - `close-issue`:`true` 或 `'true'` 均可生效 1118 | - `require-permission`:可选值有 `admin`,`write`,`read`,`none` 1119 | - 团队成员若设置 `read` 权限,则为 `read` 1120 | - 外部 Collaborator 若设置 `read` 权限,则为 `read` 1121 | - 普通用户为 `read` 权限 1122 | - 当设置 `write` 后,`admin` 和 `write` 满足条件 1123 | 1124 | [⏫ 返回列表](#列-表) 1125 | 1126 | #### `toggle-labels` 1127 | 1128 | 当一个 issue 被重新打开,判断设置的 labels 如果已经存在则进行删除,否则进行添加。 1129 | 1130 | ```yml 1131 | name: Toggle Labels 1132 | 1133 | on: 1134 | issues: 1135 | types: [reopened] 1136 | 1137 | jobs: 1138 | toggle-labels: 1139 | runs-on: ubuntu-latest 1140 | steps: 1141 | - name: Toggle labels 1142 | uses: actions-cool/issues-helper@v3 1143 | with: 1144 | actions: 'toggle-labels' 1145 | token: ${{ secrets.GITHUB_TOKEN }} 1146 | issue-number: ${{ github.event.issue.number }} 1147 | labels: 'unread,outdated' 1148 | ``` 1149 | 1150 | | 参数 | 描述 | 类型 | 必填 | 1151 | | -- | -- | -- | -- | 1152 | | actions | 操作类型 | string | ✔ | 1153 | | token | [token 说明](#token) | string | ✖ | 1154 | | issue-number | 指定的 issue,当不传时会从触发事件中获取 | number | ✖ | 1155 | | labels | 切换 labels。如果 label 已存在则删除,不存在则添加 | string | ✖ | 1156 | 1157 | [⏫ 返回列表](#列-表) 1158 | 1159 | #### `welcome` 1160 | 1161 | 当一个 issue 新建时,对首次新建 issue 的用户进行欢迎。若用户非首次新建,则无操作。 1162 | 1163 | ```yml 1164 | name: Issue Welcome 1165 | 1166 | on: 1167 | issues: 1168 | types: [opened] 1169 | 1170 | jobs: 1171 | issue-welcome: 1172 | runs-on: ubuntu-latest 1173 | steps: 1174 | - name: welcome 1175 | uses: actions-cool/issues-helper@v3 1176 | with: 1177 | actions: 'welcome' 1178 | token: ${{ secrets.GITHUB_TOKEN }} 1179 | body: hi @${{ github.event.issue.user.login }}, welcome! 1180 | labels: 'welcome1, welcome2' 1181 | assignees: 'xx1' 1182 | issue-emoji: '+1, -1, eyes' 1183 | ``` 1184 | 1185 | | 参数 | 描述 | 类型 | 必填 | 1186 | | -- | -- | -- | -- | 1187 | | actions | 操作类型 | string | ✔ | 1188 | | token | [token 说明](#token) | string | ✖ | 1189 | | body | 评论欢迎的内容,不填则不评论 | string | ✖ | 1190 | | emoji | 为该评论的增加 [emoji](#emoji-types) | string | ✖ | 1191 | | labels | 为该 issue 增加 labels | string | ✖ | 1192 | | assignees | 为该 issue 增加 assignees | string | ✖ | 1193 | | issue-emoji | 为该 issue 增加 [emoji](#emoji-types) | string | ✖ | 1194 | 1195 | - 若这 4 个可选项都不填,则无操作 1196 | 1197 | [⏫ 返回列表](#列-表) 1198 | 1199 | 1200 | ## 🎁 参 考 1201 | 1202 | 1203 | ### token 1204 | 1205 | 需拥有 push 权限的人员 token。 1206 | 1207 | - [个人 token 申请](https://github.com/settings/tokens) 1208 | - 需勾选 `Full control of private repositories` 1209 | - 项目添加 secrets 1210 | - 选择 settings,选择 secrets,选择 `New repository secret` 1211 | - `Name` 与 actions 中保持一致 1212 | - `Value` 填写刚才个人申请的 token 1213 | 1214 | 当 actions 不填写 token 时,或填写 `${{ secrets.GITHUB_TOKEN }}`,会默认为 `github-actions-bot`。[更多查看](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)。 1215 | 1216 | [⏫ 返回列表](#列-表) 1217 | 1218 | ### GitHub Docs 1219 | 1220 | - [GitHub Actions 语法](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#about-yaml-syntax-for-workflows) 1221 | - [工作流触发机制](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows) 1222 | 1223 | [⏫ 返回列表](#列-表) 1224 | 1225 | ### `outputs` 使用 1226 | 1227 | ```yml 1228 | - name: Create issue 1229 | uses: actions-cool/issues-helper@v3 1230 | id: createissue 1231 | with: 1232 | actions: 'create-issue' 1233 | token: ${{ secrets.GITHUB_TOKEN }} 1234 | - name: Check outputs 1235 | run: echo "Outputs issue_number is ${{ steps.createissue.outputs.issue-number }}" 1236 | ``` 1237 | 1238 | 更多查看: 1239 | 1240 | 1. https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/metadata-syntax-for-github-actions#outputs 1241 | 2. https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs 1242 | 1243 | [⏫ 返回列表](#列-表) 1244 | 1245 | ### 校验规则 1246 | 1247 | ```js 1248 | "title-includes": 'x1,x2' 1249 | 1250 | x1 1251 | x2 1252 | 1253 | "x1y3y2" true 1254 | "y2 x1" true 1255 | "x2" true 1256 | "x3" false 1257 | ``` 1258 | 1259 | ```js 1260 | "title-includes": 'x1,x2/y1,y2' 1261 | 1262 | x1 + y1 1263 | x2 + y1 1264 | x1 + y2 1265 | x2 + y2 1266 | 1267 | "x1y3y2" true 1268 | "y2 x1" true 1269 | "1x2y" false 1270 | "x1" false 1271 | ``` 1272 | 1273 | [⏫ 返回列表](#列-表) 1274 | 1275 | ### emoji 类型 1276 | 1277 | | content | emoji | 1278 | | -- | -- | 1279 | | `+1` | 👍 | 1280 | | `-1` | 👎 | 1281 | | `laugh` | 😄 | 1282 | | `confused` | 😕 | 1283 | | `heart` | ❤️ | 1284 | | `hooray` | 🎉 | 1285 | | `rocket` | 🚀 | 1286 | | `eyes` | 👀 | 1287 | 1288 | [⏫ 返回列表](#列-表) 1289 | 1290 | ### `comment-id` 1291 | 1292 | 点击某个评论右上角 `···` 图标,选择 `Copy link`,url 末尾数字即是 `comment_id`。 1293 | 1294 | [⏫ 返回列表](#列-表) 1295 | 1296 | 1297 | ## Actions 模板 1298 | 1299 | - 可直接使用这个 [GitHub Actions workflow template](https://github.com/actions-cool/.github) 仓库的模板 1300 | - 个人练习和测试 [Actions](https://github.com/actions-cool/test-issues-helper) 的仓库 1301 | - 也可以来 [线上使用者](#谁在使用) 的仓库参照 1302 | 1303 | ## LICENSE 1304 | 1305 | [MIT](https://github.com/actions-cool/issues-helper/blob/main/LICENSE) 1306 | --------------------------------------------------------------------------------