修复 Windows 窗口拖拽卡顿问题 #114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lithe issue claim | |
| on: | |
| issue_comment: | |
| types: [created] | |
| schedule: | |
| - cron: '17 2 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| issues: write | |
| concurrency: | |
| group: lithe-issue-claim-${{ github.repository }} | |
| cancel-in-progress: false | |
| jobs: | |
| command: | |
| if: github.event_name == 'issue_comment' && github.event.issue.pull_request == null | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - uses: actions/github-script@v7 | |
| env: | |
| CLAIM_LOGIC: ${{ github.workspace }}/.github/lithe-issue-claim/logic.mjs | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const logic = await import(process.env.CLAIM_LOGIC); | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const command = logic.parseCommand(context.payload.comment.body); | |
| if (!command) return; | |
| const login = context.payload.comment.user.login; | |
| const issue = (await github.rest.issues.get({ owner, repo, issue_number })).data; | |
| let assignee = issue.assignees?.[0]?.login; | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 }); | |
| const trusted = (c) => c.user?.type === 'Bot' && c.user?.login === 'github-actions[bot]'; | |
| const recordedClaim = [...comments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.CLAIM_MARKER)).find(Boolean); | |
| const recordedRelease = [...comments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.RELEASE_MARKER)).find((release) => logic.releaseMatchesClaim(release, recordedClaim)); | |
| const activeClaim = logic.isActiveClaim(recordedClaim, recordedRelease) ? recordedClaim : null; | |
| const maintainer = logic.isMaintainer(context.payload.comment.author_association); | |
| const comment = async (body) => github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| if (command === '/assign') { | |
| if (assignee === login) return comment(`@${login} 已经认领了这个 Issue。`); | |
| if (assignee && recordedClaim?.login === assignee && !activeClaim) { | |
| try { | |
| await github.rest.issues.removeAssignees({ owner, repo, issue_number, assignees: [assignee] }); | |
| assignee = null; | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: logic.STALE_LABEL }).catch(() => {}); | |
| } catch (error) { | |
| core.warning(`Unable to clear released assignee ${assignee}: ${error.message}`); | |
| return comment('认领没有完成:当前 Issue 的旧负责人状态尚未清理,请稍后重试或联系维护者。'); | |
| } | |
| } | |
| if (assignee) return comment(`这个 Issue 当前由 @${assignee} 负责,不能抢占。`); | |
| if (activeClaim?.login === login) return comment(`@${login} 已经登记认领了这个 Issue。`); | |
| if (activeClaim && activeClaim.login !== login) return comment(`这个 Issue 当前由 @${activeClaim.login} 负责,不能抢占。`); | |
| let official = false; | |
| try { | |
| await github.rest.issues.addAssignees({ owner, repo, issue_number, assignees: [login] }); | |
| const current = (await github.rest.issues.get({ owner, repo, issue_number })).data.assignees?.[0]?.login; | |
| official = current === login; | |
| } catch (error) { | |
| core.warning(`Unable to set assignee for ${login}; using recorded claim fallback: ${error.message}`); | |
| } | |
| const marker = logic.claimMarker(login, new Date().toISOString()); | |
| try { | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: logic.STALE_LABEL }).catch(() => {}); | |
| await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [logic.CLAIM_LABEL] }); | |
| await comment(`${marker}\n@${login} ${official ? '已认领这个 Issue。' : '已登记认领这个 Issue,等待维护者确认 GitHub 负责人字段。'}`); | |
| } catch (error) { | |
| if (official) await github.rest.issues.removeAssignees({ owner, repo, issue_number, assignees: [login] }).catch(() => {}); | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: logic.CLAIM_LABEL }).catch(() => {}); | |
| core.warning(`Unable to complete claim state for ${login}: ${error.message}`); | |
| return comment('认领没有完成:机器人无法写入完整状态,请稍后重试或联系维护者。'); | |
| } | |
| return; | |
| } | |
| const effectiveOwner = assignee || activeClaim?.login; | |
| if (!effectiveOwner) return comment('这个 Issue 当前没有负责人。'); | |
| if (effectiveOwner !== login && !maintainer) return comment(`只有当前负责人 @${effectiveOwner} 或维护者可以释放认领。`); | |
| try { | |
| await comment(`${logic.releaseMarker(effectiveOwner, new Date().toISOString())}\n@${effectiveOwner} 已释放这个 Issue,其他贡献者可以继续认领。`); | |
| } catch (error) { | |
| core.warning(`Unable to record release for ${effectiveOwner}: ${error.message}`); | |
| return comment('释放没有完成:机器人无法记录释放状态,请稍后重试或联系维护者。'); | |
| } | |
| if (assignee) await github.rest.issues.removeAssignees({ owner, repo, issue_number, assignees: [assignee] }).catch((error) => core.warning(`Unable to remove assignee: ${error.message}`)); | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: logic.CLAIM_LABEL }).catch(() => {}); | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number, name: logic.STALE_LABEL }).catch(() => {}); | |
| return; | |
| stale: | |
| if: github.event_name != 'issue_comment' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - uses: actions/github-script@v7 | |
| env: | |
| CLAIM_LOGIC: ${{ github.workspace }}/.github/lithe-issue-claim/logic.mjs | |
| with: | |
| github-token: ${{ github.token }} | |
| script: | | |
| const logic = await import(process.env.CLAIM_LOGIC); | |
| const { owner, repo } = context.repo; | |
| const issues = await github.paginate(github.rest.issues.listForRepo, { owner, repo, state: 'open', labels: logic.CLAIM_LABEL, per_page: 100 }); | |
| for (const issue of issues.filter((item) => !item.pull_request)) { | |
| const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: issue.number, per_page: 100 }); | |
| const trusted = (c) => c.user?.type === 'Bot' && c.user?.login === 'github-actions[bot]'; | |
| const claim = [...comments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.CLAIM_MARKER)).find(Boolean); | |
| if (!claim) { | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.CLAIM_LABEL }).catch(() => {}); | |
| continue; | |
| } | |
| const release = [...comments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.RELEASE_MARKER)).find((candidate) => logic.releaseMatchesClaim(candidate, claim)); | |
| if (!logic.isActiveClaim(claim, release)) { | |
| const current = (await github.rest.issues.get({ owner, repo, issue_number: issue.number })).data; | |
| let stillOwned = current.assignees?.some((a) => a.login === claim.login); | |
| if (logic.needsReleaseRecovery(claim, release, current.assignees?.map((a) => a.login))) { | |
| try { | |
| await github.rest.issues.removeAssignees({ owner, repo, issue_number: issue.number, assignees: [claim.login] }); | |
| stillOwned = false; | |
| } catch (error) { | |
| core.warning(`Unable to recover released assignee: ${error.message}`); | |
| continue; | |
| } | |
| } | |
| if (!stillOwned) { | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.CLAIM_LABEL }).catch(() => {}); | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.STALE_LABEL }).catch(() => {}); | |
| } | |
| continue; | |
| } | |
| const official = issue.assignees?.some((a) => a.login === claim.login); | |
| const progressDate = logic.latestProgressDate(comments, claim.login, claim.isoDate); | |
| const activityDate = progressDate || claim.isoDate; | |
| const now = new Date(); | |
| const warning = [...comments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.WARNING_MARKER)).find((m) => m?.login === claim.login && new Date(m.isoDate) > new Date(activityDate) && new Date(m.isoDate) <= now); | |
| if (progressDate && !warning) await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.STALE_LABEL }).catch(() => {}); | |
| const status = logic.claimStatus({ ...claim, isoDate: activityDate }, warning); | |
| if (status === 'warn') { | |
| await github.rest.issues.addLabels({ owner, repo, issue_number: issue.number, labels: [logic.STALE_LABEL] }); | |
| await github.rest.issues.createComment({ owner, repo, issue_number: issue.number, body: `${logic.warningMarker(claim.login, new Date().toISOString())}\n@${claim.login} 已认领此 Issue 30 天,但暂未看到进展。请在 7 天内更新进度或使用 /unassign 释放。` }); | |
| } else if (status === 'release') { | |
| const latestComments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: issue.number, per_page: 100 }); | |
| const latestClaim = [...latestComments].reverse().filter(trusted).map((c) => logic.parseMarker(c.body, logic.CLAIM_MARKER)).find(Boolean); | |
| if (latestClaim?.login !== claim.login || latestClaim?.isoDate !== claim.isoDate) continue; | |
| const releaseBody = `${logic.releaseMarker(claim.login, new Date().toISOString())}\n@${claim.login} 已连续 7 天未更新进度,认领已自动释放,其他贡献者可以继续申请。`; | |
| const existingRelease = latestComments.filter(trusted).map((c) => logic.parseMarker(c.body, logic.RELEASE_MARKER)).some((release) => logic.releaseMatchesClaim(release, claim)); | |
| if (!existingRelease) await github.rest.issues.createComment({ owner, repo, issue_number: issue.number, body: releaseBody }); | |
| const current = (await github.rest.issues.get({ owner, repo, issue_number: issue.number })).data; | |
| const stillOwned = current.assignees?.some((a) => a.login === claim.login); | |
| if (stillOwned) { | |
| try { | |
| await github.rest.issues.removeAssignees({ owner, repo, issue_number: issue.number, assignees: [claim.login] }); | |
| } catch (error) { | |
| core.warning(`Unable to remove stale assignee: ${error.message}`); | |
| continue; | |
| } | |
| } | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.CLAIM_LABEL }).catch(() => {}); | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: issue.number, name: logic.STALE_LABEL }).catch(() => {}); | |
| } | |
| } |