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_CLIENT_ID="op://Private/Guild Counter/client id"
|
||||
DISCORD_CLIENT_SECRET="op://Private/Guild Counter/client secret"
|
||||
DISCORD_BOT_TOKEN="op://Private/Amari Bot/Token"
|
||||
|
||||
# AWS
|
||||
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