generated from nhcarrigan/template
feat: cohort scripts
CI / dependency-pin-check-typescript (pull_request) Failing after 4s
CI / typescript (pull_request) Has been skipped
CI / dependency-pin-check-python (pull_request) Failing after 4s
CI / python (pull_request) Has been skipped
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m23s
CI / dependency-pin-check-typescript (pull_request) Failing after 4s
CI / typescript (pull_request) Has been skipped
CI / dependency-pin-check-python (pull_request) Failing after 4s
CI / python (pull_request) Has been skipped
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m23s
This commit is contained in:
@@ -10,6 +10,7 @@ GITHUB_TOKEN="op://Environment Variables - Development/Ephemere/GitHub Token"
|
|||||||
DISCORD_TOKEN="op://Environment Variables - Development/Ephemere/Discord Token"
|
DISCORD_TOKEN="op://Environment Variables - Development/Ephemere/Discord Token"
|
||||||
DISCORD_CLIENT_ID="op://Private/Guild Counter/client id"
|
DISCORD_CLIENT_ID="op://Private/Guild Counter/client id"
|
||||||
DISCORD_CLIENT_SECRET="op://Private/Guild Counter/client secret"
|
DISCORD_CLIENT_SECRET="op://Private/Guild Counter/client secret"
|
||||||
|
DISCORD_BOT_TOKEN="op://Private/Amari Bot/Token"
|
||||||
|
|
||||||
# AWS
|
# AWS
|
||||||
AWS_ACCESS_KEY_ID="op://Private/Hetzner/S3 Access Key ID"
|
AWS_ACCESS_KEY_ID="op://Private/Hetzner/S3 Access Key ID"
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Assign the Cohort role to all 155 participants.
|
||||||
|
Respects Discord rate limits with proper backoff and retry logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||||
|
GUILD_ID = "692816967895220344"
|
||||||
|
COHORT_ROLE_ID = "1464314780935258112"
|
||||||
|
|
||||||
|
BASE_URL = "https://discord.com/api/v10"
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bot {BOT_TOKEN}",
|
||||||
|
"Content-Length": "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
def assign_role_with_retry(user_id: str, role_id: str, max_retries: int = 5) -> bool:
|
||||||
|
url = f"{BASE_URL}/guilds/{GUILD_ID}/members/{user_id}/roles/{role_id}"
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
response = requests.put(url, headers=HEADERS)
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
return True
|
||||||
|
elif response.status_code == 429:
|
||||||
|
# Check headers first, fall back to JSON body
|
||||||
|
retry_after = response.headers.get("Retry-After")
|
||||||
|
if retry_after is None:
|
||||||
|
retry_after = response.headers.get("X-RateLimit-Reset-After")
|
||||||
|
if retry_after is None:
|
||||||
|
try:
|
||||||
|
retry_after = response.json().get("retry_after", 1)
|
||||||
|
except:
|
||||||
|
retry_after = 1
|
||||||
|
retry_after = float(retry_after)
|
||||||
|
print(f" Rate limited! Waiting {retry_after:.2f}s before retry...")
|
||||||
|
time.sleep(retry_after)
|
||||||
|
else:
|
||||||
|
print(f" Error {response.status_code}: {response.text}")
|
||||||
|
backoff_time = (2 ** attempt) * 0.5
|
||||||
|
print(f" Retrying in {backoff_time:.2f}s...")
|
||||||
|
time.sleep(backoff_time)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with open("team_assignments.json", "r") as f:
|
||||||
|
teams = json.load(f)
|
||||||
|
|
||||||
|
all_users = []
|
||||||
|
for team in teams:
|
||||||
|
all_users.extend(team["leaders"])
|
||||||
|
all_users.extend(team["participants"])
|
||||||
|
|
||||||
|
unique_users = list(dict.fromkeys(all_users))
|
||||||
|
|
||||||
|
print(f"Assigning Cohort role to {len(unique_users)} users...")
|
||||||
|
print(f"Role ID: {COHORT_ROLE_ID}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
fail_count = 0
|
||||||
|
|
||||||
|
for i, user_id in enumerate(unique_users, 1):
|
||||||
|
print(f"[{i}/{len(unique_users)}] Assigning to {user_id}...", end=" ")
|
||||||
|
|
||||||
|
if assign_role_with_retry(user_id, COHORT_ROLE_ID):
|
||||||
|
print("✓")
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
print("✗ FAILED")
|
||||||
|
fail_count += 1
|
||||||
|
|
||||||
|
# Small delay between requests to be nice to the API
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Success: {success_count}, Failed: {fail_count}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Assign team-specific roles to all 155 participants.
|
||||||
|
Respects Discord rate limits with proper backoff and retry logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||||
|
GUILD_ID = "692816967895220344"
|
||||||
|
|
||||||
|
BASE_URL = "https://discord.com/api/v10"
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bot {BOT_TOKEN}",
|
||||||
|
"Content-Length": "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
TEAM_ROLE_IDS = {
|
||||||
|
"Jade Jasmine": "1464314923780931677",
|
||||||
|
"Crimson Dahlia": "1464315093402784015",
|
||||||
|
"Rose Camellia": "1464315098452726106",
|
||||||
|
"Amber Wisteria": "1464315105264275600",
|
||||||
|
"Ivory Orchid": "1464315109873684593",
|
||||||
|
"Teal Iris": "1464315114378498152",
|
||||||
|
"Peach Gardenia": "1464315118904152107",
|
||||||
|
"Violet Carnation": "1464315124251754559",
|
||||||
|
"Azure Lotus": "1464315128437801177",
|
||||||
|
"Coral Sunflower": "1464315132896088168",
|
||||||
|
"Indigo Tulip": "1464315138428633241",
|
||||||
|
"Scarlet Hydrangea": "1464315142710890520",
|
||||||
|
"Mint Narcissus": "1464315149203804405",
|
||||||
|
"Sage Marigold": "1464315153599299803",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def assign_role_with_retry(user_id: str, role_id: str, max_retries: int = 5) -> bool:
|
||||||
|
url = f"{BASE_URL}/guilds/{GUILD_ID}/members/{user_id}/roles/{role_id}"
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
response = requests.put(url, headers=HEADERS)
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
return True
|
||||||
|
elif response.status_code == 429:
|
||||||
|
# Check headers first, fall back to JSON body
|
||||||
|
retry_after = response.headers.get("Retry-After")
|
||||||
|
if retry_after is None:
|
||||||
|
retry_after = response.headers.get("X-RateLimit-Reset-After")
|
||||||
|
if retry_after is None:
|
||||||
|
try:
|
||||||
|
retry_after = response.json().get("retry_after", 1)
|
||||||
|
except:
|
||||||
|
retry_after = 1
|
||||||
|
retry_after = float(retry_after)
|
||||||
|
print(f" Rate limited! Waiting {retry_after:.2f}s before retry...")
|
||||||
|
time.sleep(retry_after)
|
||||||
|
else:
|
||||||
|
print(f" Error {response.status_code}: {response.text}")
|
||||||
|
backoff_time = (2 ** attempt) * 0.5
|
||||||
|
print(f" Retrying in {backoff_time:.2f}s...")
|
||||||
|
time.sleep(backoff_time)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with open("team_assignments.json", "r") as f:
|
||||||
|
teams = json.load(f)
|
||||||
|
|
||||||
|
print(f"Assigning team roles to {len(teams)} teams...")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
total_success = 0
|
||||||
|
total_fail = 0
|
||||||
|
|
||||||
|
for team in teams:
|
||||||
|
team_name = team["name"]
|
||||||
|
role_id = TEAM_ROLE_IDS[team_name]
|
||||||
|
all_members = team["leaders"] + team["participants"]
|
||||||
|
|
||||||
|
print(f"\n[{team_name}] Assigning role to {len(all_members)} members...")
|
||||||
|
|
||||||
|
for user_id in all_members:
|
||||||
|
print(f" {user_id}...", end=" ")
|
||||||
|
|
||||||
|
if assign_role_with_retry(user_id, role_id):
|
||||||
|
print("✓")
|
||||||
|
total_success += 1
|
||||||
|
else:
|
||||||
|
print("✗ FAILED")
|
||||||
|
total_fail += 1
|
||||||
|
|
||||||
|
# Small delay between requests to be nice to the API
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print("-" * 50)
|
||||||
|
print(f"Complete! Success: {total_success}, Failed: {total_fail}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
BLOCK_EMOJIS = {
|
||||||
|
'mornings': '🌅',
|
||||||
|
'afternoons': '☀️',
|
||||||
|
'evenings': '🌆',
|
||||||
|
'nights': '🌙'
|
||||||
|
}
|
||||||
|
|
||||||
|
def load_all_data():
|
||||||
|
"""Load all evaluation data files"""
|
||||||
|
with open('discord_verification.json', 'r') as f:
|
||||||
|
verification = json.load(f)
|
||||||
|
|
||||||
|
with open('proficiency_evaluations.json', 'r') as f:
|
||||||
|
proficiency = json.load(f)
|
||||||
|
|
||||||
|
with open('availability_analysis.json', 'r') as f:
|
||||||
|
availability = json.load(f)
|
||||||
|
|
||||||
|
with open('leadership_candidates.json', 'r') as f:
|
||||||
|
candidates = json.load(f)
|
||||||
|
|
||||||
|
with open('leadership_evaluations.json', 'r') as f:
|
||||||
|
leadership = json.load(f)
|
||||||
|
|
||||||
|
return verification, proficiency, availability, candidates, leadership
|
||||||
|
|
||||||
|
def build_lookup_dicts(verification, proficiency, availability, leadership):
|
||||||
|
"""Build lookup dictionaries by discord_id"""
|
||||||
|
verified_usernames = {v[0]: v[1] for v in verification['verified']}
|
||||||
|
|
||||||
|
prof_by_id = {p['discord_id']: p for p in proficiency}
|
||||||
|
|
||||||
|
avail_by_id = {a['discord_id']: a for a in availability}
|
||||||
|
|
||||||
|
lead_by_id = {l['discord_id']: l for l in leadership}
|
||||||
|
|
||||||
|
return verified_usernames, prof_by_id, avail_by_id, lead_by_id
|
||||||
|
|
||||||
|
def format_availability_blocks(blocks):
|
||||||
|
"""Format availability blocks with emojis"""
|
||||||
|
if not blocks:
|
||||||
|
return "No consistent availability"
|
||||||
|
|
||||||
|
formatted = []
|
||||||
|
for block in ['mornings', 'afternoons', 'evenings', 'nights']:
|
||||||
|
if block in blocks:
|
||||||
|
formatted.append(f"{BLOCK_EMOJIS[block]} {block.capitalize()}")
|
||||||
|
return ", ".join(formatted)
|
||||||
|
|
||||||
|
def format_tech_stack(tech_stack):
|
||||||
|
"""Format tech stack list"""
|
||||||
|
if not tech_stack:
|
||||||
|
return "Not specified"
|
||||||
|
return ", ".join(sorted(tech_stack))
|
||||||
|
|
||||||
|
def generate_participants_md(non_leader_ids, verified_usernames, prof_by_id, avail_by_id):
|
||||||
|
"""Generate participants.md for non-leaders"""
|
||||||
|
lines = [
|
||||||
|
"# Cohort Participants",
|
||||||
|
"",
|
||||||
|
f"**Total Participants**: {len(non_leader_ids)}",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
beginner_count = 0
|
||||||
|
intermediate_count = 0
|
||||||
|
advanced_count = 0
|
||||||
|
|
||||||
|
for discord_id in sorted(non_leader_ids):
|
||||||
|
if discord_id not in verified_usernames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
username = verified_usernames.get(discord_id, "Unknown")
|
||||||
|
prof = prof_by_id.get(discord_id, {})
|
||||||
|
avail = avail_by_id.get(discord_id, {})
|
||||||
|
|
||||||
|
proficiency = prof.get('final_proficiency', 'unknown')
|
||||||
|
tech_stack = prof.get('tech_stack', [])
|
||||||
|
blocks = avail.get('available_blocks', [])
|
||||||
|
notes = prof.get('notes', [])
|
||||||
|
|
||||||
|
if proficiency == 'beginner':
|
||||||
|
beginner_count += 1
|
||||||
|
elif proficiency == 'intermediate':
|
||||||
|
intermediate_count += 1
|
||||||
|
elif proficiency == 'advanced':
|
||||||
|
advanced_count += 1
|
||||||
|
|
||||||
|
lines.append(f"## {discord_id}")
|
||||||
|
lines.append(f"**Username**: @{username}")
|
||||||
|
lines.append(f"**Technical Proficiency**: {proficiency.capitalize()}")
|
||||||
|
lines.append(f"**Tech Stack**: {format_tech_stack(tech_stack)}")
|
||||||
|
lines.append(f"**Availability**: {format_availability_blocks(blocks)}")
|
||||||
|
if notes:
|
||||||
|
lines.append(f"**Notes**: {', '.join(notes)}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
summary = [
|
||||||
|
"# Cohort Participants",
|
||||||
|
"",
|
||||||
|
f"**Total Participants**: {len([id for id in non_leader_ids if id in verified_usernames])}",
|
||||||
|
"",
|
||||||
|
"### Proficiency Breakdown",
|
||||||
|
f"- Beginner: {beginner_count}",
|
||||||
|
f"- Intermediate: {intermediate_count}",
|
||||||
|
f"- Advanced: {advanced_count}",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
return "\n".join(summary + lines[6:])
|
||||||
|
|
||||||
|
def leadership_fit_label(score):
|
||||||
|
"""Convert leadership score to label"""
|
||||||
|
if score >= 6:
|
||||||
|
return "Excellent"
|
||||||
|
elif score >= 4:
|
||||||
|
return "Good"
|
||||||
|
elif score >= 2:
|
||||||
|
return "Adequate"
|
||||||
|
else:
|
||||||
|
return "Needs Review"
|
||||||
|
|
||||||
|
def generate_leaders_md(leader_ids, verified_usernames, prof_by_id, avail_by_id, lead_by_id):
|
||||||
|
"""Generate leaders.md for leadership candidates"""
|
||||||
|
verified_leaders = [id for id in leader_ids if id in verified_usernames]
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# Cohort Leaders",
|
||||||
|
"",
|
||||||
|
f"**Total Leaders**: {len(verified_leaders)}",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
sorted_leaders = sorted(verified_leaders, key=lambda x: lead_by_id.get(x, {}).get('leadership_score', 0), reverse=True)
|
||||||
|
|
||||||
|
for discord_id in sorted_leaders:
|
||||||
|
username = verified_usernames.get(discord_id, "Unknown")
|
||||||
|
prof = prof_by_id.get(discord_id, {})
|
||||||
|
avail = avail_by_id.get(discord_id, {})
|
||||||
|
lead = lead_by_id.get(discord_id, {})
|
||||||
|
|
||||||
|
proficiency = prof.get('final_proficiency', 'unknown')
|
||||||
|
tech_stack = prof.get('tech_stack', [])
|
||||||
|
blocks = avail.get('available_blocks', [])
|
||||||
|
|
||||||
|
leadership_score = lead.get('leadership_score', 0)
|
||||||
|
leadership_fit = lead.get('leadership_fit', 'unknown')
|
||||||
|
leadership_notes = lead.get('notes', [])
|
||||||
|
prof_notes = prof.get('notes', [])
|
||||||
|
|
||||||
|
lines.append(f"## {discord_id}")
|
||||||
|
lines.append(f"**Username**: @{username}")
|
||||||
|
lines.append(f"**Leadership Fit**: {leadership_fit.capitalize()} (Score: {leadership_score})")
|
||||||
|
lines.append(f"**Technical Proficiency**: {proficiency.capitalize()}")
|
||||||
|
lines.append(f"**Tech Stack**: {format_tech_stack(tech_stack)}")
|
||||||
|
lines.append(f"**Availability**: {format_availability_blocks(blocks)}")
|
||||||
|
if leadership_notes:
|
||||||
|
lines.append(f"**Leadership Notes**: {', '.join(leadership_notes)}")
|
||||||
|
if prof_notes:
|
||||||
|
lines.append(f"**Technical Notes**: {', '.join(prof_notes)}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
excellent = sum(1 for id in verified_leaders if lead_by_id.get(id, {}).get('leadership_fit') == 'excellent')
|
||||||
|
good = sum(1 for id in verified_leaders if lead_by_id.get(id, {}).get('leadership_fit') == 'good')
|
||||||
|
adequate = sum(1 for id in verified_leaders if lead_by_id.get(id, {}).get('leadership_fit') == 'adequate')
|
||||||
|
|
||||||
|
summary = [
|
||||||
|
"# Cohort Leaders",
|
||||||
|
"",
|
||||||
|
f"**Total Leaders**: {len(verified_leaders)}",
|
||||||
|
"",
|
||||||
|
"### Leadership Fit Breakdown",
|
||||||
|
f"- Excellent: {excellent}",
|
||||||
|
f"- Good: {good}",
|
||||||
|
f"- Adequate: {adequate}",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
return "\n".join(summary + lines[6:])
|
||||||
|
|
||||||
|
def main():
|
||||||
|
verification, proficiency, availability, candidates, leadership = load_all_data()
|
||||||
|
|
||||||
|
verified_usernames, prof_by_id, avail_by_id, lead_by_id = build_lookup_dicts(
|
||||||
|
verification, proficiency, availability, leadership
|
||||||
|
)
|
||||||
|
|
||||||
|
leader_ids = set(candidates['leaders'])
|
||||||
|
non_leader_ids = set(candidates['non_leaders'])
|
||||||
|
|
||||||
|
verified_ids = set(verified_usernames.keys())
|
||||||
|
leader_ids = leader_ids & verified_ids
|
||||||
|
non_leader_ids = non_leader_ids & verified_ids
|
||||||
|
|
||||||
|
participants_md = generate_participants_md(non_leader_ids, verified_usernames, prof_by_id, avail_by_id)
|
||||||
|
with open('participants.md', 'w') as f:
|
||||||
|
f.write(participants_md)
|
||||||
|
print(f"Generated participants.md with {len(non_leader_ids)} participants")
|
||||||
|
|
||||||
|
leaders_md = generate_leaders_md(leader_ids, verified_usernames, prof_by_id, avail_by_id, lead_by_id)
|
||||||
|
with open('leaders.md', 'w') as f:
|
||||||
|
f.write(leaders_md)
|
||||||
|
print(f"Generated leaders.md with {len(leader_ids)} leaders")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Generate hourly time slots from Feb 1 to March 3, 2026
|
||||||
|
# 24 hours a day, America/Los_Angeles timezone
|
||||||
|
start_date = datetime(2026, 2, 1, 0, 0) # Feb 1, 2026, midnight
|
||||||
|
end_date = datetime(2026, 3, 3, 23, 0) # March 3, 2026, 11pm
|
||||||
|
|
||||||
|
times = []
|
||||||
|
current = start_date
|
||||||
|
while current <= end_date:
|
||||||
|
# Format: YYYY-MM-DDTHH:MM
|
||||||
|
times.append(current.strftime("%Y-%m-%dT%H:%M"))
|
||||||
|
current += timedelta(hours=1)
|
||||||
|
|
||||||
|
print(f"Generated {len(times)} time slots")
|
||||||
|
print(f"First: {times[0]}")
|
||||||
|
print(f"Last: {times[-1]}")
|
||||||
|
|
||||||
|
# Save to file for use
|
||||||
|
with open('/home/naomi/docs/cohort/crabfit_timeslots.json', 'w') as f:
|
||||||
|
json.dump(times, f)
|
||||||
|
|
||||||
|
print("Saved to crabfit_timeslots.json")
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Amari's bot token
|
||||||
|
TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||||
|
GUILD_ID = "692816967895220344"
|
||||||
|
|
||||||
|
# File to save message IDs
|
||||||
|
MESSAGE_IDS_FILE = "team_message_ids.json"
|
||||||
|
|
||||||
|
# Team channel IDs and role IDs
|
||||||
|
TEAMS = {
|
||||||
|
"Jade Jasmine": {"channel_id": "1464316501573107886", "role_id": "1464314923780931677"},
|
||||||
|
"Crimson Dahlia": {"channel_id": "1464316744909852682", "role_id": "1464315093402784015"},
|
||||||
|
"Rose Camellia": {"channel_id": "1464316751268286611", "role_id": "1464315098452726106"},
|
||||||
|
"Amber Wisteria": {"channel_id": "1464316761410113641", "role_id": "1464315105264275600"},
|
||||||
|
"Ivory Orchid": {"channel_id": "1464316770889240730", "role_id": "1464315109873684593"},
|
||||||
|
"Teal Iris": {"channel_id": "1464316776459407448", "role_id": "1464315114378498152"},
|
||||||
|
"Peach Gardenia": {"channel_id": "1464316785040953543", "role_id": "1464315118904152107"},
|
||||||
|
"Violet Carnation": {"channel_id": "1464316805261824032", "role_id": "1464315124251754559"},
|
||||||
|
"Azure Lotus": {"channel_id": "1464316814455472139", "role_id": "1464315128437801177"},
|
||||||
|
"Coral Sunflower": {"channel_id": "1464316819711066263", "role_id": "1464315132896088168"},
|
||||||
|
"Indigo Tulip": {"channel_id": "1464316826384072925", "role_id": "1464315138428633241"},
|
||||||
|
"Scarlet Hydrangea": {"channel_id": "1464316839306985506", "role_id": "1464315142710890520"},
|
||||||
|
"Mint Narcissus": {"channel_id": "1464316844251807952", "role_id": "1464315149203804405"},
|
||||||
|
"Sage Marigold": {"channel_id": "1464316850669093040", "role_id": "1464315153599299803"},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load team assignments and convert to dict by team name
|
||||||
|
with open("team_assignments.json", "r") as f:
|
||||||
|
team_list = json.load(f)
|
||||||
|
team_data = {team["name"]: team for team in team_list}
|
||||||
|
|
||||||
|
# Load applicants to get project_url by discord_id
|
||||||
|
with open("applicants_to_evaluate.json", "r") as f:
|
||||||
|
applicants = json.load(f)
|
||||||
|
applicant_lookup = {str(a["discord_id"]): a for a in applicants}
|
||||||
|
|
||||||
|
def extract_github_username(url):
|
||||||
|
"""Extract GitHub username from various URL formats"""
|
||||||
|
if not url:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
url = url.strip()
|
||||||
|
|
||||||
|
# Handle GitLab special case (RashiqAzhan)
|
||||||
|
if "gitlab.com" in url:
|
||||||
|
# We know this is RashiqAzhan from earlier confirmation
|
||||||
|
return "RashiqAzhan"
|
||||||
|
|
||||||
|
# Handle GitHub Pages URLs
|
||||||
|
if ".github.io" in url:
|
||||||
|
# Extract username from username.github.io format
|
||||||
|
parts = url.replace("https://", "").replace("http://", "").split(".")
|
||||||
|
if parts:
|
||||||
|
return parts[0]
|
||||||
|
|
||||||
|
# Handle plain usernames (no URL)
|
||||||
|
if not url.startswith("http"):
|
||||||
|
return url
|
||||||
|
|
||||||
|
# Handle standard GitHub URLs
|
||||||
|
if "github.com" in url:
|
||||||
|
# Remove protocol and github.com
|
||||||
|
path = url.replace("https://", "").replace("http://", "").replace("github.com/", "")
|
||||||
|
# Get just the username (first part of path)
|
||||||
|
username = path.split("/")[0]
|
||||||
|
return username
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
def build_message(team_name, role_id, leader_ids, participant_ids):
|
||||||
|
"""Build the welcome message for a team"""
|
||||||
|
lines = [
|
||||||
|
f"# {team_name}",
|
||||||
|
"",
|
||||||
|
f"Welcome, <@&{role_id}>. This is your private team channel — a space for you to collaborate, support one another, and build something meaningful together.",
|
||||||
|
"",
|
||||||
|
"## Roster",
|
||||||
|
"",
|
||||||
|
"**Leadership**",
|
||||||
|
]
|
||||||
|
|
||||||
|
for discord_id in leader_ids:
|
||||||
|
applicant = applicant_lookup.get(str(discord_id), {})
|
||||||
|
project_url = applicant.get("project_url", "")
|
||||||
|
github_username = extract_github_username(project_url)
|
||||||
|
lines.append(f"- <@{discord_id}>: https://github.com/{github_username}")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
lines.append("**Participants**")
|
||||||
|
|
||||||
|
for discord_id in participant_ids:
|
||||||
|
applicant = applicant_lookup.get(str(discord_id), {})
|
||||||
|
project_url = applicant.get("project_url", "")
|
||||||
|
github_username = extract_github_username(project_url)
|
||||||
|
lines.append(f"- <@{discord_id}>: https://github.com/{github_username}")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
lines.append("## Project Info")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Coming soon. 💜")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def send_message(channel_id, content):
|
||||||
|
"""Send a message to a channel"""
|
||||||
|
url = f"https://discord.com/api/v10/channels/{channel_id}/messages"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bot {TOKEN}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {"content": content}
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 429:
|
||||||
|
retry_after = float(response.headers.get("Retry-After", response.headers.get("X-RateLimit-Reset-After", 1)))
|
||||||
|
print(f"Rate limited, waiting {retry_after}s...")
|
||||||
|
time.sleep(retry_after)
|
||||||
|
return send_message(channel_id, content)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error sending message: {response.status_code} - {response.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def pin_message(channel_id, message_id):
|
||||||
|
"""Pin a message in a channel"""
|
||||||
|
url = f"https://discord.com/api/v10/channels/{channel_id}/pins/{message_id}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bot {TOKEN}",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.put(url, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 429:
|
||||||
|
retry_after = float(response.headers.get("Retry-After", response.headers.get("X-RateLimit-Reset-After", 1)))
|
||||||
|
print(f"Rate limited, waiting {retry_after}s...")
|
||||||
|
time.sleep(retry_after)
|
||||||
|
return pin_message(channel_id, message_id)
|
||||||
|
|
||||||
|
return response.status_code == 204
|
||||||
|
|
||||||
|
def main():
|
||||||
|
message_ids = {}
|
||||||
|
|
||||||
|
for team_name, team_info in TEAMS.items():
|
||||||
|
channel_id = team_info["channel_id"]
|
||||||
|
role_id = team_info["role_id"]
|
||||||
|
|
||||||
|
# Get team members from team_data
|
||||||
|
team = team_data.get(team_name, {"leaders": [], "participants": []})
|
||||||
|
leaders = team.get("leaders", [])
|
||||||
|
participants = team.get("participants", [])
|
||||||
|
|
||||||
|
# Build the message
|
||||||
|
message_content = build_message(team_name, role_id, leaders, participants)
|
||||||
|
|
||||||
|
print(f"Sending message to {team_name}...")
|
||||||
|
|
||||||
|
# Send the message
|
||||||
|
result = send_message(channel_id, message_content)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
message_id = result["id"]
|
||||||
|
message_ids[team_name] = {
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"message_id": message_id,
|
||||||
|
"role_id": role_id
|
||||||
|
}
|
||||||
|
print(f" Message sent! ID: {message_id}")
|
||||||
|
|
||||||
|
# Pin the message
|
||||||
|
print(f" Pinning message...")
|
||||||
|
if pin_message(channel_id, message_id):
|
||||||
|
print(f" Pinned!")
|
||||||
|
else:
|
||||||
|
print(f" Failed to pin")
|
||||||
|
else:
|
||||||
|
print(f" Failed to send message")
|
||||||
|
|
||||||
|
# Small delay between teams
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# Save message IDs to file
|
||||||
|
with open(MESSAGE_IDS_FILE, "w") as f:
|
||||||
|
json.dump(message_ids, f, indent=2)
|
||||||
|
|
||||||
|
print(f"\nDone! Message IDs saved to {MESSAGE_IDS_FILE}")
|
||||||
|
print(f"Successfully sent and pinned messages for {len(message_ids)} teams")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||||
|
GUILD_ID = "692816967895220344"
|
||||||
|
BASE_URL = "https://discord.com/api/v10"
|
||||||
|
|
||||||
|
# Read Discord IDs from table.md
|
||||||
|
with open("table.md", "r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
lines = content.strip().split("\n")
|
||||||
|
|
||||||
|
# Find the table header line (starts with |)
|
||||||
|
header_line = None
|
||||||
|
header_idx = 0
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("| Discord"):
|
||||||
|
header_line = line
|
||||||
|
header_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if not header_line:
|
||||||
|
print("Could not find table header!")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
headers = [h.strip() for h in header_line.split("|")[1:-1]]
|
||||||
|
|
||||||
|
discord_idx = 0 # Discord ID is the first column
|
||||||
|
|
||||||
|
discord_ids = []
|
||||||
|
for line in lines[header_idx + 2:]: # Skip header and separator
|
||||||
|
if not line.startswith("|"):
|
||||||
|
continue
|
||||||
|
cols = [c.strip() for c in line.split("|")[1:-1]]
|
||||||
|
if len(cols) > discord_idx:
|
||||||
|
discord_id = cols[discord_idx].strip()
|
||||||
|
if discord_id and discord_id.isdigit():
|
||||||
|
discord_ids.append(discord_id)
|
||||||
|
|
||||||
|
print(f"Found {len(discord_ids)} Discord IDs to verify")
|
||||||
|
|
||||||
|
# Verify each ID against the guild
|
||||||
|
verified = []
|
||||||
|
missing = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for i, discord_id in enumerate(discord_ids):
|
||||||
|
url = f"{BASE_URL}/guilds/{GUILD_ID}/members/{discord_id}"
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header("Authorization", f"Bot {BOT_TOKEN}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
username = data.get("user", {}).get("username", "Unknown")
|
||||||
|
verified.append((discord_id, username))
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ✓ {discord_id} - {username}")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
missing.append(discord_id)
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ✗ {discord_id} - NOT IN SERVER")
|
||||||
|
elif e.code == 429:
|
||||||
|
# Rate limited - wait and retry
|
||||||
|
retry_after = json.loads(e.read().decode()).get("retry_after", 1)
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] Rate limited, waiting {retry_after}s...")
|
||||||
|
time.sleep(retry_after + 0.5)
|
||||||
|
# Retry
|
||||||
|
try:
|
||||||
|
req2 = urllib.request.Request(url)
|
||||||
|
req2.add_header("Authorization", f"Bot {BOT_TOKEN}")
|
||||||
|
response = urllib.request.urlopen(req2)
|
||||||
|
data = json.loads(response.read().decode())
|
||||||
|
username = data.get("user", {}).get("username", "Unknown")
|
||||||
|
verified.append((discord_id, username))
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ✓ {discord_id} - {username} (after retry)")
|
||||||
|
except urllib.error.HTTPError as e2:
|
||||||
|
if e2.code == 404:
|
||||||
|
missing.append(discord_id)
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ✗ {discord_id} - NOT IN SERVER (after retry)")
|
||||||
|
else:
|
||||||
|
errors.append((discord_id, f"HTTP {e2.code}"))
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ? {discord_id} - Error {e2.code}")
|
||||||
|
else:
|
||||||
|
errors.append((discord_id, f"HTTP {e.code}"))
|
||||||
|
print(f"[{i+1}/{len(discord_ids)}] ? {discord_id} - Error {e.code}")
|
||||||
|
|
||||||
|
# Small delay to avoid rate limits
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print(f"\n=== SUMMARY ===")
|
||||||
|
print(f"Verified: {len(verified)}")
|
||||||
|
print(f"Missing: {len(missing)}")
|
||||||
|
print(f"Errors: {len(errors)}")
|
||||||
|
|
||||||
|
# Save results
|
||||||
|
with open("discord_verification.json", "w") as f:
|
||||||
|
json.dump({
|
||||||
|
"verified": verified,
|
||||||
|
"missing": missing,
|
||||||
|
"errors": errors
|
||||||
|
}, f, indent=2)
|
||||||
|
|
||||||
|
print("\nResults saved to discord_verification.json")
|
||||||
Reference in New Issue
Block a user