From edb88a8644acbc7d58fc5796f3b3c9b5f46763bc Mon Sep 17 00:00:00 2001 From: Matthew Weber Date: Fri, 3 Oct 2025 00:39:40 -0400 Subject: [PATCH] set up --- generate_calendar.py | 178 +++++++++++++++++++++++++++++++++++++++++++ meetings.yaml | 14 ++++ update-calendar.yaml | 50 ++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 generate_calendar.py create mode 100644 meetings.yaml create mode 100644 update-calendar.yaml diff --git a/generate_calendar.py b/generate_calendar.py new file mode 100644 index 0000000..626f8c3 --- /dev/null +++ b/generate_calendar.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +Generate calendar.ics and README.md from meetings.yaml +""" + +import yaml +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo +import hashlib + + +def load_meetings(yaml_file='meetings.yaml'): + """Load meetings from YAML file""" + with open(yaml_file, 'r') as f: + data = yaml.safe_load(f) + return data['meetings'] + + +def filter_meetings(meetings, months=3): + """Filter meetings to show only those within the last/next N months""" + now = datetime.now() + cutoff_past = now - timedelta(days=30 * months) + cutoff_future = now + timedelta(days=30 * months) + + filtered = [] + for meeting in meetings: + meeting_date = datetime.strptime(meeting['date'], '%Y-%m-%d') + if cutoff_past <= meeting_date <= cutoff_future: + filtered.append(meeting) + + return sorted(filtered, key=lambda m: m['date']) + + +def generate_ics(meetings, output_file='calendar.ics'): + """Generate iCalendar file from meetings""" + lines = [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Linux Cast User Group//Events//EN', + 'CALSCALE:GREGORIAN', + 'METHOD:PUBLISH', + 'X-WR-CALNAME:Linux Cast User Group Meetings', + 'X-WR-TIMEZONE:America/New_York', + 'X-WR-CALDESC:Bi-monthly meetings for the Linux Cast User Group', + '' + ] + + for meeting in meetings: + # Parse date and time + date_str = meeting['date'] + time_str = meeting['time'] + tz_str = meeting['timezone'] + + dt = datetime.strptime(f"{date_str} {time_str}", '%Y-%m-%d %H:%M') + tz = ZoneInfo(tz_str) + dt_local = dt.replace(tzinfo=tz) + + # Convert to UTC for iCalendar + dt_utc = dt_local.astimezone(ZoneInfo('UTC')) + dt_end_utc = dt_utc + timedelta(hours=1) + + # Generate unique ID + uid_base = f"{date_str}-{meeting['title']}" + uid = hashlib.md5(uid_base.encode()).hexdigest() + + # Format timestamps + dtstamp = datetime.now(ZoneInfo('UTC')).strftime('%Y%m%dT%H%M%SZ') + dtstart = dt_utc.strftime('%Y%m%dT%H%M%SZ') + dtend = dt_end_utc.strftime('%Y%m%dT%H%M%SZ') + + lines.extend([ + 'BEGIN:VEVENT', + f'UID:{uid}@thelinuxcast.org', + f'DTSTAMP:{dtstamp}', + f'DTSTART:{dtstart}', + f'DTEND:{dtend}', + f'SUMMARY:Linux Cast User Group: {meeting["title"]}', + f'DESCRIPTION:{meeting["title"]}\\n\\n{meeting["description"]}', + f'LOCATION:{meeting.get("location", "Online")}', + 'STATUS:CONFIRMED', + 'END:VEVENT', + '' + ]) + + lines.append('END:VCALENDAR') + + with open(output_file, 'w') as f: + f.write('\n'.join(lines)) + + print(f"Generated {output_file}") + + +def generate_readme(meetings, output_file='README.md'): + """Generate README.md from meetings""" + now = datetime.now() + + # Separate past and upcoming meetings + past_meetings = [] + upcoming_meetings = [] + + for meeting in meetings: + meeting_date = datetime.strptime(meeting['date'], '%Y-%m-%d') + if meeting_date < now: + past_meetings.append(meeting) + else: + upcoming_meetings.append(meeting) + + # Sort + past_meetings = sorted(past_meetings, key=lambda m: m['date'], reverse=True) + upcoming_meetings = sorted(upcoming_meetings, key=lambda m: m['date']) + + lines = [ + '# Linux Cast User Group Meetings', + '', + '**[Subscribe to Calendar](calendar.ics)** | **[Download ICS](calendar.ics)**', + '', + ] + + # Upcoming events + if upcoming_meetings: + lines.append('## Upcoming Events') + lines.append('') + + for meeting in upcoming_meetings: + dt = datetime.strptime(f"{meeting['date']} {meeting['time']}", '%Y-%m-%d %H:%M') + formatted_date = dt.strftime('%B %d, %Y at %I:%M %p') + + lines.extend([ + f"### {meeting['title']}", + f"- **Date:** {formatted_date} EST", + f"- **Location:** {meeting.get('location', 'Online')}", + f"- **Description:** {meeting['description']}", + '' + ]) + + # Recent past events + if past_meetings: + lines.append('## Recent Past Events') + lines.append('') + + for meeting in past_meetings: + dt = datetime.strptime(f"{meeting['date']} {meeting['time']}", '%Y-%m-%d %H:%M') + formatted_date = dt.strftime('%B %d, %Y at %I:%M %p') + + lines.extend([ + f"### {meeting['title']}", + f"- **Date:** {formatted_date} EST", + f"- **Location:** {meeting.get('location', 'Online')}", + f"- **Description:** {meeting['description']}", + '' + ]) + + with open(output_file, 'w') as f: + f.write('\n'.join(lines)) + + print(f"Generated {output_file}") + + +def main(): + """Main function""" + print("Loading meetings from meetings.yaml...") + meetings = load_meetings() + + print(f"Found {len(meetings)} total meetings") + + # Filter to 3 months + filtered_meetings = filter_meetings(meetings, months=3) + print(f"Filtered to {len(filtered_meetings)} meetings within 3 months") + + # Generate files + generate_ics(filtered_meetings) + generate_readme(filtered_meetings) + + print("Done!") + + +if __name__ == '__main__': + main() diff --git a/meetings.yaml b/meetings.yaml new file mode 100644 index 0000000..e250680 --- /dev/null +++ b/meetings.yaml @@ -0,0 +1,14 @@ +meetings: + - date: "2025-10-09" + time: "11:15" + timezone: "America/Detroit" + title: "EARLY LUG" + description: "Join us for the first Linux User Group of the Month" + location: "Online" + + - date: "2025-11-23" + time: "20:15" + timezone: "America/Detroit" + title: "LATE LUG" + description: "Join us for the second Linux User Group of the Month" + location: "Online" diff --git a/update-calendar.yaml b/update-calendar.yaml new file mode 100644 index 0000000..4bd6c2e --- /dev/null +++ b/update-calendar.yaml @@ -0,0 +1,50 @@ +name: Update Calendar + +on: + push: + branches: + - main + paths: + - 'meetings.yaml' + - 'generate_calendar.py' + - '.gitea/workflows/update-calendar.yaml' + schedule: + # Run daily at 00:00 UTC + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + update-calendar: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install pyyaml + + - name: Generate calendar files + run: | + python generate_calendar.py + + - name: Check for changes + id: check_changes + run: | + git diff --quiet calendar.ics README.md || echo "changed=true" >> $GITHUB_OUTPUT + + - name: Commit and push changes + if: steps.check_changes.outputs.changed == 'true' + run: | + git config --local user.email "noreply@thelinuxcast.org" + git config --local user.name "Calendar Bot" + git add calendar.ics README.md + git commit -m "Auto-update calendar files + + 🤖 Generated with automation" + git push