source-19hz

Electronic music events from 19hz.info for one region, between two dates. Regions: BayArea, LosAngeles, Seattle, ORE (Portland), Denver, Phoenix, Texas, CHI, Detroit, Iowa, Atlanta, Miami, DC, Massachusetts. Code, no model: free and complete.

Agent monad: other monads start it. · version 2 · 1 steps

Shared code

const text = s => s.replace(/<br\s*\/?>/gi, ' ').replace(/<[^>]+>/g, '')
  .replace(/&amp;/g, '&').replace(/&#0?39;|&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
  .replace(/\s+/g, ' ').trim();

// "(9pm-2am)" or "(Fri: 8am-Sun: 4pm)" -> "21:00" / "08:00"; null when no time is printed.
function startTime(s) {
  const m = s.match(/\((?:[A-Za-z]+:\s*)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i);
  if (!m) return null;
  const h = (+m[1] % 12) + (m[3].toLowerCase() === 'pm' ? 12 : 0);
  return String(h).padStart(2, '0') + ':' + (m[2] || '00');
}

parse

uses web · timeout 2m, 2 retries

const { region, from, to } = ctx.config;
const url = 'https://19hz.info/eventlisting_' + region + '.php';
const events = await ctx.cache.use(region + ':' + new Date().toISOString().slice(0, 10), 864e5, async () => {
  const html = await ctx.web.text(url);
  const out = [];
  for (const [, row] of html.matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/g)) {
    const cells = row.split(/<td[^>]*>/i).slice(1).map(c => c.replace(/<\/td>\s*$/i, ''));
    if (cells.length < 7) continue;
    const day = text(cells[6]).replace(/\//g, '-');
    if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) continue;
    const [title, place = ''] = text(cells[1]).split(/ @ (?=[^@]*$)/);
    const venueCity = place.match(/^(.*?)\s*\(([^)]+)\)\s*$/);
    const time = startTime(text(cells[0]));
    out.push({
      title,
      start: time ? day + 'T' + time : day,
      venue: venueCity ? venueCity[1] : place,
      city: venueCity ? venueCity[2] : '',
      url: cells[1].match(/href=['"]([^'"]+)/)?.[1] || url,
      price: text(cells[3]),
      tags: text(cells[2]),
    });
  }
  return out;
});
const inWindow = events.filter(e => e.start.slice(0, 10) >= from && e.start.slice(0, 10) <= to);
await ctx.log(url + ': ' + inWindow.length + ' of ' + events.length + ' events between ' + from + ' and ' + to);
return { events: inWindow };