#!/usr/bin/env node /** * Daily live event recap mailer. * Queries today's events (JST) from SQLite and sends an HTML digest via the * mail container (Postfix + OpenDKIM) which delivers directly to recipient MX. * * Env vars: * DB_PATH path to SQLite database (default: /app/data/events.db) * MAIL_HOST SMTP host (default: mail — the Postfix container) * MAIL_PORT SMTP port (default: 25) * RECAP_TO recipient address (default: mazideyabai@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 EHLO_DOMAIN = "golive.yyamashita.com"; const TO = process.env.RECAP_TO ?? "mazideyabai@gmail.com"; const APP_URL = "https://golive.yyamashita.com"; // ---- 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 fmtJST(dateStr: string): string { const DOW = ["日", "月", "火", "水", "木", "金", "土"]; const d = new Date(`${dateStr}T12:00:00Z`); const [y, m, day] = dateStr.split("-"); return `${y}/${m}/${day}(${DOW[d.getUTCDay()]})`; } // ---- 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[], date: string): string { const pageUrl = `${APP_URL}/events/by-date?date=${date}`; const head = `
本日のイベントはまだ登録されていません。
`; } let body = `| 会場 | アーティスト / タイトル | 開演 |
|---|---|---|
| ${esc(e.venue_name)} | ${nameCell} | ${time} |