diff --git a/package.json b/package.json index 5a7391d..0a625f7 100644 --- a/package.json +++ b/package.json @@ -34,24 +34,24 @@ "author": "Michael J. Radwin (https://github.com/mjradwin)", "license": "BSD-2-Clause", "devDependencies": { - "@eslint/js": "^9.11.1", + "@eslint/js": "^9.12.0", "@rollup/plugin-commonjs": "^28.0.0", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-terser": "^0.4.4", "bootstrap5-autocomplete": "^1.1.31", "csv-parse": "^5.5.6", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "eslint-plugin-n": "^17.10.3", - "globals": "^15.9.0", - "rollup": "^4.22.5" + "globals": "^15.10.0", + "rollup": "^4.24.0" }, "dependencies": { - "@hebcal/core": "^5.5.0", + "@hebcal/core": "^5.5.1", "@hebcal/geo-sqlite": "^5.0.6", "@hebcal/hdate": "^0.11.4", "@hebcal/icalendar": "^5.1.1", "@hebcal/learning": "^5.1.1", - "@hebcal/leyning": "^8.2.4", + "@hebcal/leyning": "^8.2.5", "@hebcal/locales": "^5.0.1", "@hebcal/rest-api": "^5.1.2", "@hebcal/triennial": "^5.1.3", @@ -77,7 +77,7 @@ "koa-send": "^5.0.1", "koa-static": "^5.0.0", "koa-timeout-v2": "^1.0.0", - "maxmind": "^4.3.21", + "maxmind": "^4.3.22", "murmurhash3": "^0.5.0", "mysql2": "^3.11.3", "nodemailer": "^6.9.15", diff --git a/src/shortUrlRedir.js b/src/shortUrlRedir.js index f384967..a0740d2 100644 --- a/src/shortUrlRedir.js +++ b/src/shortUrlRedir.js @@ -1,3 +1,8 @@ +import {basename, dirname} from 'node:path'; +import {HebrewCalendar, parshiot} from '@hebcal/core'; +import {makeAnchor} from '@hebcal/rest-api'; +import dayjs from 'dayjs'; + const shortToLong = { h: 'holidays', s: 'sedrot', @@ -41,7 +46,62 @@ export function shortUrlRedir(ctx) { if (!dest) { ctx.throw(500, `Unknown short URL '${shortStr}'`); } - const destUrl = `https://www.hebcal.com/${dest}/${base}?` + qs.toString(); + let destUrl; + if (dest === 'sedrot') { + destUrl = shortParshaRedir(ctx, base, qs); + } + if (!destUrl) { + destUrl = `https://www.hebcal.com/${dest}/${base}?` + qs.toString(); + } ctx.set('Cache-Control', 'private'); ctx.redirect(destUrl); } + +function isValidDouble(id) { + switch (id) { + case 21: // Vayakhel-Pekudei + case 26: // Tazria-Metzora + case 28: // Achrei Mot-Kedoshim + case 31: // Behar-Bechukotai + case 38: // Chukat-Balak + case 41: // Matot-Masei + case 50: // Nitzavim-Vayeilech + return true; + } + return false; +} + +function shortParshaRedir(ctx, str, qs) { + const code = str.charCodeAt(0); + if (code < 48 || code > 57) { + return false; // not a number, let old redirect logic happen + } + const parshaStr = basename(str); + const parshaId = parseInt(parshaStr, 10) - 1; + if (isNaN(parshaId) || parshaId > parshiot.length - 1) { + ctx.throw(400, `invalid short redirect parsha: ${parshaStr}`); + } + const doubled = parshaStr.endsWith('d'); + if (doubled && !isValidDouble(parshaId)) { + ctx.throw(400, `invalid short redirect double parsha: ${parshaStr}`); + } + const yearStr = dirname(str); + const year = parseInt(yearStr, 10); + const il = yearStr.endsWith('i'); + const sedra = HebrewCalendar.getSedra(year, il); + const hd = sedra.find(doubled ? -parshaId : parshaId); + if (hd === null) { + ctx.throw(404, `short redirect not found: ${str}`); + return false; + } + const name0 = doubled ? + (parshiot[parshaId] + '-' + parshiot[parshaId + 1]) : + parshiot[parshaId]; + const name = makeAnchor(name0); + const d = dayjs(hd.greg()); + const fmtDt = d.format('YYYYMMDD'); + if (il) { + qs.set('i', 'on'); + } + return `https://www.hebcal.com/sedrot/${name}-${fmtDt}?` + qs.toString(); +}