PQ Interop #9, September 10, 2025 #178
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: Protocol Call Handler | |
on: | |
issues: | |
types: [opened, edited] | |
jobs: | |
check_label: | |
runs-on: ubuntu-latest | |
outputs: | |
has_protocol_label: ${{ steps.check_label.outputs.has_protocol_label }} | |
steps: | |
- name: Check for protocol-call label | |
id: check_label | |
run: | | |
if [[ "${{ contains(github.event.issue.labels.*.name, 'protocol-call') }}" == "true" ]]; then | |
echo "has_protocol_label=true" >> $GITHUB_OUTPUT | |
echo "✅ Issue has protocol-call label" | |
else | |
echo "has_protocol_label=false" >> $GITHUB_OUTPUT | |
echo "❌ Issue does not have protocol-call label" | |
fi | |
check_membership: | |
needs: check_label | |
runs-on: ubuntu-latest | |
if: needs.check_label.outputs.has_protocol_label == 'true' | |
outputs: | |
is_member: ${{ steps.set_output.outputs.is_member }} | |
steps: | |
- name: Check Organization Membership | |
id: check_org_membership | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
// Check if the user is a member of ethereum or ethcatherders orgs | |
let is_member = false; | |
const username = context.actor; | |
// Define trusted contributors list once at the beginning | |
const trustedContributors = [ | |
"poojaranjan", "adietrichs", "carlbeek", "timbeiko", "nconsigny", | |
"justindrake", "vbuterin", "hwwhww", "mathhhewkeil", "jrudolf", | |
"soispoke", "pipermerriam", "potuz", "will-corcoran", "ralexstokes", | |
"samwilsn", "shemnon", "nixorokish", "dcrapis", "gabrocheleau", | |
"JoshDavisLight", "nerolation", "jihoonsong" | |
]; | |
console.log(`Checking membership for user: ${username}`); | |
try { | |
// Try multiple methods to check membership to be thorough | |
// First, try direct membership check - this only works for public members or if token has sufficient permissions | |
async function checkDirectMembership(org, user) { | |
try { | |
await github.rest.orgs.checkMembershipForUser({ | |
org: org, | |
username: user | |
}); | |
return true; | |
} catch (error) { | |
console.log(`Direct membership check for ${user} in ${org} failed: ${error.message}`); | |
return false; | |
} | |
} | |
// Next, try to get both public and private members | |
async function getAllOrgMembers(org) { | |
const members = new Set(); | |
const perPage = 100; // Maximum allowed | |
// Get public members | |
try { | |
console.log(`Fetching public members for ${org}...`); | |
for await (const response of github.paginate.iterator( | |
github.rest.orgs.listPublicMembers, | |
{ org, per_page: perPage } | |
)) { | |
for (const member of response.data) { | |
members.add(member.login.toLowerCase()); | |
} | |
} | |
console.log(`Found ${members.size} public members in ${org}`); | |
} catch (error) { | |
console.log(`Error fetching public members for ${org}: ${error.message}`); | |
} | |
// Get all members (includes private, if token has permissions) | |
try { | |
console.log(`Fetching all members for ${org}...`); | |
for await (const response of github.paginate.iterator( | |
github.rest.orgs.listMembers, | |
{ org, per_page: perPage } | |
)) { | |
for (const member of response.data) { | |
members.add(member.login.toLowerCase()); | |
} | |
} | |
console.log(`Found total of ${members.size} members in ${org} (public + private that token can see)`); | |
} catch (error) { | |
console.log(`Error fetching all members for ${org}: ${error.message}`); | |
} | |
return Array.from(members); | |
} | |
// Check Ethereum org | |
console.log('Checking ethereum organization membership...'); | |
if (await checkDirectMembership('ethereum', username)) { | |
console.log(`✅ ${username} is confirmed as a member of ethereum org via direct API check`); | |
is_member = true; | |
} else { | |
const ethereumMembers = await getAllOrgMembers('ethereum'); | |
console.log(`Collected ${ethereumMembers.length} ethereum members to check against`); | |
if (ethereumMembers.includes(username.toLowerCase())) { | |
console.log(`✅ ${username} is a member of ethereum org`); | |
is_member = true; | |
} else { | |
console.log(`❌ ${username} is not found in ethereum org member list`); | |
} | |
} | |
// If not in ethereum, check ethcatherders org | |
if (!is_member) { | |
console.log('Checking ethcatherders organization membership...'); | |
if (await checkDirectMembership('ethcatherders', username)) { | |
console.log(`✅ ${username} is confirmed as a member of ethcatherders org via direct API check`); | |
is_member = true; | |
} else { | |
const catMembers = await getAllOrgMembers('ethcatherders'); | |
console.log(`Collected ${catMembers.length} ethcatherders members to check against`); | |
if (catMembers.includes(username.toLowerCase())) { | |
console.log(`✅ ${username} is a member of ethcatherders org`); | |
is_member = true; | |
} else { | |
console.log(`❌ ${username} is not found in ethcatherders org member list`); | |
} | |
} | |
} | |
// Fallback to a list of trusted contributors if not in either org | |
if (!is_member) { | |
if (trustedContributors.includes(username.toLowerCase())) { | |
console.log(`✅ ${username} is in the trusted contributors list`); | |
is_member = true; | |
} else { | |
console.log(`❌ ${username} is not in the trusted contributors list`); | |
} | |
} | |
console.log(`Final membership determination for ${username}: ${is_member ? 'Approved ✅' : 'Not approved ❌'}`); | |
} catch (error) { | |
console.log(`Error in membership check: ${error.message}`); | |
console.log(`Stack trace: ${error.stack}`); | |
// If we're here, something went wrong with the API checks | |
// Check if user is in trusted list as a fallback | |
if (trustedContributors.includes(username.toLowerCase())) { | |
console.log(`✅ FALLBACK: ${username} is in the trusted contributors list`); | |
is_member = true; | |
} | |
} | |
return is_member; | |
- name: Set output | |
id: set_output | |
run: echo "is_member=${{ steps.check_org_membership.outputs.result }}" >> $GITHUB_OUTPUT | |
handle_protocol_call: | |
needs: check_membership | |
runs-on: ubuntu-latest | |
if: needs.check_membership.outputs.is_member == 'true' | |
steps: | |
- name: Check out code | |
uses: actions/checkout@v3 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: '3.9' | |
- name: Upgrade pip, setuptools, and wheel | |
run: | | |
pip install --upgrade pip setuptools wheel | |
- name: Install PyGithub Explicitly | |
run: | | |
pip install PyGithub>=1.55.1 | |
- name: Install dependencies | |
run: | | |
pip install --upgrade pip | |
pip install -e .github/ACDbot/ | |
pip install pytz google-api-python-client | |
pip install -r .github/ACDbot/requirements.txt | |
- name: Verify Installed Packages | |
run: | | |
pip list | |
- name: Log protocol call processing | |
run: | | |
echo "Processing protocol call issue #${{ github.event.issue.number }}" | |
echo "Issue title: ${{ github.event.issue.title }}" | |
echo "Issue labels: ${{ github.event.issue.labels.*.name }}" | |
- name: Handle protocol call | |
run: | | |
python .github/ACDbot/scripts/handle_protocol_call.py \ | |
--issue_number "${{ github.event.issue.number }}" \ | |
--repo "${{ github.repository }}" | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
ZOOM_CLIENT_ID: ${{ secrets.ZOOM_CLIENT_ID }} | |
ZOOM_CLIENT_SECRET: ${{ secrets.ZOOM_CLIENT_SECRET }} | |
ZOOM_ACCOUNT_ID: ${{ secrets.ZOOM_ACCOUNT_ID }} | |
ZOOM_ALTERNATIVE_HOSTS: ${{ secrets.ZOOM_ALTERNATIVE_HOSTS }} | |
ZOOM_REFRESH_TOKEN: ${{ secrets.ZOOM_REFRESH_TOKEN }} | |
DISCOURSE_API_KEY: ${{ secrets.DISCOURSE_API_KEY }} | |
DISCOURSE_API_USERNAME: ${{ secrets.DISCOURSE_API_USERNAME }} | |
DISCOURSE_BASE_URL: ${{ vars.DISCOURSE_BASE_URL }} | |
GCAL_ID: ${{ vars.GCAL_ID }} | |
GCAL_SERVICE_ACCOUNT_KEY: ${{ secrets.GCAL_SERVICE_ACCOUNT_KEY }} | |
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} | |
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} | |
YOUTUBE_REFRESH_TOKEN: ${{ secrets.YOUTUBE_REFRESH_TOKEN }} | |
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }} | |
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} | |
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
TELEGRAM_CHAT_ID: ${{ vars.TELEGRAM_CHAT_ID }} | |
SENDER_EMAIL: ${{ secrets.SENDER_EMAIL }} | |
SENDER_EMAIL_PASSWORD: ${{ secrets.SENDER_EMAIL_PASSWORD }} | |
SMTP_SERVER: ${{ secrets.SMTP_SERVER }} | |
- name: Commit mapping file | |
if: always() | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
git config --local user.email "[email protected]" | |
git config --local user.name "GitHub Action" | |
if git diff --quiet .github/ACDbot/meeting_topic_mapping.json; then | |
echo "No changes to mapping file" | |
else | |
git add .github/ACDbot/meeting_topic_mapping.json | |
git commit -m "Update mapping for issue ${{ github.event.issue.number }}" | |
git push | |
echo "Committed mapping file changes" | |
fi | |
permissions: | |
contents: write | |
issues: write | |
# Prevent concurrent runs of the same issue | |
concurrency: | |
group: issue-${{ github.event.issue.number }} | |
cancel-in-progress: false |