• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
nbbaier

nbbaier

chicago-resident-days

Public
Like
chicago-resident-days
Home
Code
7
.claude
4
docs
.vtignore
AGENTS.md
SYSTEM.md
biome.json
deno.json
Environment variables
2
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
.claude
/
bsky-bot-thesis.md
Code
/
.claude
/
bsky-bot-thesis.md
Search
1/26/2026
Viewing readonly version of main branch: v14
View latest version
bsky-bot-thesis.md

Chicago Free Museum Days Bluesky Bot

The Opportunity

There's a clear gap in the Chicago cultural information ecosystem: no Bluesky bot exists to notify residents about free museum days.

The information aggregation problem is largely solved by existing websites (Choose Chicago, Upparent, Do312), but the distribution/discoverability problem is not.

A Bluesky bot would:

  • Passively surface free days to people who already use the platform
  • Fill a civic information niche that's underserved on Bluesky
  • Require minimal code and maintenance
  • Generate reusable data for other formats (ICS feed, embeddable widget)

Existing Landscape

Web Aggregators

SiteFormatCalendarICS/SubscribeFilteringStrengthsGaps
Choose ChicagoEditorial✗✗✗Official, comprehensiveStatic, no interactivity
UpparentInteractive list✓ Date pickerNewsletter✓ Sort/filterCalendar view, ratingsParent-focused, no ICS
Do312Long-form guide✗Newsletter✗Rich descriptionsMust manually parse
My Kid ListEditorial list✗Daily/weekly email✗Comprehensive, updatedStatic layout
Loop ChicagoBlog post✗✗✗Clean layoutAnnual refresh only

Existing Social Bot

@FreeMuseumDay on Twitter

  • Covers: Portland, SF, Boston, Chicago, LA, NYC, Seattle
  • Links to freemuseumday.org for details
  • Status: Appears dormant or minimally active on Chicago posts

Bluesky Bot Space

  • No free museum days bot exists on Bluesky
  • Bluesky is growing rapidly but has fewer bots than Twitter
  • Civic information niche is underserved
  • Lower noise/competition for attention

What Needs to Be Scraped

Data Sources (Priority Order)

1. Aggregator Sites (Recommended Primary)

These do the heavy lifting of compiling from individual institutions. Scrape these instead of hitting each museum:

  • Choose Chicago

    • Format: H3 headings per museum + bulleted date lists
    • Structure: Clean semantic HTML (<ul><li>)
    • Update frequency: Periodic (2-3x per year)
    • Scraping strategy: Parse H3 for institution name, extract <li> dates
  • Loop Chicago

    • Format: Museum section + free day listings
    • Structure: Similar semantic HTML layout
    • Update frequency: Annual refresh (early January)
    • Scraping strategy: Same as above

2. Individual Museum Sites (Fallback/Supplement)

Recurring patterns (no scraping needed, hardcode rules):

  • Field Museum: Every Wednesday
  • DuSable Black History Museum: Every Wednesday
  • Peggy Notebaert Nature Museum: Every Thursday
  • MCA Chicago: Every Tuesday 5-9pm (year-round)
  • Lincoln Park Zoo: Every day (always free)
  • National Museum of Mexican Art: Every day (always free)
  • Chicago Cultural Center: Every day (always free)

Scattered/seasonal dates (need scraping):

  • Art Institute: Seasonal weekday windows (Jan-Feb, etc.)
  • Shedd Aquarium: Specific date ranges, varies by month
  • Griffin MSI: 15-20 specific dates scattered across year
  • Chicago History Museum: Mix of holidays + promotional dates
  • Adler Planetarium: Scattered dates + seasonal Tuesday nights
  • Chicago Botanic Garden: Specific dates, requires registration

Scraping challenges:

  • Some sites use JavaScript rendering (need Playwright/Puppeteer)
  • Some return 403 on direct fetches (Shedd)
  • Date formats inconsistent within and across sites
  • Eligibility rules embedded in prose

