Install
openclaw skills install @eggyrooch-blip/calendar-subscribeTurn a school or work timetable (xlsx / csv) into a shareable HTTPS ICS subscription. Split class blocks into sessions, add one travel alarm per day (first class only), host a WeChat/iOS/Android landing page. Use when the user says 课表订阅, 日历订阅, timetable to ICS, calendar subscription, 同步到日历, webcal, 开学课表, share calendar with classmates. NOT FOR one-off local Calendar.app AppleScript dumps, Google Calendar API event-by-event writes, or todo/reminder apps.
openclaw skills install @eggyrooch-blip/calendar-subscribeTurn a timetable file into a subscription calendar (one HTTPS .ics + a tiny landing page). Classmates add it once; you update the file when the schedule changes.
User wants classmates / family to subscribe, not import a one-shot dump. Typical: school timetable, weekend MBA grid, recurring office hours.
TZID, not floating local), CRLF, UTF-8 line fold on character boundaries..ics on any public HTTPS static path. Content-Type: text/calendar.webcal://; Android → Google Calendar cid= + raw ICS.curl -sI both /path and /path/; count BEGIN:VEVENT and BEGIN:VALARM; VALARM count must equal distinct class days.date,start,end,title,location,notes
2026-09-12,09:00,10:30,Finance,Room 222,week 1
Default skip regex: 不上课|放假|补班
If the sheet only lists two clock ranges per period (e.g. 09:00–10:30 and 10:45–12:15), emit two events, not four 45-minute 节.
METHOD:PUBLISH, X-WR-CALNAME, X-WR-TIMEZONE, REFRESH-INTERVAL;VALUE=DURATION:P1DDTSTART / DTEND with TZID=<IANA> (default Asia/Shanghai)UID from date+start+title so republishing does not duplicate on clients that key on UID\, ;, ,, newlinesVALARM TRIGGER:-PT{N}M only on the day's first eventUse scripts/build_ics.py. Example:
python3 scripts/build_ics.py \
--xlsx timetable.xlsx \
--out timetable.ics \
--name "Fall timetable" \
--location "Room 222" \
--tz Asia/Shanghai \
--travel-minutes 90
CSV path: --csv events.csv instead of --xlsx.
Any HTTPS static file is enough. Apple Calendar, Google Calendar, and most Android calendars fetch from their servers, so the URL must be reachable from the public internet, not only the author's LAN.
Minimum nginx (adapt root and URL prefix):
location = /cal {
return 301 /cal/;
}
location ^~ /cal/ {
alias /var/www/cal/;
index index.html;
types {
text/calendar ics;
text/html html;
image/jpeg jpg jpeg;
image/png png;
}
add_header Cache-Control "public, max-age=3600";
}
Landing template: references/landing.html. Replace ICS_URL and PAGE_URL.
Poster / QR: generate a real QR of PAGE_URL (script or qrcode lib). Never let an image model draw the code. Leave a square hole on the poster and composite.
curl -sI https://example.com/cal # must 301 → /cal/
curl -sI https://example.com/cal/ # text/html 200
curl -sI https://example.com/cal/timetable.ics # text/calendar
Count:
BEGIN:VEVENT = number of real sessions (not holidays)BEGIN:VALARM = number of distinct class days/cal without a trailing slash is not /cal/. A prefix location ^~ /cal/ misses /cal. On a SPA host the bare path falls through to index.html. If that app sends Cross-Origin-Embedder-Policy: require-corp, the page looks "broken" or blank. Always add location = /cal { return 301 /cal/; }.sites-enabled/. A .bak is loaded as a second server; duplicate listen 443 fails nginx -t.return 404 (leftover certbot) makes http links look dead. 301 to https.add_header drops parent add_headers. COOP/COEP on the server block will not apply — usually what you want for a calendar page.MicroMessenger and tell the user to open in the system browser first.https://calendar.google.com/calendar/r?cid= link works in a browser; OEM calendars can open the raw .ics as a one-shot import (no later refresh).bytes[:73] can land inside a 3-byte Chinese character and crash encode. Fold on characters, measuring encoded length.data_only=True.User: 把这张课表做成可以分享的日历订阅
→ parse xlsx → ICS → host → landing → verify VEVENT/VALARM
User: 通勤提前一个半小时,别每节课都响
→ --travel-minutes 90, first class of each day only