From ef15da5f2bbf9123cb83b0e0ce7a45d357f1f8e6 Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 13 May 2026 02:53:24 +0800 Subject: [PATCH] chore: simpler rss proposal --- .github/ISSUE_TEMPLATE/rss_request_en.yml | 9 +- .github/ISSUE_TEMPLATE/rss_request_zh.yml | 9 +- .github/workflows/comment-on-issue.yml | 36 ++++- scripts/workflow/test-issue/check-issue.mjs | 143 ++++++++++++++++++++ 4 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 scripts/workflow/test-issue/check-issue.mjs diff --git a/.github/ISSUE_TEMPLATE/rss_request_en.yml b/.github/ISSUE_TEMPLATE/rss_request_en.yml index da24c323d..d753cba55 100644 --- a/.github/ISSUE_TEMPLATE/rss_request_en.yml +++ b/.github/ISSUE_TEMPLATE/rss_request_en.yml @@ -39,6 +39,7 @@ body: - Study - Scientific Journal - Finance + - Sport - Uncategorized validations: required: true @@ -51,14 +52,6 @@ body: validations: required: true - - type: textarea - id: description - attributes: - label: Website description - placeholder: A short description of the website - validations: - required: true - - type: textarea id: content attributes: diff --git a/.github/ISSUE_TEMPLATE/rss_request_zh.yml b/.github/ISSUE_TEMPLATE/rss_request_zh.yml index bb10bbf94..9f4f13038 100644 --- a/.github/ISSUE_TEMPLATE/rss_request_zh.yml +++ b/.github/ISSUE_TEMPLATE/rss_request_zh.yml @@ -39,6 +39,7 @@ body: - 学习 - 科学期刊 - 金融 + - 体育 - 其他 validations: required: true @@ -51,14 +52,6 @@ body: validations: required: true - - type: textarea - id: description - attributes: - label: 网站描述 - placeholder: 对网站的简短描述 - validations: - required: true - - type: textarea id: content attributes: diff --git a/.github/workflows/comment-on-issue.yml b/.github/workflows/comment-on-issue.yml index 6e8924b9f..3778077b7 100644 --- a/.github/workflows/comment-on-issue.yml +++ b/.github/workflows/comment-on-issue.yml @@ -3,6 +3,12 @@ name: Comment on Issue on: issues: types: [opened, edited, reopened] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to run the check issue job against' + required: true + type: number jobs: testRoute: @@ -11,7 +17,7 @@ jobs: timeout-minutes: 5 permissions: issues: write - if: github.event.sender.login != 'issuehunt-oss[bot]' + if: github.event_name == 'issues' && github.event.sender.login != 'issuehunt-oss[bot]' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 @@ -28,3 +34,31 @@ jobs: script: | const { default: callMaintainer } = await import('${{ github.workspace }}/scripts/workflow/test-issue/call-maintainer.mjs') await callMaintainer({ github, context, core }) + + checkIssue: + name: Check issue + runs-on: ubuntu-slim + timeout-minutes: 5 + permissions: + issues: write + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issues' && contains(fromJSON('["opened", "edited"]'), github.event.action) && github.event.sender.login != 'issuehunt-oss[bot]' && !contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association)) + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: 'pnpm' + - name: Install dependencies (pnpm) # import remark-parse and unified + run: pnpm i + - name: Check issue format + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + ISSUE_NUMBER: ${{ inputs.issue_number }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { default: checkIssue } = await import('${{ github.workspace }}/scripts/workflow/test-issue/check-issue.mjs') + await checkIssue({ github, context, core }) diff --git a/scripts/workflow/test-issue/check-issue.mjs b/scripts/workflow/test-issue/check-issue.mjs new file mode 100644 index 000000000..c866c0298 --- /dev/null +++ b/scripts/workflow/test-issue/check-issue.mjs @@ -0,0 +1,143 @@ +import remarkParse from 'remark-parse'; +import { unified } from 'unified'; + +const rssBugLabel = 'RSS bug'; +const rssEnhancementLabel = 'RSS enhancement'; +const rssProposalLabel = 'RSS proposal'; +const commentMarker = ''; +const triageLabels = new Set([rssBugLabel, rssEnhancementLabel, rssProposalLabel]); + +const bugReportTemplate = { + en: ['Routes', 'Related documentation', 'What is expected?', 'What is actually happening?'], + zh: ['路由地址', '相关文档', '预期是什么?', '实际发生了什么?'], + docs: { + en: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/bug_report_en.yml', + zh: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/bug_report_zh.yml', + }, +}; + +const rssProposalTemplate = { + en: ['Category', 'Website URL', 'What content should be included?'], + zh: ['类型', '网站地址', '需要生成什么内容?'], + docs: { + en: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/rss_request_en.yml', + zh: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/rss_request_zh.yml', + }, +}; + +const featureRequestTemplate = { + en: ['What feature is it?', 'What problem does this feature solve?'], + zh: ['这是一个什么样的功能?', '这个功能可以解决什么问题?'], + docs: { + en: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/feature_request_en.yml', + zh: 'https://github.com/DIYgod/RSSHub/blob/master/.github/ISSUE_TEMPLATE/feature_request_zh.yml', + }, +}; + +async function alreadyCommented(github, issueFacts) { + const comments = await github.paginate(github.rest.issues.listComments, { ...issueFacts, per_page: 100 }); + return comments.some((c) => c.body?.includes(commentMarker)); +} + +/** + * @param {import('mdast').Node} node + * @returns {string} + */ +function extractText(node) { + return node.type === 'text' ? node.value : node.children.map((c) => extractText(c)).join(''); +} + +/** + * @param {{ github: ReturnType, context: typeof import('@actions/github').context, core: typeof import('@actions/core') }} githubScript + * @returns {Promise} + */ +export default async function checkIssue({ github, context, core }) { + let issue = context.payload.issue; + if (!issue) { + core.info(`Fetching issue #${process.env.ISSUE_NUMBER} (workflow_dispatch)`); + const { data } = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.ISSUE_NUMBER), + }); + issue = data; + } + const issueFacts = { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + }; + + const labels = issue.labels.map((l) => l.name); + core.debug(`labels: ${JSON.stringify(labels)}`); + + const matched = labels.find((l) => triageLabels.has(l)); + if (!matched) { + if (await alreadyCommented(github, issueFacts)) { + core.info('Already commented, skipping'); + return; + } + core.info('No triage label found, closing as not planned'); + await github.rest.issues.createComment({ + ...issueFacts, + body: `${commentMarker}\nThis issue was closed automatically because it bypasses the required template. Please open a new issue following the template.`, + }); + await github.rest.issues.update({ + ...issueFacts, + state: 'closed', + state_reason: 'not_planned', + }); + return; + } + + const ast = unified() + .use(remarkParse) + .parse(issue.body ?? ''); + const headings = new Set(ast.children.filter((n) => n.type === 'heading').map((n) => extractText(n).trim())); + core.debug(`headings: ${JSON.stringify([...headings])}`); + + const ctx = { github, core, issueFacts, headings }; + + switch (matched) { + case rssBugLabel: + await runHeadingCheck(ctx, 'bug report', bugReportTemplate); + break; + case rssProposalLabel: + await runHeadingCheck(ctx, 'RSS proposal', rssProposalTemplate); + break; + case rssEnhancementLabel: + await runHeadingCheck(ctx, 'feature request', featureRequestTemplate); + break; + // no default + } +} + +async function runHeadingCheck({ github, core, issueFacts, headings }, templateName, template) { + const enOk = template.en.every((h) => headings.has(h)); + const zhOk = template.zh.every((h) => headings.has(h)); + + if (enOk || zhOk) { + core.info(`${templateName} heading check passed`); + return; + } + + const missingEn = template.en.filter((h) => !headings.has(h)); + const missingZh = template.zh.filter((h) => !headings.has(h)); + const missing = missingEn.length <= missingZh.length ? missingEn : missingZh; + core.warning(`${templateName} heading check failed, missing: ${missing.join(', ')}`); + + if (await alreadyCommented(github, issueFacts)) { + core.info(`${templateName} already commented, skipping`); + return; + } + + await github.rest.issues.createComment({ + ...issueFacts, + body: `${commentMarker}\nThis issue was closed automatically because it bypasses the required template. Please open a new issue following the template.`, + }); + await github.rest.issues.update({ + ...issueFacts, + state: 'closed', + state_reason: 'not_planned', + }); +}