3. City/Civic Data Sources (Low Priority)

  • Checked data.cityofchicago.org — no dedicated museum free days API
  • Not likely to exist in the near future

Data Normalization Challenge

Free days fall into two categories that need different handling:

Recurring patterns:

- institution: "Field Museum" free_day_type: "recurring" pattern: "every Wednesday" time: "9am-5pm" proof_of_residency: "Illinois ID required"

Scattered/seasonal dates:

- institution: "Art Institute" free_day_type: "dated" dates: - start: "2026-01-05" end: "2026-02-28" pattern: "weekdays only" time: "11am-close" proof_of_residency: "ZIP code verification"

Normalization needs:

  • Convert prose patterns ("January 19-23, 27-30") → actual date lists
  • Standardize time formats
  • Document proof-of-residency requirements consistently
  • Flag when scraping source becomes stale

Why a Bluesky Bot is the Right First Step

Advantages

  1. Minimal code required

    • ~100 lines of TypeScript/JavaScript
    • Bluesky SDK is simple and well-documented
    • GitHub Actions handles scheduling
  2. Zero infrastructure cost

    • GitHub Actions is free
    • No database (data lives in git)
    • No hosting fees
  3. Low maintenance burden

    • Monthly or quarterly scrape updates
    • GitHub Actions logs for monitoring
    • Community PRs can help maintain data
  4. Proven format for Bluesky

    • Simple daily/weekly posts work well on platform
    • Civic information is valued by Bluesky community
    • Low friction to follow and engage
  5. Reusable data pipeline

    • Scraper output feeds bot, ICS feed, future embeddable widget
    • Single source of truth in version control
    • Easy to validate and review changes

Why NOT a Website

  • Aggregator sites already solve this problem adequately
  • Users are fragmented across multiple platforms (email, calendar, social)
  • Web traffic for this niche is low
  • Maintenance burden higher than a bot

Why NOT an Email Newsletter

  • Lower engagement than social platform for casual discovery
  • Requires email list building and maintenance
  • Subject to spam filters
  • Already exists in limited form (My Kid List offers daily/weekly emails)

Why Bluesky Specifically

  • No competition. Twitter has @FreeMuseumDay (dormant). Bluesky has nothing.
  • Growing audience. Bluesky is experiencing rapid adoption, especially among civic-minded users.
  • Civic information is valued. Bluesky users appreciate local information, mutual aid bots, etc.
  • Easier to build bot culture. Fewer bots total = less noise, more discovery.
  • Lower barrier to entry. Small bots can find their audience; don't need millions of followers.

Technical Approach

Data Pipeline

Choose Chicago / Loop Chicago (web) ↓ Scraper (Node.js + cheerio or similar) ↓ Normalize to YAML (recutils format) ↓ GitHub repo with git history ↓ [Branch 1] Bluesky Bot (posts daily/weekly) [Branch 2] ICS Feed Generator (for calendar apps) [Branch 3] Future: Embeddable widget

Minimal Bot Implementation

Stack:

  • Node.js + TypeScript (or Python)
  • Bluesky SDK (@atproto/api)
  • GitHub Actions (cron schedule)
  • Data: YAML files in git

Posting schedule:

  • Monday morning: "Here's what's free this week"
  • Could also do: Daily post, weekly digest, or real-time alerts

Example bot post:

🏛️ What's free in Chicago this week:

• Art Institute: Mon-Fri (weekdays only)
• Field Museum: Wednesday
• MCA: Tuesday evenings 5-9pm

+ 3 more (thread 🧵)

Data source: github.com/your-repo

Data Storage Format

Use YAML or recutils (your prior research interest):

YAML approach:

