name: OpenClaw Gate on: issues: types: [opened] pull_request_target: types: [opened] jobs: check-contributor: runs-on: ubuntu-latest permissions: contents: read issues: write pull-requests: write steps: - name: Check contributor uses: actions/github-script@v7 with: script: | const isPR = !!context.payload.pull_request; const author = isPR ? context.payload.pull_request.user.login : context.payload.issue.user.login; const number = isPR ? context.payload.pull_request.number : context.payload.issue.number; const defaultBranch = context.payload.repository.default_branch; if (author.endsWith('[bot]') || author === 'dependabot[bot]') { console.log(`Skipping bot: ${author}`); return; } const APPROVED_FILE = '.github/APPROVED_CONTRIBUTORS'; const VALID_CAPABILITIES = new Set(['issue', 'pr']); // --- Check APPROVED_CONTRIBUTORS --- async function getTextFile(path) { const { data } = await github.rest.repos.getContent({ owner: context.repo.owner, repo: context.repo.repo, path, ref: defaultBranch, }); if (!('content' in data) || typeof data.content !== 'string') { throw new Error(`Expected file content for ${path}`); } return Buffer.from(data.content, 'base64').toString('utf8'); } try { const content = await getTextFile(APPROVED_FILE); const approved = new Map(); for (const rawLine of content.split('\n')) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const parts = line.split(/\s+/); if (parts.length !== 2) continue; const [username, capability] = parts; const normalizedCapability = capability.toLowerCase(); if (!VALID_CAPABILITIES.has(normalizedCapability)) continue; approved.set(username.toLowerCase(), normalizedCapability); } if (approved.has(author.toLowerCase())) { console.log(`${author} is in APPROVED_CONTRIBUTORS, passing`); return; } } catch (err) { console.log(`Could not read APPROVED_CONTRIBUTORS: ${err.message}`); } // --- Also pass collaborators with write+ access --- try { const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: author, }); if (['admin', 'maintain', 'write'].includes(perm.permission)) { console.log(`${author} is a collaborator (${perm.permission}), passing`); return; } } catch { // not a collaborator } // --- Check if user opened issues/PRs on openclaw/openclaw --- async function hasOpenClawActivity(username) { try { const { data } = await github.rest.search.issuesAndPullRequests({ q: `repo:openclaw/openclaw author:${username}`, per_page: 1, }); if (data.total_count > 0) { console.log(`${username} has opened ${data.total_count} issues/PRs on openclaw/openclaw`); return true; } } catch (err) { console.log(`Search failed: ${err.message}`); } return false; } const hasActivity = await hasOpenClawActivity(author); if (!hasActivity) { console.log(`${author} has no openclaw/openclaw activity, passing`); return; } // --- Add openclaw label --- console.log(`${author} has openclaw/openclaw activity, adding label`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: number, labels: ['possibly-openclaw-clanker'], });