#!/usr/bin/env node /** * Daily live event recap mailer. * Queries upcoming events (tomorrow + 6 days) from SQLite and sends an HTML * digest via the internal Postfix container (mail:25 on the web Docker network). * * Env vars: * DB_PATH path to SQLite database (default: /app/data/events.db) * MAIL_HOST SMTP relay hostname (default: mail) * MAIL_PORT SMTP port (default: 25) * RECAP_TO recipient address (default: efemeraladdress+golive@gmail.com) */ import Database from "better-sqlite3"; import { createConnection } from "net"; const DB_PATH = process.env.DB_PATH ?? "/app/data/events.db"; const MAIL_HOST = process.env.MAIL_HOST ?? "mail"; const MAIL_PORT = parseInt(process.env.MAIL_PORT ?? "25", 10); const FROM = "noreply@golive.yyamashita.com"; const TO = process.env.RECAP_TO ?? "efemeraladdress+golive@gmail.com"; const DAYS = 7; // ---- date helpers (JST = UTC+9) ---------------------------------------- const JST_MS = 9 * 60 * 60 * 1000; function nowJST(): Date { return new Date(Date.now() + JST_MS); } function isoDate(d: Date): string { // Returns YYYY-MM-DD in JST return new Date(d.getTime() + JST_MS).toISOString().slice(0, 10); } function addDays(d: Date, n: number): Date { const r = new Date(d); r.setUTCDate(r.getUTCDate() + n); return r; } function fmtJST(dateStr: string): string { const DOW = ["日", "月", "火", "水", "木", "金", "土"]; // Parse as JST midnight const d = new Date(`${dateStr}T00:00:00+09:00`); const [y, m, day] = dateStr.split("-"); return `${y}/${m}/${day}(${DOW[d.getDay()]})`; } // ---- database ---------------------------------------------------------- interface EventRow { id: number; venue_name: string; title: string; artist: string | null; date: string; start_time: string | null; price: string | null; ticket_url: string | null; source_url: string | null; } function fetchEvents(from: string, to: string): EventRow[] { const db = new Database(DB_PATH, { readonly: true }); try { return db .prepare( `SELECT e.id, v.name AS venue_name, e.title, e.artist, e.date, e.start_time, e.price, e.ticket_url, e.source_url FROM events e JOIN venues v ON e.venue_id = v.id WHERE e.date >= ? AND e.date <= ? ORDER BY e.date ASC, e.start_time ASC` ) .all(from, to) as EventRow[]; } finally { db.close(); } } // ---- HTML builder ------------------------------------------------------- function esc(s: string): string { return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function buildHtml(events: EventRow[], from: string, to: string): string { const head = `
${esc(fmtJST(from))} 〜 ${esc(fmtJST(to))} のイベント一覧
`; if (events.length === 0) { return ( head + `この期間のイベントはまだ登録されていません。
` ); } const byDate = new Map| 会場 | アーティスト / タイトル | 開演 | 料金 | リンク |
|---|---|---|---|---|
| ${esc(e.venue_name)} | ${nameCell} | ${time} | ${price} | ${link} |