institutions: - id: "art-institute" name: "Art Institute of Chicago" address: "111 S Michigan Ave" website: "artic.edu" free_days: - type: "seasonal" periods: - name: "Winter Weekdays" start: "2026-01-05" end: "2026-02-28" pattern: "Mon, Wed, Thu, Fri" time: "11am-close" proof_of_residency: "ZIP code verification" - type: "permanent" eligibility: - "Children under 14 (always free)" - "Chicago residents under 18 (always free)" - id: "field-museum" name: "Field Museum" address: "1400 S Lake Shore Dr" website: "fieldmuseum.org" free_days: - type: "recurring" pattern: "every Wednesday" time: "9am-5pm" proof_of_residency: "Illinois ID required"

Recutils approach:

id: art-institute
name: Art Institute of Chicago
address: 111 S Michigan Ave
website: artic.edu
free_day_type: seasonal
free_period_start: 2026-01-05
free_period_end: 2026-02-28
free_pattern: Mon,Wed,Thu,Fri
free_time: 11am-close
proof_of_residency: ZIP code verification

Either format works; choose based on preference.


First Steps to Validate

  1. Audit the scraping targets (Done ✓)

    • Choose Chicago and Loop Chicago are primary targets
    • Both have semantic HTML suitable for scraping
    • Update frequency: 2-3x per year
  2. Build a minimal scraper

    • Extract one institution's data from Choose Chicago
    • Test date normalization logic
    • Validate against official museum sites
  3. Create test data file

    • Manually enter ~15 institutions with current free days
    • Structure in YAML/recutils
    • This becomes the seed for bot posts
  4. Build minimal bot

    • Post hardcoded message to test Bluesky SDK
    • Schedule with GitHub Actions
    • Verify it works
  5. Automate scraper → data

    • Hook scraper into GitHub Actions workflow
    • Run weekly/monthly, commit changes as PRs
    • Review diffs before merging
  6. Launch bot

    • Post sample messages for next week
    • Seed with followers (share in Chicago communities)
    • Monitor engagement

Success Metrics

  • Low bar for success:

    • Bot posts on schedule without errors
    • Data stays in sync with institution calendars
    • Minimal manual maintenance (<1hr/month)
  • Bonus outcomes:

    • Community contributions (PRs with new institutions or corrections)
    • ICS feed reuse (users subscribe in calendar apps)
    • Potential expansion to other cities
  • Failure case:

    • Requires more than 2-3 hours of maintenance per month
    • Museums change data format too frequently
    • Bluesky account gets no engagement (unlikely, but acceptable)

Even failure is low-cost: you've proven the concept and built reusable data/code.


Risks & Mitigations

RiskLikelihoodMitigation
Scraper breaks when site changesMediumMonitor scraper logs, pin HTML selectors to institutions, accept manual updates
Date format inconsistenciesHighBuild robust date parser, test against 2-3 years of historical data
Proof-of-residency rules changeLowVersion control, document changes in git history
Low bot engagementLow (civic info is valued)Share in Chicago subreddits, tag museums, local journalists
Maintenance burden growsMediumKeep data structure simple, automate everything possible

Next: The ICS Feed

Once the bot is stable, generating an ICS calendar feed is trivial:

// Pseudocode institutions.forEach(inst => { inst.free_days.forEach(day => { const event = { summary: `Free: ${inst.name}`, date: day.date, description: `Address: ${inst.address}\nProof: ${day.proof_of_residency}`, }; calendar.addEvent(event); }); }); fs.writeFileSync("chicago-free-days.ics", calendar.toICS());

Users could subscribe in Google Calendar, Apple Calendar, Outlook. This is Direction 2 from your research.


Conclusion

Building a Bluesky bot is the highest-ROI first step because it:

  • Validates the data collection and normalization process
  • Fills a genuine gap on the platform
  • Requires minimal code and infrastructure
  • Generates reusable data for ICS feed and future formats
  • Is low-cost to maintain or abandon

The bar for success is low. The potential upside is genuine (civic value + community engagement). The learning is high (data pipeline, scraping, bot APIs, git workflows).

Worth building.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.