generated from nhcarrigan/template
docs: add data file documentation and fix data path resolution
All Python cohort scripts now use DATA_DIR = Path(__file__).parent.parent.parent / "data" to correctly resolve the repo-root data/ directory regardless of the working directory set by run.sh. All TypeScript scripts have expanded JSDoc headers documenting data file requirements and environment variables.
This commit is contained in:
@@ -1,15 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add GitHub users to their appropriate teams in nhcarrigan-spring-2026-cohort org"""
|
||||
"""Add GitHub users to their appropriate teams in the cohort GitHub organisation.
|
||||
|
||||
Uses the GitHub CLI to add each member to their corresponding team and, for
|
||||
leaders, to the team's leaders sub-team.
|
||||
|
||||
Data files (place in data/):
|
||||
- team_assignments.json Team rosters with leaders and participants per team
|
||||
- discord_to_github.json Mapping of Discord IDs to GitHub usernames
|
||||
|
||||
Env vars:
|
||||
- None (uses `gh` CLI for authentication)
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# Load team assignments and Discord to GitHub mappings
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
with open("discord_to_github.json") as f:
|
||||
with open(DATA_DIR / "discord_to_github.json") as f:
|
||||
discord_to_github = json.load(f)
|
||||
|
||||
# Map team names to GitHub team slugs
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
"""Analyse applicant availability from a markdown table and produce UTC block stats.
|
||||
|
||||
Reads a markdown table of availability responses and a Discord verification file,
|
||||
then produces a JSON summary of coverage across morning/afternoon/evening UTC blocks
|
||||
for each day of the week.
|
||||
|
||||
Data files (place in data/):
|
||||
- table.md Markdown table of applicant availability responses
|
||||
- discord_verification.json Discord ID verification results (from verify_discord.py)
|
||||
|
||||
Outputs (written to data/):
|
||||
- availability_analysis.json UTC block distribution per applicant
|
||||
|
||||
Env vars:
|
||||
- None
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||
|
||||
@@ -99,7 +119,7 @@ def analyze_applicant_availability(timezone_str: str, day_slots: dict) -> dict:
|
||||
|
||||
def parse_table_md() -> list[dict]:
|
||||
"""Parse table.md and extract availability data"""
|
||||
with open("table.md") as f:
|
||||
with open(DATA_DIR / "table.md") as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.strip().split("\n")
|
||||
@@ -131,7 +151,7 @@ def parse_table_md() -> list[dict]:
|
||||
|
||||
|
||||
def main():
|
||||
with open("discord_verification.json") as f:
|
||||
with open(DATA_DIR / "discord_verification.json") as f:
|
||||
verification = json.load(f)
|
||||
|
||||
verified_ids = {v[0] for v in verification["verified"]}
|
||||
@@ -167,7 +187,7 @@ def main():
|
||||
}
|
||||
)
|
||||
|
||||
with open("availability_analysis.json", "w") as f:
|
||||
with open(DATA_DIR / "availability_analysis.json", "w") as f:
|
||||
json.dump(availability_results, f, indent=2)
|
||||
|
||||
block_distribution = defaultdict(int)
|
||||
|
||||
@@ -6,9 +6,12 @@ Respects Discord rate limits with proper backoff and retry logic.
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
GUILD_ID = "692816967895220344"
|
||||
COHORT_ROLE_ID = "1464314780935258112"
|
||||
@@ -48,7 +51,7 @@ def assign_role_with_retry(user_id: str, role_id: str, max_retries: int = 5) ->
|
||||
|
||||
|
||||
def main():
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
all_users = []
|
||||
|
||||
@@ -6,9 +6,12 @@ Respects Discord rate limits with proper backoff and retry logic.
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
GUILD_ID = "692816967895220344"
|
||||
|
||||
@@ -64,7 +67,7 @@ def assign_role_with_retry(user_id: str, role_id: str, max_retries: int = 5) ->
|
||||
|
||||
|
||||
def main():
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
print(f"Assigning team roles to {len(teams)} teams...")
|
||||
|
||||
@@ -11,9 +11,12 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
DISCORD_API_BASE = "https://discord.com/api/v10"
|
||||
GITHUB_API_BASE = "https://api.github.com"
|
||||
@@ -440,10 +443,10 @@ def build_report(
|
||||
async def main() -> None:
|
||||
"""Run the catch-up activity report."""
|
||||
print("Loading data files...")
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
team_data: list[dict] = json.load(f)
|
||||
|
||||
with open("discord_to_github.json") as f:
|
||||
with open(DATA_DIR / "discord_to_github.json") as f:
|
||||
discord_to_github: dict[str, str] = json.load(f)
|
||||
|
||||
print("Getting GitHub token via gh CLI...")
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
"""Quick dry-run to check Discord message lengths before sending."""
|
||||
"""Dry-run check of Discord message lengths before sending the activity report.
|
||||
|
||||
Parses the catch_up_report.md table, formats each team's data into a monospace
|
||||
Discord table, and reports whether any message would exceed Discord's 2000-char limit.
|
||||
Run this before send_activity_report.py to catch length issues early.
|
||||
|
||||
Data files (place in data/):
|
||||
- catch_up_report.md Activity report generated by catch_up_report.py
|
||||
|
||||
Env vars:
|
||||
- None
|
||||
"""
|
||||
|
||||
FIELDS = [
|
||||
("Discord Username", "Name", 18),
|
||||
@@ -11,7 +22,7 @@ FIELDS = [
|
||||
("Commits", "Commits", 7),
|
||||
]
|
||||
|
||||
REPORT_PATH = "catch_up_report.md"
|
||||
REPORT_PATH = "data/catch_up_report.md"
|
||||
|
||||
|
||||
def parse_report(path: str) -> dict[str, list[dict]]:
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Discord Team Activity Checker
|
||||
Checks for team members who haven't sent messages in their channels within 36 hours
|
||||
"""Check for team members who have not sent a message in their channel within 36 hours.
|
||||
|
||||
Scans each team's Discord channel and flags members with no recent activity.
|
||||
Optionally sends a direct mention message to inactive members.
|
||||
|
||||
Data files (place in data/):
|
||||
- team_assignments.json Team rosters with leaders and participants per team
|
||||
|
||||
Outputs (written to data/):
|
||||
- discord_activity_report.json Inactive members per team with last-seen timestamps
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -8,16 +19,19 @@ import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# Configuration
|
||||
DISCORD_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
DISCORD_API_BASE = "https://discord.com/api/v10"
|
||||
INACTIVE_THRESHOLD_HOURS = 36
|
||||
|
||||
# Load team assignments from file
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
team_data = json.load(f)
|
||||
|
||||
# Build TEAMS dictionary with channel IDs and member lists
|
||||
@@ -233,7 +247,7 @@ async def main():
|
||||
print("\n" + "=" * 80)
|
||||
|
||||
# Save results to JSON
|
||||
with open("discord_activity_report.json", "w") as f:
|
||||
with open(DATA_DIR / "discord_activity_report.json", "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
"""Evaluate the technical proficiency of cohort applicants using their GitHub profiles.
|
||||
|
||||
Fetches each applicant's public GitHub repositories and scores their proficiency as
|
||||
Beginner, Intermediate, or Advanced based on language variety, repo count, commit
|
||||
activity, and presence of certain technologies.
|
||||
|
||||
Data files (place in data/):
|
||||
- applicants_to_evaluate.json List of applicants with GitHub usernames
|
||||
|
||||
Outputs (written to data/):
|
||||
- proficiency_evaluations.json Proficiency scores and tech stacks per applicant
|
||||
|
||||
Env vars:
|
||||
- None (uses public GitHub API; may be rate-limited without authentication)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# GitHub API (no auth needed for public repos, but rate limited)
|
||||
GITHUB_API = "https://api.github.com"
|
||||
@@ -234,7 +253,7 @@ def evaluate_applicant(applicant: dict, index: int, total: int) -> dict:
|
||||
|
||||
def main():
|
||||
# Load applicants
|
||||
with open("applicants_to_evaluate.json") as f:
|
||||
with open(DATA_DIR / "applicants_to_evaluate.json") as f:
|
||||
applicants = json.load(f)
|
||||
|
||||
print(f"Evaluating {len(applicants)} applicants...\n")
|
||||
@@ -249,7 +268,7 @@ def main():
|
||||
print(f" Progress: {i + 1}/{len(applicants)} complete")
|
||||
|
||||
# Save results
|
||||
with open("proficiency_evaluations.json", "w") as f:
|
||||
with open(DATA_DIR / "proficiency_evaluations.json", "w") as f:
|
||||
json.dump(evaluations, f, indent=2)
|
||||
|
||||
# Summary
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix permissions for cohort-team-michael-and-yoon channel.
|
||||
"""Fix Send Messages / Send Messages in Threads permissions for a Discord channel.
|
||||
|
||||
Deny Send Messages in Threads for @everyone and @cohort.
|
||||
Denies Send Messages and Send Messages in Threads for both @everyone and the
|
||||
@cohort role on the target channel. Update CHANNEL_ID and COHORT_ROLE_ID before
|
||||
running.
|
||||
|
||||
Data files (place in data/):
|
||||
- None
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
"""Generate markdown participant and leader profile files for the cohort.
|
||||
|
||||
Reads all evaluation data files and produces two markdown files summarising
|
||||
each member's tech stack, availability, proficiency, and leadership assessment.
|
||||
|
||||
Data files (place in data/):
|
||||
- discord_verification.json Discord ID verification results (from verify_discord.py)
|
||||
- proficiency_evaluations.json Proficiency scores (from evaluate_technical_proficiency.py)
|
||||
- availability_analysis.json Availability UTC blocks (from analyse_availability.py)
|
||||
- leadership_candidates.json List of applicants who expressed interest in leading
|
||||
- leadership_evaluations.json Leadership assessment scores
|
||||
|
||||
Outputs (written to data/):
|
||||
- participants.md Markdown profile for each participant
|
||||
- leaders.md Markdown profile for each leader candidate
|
||||
|
||||
Env vars:
|
||||
- None
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
BLOCK_EMOJIS = {"mornings": "🌅", "afternoons": "☀️", "evenings": "🌆", "nights": "🌙"}
|
||||
|
||||
|
||||
def load_all_data():
|
||||
"""Load all evaluation data files"""
|
||||
with open("discord_verification.json") as f:
|
||||
with open(DATA_DIR / "discord_verification.json") as f:
|
||||
verification = json.load(f)
|
||||
|
||||
with open("proficiency_evaluations.json") as f:
|
||||
with open(DATA_DIR / "proficiency_evaluations.json") as f:
|
||||
proficiency = json.load(f)
|
||||
|
||||
with open("availability_analysis.json") as f:
|
||||
with open(DATA_DIR / "availability_analysis.json") as f:
|
||||
availability = json.load(f)
|
||||
|
||||
with open("leadership_candidates.json") as f:
|
||||
with open(DATA_DIR / "leadership_candidates.json") as f:
|
||||
candidates = json.load(f)
|
||||
|
||||
with open("leadership_evaluations.json") as f:
|
||||
with open(DATA_DIR / "leadership_evaluations.json") as f:
|
||||
leadership = json.load(f)
|
||||
|
||||
return verification, proficiency, availability, candidates, leadership
|
||||
@@ -230,14 +253,14 @@ def main():
|
||||
participants_md = generate_participants_md(
|
||||
non_leader_ids, verified_usernames, prof_by_id, avail_by_id
|
||||
)
|
||||
with open("participants.md", "w") as f:
|
||||
with open(DATA_DIR / "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:
|
||||
with open(DATA_DIR / "leaders.md", "w") as f:
|
||||
f.write(leaders_md)
|
||||
print(f"Generated leaders.md with {len(leader_ids)} leaders")
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
"""Generate hourly timeslot JSON for use with Crabfit scheduling tool.
|
||||
|
||||
Produces a list of ISO-format datetime strings covering every hour across the
|
||||
scheduling window. Update the start_date and end_date constants before running.
|
||||
|
||||
Outputs (written to data/):
|
||||
- crabfit_timeslots.json List of hourly timeslot strings
|
||||
|
||||
Env vars:
|
||||
- None
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# Generate hourly time slots from Feb 1 to March 3, 2026
|
||||
# 24 hours a day, America/Los_Angeles timezone
|
||||
@@ -18,7 +33,7 @@ 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:
|
||||
with open(DATA_DIR / "crabfit_timeslots.json", "w") as f:
|
||||
json.dump(times, f)
|
||||
|
||||
print("Saved to crabfit_timeslots.json")
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
GUILD_ID = "692816967895220344"
|
||||
COHORT_ROLE_ID = "1464314780935258112"
|
||||
@@ -72,7 +75,7 @@ async def main() -> None:
|
||||
f"{i}. {member['display_name']} (@{member['username']}) - ID: {member['id']}" # noqa: E501
|
||||
)
|
||||
|
||||
with open("active_cohort_members.json", "w") as f:
|
||||
with open(DATA_DIR / "active_cohort_members.json", "w") as f:
|
||||
json.dump(cohort_members, f, indent=2)
|
||||
|
||||
print("\nSaved to active_cohort_members.json")
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove cohort and team roles from inactive Discord members."""
|
||||
"""Remove the Cohort and team-specific Discord roles from a list of members.
|
||||
|
||||
Update INACTIVE_MEMBERS and MEMBER_TO_TEAM before running to target the correct
|
||||
members. Removes both the cohort-wide role and the member's team role.
|
||||
|
||||
Data files (place in data/):
|
||||
- None (member IDs and team mappings are defined as constants in the script)
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
@@ -18,9 +18,12 @@ import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
DISCORD_API_BASE = "https://discord.com/api/v10"
|
||||
DISCORD_GUILD_ID = "692816967895220344"
|
||||
@@ -71,7 +74,7 @@ class MemberRemover:
|
||||
|
||||
def find_member_info(self) -> bool:
|
||||
"""Find which team the member is on and their role."""
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
for team in teams:
|
||||
@@ -88,7 +91,7 @@ class MemberRemover:
|
||||
|
||||
def remove_from_team_assignments(self) -> None:
|
||||
"""Remove member from team_assignments.json."""
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
for team in teams:
|
||||
@@ -103,19 +106,19 @@ class MemberRemover:
|
||||
f"✅ Removed from {team['name']} participants in team_assignments.json"
|
||||
)
|
||||
|
||||
with open("team_assignments.json", "w") as f:
|
||||
with open(DATA_DIR / "team_assignments.json", "w") as f:
|
||||
json.dump(teams, f, indent=2)
|
||||
|
||||
def remove_from_discord_to_github(self) -> None:
|
||||
"""Remove member from discord_to_github.json."""
|
||||
with open("discord_to_github.json") as f:
|
||||
with open(DATA_DIR / "discord_to_github.json") as f:
|
||||
mappings = json.load(f)
|
||||
|
||||
if self.discord_id in mappings:
|
||||
self.github_username = mappings[self.discord_id]
|
||||
del mappings[self.discord_id]
|
||||
|
||||
with open("discord_to_github.json", "w") as f:
|
||||
with open(DATA_DIR / "discord_to_github.json", "w") as f:
|
||||
json.dump(mappings, f, indent=2)
|
||||
|
||||
print(f"✅ Removed {self.github_username} from discord_to_github.json")
|
||||
@@ -135,7 +138,7 @@ class MemberRemover:
|
||||
|
||||
async def remove_discord_roles(self) -> None:
|
||||
"""Remove Discord roles from the member."""
|
||||
with open("team_message_ids.json") as f:
|
||||
with open(DATA_DIR / "team_message_ids.json") as f:
|
||||
team_message_data = json.load(f)
|
||||
|
||||
if self.team_name not in team_message_data:
|
||||
|
||||
@@ -6,6 +6,9 @@ before running this script.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# Discord IDs of members who have resigned - update before running
|
||||
RESIGNED_IDS: list[str] = []
|
||||
@@ -13,7 +16,7 @@ RESIGNED_IDS: list[str] = []
|
||||
|
||||
def main() -> None:
|
||||
"""Remove resigned members from team assignments."""
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
changes_made = []
|
||||
@@ -36,7 +39,7 @@ def main() -> None:
|
||||
changes_made.append(f"Removed {removed} participant(s) from {team_name}")
|
||||
print(f"⚠️ {team_name}: Proficiency distribution may need updating")
|
||||
|
||||
with open("team_assignments.json", "w") as f:
|
||||
with open(DATA_DIR / "team_assignments.json", "w") as f:
|
||||
json.dump(teams, f, indent=2)
|
||||
|
||||
if changes_made:
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
"""Send formatted activity report tables to each team channel via Amari bot."""
|
||||
"""Send formatted activity report tables to each team's Discord channel.
|
||||
|
||||
Parses catch_up_report.md and posts a monospace table of each member's Discord
|
||||
and GitHub activity stats to their respective team channel.
|
||||
|
||||
Data files (place in data/):
|
||||
- catch_up_report.md Activity report generated by catch_up_report.py
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
@@ -25,7 +35,9 @@ CHANNEL_IDS = {
|
||||
"Sage Marigold": "1464316850669093040",
|
||||
}
|
||||
|
||||
REPORT_PATH = os.path.join(os.path.dirname(__file__), "catch_up_report.md")
|
||||
REPORT_PATH = os.path.join(
|
||||
os.path.dirname(__file__), "..", "..", "data", "catch_up_report.md"
|
||||
)
|
||||
|
||||
FIELDS = [
|
||||
("Discord Username", "Name", 18),
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
"""Send check-in messages to all team channels."""
|
||||
"""Send biweekly check-in messages to all team Discord channels.
|
||||
|
||||
Posts a check-in prompt to each team channel and automatically creates a thread
|
||||
for responses. Members who do not respond by the deadline may be removed for
|
||||
inactivity.
|
||||
|
||||
Data files (place in data/):
|
||||
- team_message_ids.json Channel IDs per team (generated by send_team_messages.py)
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
DISCORD_API = "https://discord.com/api/v10"
|
||||
GUILD_ID = "692816967895220344"
|
||||
@@ -22,7 +36,7 @@ CHECK_IN_MESSAGE = """@everyone it is time for Naomi to do a check in. I need **
|
||||
|
||||
async def send_checkin_to_teams() -> None:
|
||||
"""Send check-in message to all team channels (except Jade Jasmine)."""
|
||||
with open("team_message_ids.json") as f:
|
||||
with open(DATA_DIR / "team_message_ids.json") as f:
|
||||
team_data = json.load(f)
|
||||
|
||||
headers = {
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Send team check-in messages to all 14 teams."""
|
||||
"""Send a capacity check-in message to each team channel.
|
||||
|
||||
Posts a message asking whether the team feels able to complete their project
|
||||
given their current member count, and invites them to request support if needed.
|
||||
|
||||
Data files (place in data/):
|
||||
- team_message_ids.json Channel and role IDs per team (from send_team_messages.py)
|
||||
- team_assignments.json Team rosters used to report current member counts
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
NAOMI_ID = "465650873650118659"
|
||||
|
||||
@@ -58,10 +72,10 @@ async def send_message(
|
||||
|
||||
async def main() -> None:
|
||||
"""Send check-in messages to all teams."""
|
||||
with open("team_message_ids.json") as f:
|
||||
with open(DATA_DIR / "team_message_ids.json") as f:
|
||||
team_data = json.load(f)
|
||||
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
"""Send initial welcome and roster messages to all team Discord channels.
|
||||
|
||||
Creates a pinned roster message in each team channel and stores the resulting
|
||||
message ID, channel ID, and role ID in team_message_ids.json for use by
|
||||
other scripts (send_checkin.py, update_roster_messages.py, etc.).
|
||||
|
||||
Data files (place in data/):
|
||||
- team_assignments.json Team rosters with leaders and participants per team
|
||||
- applicants_to_evaluate.json Applicant data including Discord channel/role IDs
|
||||
|
||||
Outputs (written to data/):
|
||||
- team_message_ids.json Channel ID, message ID, and role ID per team
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
# Amari's bot token
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
GUILD_ID = "692816967895220344"
|
||||
|
||||
@@ -72,12 +91,12 @@ TEAMS = {
|
||||
}
|
||||
|
||||
# Load team assignments and convert to dict by team name
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") 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") as f:
|
||||
with open(DATA_DIR / "applicants_to_evaluate.json") as f:
|
||||
applicants = json.load(f)
|
||||
applicant_lookup = {str(a["discord_id"]): a for a in applicants}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import aiohttp
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
GUILD_ID = "692816967895220344"
|
||||
|
||||
@@ -62,13 +65,13 @@ def generate_roster(team: dict, discord_to_github: dict) -> str:
|
||||
|
||||
async def main() -> None:
|
||||
"""Update roster messages for all teams."""
|
||||
with open("team_message_ids.json") as f:
|
||||
with open(DATA_DIR / "team_message_ids.json") as f:
|
||||
team_data = json.load(f)
|
||||
|
||||
with open("team_assignments.json") as f:
|
||||
with open(DATA_DIR / "team_assignments.json") as f:
|
||||
teams = json.load(f)
|
||||
|
||||
with open("discord_to_github.json") as f:
|
||||
with open(DATA_DIR / "discord_to_github.json") as f:
|
||||
discord_to_github = json.load(f)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
"""Verify Discord user IDs from a markdown table of applicant data.
|
||||
|
||||
Reads a markdown table containing Discord IDs and checks each one against the
|
||||
Discord API to confirm the user exists. Handles rate limits automatically.
|
||||
|
||||
Data files (place in data/):
|
||||
- table.md Markdown table of applicants including a Discord ID column
|
||||
|
||||
Outputs (written to data/):
|
||||
- discord_verification.json Verification result (valid/invalid) per Discord ID
|
||||
|
||||
Env vars:
|
||||
- DISCORD_BOT_TOKEN Bot token for the Discord API
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent.parent / "data"
|
||||
|
||||
# Configuration
|
||||
BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
|
||||
@@ -10,7 +28,7 @@ GUILD_ID = "692816967895220344"
|
||||
BASE_URL = "https://discord.com/api/v10"
|
||||
|
||||
# Read Discord IDs from table.md
|
||||
with open("table.md") as f:
|
||||
with open(DATA_DIR / "table.md") as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.strip().split("\n")
|
||||
@@ -104,7 +122,7 @@ print(f"Missing: {len(missing)}")
|
||||
print(f"Errors: {len(errors)}")
|
||||
|
||||
# Save results
|
||||
with open("discord_verification.json", "w") as f:
|
||||
with open(DATA_DIR / "discord_verification.json", "w") as f:
|
||||
json.dump({"verified": verified, "missing": missing, "errors": errors}, f, indent=2)
|
||||
|
||||
print("\nResults saved to discord_verification.json")
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
/**
|
||||
* @file Delete hidden/outdated translations from a Crowdin project.
|
||||
* Reads a list of string IDs from data/crowdin-strings.json, checks which
|
||||
* have already been processed via the log file, and deletes translations
|
||||
* for unprocessed strings across all active languages in parallel.
|
||||
* Data files (place in data/):
|
||||
* - crowdin-strings.json String IDs to clear (from crowdin/writeData.ts)
|
||||
* - crowdin-strings-hidden.txt Log of already-processed string IDs
|
||||
* Env vars:
|
||||
* CROWDIN_PROJECT_ID - Crowdin project numeric ID
|
||||
* CROWDIN_API_URL - Base URL of the Crowdin API
|
||||
* CROWDIN_TOKEN - Crowdin personal access token.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Trigger a pre-translation run on a Crowdin project and wait for completion.
|
||||
* Fetches all active languages and files, submits a pre-translation request,
|
||||
* and polls for progress every 5 seconds until it reaches 100%%.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* CROWDIN_PROJECT_ID - Crowdin project numeric ID
|
||||
* CROWDIN_API_URL - Base URL of the Crowdin API
|
||||
* CROWDIN_TOKEN - Crowdin personal access token.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
/**
|
||||
* @file Create discussion threads for CYC talk submissions in a Discord channel.
|
||||
* Iterates over a hardcoded list of talk titles and speakers and creates a
|
||||
* Discord forum thread for each one. Update the data array and CHANNEL_ID before running.
|
||||
* Data files: None (talk data is hardcoded in the script)
|
||||
* Env vars:
|
||||
* DISCORD_TOKEN - Bot token for the Discord API.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
/**
|
||||
* @file Count and categorise all Discord servers a user belongs to.
|
||||
* Uses the Discord OAuth2 PKCE flow with a local callback server to authenticate,
|
||||
* then fetches and categorises all guilds (owned, admin, moderated, partnered,
|
||||
* verified, community, discoverable) and serves an HTML dashboard on localhost.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* DISCORD_CLIENT_ID - OAuth2 application client ID
|
||||
* DISCORD_CLIENT_SECRET - OAuth2 application client secret (optional)
|
||||
* DISCORD_REDIRECT_URI - OAuth2 redirect URI (default: http://127.0.0.1:8721/callback)
|
||||
* DISCORD_SCOPES - OAuth2 scopes (default: identify guilds).
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Set auto-close settings on all Discourse forum categories.
|
||||
* Fetches all categories and subcategories and enables auto-close based on
|
||||
* last post (672 hours / 28 days) for each one.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* DISCOURSE_URL - Base URL of the Discourse instance
|
||||
* DISCOURSE_API_KEY - Discourse API key
|
||||
* DISCOURSE_API_USERNAME - Discourse API username.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* @file Close inactive Discourse topics that have had no activity for 28+ days.
|
||||
* Skips topics older than 6 months (already archived) and any that are already closed.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* DISCOURSE_URL - Base URL of the Discourse instance
|
||||
* DISCOURSE_API_KEY - Discourse API key
|
||||
* DISCOURSE_API_USERNAME - Discourse API username.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
/**
|
||||
* @file Delete a file from every repository across all nhcarrigan Gitea orgs.
|
||||
* Prompts for the file path to delete, then removes it from every repo
|
||||
* across nhcarrigan, nhcarrigan-private, and nhcarrigan-games.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* GITEA_TOKEN - Gitea personal access token with repo write permissions.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* @file Upload a file from data/ to every repository across all nhcarrigan Gitea orgs.
|
||||
* Prompts for the local filename and destination path, then creates or updates
|
||||
* the file in every repo across nhcarrigan, nhcarrigan-private, and nhcarrigan-games.
|
||||
* Data files (place in data/):
|
||||
* - Any file to upload (prompted interactively)
|
||||
* Env vars:
|
||||
* GITEA_TOKEN - Gitea personal access token with repo write permissions.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* @file Conditionally upload a file to Gitea repos based on whether another file exists.
|
||||
* Prompts for the file to upload, a condition file path, and whether to upload
|
||||
* when the condition file exists or does not exist.
|
||||
* Data files (place in data/):
|
||||
* - Any file to upload (prompted interactively)
|
||||
* Env vars:
|
||||
* GITEA_TOKEN - Gitea personal access token with repo write permissions.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Audit npm packages across a GitHub organisation for known vulnerabilities.
|
||||
* Lists all repositories in the target org, fetches each package.json, and checks
|
||||
* for usage of packages in the hardcoded vulnerable package list.
|
||||
* Update the vulnerable package list and orgName constant before running.
|
||||
* Outputs (written to data/):
|
||||
* - npm-vulnerabilities.txt Repos and packages with vulnerability findings
|
||||
* Env vars:
|
||||
* GITHUB_TOKEN - GitHub personal access token with org read permissions.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Onboard a new mentee to the nhcarrigan-mentorship GitHub organisation.
|
||||
* Prompts for the mentee's Discord ID, full name, and GitHub username, creates
|
||||
* a personal repository in the org, adds the mentee as a collaborator with
|
||||
* maintain permissions, and posts a welcome message to the Discord channel.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* GITHUB_TOKEN - GitHub personal access token with org and repo permissions
|
||||
* DISCORD_TOKEN - Bot token for the Discord API.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/**
|
||||
* @file Post user story markdown files as GitHub issue descriptions.
|
||||
* Reads all markdown files from data/stories/ and updates the corresponding
|
||||
* GitHub issue with that content. Filename format: {repo-name}-{issue-number}.md.
|
||||
* Data files (place in data/stories/):
|
||||
* - {repo-name}-{issue-number}.md User story content for each issue
|
||||
* Env vars:
|
||||
* GITHUB_TOKEN - GitHub personal access token with repo write permissions.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
/**
|
||||
* @file Tag MP3 files with ID3v2 metadata and album art for Neuro-sama tracks.
|
||||
* Reads all MP3 files from the download directory, extracts the title from
|
||||
* the filename, sets the artist to "Neuro-sama", and applies the cover image.
|
||||
* Update the directory and cover constants before running.
|
||||
* Data files: None (reads from a local directory path defined in the script)
|
||||
* Env vars: None (uses eyeD3 and id3v2 CLI tools).
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
/**
|
||||
* @file Upload all files in the data/ directory to an S3-compatible bucket.
|
||||
* Displays a tree of files to upload, prompts for confirmation, then uploads
|
||||
* with a progress bar showing per-file status.
|
||||
* Data files (place in data/):
|
||||
* - All files to upload are read from the data/ directory recursively
|
||||
* Env vars:
|
||||
* AWS_ACCESS_KEY_ID - S3 access key
|
||||
* AWS_SECRET_ACCESS_KEY - S3 secret key
|
||||
* S3_ENDPOINT - S3-compatible endpoint URL.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Fix incorrect Content-Type metadata on objects in an S3-compatible bucket.
|
||||
* Lists all objects, detects MIME type from extension, and re-uploads metadata
|
||||
* for any file whose stored Content-Type does not match the expected type.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* AWS_ACCESS_KEY_ID - S3 access key
|
||||
* AWS_SECRET_ACCESS_KEY - S3 secret key
|
||||
* S3_ENDPOINT - S3-compatible endpoint URL.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Delete all objects from an S3-compatible bucket.
|
||||
* Prompts for the bucket name, requires double confirmation (type name + yes/no),
|
||||
* then lists and deletes all objects in batches with a progress bar.
|
||||
* Data files: None
|
||||
* Env vars:
|
||||
* AWS_ACCESS_KEY_ID - S3 access key
|
||||
* AWS_SECRET_ACCESS_KEY - S3 secret key
|
||||
* S3_ENDPOINT - S3-compatible endpoint URL.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
/**
|
||||
* @file Upload a single file from the data/ directory to an S3-compatible bucket.
|
||||
* Prompts for the local filename and the destination path in the bucket.
|
||||
* Data files (place in data/):
|
||||
* - Any file to upload (prompted interactively)
|
||||
* Env vars:
|
||||
* AWS_ACCESS_KEY_ID - S3 access key
|
||||
* AWS_SECRET_ACCESS_KEY - S3 secret key
|
||||
* S3_ENDPOINT - S3-compatible endpoint URL.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
/* eslint-disable max-lines -- Necessary for all of the HTML templating. */
|
||||
/**
|
||||
* @file Generate a public HTML security report from DefectDojo findings.
|
||||
* Fetches all active, verified findings from DefectDojo and produces a styled
|
||||
* HTML dashboard grouped by project with severity counts.
|
||||
* Outputs (written to data/):
|
||||
* - public_security_report.html HTML security report
|
||||
* Env vars:
|
||||
* DOJO_TOKEN - DefectDojo API token.
|
||||
* @copyright NHCarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
|
||||
Reference in New Issue
Block a user