@sebbo2002/tgtg-ical 3.0.9-develop.3 → 3.0.9-develop.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{init as I}from"@sentry/node";import{readFileSync as A}from"fs";import{dirname as k,resolve as g}from"path";import{fileURLToPath as x}from"url";I({dsn:process.env.SENTRY_DSN||"https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9"});var u=k(x(import.meta.url));u.endsWith("/dist")?u=g(u,"..","src"):u=g(u,"..");function v(l){return g(u,l||"")}var D;try{D=JSON.parse(A(v("../package.json"),"utf8")).version}catch(l){console.log("Failed getting tgtg-ical version:"),console.error(l)}var c={baseMail:process.env.BASE_MAIL||"@tgtg-ical.sebbo.net",baseUrl:process.env.BASE_URL||"https://tgtg-ical.sebbo.net",src:v,version:D};import{PrismaClient as P}from"@prisma/client";var z=new P,i=z;import{captureException as B}from"@sentry/node";import E from"he";import{simpleParser as T}from"mailparser";import n from"moment-timezone";var b={"\u{1F32D}":[/Ikea/],"\u{1F32E}":[/Enchilada/,/Besitos/],"\u{1F354}":[/Burger/,/McDonald[‘|']?s/,/Burger King/,/Hans im Glück/,/Peter Pane/],"\u{1F355}":[/Domino‘s/,/Pizza Hut/,/L[‘|']Osteria/,/Call a Pizza/,/Smiley[‘|']s Pizza/],"\u{1F357}":[/KFC/],"\u{1F35D}":[/LaTagliatella/,/Vapiano/],"\u{1F364}":[/Nordsee/],"\u{1F369}":[/Donut/,/Dunkin/],"\u{1F36A}":[/Starbucks/],"\u{1F950}":[/LeCroBag/],"\u{1F956}":[/Bäckerei/,/Back/,/BackWerk/,/Kamps/,/Kamps Backstuben/,/Junge Die Bäckerei/,/Back-Factory/],"\u{1F957}":[/dean ?& ?david/],"\u{1F968}":[/Brezel/,/Ditsch/],"\u{1F969}":[/Steakhouse/,/Block House/,/Jim Block/],"\u{1F96A}":[/Sandwich/,/Tank ?& ?Rast/,/T&R Raststätten/,/Autohöfe/,/Aral/,/Shell/,/Jet Tank/,/Subway/,/Caf[é|e] bonjour/,/Total Deutschland/,/Esso:? Snack ?& ?Shop/],"\u{1F6D2}":[/Edeka/,/Netto/,/Rewe/,/Penny/,/Lidl/,/Kaufland/,/Aldi/,/dm/,/Rossmann/,/Globus/,/Metro/,/Norma/,/Tegut/]};function w(l){for(let t in b)if(!!b[t].map(a=>new RegExp(a,"i")).find(a=>a.test(l)))return t;return null}var M=class{static async geocode(t){let r=await fetch("https://nominatim.openstreetmap.org/search?format=json&limit=1&q="+encodeURIComponent(t),{headers:{Referer:c.baseUrl,"User-Agent":`tgtg-ical/${c.version} (${c.baseUrl})`}});if(!r.ok)throw new Error("Geocoding failed: "+r.statusText);await new Promise(a=>setTimeout(a,1e3));let e=await r.json();return!Array.isArray(e)||e.length===0?{latitude:null,longitude:null}:{latitude:parseFloat(e[0].lat),longitude:parseFloat(e[0].lon)}}static async handleMail(t){try{let r=await this.parseMail(t.raw);r&&await this.applyParsedMail(r),await i.mail.delete({where:{id:t.id}})}catch(r){let e=B(r);await i.mail.update({data:{error:r instanceof Error?r.stack:String(r),erroredAt:new Date,errorId:e,version:c.version},where:{id:t.id}})}}static async inhaleMail(t){let r=await i.mail.create({data:{raw:t}});await this.handleMail(r)}static async parseMail(t,r=c.baseMail){let e=await T(t,{skipHtmlToText:!0,skipImageLinks:!0,skipTextLinks:!0,skipTextToHtml:!0});if(!e.from?.value[0].address?.endsWith("toogoodtogo.com"))throw new Error("Not a TGTG email!");let a=(Array.isArray(e.to)?e.to:[e.to]).map(o=>o?.value).flat().filter(o=>!!o).map(o=>o.address).find(o=>o?.endsWith(r));if(!a){let o=e.headers.get("received"),d=new RegExp(`([\\w-]+${c.baseMail})`,"i");Array.isArray(o)&&o.forEach(s=>{let m=(typeof s=="string"?s:s.value).match(d);m&&(a=m[1])})}if(!a)throw new Error("No recipient found!");if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let o=this.parseOrderMail(e);return{to:a,...o}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let o=this.parseChangeMail(e);return{to:a,...o}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let o=this.parseCancellationMail(e);if(o)return{to:a,...o}}if(e.headers.get("x-pm-tag")==="invoice"){let o=this.parseInvoiceMail(e);if(o)return{to:a,...o}}if(!(!e.headers.get("x-pm-tag")&&!e.headers.get("x-pm-message-id")))throw e.headers.get("x-pm-tag")?new Error(`Unsupported email type: ${e.headers.get("x-pm-tag")}`):new Error("Not implemented!")}static async runCleanup(){let t=await i.mail.findMany({orderBy:{erroredAt:"asc"},take:10,where:{OR:[{error:null},{version:null},{version:{not:c.version}}]}});for(let r of t)await this.handleMail(r);await i.user.deleteMany({where:{OR:[{lastSeenAt:{lt:n().subtract(8,"weeks").toDate()}},{createdAt:{lt:n().subtract(3,"hours").toDate()},lastSeenAt:{equals:i.user.fields.createdAt}}]}}),await i.event.deleteMany({where:{to:{lt:n().subtract(4,"weeks").toDate()}}}),await i.mail.deleteMany({where:{createdAt:{lt:n().subtract(2,"weeks").toDate()}}})}static async applyParsedMail(t){let r=await this.findUser(t.to);if(t.type==="order"){let e=await this.getLocation(t.location);await i.event.upsert({create:{amount:t.amount,from:t.time.from.toDate(),location:{connect:{id:e.id}},orderedAt:t.time.order.toDate(),orderId:t.orderId,price:t.price,to:t.time.to.toDate(),user:{connect:{id:r.id}}},update:{amount:t.amount,from:t.time.from.toDate(),location:{connect:{id:e.id}},price:t.price,to:t.time.to.toDate()},where:{orderId:t.orderId,userId:r.id}})}else if(t.type==="change")await i.event.update({data:{from:t.time.from.toDate(),to:t.time.to.toDate()},where:{orderId:t.orderId,userId:r.id}});else if(t.type==="cancel")await i.event.update({data:{canceledAt:t.cancelledAt.toDate()},where:{orderId:t.orderId,userId:r.id}});else if(t.type==="invoice")await i.event.update({data:{invoicedAt:t.invoicedAt.toDate()},where:{orderId:t.orderId,userId:r.id}});else throw new Error("Unknown email type!")}static async findUser(t){if(!t)throw new Error("Did not found a valid recipient!");let r=t.split("@")[0],e=await i.user.findUnique({where:{prefix:r}});if(!e)throw new Error(`User with email prefix ${r} not found!`);return e}static async getLocation(t){let r=await i.location.findFirst({where:{address:t.address,name:t.name}});if(!r){let e=w(t.name),{latitude:a,longitude:o}=await this.geocode(t.address);r=await i.location.create({data:{address:t.address,emoji:e,latitude:a,longitude:o,name:t.name}})}return r}static parseCancellationMail(t){let e=(t.subject||"").match(/\((\w+)\)/);if(e)return{cancelledAt:n(t.date),orderId:e[1],type:"cancel"}}static parseChangeMail(t){let r=[(t.html||"").match(/https:\/\/share.toogoodtogo.com\/receipts\/details\/(\w+)/),(t.html||"").match(/(\d{1,2}\.\d{2}\.\d{2}) zwischen (\d{1,2}:\d{2}) und (\d{1,2}:\d{2})(?: Uhr)? (\w+)+ \(/)];if(!r[0])throw new Error("Order ID not found!");if(!r[1])throw new Error("Date / Time not found!");let e=r[1][4];e==="MEZ"&&(e="MET");let a=n.tz(r[1][1]+" "+r[1][2],"DD.MM.YY HH:mm",e),o=n.tz(r[1][1]+" "+r[1][3],"DD.MM.YY HH:mm",e);return{orderId:r[0][1].trim(),time:{from:a,to:o},type:"change"}}static parseInvoiceMail(t){let e=(t.html||"").match(/Die Rechnung für deine Bestellung (\w+)/);if(e)return{invoicedAt:n(t.date),orderId:e[1],type:"invoice"};throw new Error("Order Id not found!")}static parseOrderMail(t){let r=t.html||"",e=[r.match(/\/order\/([^/]+)\//),r.match(/Wir bestätigen hiermit deine Bestellung bei ([^(.<]+)/),r.match(/<span>Du kannst deine Bestellung am (\d{1,2}\.\d{2}\.\d{2}) zwischen (\d{1,2}:\d{2}) und (\d{1,2}:\d{2}) Uhr (\w+)[^:]+: (.+).<\/span><\/div>/),r.match(/<span>Du kannst deine Bestellung zwischen (\d{1,2}\.\d{1,2}), (\d{1,2}:\d{2}) und (\d{1,2}:\d{2})[^:]+: (.+).<\/span><\/div>/),r.match(/<b>Datum:<\/b>\s+<span>(\d{1,2}\.\d{2}\.\d{2})<\/span>/),r.match(/Abholzeit:<\/b>\s+<span>(\d{1,2}:\d{2}) - (\d{1,2}:\d{2}) (\w+)/),r.match(/Anzahl: (\d+)/),r.match(/Anzahl:<\/b>\s+<span>(\d+)/),r.match(/Gesamtpreis: ([\d,.]+)[^\d,.]/),r.match(/Gesamtpreis:<\/b>\s+<span>([\d,.]+)[^\d,.]/),r.match(/Standort:<\/b>\s+<span>([^<]+)/)];if(!e[0])throw new Error("Order ID not found!");if(!e[1])throw new Error("Location name not found!");if(!e[2]&&!e[3]&&!(e[4]&&e[5]))throw new Error("Date, time and address not found (1)!");let a="MET";e[2]&&(a=e[2][4]),e[5]&&(a=e[5][3]),a==="MEZ"&&(a="MET");let o=n(t.date),d,s,p;if(e[2]&&(d=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",a),s=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",a),p=e[2][5].trim()),e[3]){let y=o.year();d=n.tz(e[3][1]+"."+y+" "+e[3][2],"DD.MM.YYYY HH:mm",a),s=n.tz(e[3][1]+"."+y+" "+e[3][3],"DD.MM.YYYY HH:mm",a),p=e[3][4].trim(),d.isBefore(o)&&d.add(1,"year"),s.isBefore(d)&&s.add(1,"year")}if(e[4]&&e[5]&&e[10]&&(d=n.tz(e[4][1]+" "+e[5][1],"DD.MM.YY HH:mm",a),s=n.tz(e[4][1]+" "+e[5][2],"DD.MM.YY HH:mm",a),p=e[10][1].trim()),!d||!s||!p)throw new Error("Date, time or address not found (2)!");let m=0;if(e[6])m=parseInt(e[6][1],10);else if(e[7])m=parseInt(e[7][1],10);else throw new Error("Amount not found!");if(isNaN(m))throw new Error("Amount (1) is not a number!");let f=0;if(e[8])f=parseInt(e[8][1].replace(/[.|,]/g,""));else if(e[9])f=parseInt(e[9][1].replace(/[.|,]/g,""));else throw new Error("Price not found!");if(isNaN(f))throw new Error("Price is not a number!");let h=E.decode(e[1][1].trim());return h.endsWith("!")&&(h=h.slice(0,-1)),{amount:m,location:{address:E.decode(p),name:h},orderId:e[0][1].trim(),price:f,time:{from:d,order:o,to:s},type:"order"}}};export{c as a,i as b,M as c};
|
|
2
|
+
//# sourceMappingURL=chunk-5TQHQ2B4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/config.ts","../src/lib/db.ts","../src/lib/parser.ts","../src/lib/emoji.ts"],"sourcesContent":["import { init } from '@sentry/node';\nimport { readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\ninit({\n dsn:\n process.env.SENTRY_DSN ||\n 'https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9',\n});\n\nlet dir = dirname(fileURLToPath(import.meta.url));\nif (dir.endsWith('/dist')) {\n dir = resolve(dir, '..', 'src');\n} else {\n dir = resolve(dir, '..');\n}\n\nfunction src(path?: string) {\n return resolve(dir, path || '');\n}\n\nlet version: string | undefined;\ntry {\n const pkg = JSON.parse(readFileSync(src('../package.json'), 'utf8'));\n version = pkg.version;\n} catch (error) {\n console.log('Failed getting tgtg-ical version:');\n console.error(error);\n}\n\nexport default {\n baseMail: process.env.BASE_MAIL || '@tgtg-ical.sebbo.net',\n baseUrl: process.env.BASE_URL || 'https://tgtg-ical.sebbo.net',\n src,\n version,\n};\n","import { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nexport default prisma;\n","import type { Location, Mail, User } from '@prisma/client';\n\nimport { captureException } from '@sentry/node';\nimport he from 'he';\nimport { type ParsedMail, simpleParser } from 'mailparser';\nimport moment from 'moment-timezone';\n\nimport config from './config.js';\nimport Config from './config.js';\nimport prisma from './db.js';\nimport getEmoji from './emoji.js';\n\ntype AnyMail = (CancellationMail | ChangeMail | InvoiceMail | OrderMail) & {\n to: string;\n};\n\ninterface CancellationMail {\n cancelledAt: moment.Moment;\n orderId: string;\n type: 'cancel';\n}\n\ninterface ChangeMail {\n orderId: string;\n time: {\n from: moment.Moment;\n to: moment.Moment;\n };\n type: 'change';\n}\n\ninterface InvoiceMail {\n invoicedAt: moment.Moment;\n orderId: string;\n type: 'invoice';\n}\n\ninterface OrderMail {\n amount: number;\n location: {\n address: string;\n name: string;\n };\n orderId: string;\n price: number;\n time: {\n from: moment.Moment;\n order: moment.Moment;\n to: moment.Moment;\n };\n type: 'order';\n}\n\nexport default class Parser {\n public static async geocode(\n address: string,\n ): Promise<\n | { latitude: null; longitude: null }\n | { latitude: number; longitude: number }\n > {\n const response = await fetch(\n 'https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' +\n encodeURIComponent(address),\n {\n headers: {\n Referer: config.baseUrl,\n 'User-Agent': `tgtg-ical/${config.version} (${config.baseUrl})`,\n },\n },\n );\n if (!response.ok) {\n throw new Error('Geocoding failed: ' + response.statusText);\n }\n\n // super simple way to ensure a maximum of 1 request per second in cronjobs\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n const data = await response.json();\n if (!Array.isArray(data) || data.length === 0) {\n return {\n latitude: null,\n longitude: null,\n };\n }\n\n return {\n latitude: parseFloat(data[0].lat),\n longitude: parseFloat(data[0].lon),\n };\n }\n\n public static async handleMail(mail: Mail) {\n try {\n const parsed = await this.parseMail(mail.raw);\n if (parsed) {\n await this.applyParsedMail(parsed);\n }\n\n await prisma.mail.delete({\n where: {\n id: mail.id,\n },\n });\n } catch (error) {\n const errorId = captureException(error);\n await prisma.mail.update({\n data: {\n error: error instanceof Error ? error.stack : String(error),\n erroredAt: new Date(),\n errorId,\n version: config.version,\n },\n where: {\n id: mail.id,\n },\n });\n }\n }\n\n public static async inhaleMail(email: string): Promise<void> {\n const mail = await prisma.mail.create({\n data: {\n raw: email,\n },\n });\n\n await this.handleMail(mail);\n }\n\n public static async parseMail(\n mail: string,\n baseMailPostfix = config.baseMail,\n ): Promise<AnyMail | undefined> {\n // parse email\n const email = await simpleParser(mail, {\n skipHtmlToText: true,\n skipImageLinks: true,\n skipTextLinks: true,\n skipTextToHtml: true,\n });\n\n if (!email.from?.value[0].address?.endsWith('toogoodtogo.com')) {\n throw new Error('Not a TGTG email!');\n }\n\n let to: string | undefined = (\n Array.isArray(email.to) ? email.to : [email.to]\n )\n .map((to) => to?.value)\n .flat()\n .filter((address) => !!address)\n .map((address) => address.address)\n .find((address) => address?.endsWith(baseMailPostfix));\n\n if (!to) {\n const received = email.headers.get('received');\n const regexp = new RegExp(`([\\\\w-]+${Config.baseMail})`, 'i');\n if (Array.isArray(received)) {\n received.forEach((r) => {\n const s = typeof r === 'string' ? r : r.value;\n const match = s.match(regexp);\n if (match) {\n to = match[1];\n }\n });\n }\n }\n if (!to) {\n throw new Error('No recipient found!');\n }\n\n // Order confirmation\n if (email.headers.get('x-pm-tag') === 'consumer_order_confirm') {\n const order = this.parseOrderMail(email);\n return {\n to,\n ...order,\n };\n }\n\n // Time Changed\n if (email.headers.get('x-pm-tag') === 'collection_time_changed') {\n const change = this.parseChangeMail(email);\n return {\n to,\n ...change,\n };\n }\n\n // Cancellation\n if (email.headers.get('x-pm-tag') === 'consumer_order_reverted') {\n const cancellation = this.parseCancellationMail(email);\n if (cancellation) {\n return {\n to,\n ...cancellation,\n };\n }\n }\n\n // is invoice?\n if (email.headers.get('x-pm-tag') === 'invoice') {\n const invoice = this.parseInvoiceMail(email);\n if (invoice) {\n return {\n to,\n ...invoice,\n };\n }\n }\n\n // Non-transactional email\n if (\n !email.headers.get('x-pm-tag') &&\n !email.headers.get('x-pm-message-id')\n ) {\n return undefined;\n }\n\n // Unsupported email\n if (email.headers.get('x-pm-tag')) {\n throw new Error(\n `Unsupported email type: ${email.headers.get('x-pm-tag')}`,\n );\n }\n\n throw new Error('Not implemented!');\n }\n\n public static async runCleanup(): Promise<void> {\n const mails = await prisma.mail.findMany({\n orderBy: {\n erroredAt: 'asc',\n },\n take: 10,\n where: {\n OR: [\n { error: null },\n { version: null },\n { version: { not: config.version } },\n ],\n },\n });\n\n for (const mail of mails) {\n await this.handleMail(mail);\n }\n\n // Cleanup Users\n await prisma.user.deleteMany({\n where: {\n OR: [\n {\n lastSeenAt: {\n lt: moment().subtract(8, 'weeks').toDate(),\n },\n },\n {\n createdAt: {\n lt: moment().subtract(3, 'hours').toDate(),\n },\n lastSeenAt: {\n equals: prisma.user.fields.createdAt,\n },\n },\n ],\n },\n });\n\n // Cleanup Events\n await prisma.event.deleteMany({\n where: {\n to: {\n lt: moment().subtract(4, 'weeks').toDate(),\n },\n },\n });\n\n // Cleanup Mails\n await prisma.mail.deleteMany({\n where: {\n createdAt: {\n lt: moment().subtract(2, 'weeks').toDate(),\n },\n },\n });\n }\n\n private static async applyParsedMail(email: AnyMail): Promise<void> {\n const user = await this.findUser(email.to);\n\n if (email.type === 'order') {\n const location = await this.getLocation(email.location);\n\n await prisma.event.upsert({\n create: {\n amount: email.amount,\n from: email.time.from.toDate(),\n location: {\n connect: {\n id: location.id,\n },\n },\n orderedAt: email.time.order.toDate(),\n orderId: email.orderId,\n price: email.price,\n to: email.time.to.toDate(),\n user: {\n connect: {\n id: user.id,\n },\n },\n },\n update: {\n amount: email.amount,\n from: email.time.from.toDate(),\n location: {\n connect: {\n id: location.id,\n },\n },\n price: email.price,\n to: email.time.to.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'change') {\n await prisma.event.update({\n data: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'cancel') {\n await prisma.event.update({\n data: {\n canceledAt: email.cancelledAt.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'invoice') {\n await prisma.event.update({\n data: {\n invoicedAt: email.invoicedAt.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else {\n throw new Error('Unknown email type!');\n }\n }\n\n private static async findUser(to: string): Promise<User> {\n if (!to) {\n throw new Error('Did not found a valid recipient!');\n }\n\n const prefix = to.split('@')[0];\n const user = await prisma.user.findUnique({\n where: { prefix },\n });\n if (!user) {\n throw new Error(`User with email prefix ${prefix} not found!`);\n }\n\n return user;\n }\n\n private static async getLocation(\n input: OrderMail['location'],\n ): Promise<Location> {\n let location = await prisma.location.findFirst({\n where: {\n address: input.address,\n name: input.name,\n },\n });\n if (!location) {\n const emoji = getEmoji(input.name);\n const { latitude, longitude } = await this.geocode(input.address);\n location = await prisma.location.create({\n data: {\n address: input.address,\n emoji,\n latitude,\n longitude,\n name: input.name,\n },\n });\n }\n\n return location;\n }\n\n private static parseCancellationMail(\n email: ParsedMail,\n ): CancellationMail | undefined {\n const subject = email.subject || '';\n const match = subject.match(/\\((\\w+)\\)/);\n if (match) {\n return {\n cancelledAt: moment(email.date),\n orderId: match[1],\n type: 'cancel',\n };\n }\n }\n\n private static parseChangeMail(email: ParsedMail): ChangeMail {\n const matches = [\n (email.html || '').match(\n /https:\\/\\/share.toogoodtogo.com\\/receipts\\/details\\/(\\w+)/,\n ),\n (email.html || '').match(\n /(\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})(?: Uhr)? (\\w+)+ \\(/,\n ),\n ];\n if (!matches[0]) {\n throw new Error('Order ID not found!');\n }\n if (!matches[1]) {\n throw new Error('Date / Time not found!');\n }\n\n let timezone = matches[1][4];\n if (timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n const from = moment.tz(\n matches[1][1] + ' ' + matches[1][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n const to = moment.tz(\n matches[1][1] + ' ' + matches[1][3],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n\n return {\n orderId: matches[0][1].trim(),\n time: {\n from,\n to,\n },\n type: 'change',\n };\n }\n\n private static parseInvoiceMail(email: ParsedMail): InvoiceMail {\n const html = email.html || '';\n const match = html.match(/Die Rechnung für deine Bestellung (\\w+)/);\n if (match) {\n return {\n invoicedAt: moment(email.date),\n orderId: match[1],\n type: 'invoice',\n };\n }\n\n throw new Error('Order Id not found!');\n }\n\n private static parseOrderMail(email: ParsedMail): OrderMail {\n const html = email.html || '';\n const matches = [\n html.match(/\\/order\\/([^/]+)\\//),\n html.match(/Wir bestätigen hiermit deine Bestellung bei ([^(.<]+)/),\n html.match(\n /<span>Du kannst deine Bestellung am (\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2}) Uhr (\\w+)[^:]+: (.+).<\\/span><\\/div>/,\n ),\n html.match(\n /<span>Du kannst deine Bestellung zwischen (\\d{1,2}\\.\\d{1,2}), (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})[^:]+: (.+).<\\/span><\\/div>/,\n ),\n html.match(\n /<b>Datum:<\\/b>\\s+<span>(\\d{1,2}\\.\\d{2}\\.\\d{2})<\\/span>/,\n ),\n html.match(\n /Abholzeit:<\\/b>\\s+<span>(\\d{1,2}:\\d{2}) - (\\d{1,2}:\\d{2}) (\\w+)/,\n ),\n html.match(/Anzahl: (\\d+)/),\n html.match(/Anzahl:<\\/b>\\s+<span>(\\d+)/),\n html.match(/Gesamtpreis: ([\\d,.]+)[^\\d,.]/),\n html.match(/Gesamtpreis:<\\/b>\\s+<span>([\\d,.]+)[^\\d,.]/),\n html.match(/Standort:<\\/b>\\s+<span>([^<]+)/),\n ];\n if (!matches[0]) {\n throw new Error('Order ID not found!');\n }\n if (!matches[1]) {\n throw new Error('Location name not found!');\n }\n if (!matches[2] && !matches[3] && !(matches[4] && matches[5])) {\n throw new Error('Date, time and address not found (1)!');\n }\n\n let timezone = 'MET';\n if (matches[2]) {\n timezone = matches[2][4];\n }\n if (matches[5]) {\n timezone = matches[5][3];\n }\n if (timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n const now = moment(email.date);\n let from: moment.Moment | undefined;\n let to: moment.Moment | undefined;\n let address: string | undefined;\n\n if (matches[2]) {\n from = moment.tz(\n matches[2][1] + ' ' + matches[2][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[2][1] + ' ' + matches[2][3],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n address = matches[2][5].trim();\n }\n if (matches[3]) {\n const year = now.year();\n from = moment.tz(\n matches[3][1] + '.' + year + ' ' + matches[3][2],\n 'DD.MM.YYYY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[3][1] + '.' + year + ' ' + matches[3][3],\n 'DD.MM.YYYY HH:mm',\n timezone,\n );\n address = matches[3][4].trim();\n\n if (from.isBefore(now)) {\n from.add(1, 'year');\n }\n if (to.isBefore(from)) {\n to.add(1, 'year');\n }\n }\n if (matches[4] && matches[5] && matches[10]) {\n from = moment.tz(\n matches[4][1] + ' ' + matches[5][1],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[4][1] + ' ' + matches[5][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n address = matches[10][1].trim();\n }\n\n if (!from || !to || !address) {\n throw new Error('Date, time or address not found (2)!');\n }\n\n let amount = 0;\n if (matches[6]) {\n amount = parseInt(matches[6][1], 10);\n } else if (matches[7]) {\n amount = parseInt(matches[7][1], 10);\n } else {\n throw new Error('Amount not found!');\n }\n if (isNaN(amount)) {\n throw new Error('Amount (1) is not a number!');\n }\n\n let price = 0;\n if (matches[8]) {\n price = parseInt(matches[8][1].replace(/[.|,]/g, ''));\n } else if (matches[9]) {\n price = parseInt(matches[9][1].replace(/[.|,]/g, ''));\n } else {\n throw new Error('Price not found!');\n }\n if (isNaN(price)) {\n throw new Error('Price is not a number!');\n }\n\n let name = he.decode(matches[1][1].trim());\n if (name.endsWith('!')) {\n name = name.slice(0, -1);\n }\n\n return {\n amount,\n location: {\n address: he.decode(address),\n name,\n },\n orderId: matches[0][1].trim(),\n price,\n time: {\n from,\n order: now,\n to,\n },\n type: 'order',\n };\n }\n}\n","export const DEFAULT_EMOJI = '🍴';\nexport const EMOJIS: Record<string, RegExp[]> = {\n '🌭': [/Ikea/],\n '🌮': [/Enchilada/, /Besitos/],\n '🍔': [\n /Burger/,\n /McDonald[‘|']?s/,\n /Burger King/,\n /Hans im Glück/,\n /Peter Pane/,\n ],\n '🍕': [\n /Domino‘s/,\n /Pizza Hut/,\n /L[‘|']Osteria/,\n /Call a Pizza/,\n /Smiley[‘|']s Pizza/,\n ],\n '🍗': [/KFC/],\n '🍝': [/LaTagliatella/, /Vapiano/],\n '🍤': [/Nordsee/],\n '🍩': [/Donut/, /Dunkin/],\n '🍪': [/Starbucks/],\n '🥐': [/LeCroBag/],\n '🥖': [\n /Bäckerei/,\n /Back/,\n /BackWerk/,\n /Kamps/,\n /Kamps Backstuben/,\n /Junge Die Bäckerei/,\n /Back-Factory/,\n ],\n '🥗': [/dean ?& ?david/],\n '🥨': [/Brezel/, /Ditsch/],\n '🥩': [/Steakhouse/, /Block House/, /Jim Block/],\n '🥪': [\n /Sandwich/,\n /Tank ?& ?Rast/,\n /T&R Raststätten/,\n /Autohöfe/,\n /Aral/,\n /Shell/,\n /Jet Tank/,\n /Subway/,\n /Caf[é|e] bonjour/,\n /Total Deutschland/,\n /Esso:? Snack ?& ?Shop/,\n ],\n '🛒': [\n /Edeka/,\n /Netto/,\n /Rewe/,\n /Penny/,\n /Lidl/,\n /Kaufland/,\n /Aldi/,\n /dm/,\n /Rossmann/,\n /Globus/,\n /Metro/,\n /Norma/,\n /Tegut/,\n ],\n};\n\nexport default function getEmoji(location: string): null | string {\n for (const emoji in EMOJIS) {\n const regExps = EMOJIS[emoji].map((name) => new RegExp(name, 'i'));\n const match = !!regExps.find((regExp) => regExp.test(location));\n if (match) {\n return emoji;\n }\n }\n\n return null;\n}\n"],"mappings":"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","names":["init","readFileSync","dirname","resolve","fileURLToPath","dir","src","path","version","error","config_default","PrismaClient","prisma","db_default","captureException","he","simpleParser","moment","DEFAULT_EMOJI","EMOJIS","getEmoji","location","emoji","name","regExp","Parser","address","response","config_default","resolve","data","mail","parsed","db_default","error","errorId","captureException","email","baseMailPostfix","simpleParser","to","received","regexp","r","match","order","change","cancellation","invoice","mails","moment","user","location","prefix","input","emoji","getEmoji","latitude","longitude","matches","timezone","from","html","now","year","amount","price","name","he"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/config.ts","../src/lib/db.ts","../src/lib/parser.ts","../src/lib/emoji.ts"],"sourcesContent":["import { init } from '@sentry/node';\nimport { readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\ninit({\n dsn:\n process.env.SENTRY_DSN ||\n 'https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9',\n});\n\nlet dir = dirname(fileURLToPath(import.meta.url));\nif (dir.endsWith('/dist')) {\n dir = resolve(dir, '..', 'src');\n} else {\n dir = resolve(dir, '..');\n}\n\nfunction src(path?: string) {\n return resolve(dir, path || '');\n}\n\nlet version: string | undefined;\ntry {\n const pkg = JSON.parse(readFileSync(src('../package.json'), 'utf8'));\n version = pkg.version;\n} catch (error) {\n console.log('Failed getting tgtg-ical version:');\n console.error(error);\n}\n\nexport default {\n baseMail: process.env.BASE_MAIL || '@tgtg-ical.sebbo.net',\n baseUrl: process.env.BASE_URL || 'https://tgtg-ical.sebbo.net',\n src,\n version,\n};\n","import { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nexport default prisma;\n","import type { Location, Mail, User } from '@prisma/client';\n\nimport { captureException } from '@sentry/node';\nimport he from 'he';\nimport { type ParsedMail, simpleParser } from 'mailparser';\nimport moment from 'moment-timezone';\n\nimport config from './config.js';\nimport Config from './config.js';\nimport prisma from './db.js';\nimport getEmoji from './emoji.js';\n\ntype AnyMail = (CancellationMail | ChangeMail | InvoiceMail | OrderMail) & {\n to: string;\n};\n\ninterface CancellationMail {\n cancelledAt: moment.Moment;\n orderId: string;\n type: 'cancel';\n}\n\ninterface ChangeMail {\n orderId: string;\n time: {\n from: moment.Moment;\n to: moment.Moment;\n };\n type: 'change';\n}\n\ninterface InvoiceMail {\n invoicedAt: moment.Moment;\n orderId: string;\n type: 'invoice';\n}\n\ninterface OrderMail {\n amount: number;\n location: {\n address: string;\n name: string;\n };\n orderId: string;\n price: number;\n time: {\n from: moment.Moment;\n order: moment.Moment;\n to: moment.Moment;\n };\n type: 'order';\n}\n\nexport default class Parser {\n public static async geocode(\n address: string,\n ): Promise<\n | { latitude: null; longitude: null }\n | { latitude: number; longitude: number }\n > {\n const response = await fetch(\n 'https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' +\n encodeURIComponent(address),\n {\n headers: {\n Referer: config.baseUrl,\n 'User-Agent': `tgtg-ical/${config.version} (${config.baseUrl})`,\n },\n },\n );\n if (!response.ok) {\n throw new Error('Geocoding failed: ' + response.statusText);\n }\n\n // super simple way to ensure a maximum of 1 request per second in cronjobs\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n const data = await response.json();\n if (!Array.isArray(data) || data.length === 0) {\n return {\n latitude: null,\n longitude: null,\n };\n }\n\n return {\n latitude: parseFloat(data[0].lat),\n longitude: parseFloat(data[0].lon),\n };\n }\n\n public static async handleMail(mail: Mail) {\n try {\n const parsed = await this.parseMail(mail.raw);\n if (parsed) {\n await this.applyParsedMail(parsed);\n }\n\n await prisma.mail.delete({\n where: {\n id: mail.id,\n },\n });\n } catch (error) {\n const errorId = captureException(error);\n await prisma.mail.update({\n data: {\n error: error instanceof Error ? error.stack : String(error),\n erroredAt: new Date(),\n errorId,\n version: config.version,\n },\n where: {\n id: mail.id,\n },\n });\n }\n }\n\n public static async inhaleMail(email: string): Promise<void> {\n const mail = await prisma.mail.create({\n data: {\n raw: email,\n },\n });\n\n await this.handleMail(mail);\n }\n\n public static async parseMail(\n mail: string,\n baseMailPostfix = config.baseMail,\n ): Promise<AnyMail | undefined> {\n // parse email\n const email = await simpleParser(mail, {\n skipHtmlToText: true,\n skipImageLinks: true,\n skipTextLinks: true,\n skipTextToHtml: true,\n });\n\n if (!email.from?.value[0].address?.endsWith('toogoodtogo.com')) {\n throw new Error('Not a TGTG email!');\n }\n\n let to: string | undefined = (\n Array.isArray(email.to) ? email.to : [email.to]\n )\n .map((to) => to?.value)\n .flat()\n .filter((address) => !!address)\n .map((address) => address.address)\n .find((address) => address?.endsWith(baseMailPostfix));\n\n if (!to) {\n const received = email.headers.get('received');\n const regexp = new RegExp(`([\\\\w-]+${Config.baseMail})`, 'i');\n if (Array.isArray(received)) {\n received.forEach((r) => {\n const s = typeof r === 'string' ? r : r.value;\n const match = s.match(regexp);\n if (match) {\n to = match[1];\n }\n });\n }\n }\n if (!to) {\n throw new Error('No recipient found!');\n }\n\n // Order confirmation\n if (email.headers.get('x-pm-tag') === 'consumer_order_confirm') {\n const order = this.parseOrderMail(email);\n return {\n to,\n ...order,\n };\n }\n\n // Time Changed\n if (email.headers.get('x-pm-tag') === 'collection_time_changed') {\n const change = this.parseChangeMail(email);\n return {\n to,\n ...change,\n };\n }\n\n // Cancellation\n if (email.headers.get('x-pm-tag') === 'consumer_order_reverted') {\n const cancellation = this.parseCancellationMail(email);\n if (cancellation) {\n return {\n to,\n ...cancellation,\n };\n }\n }\n\n // is invoice?\n if (email.headers.get('x-pm-tag') === 'invoice') {\n const invoice = this.parseInvoiceMail(email);\n if (invoice) {\n return {\n to,\n ...invoice,\n };\n }\n }\n\n // Non-transactional email\n if (\n !email.headers.get('x-pm-tag') &&\n !email.headers.get('x-pm-message-id')\n ) {\n return undefined;\n }\n\n // Unsupported email\n if (email.headers.get('x-pm-tag')) {\n throw new Error(\n `Unsupported email type: ${email.headers.get('x-pm-tag')}`,\n );\n }\n\n throw new Error('Not implemented!');\n }\n\n public static async runCleanup(): Promise<void> {\n const mails = await prisma.mail.findMany({\n orderBy: {\n erroredAt: 'asc',\n },\n take: 10,\n where: {\n OR: [\n { error: null },\n { version: null },\n { version: { not: config.version } },\n ],\n },\n });\n\n for (const mail of mails) {\n await this.handleMail(mail);\n }\n\n // Cleanup Users\n await prisma.user.deleteMany({\n where: {\n OR: [\n {\n lastSeenAt: {\n lt: moment().subtract(8, 'weeks').toDate(),\n },\n },\n {\n createdAt: {\n lt: moment().subtract(3, 'hours').toDate(),\n },\n lastSeenAt: {\n equals: prisma.user.fields.createdAt,\n },\n },\n ],\n },\n });\n\n // Cleanup Events\n await prisma.event.deleteMany({\n where: {\n to: {\n lt: moment().subtract(4, 'weeks').toDate(),\n },\n },\n });\n\n // Cleanup Mails\n await prisma.mail.deleteMany({\n where: {\n createdAt: {\n lt: moment().subtract(2, 'weeks').toDate(),\n },\n },\n });\n }\n\n private static async applyParsedMail(email: AnyMail): Promise<void> {\n const user = await this.findUser(email.to);\n\n if (email.type === 'order') {\n const location = await this.getLocation(email.location);\n\n await prisma.event.upsert({\n create: {\n amount: email.amount,\n from: email.time.from.toDate(),\n location: {\n connect: {\n id: location.id,\n },\n },\n orderedAt: email.time.order.toDate(),\n orderId: email.orderId,\n price: email.price,\n to: email.time.to.toDate(),\n user: {\n connect: {\n id: user.id,\n },\n },\n },\n update: {\n amount: email.amount,\n from: email.time.from.toDate(),\n location: {\n connect: {\n id: location.id,\n },\n },\n price: email.price,\n to: email.time.to.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'change') {\n await prisma.event.update({\n data: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'cancel') {\n await prisma.event.update({\n data: {\n canceledAt: email.cancelledAt.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else if (email.type === 'invoice') {\n await prisma.event.update({\n data: {\n invoicedAt: email.invoicedAt.toDate(),\n },\n where: {\n orderId: email.orderId,\n userId: user.id,\n },\n });\n } else {\n throw new Error('Unknown email type!');\n }\n }\n\n private static async findUser(to: string): Promise<User> {\n if (!to) {\n throw new Error('Did not found a valid recipient!');\n }\n\n const prefix = to.split('@')[0];\n const user = await prisma.user.findUnique({\n where: { prefix },\n });\n if (!user) {\n throw new Error(`User with email prefix ${prefix} not found!`);\n }\n\n return user;\n }\n\n private static async getLocation(\n input: OrderMail['location'],\n ): Promise<Location> {\n let location = await prisma.location.findFirst({\n where: {\n address: input.address,\n name: input.name,\n },\n });\n if (!location) {\n const emoji = getEmoji(input.name);\n const { latitude, longitude } = await this.geocode(input.address);\n location = await prisma.location.create({\n data: {\n address: input.address,\n emoji,\n latitude,\n longitude,\n name: input.name,\n },\n });\n }\n\n return location;\n }\n\n private static parseCancellationMail(\n email: ParsedMail,\n ): CancellationMail | undefined {\n const subject = email.subject || '';\n const match = subject.match(/\\((\\w+)\\)/);\n if (match) {\n return {\n cancelledAt: moment(email.date),\n orderId: match[1],\n type: 'cancel',\n };\n }\n }\n\n private static parseChangeMail(email: ParsedMail): ChangeMail {\n const matches = [\n (email.html || '').match(\n /https:\\/\\/share.toogoodtogo.com\\/receipts\\/details\\/(\\w+)/,\n ),\n (email.html || '').match(\n /(\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})(?: Uhr)? (\\w+)+ \\(/,\n ),\n ];\n if (!matches[0]) {\n throw new Error('Order ID not found!');\n }\n if (!matches[1]) {\n throw new Error('Date / Time not found!');\n }\n\n let timezone = matches[1][4];\n if (timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n const from = moment.tz(\n matches[1][1] + ' ' + matches[1][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n const to = moment.tz(\n matches[1][1] + ' ' + matches[1][3],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n\n return {\n orderId: matches[0][1].trim(),\n time: {\n from,\n to,\n },\n type: 'change',\n };\n }\n\n private static parseInvoiceMail(email: ParsedMail): InvoiceMail {\n const html = email.html || '';\n const match = html.match(/Die Rechnung für deine Bestellung (\\w+)/);\n if (match) {\n return {\n invoicedAt: moment(email.date),\n orderId: match[1],\n type: 'invoice',\n };\n }\n\n throw new Error('Order Id not found!');\n }\n\n private static parseOrderMail(email: ParsedMail): OrderMail {\n const html = email.html || '';\n const matches = [\n html.match(/\\/order\\/([^/]+)\\//),\n html.match(/Wir bestätigen hiermit deine Bestellung bei ([^(.<]+)/),\n html.match(\n /<span>Du kannst deine Bestellung am (\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2}) Uhr (\\w+)[^:]+: (.+).<\\/span><\\/div>/,\n ),\n html.match(\n /<span>Du kannst deine Bestellung zwischen (\\d{1,2}\\.\\d{1,2}), (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})[^:]+: (.+).<\\/span><\\/div>/,\n ),\n html.match(\n /<b>Datum:<\\/b>\\s+<span>(\\d{1,2}\\.\\d{2}\\.\\d{2})<\\/span>/,\n ),\n html.match(\n /Abholzeit:<\\/b>\\s+<span>(\\d{1,2}:\\d{2}) - (\\d{1,2}:\\d{2}) (\\w+)/,\n ),\n html.match(/Anzahl: (\\d+)/),\n html.match(/Anzahl:<\\/b>\\s+<span>(\\d+)/),\n html.match(/Gesamtpreis: ([\\d,.]+)[^\\d,.]/),\n html.match(/Gesamtpreis:<\\/b>\\s+<span>([\\d,.]+)[^\\d,.]/),\n html.match(/Standort:<\\/b>\\s+<span>([^<]+)/),\n ];\n if (!matches[0]) {\n throw new Error('Order ID not found!');\n }\n if (!matches[1]) {\n throw new Error('Location name not found!');\n }\n if (!matches[2] && !matches[3] && !(matches[4] && matches[5])) {\n throw new Error('Date, time and address not found (1)!');\n }\n\n let timezone = 'MET';\n if (matches[2]) {\n timezone = matches[2][4];\n }\n if (matches[5]) {\n timezone = matches[5][3];\n }\n if (timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n const now = moment(email.date);\n let from: moment.Moment | undefined;\n let to: moment.Moment | undefined;\n let address: string | undefined;\n\n if (matches[2]) {\n from = moment.tz(\n matches[2][1] + ' ' + matches[2][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[2][1] + ' ' + matches[2][3],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n address = matches[2][5].trim();\n }\n if (matches[3]) {\n const year = now.year();\n from = moment.tz(\n matches[3][1] + '.' + year + ' ' + matches[3][2],\n 'DD.MM.YYYY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[3][1] + '.' + year + ' ' + matches[3][3],\n 'DD.MM.YYYY HH:mm',\n timezone,\n );\n address = matches[3][4].trim();\n\n if (from.isBefore(now)) {\n from.add(1, 'year');\n }\n if (to.isBefore(from)) {\n to.add(1, 'year');\n }\n }\n if (matches[4] && matches[5] && matches[10]) {\n from = moment.tz(\n matches[4][1] + ' ' + matches[5][1],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n to = moment.tz(\n matches[4][1] + ' ' + matches[5][2],\n 'DD.MM.YY HH:mm',\n timezone,\n );\n address = matches[10][1].trim();\n }\n\n if (!from || !to || !address) {\n throw new Error('Date, time or address not found (2)!');\n }\n\n let amount = 0;\n if (matches[6]) {\n amount = parseInt(matches[6][1], 10);\n } else if (matches[7]) {\n amount = parseInt(matches[7][1], 10);\n } else {\n throw new Error('Amount not found!');\n }\n if (isNaN(amount)) {\n throw new Error('Amount (1) is not a number!');\n }\n\n let price = 0;\n if (matches[8]) {\n price = parseInt(matches[8][1].replace(/[.|,]/g, ''));\n } else if (matches[9]) {\n price = parseInt(matches[9][1].replace(/[.|,]/g, ''));\n } else {\n throw new Error('Price not found!');\n }\n if (isNaN(price)) {\n throw new Error('Price is not a number!');\n }\n\n let name = he.decode(matches[1][1].trim());\n if (name.endsWith('!')) {\n name = name.slice(0, -1);\n }\n\n return {\n amount,\n location: {\n address: he.decode(address),\n name,\n },\n orderId: matches[0][1].trim(),\n price,\n time: {\n from,\n order: now,\n to,\n },\n type: 'order',\n };\n }\n}\n","export const DEFAULT_EMOJI = '🍴';\nexport const EMOJIS: Record<string, RegExp[]> = {\n '🌭': [/Ikea/],\n '🌮': [/Enchilada/, /Besitos/],\n '🍔': [\n /Burger/,\n /McDonald[‘|']?s/,\n /Burger King/,\n /Hans im Glück/,\n /Peter Pane/,\n ],\n '🍕': [\n /Domino‘s/,\n /Pizza Hut/,\n /L[‘|']Osteria/,\n /Call a Pizza/,\n /Smiley[‘|']s Pizza/,\n ],\n '🍗': [/KFC/],\n '🍝': [/LaTagliatella/, /Vapiano/],\n '🍤': [/Nordsee/],\n '🍩': [/Donut/, /Dunkin/],\n '🍪': [/Starbucks/],\n '🥐': [/LeCroBag/],\n '🥖': [\n /Bäckerei/,\n /Back/,\n /BackWerk/,\n /Kamps/,\n /Kamps Backstuben/,\n /Junge Die Bäckerei/,\n /Back-Factory/,\n ],\n '🥗': [/dean ?& ?david/],\n '🥨': [/Brezel/, /Ditsch/],\n '🥩': [/Steakhouse/, /Block House/, /Jim Block/],\n '🥪': [\n /Sandwich/,\n /Tank ?& ?Rast/,\n /T&R Raststätten/,\n /Autohöfe/,\n /Aral/,\n /Shell/,\n /Jet Tank/,\n /Subway/,\n /Caf[é|e] bonjour/,\n /Total Deutschland/,\n /Esso:? Snack ?& ?Shop/,\n ],\n '🛒': [\n /Edeka/,\n /Netto/,\n /Rewe/,\n /Penny/,\n /Lidl/,\n /Kaufland/,\n /Aldi/,\n /dm/,\n /Rossmann/,\n /Globus/,\n /Metro/,\n /Norma/,\n /Tegut/,\n ],\n};\n\nexport default function getEmoji(location: string): null | string {\n for (const emoji in EMOJIS) {\n const regExps = EMOJIS[emoji].map((name) => new RegExp(name, 'i'));\n const match = !!regExps.find((regExp) => regExp.test(location));\n if (match) {\n return emoji;\n }\n }\n\n return null;\n}\n"],"mappings":"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","names":["init","readFileSync","dirname","resolve","fileURLToPath","dir","src","path","version","error","config_default","PrismaClient","prisma","db_default","captureException","he","simpleParser","moment","EMOJIS","getEmoji","location","emoji","name","regExp","Parser","address","response","config_default","resolve","data","mail","parsed","db_default","error","errorId","captureException","email","baseMailPostfix","simpleParser","to","received","regexp","r","match","order","change","cancellation","invoice","mails","moment","user","location","prefix","input","emoji","getEmoji","latitude","longitude","matches","timezone","from","html","now","year","amount","price","name","he"]}
|
package/dist/cleanup.js
CHANGED
package/dist/inhale-mail.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{c as r}from"./chunk-5TQHQ2B4.js";var s="";process.stdin.on("data",e=>{s+=e});process.stdin.on("end",()=>{r.inhaleMail(s).catch(e=>{console.error(e),process.exit(1)})});
|
|
3
3
|
//# sourceMappingURL=inhale-mail.js.map
|
package/dist/start.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as i,b as o,c as u
|
|
3
|
-
${
|
|
2
|
+
import{a as i,b as o,c as u}from"./chunk-5TQHQ2B4.js";import C from"cookie-parser";import p from"express";import{generateName as f,generateNameWithNumber as g}from"@criblinc/docker-names";import"@prisma/client";import{readFile as w}from"fs/promises";import{ICalAlarmType as I,ICalCalendar as E,ICalEventStatus as d}from"ical-generator";import y from"moment-timezone";import{randomUUID as U}from"crypto";var s=class{static async createUser(){let e;for(let t=0;;t++){let r=this.generatePrefix(t);try{if(e=await o.user.create({data:{prefix:r}}),e)break}catch(a){if(a&&typeof a=="object"&&"code"in a&&a.code==="P2002")continue;throw a}}if(!e)throw new Error("User not created");return e}static async generateCalendar(e){let t=await o.user.findUniqueOrThrow({select:{event:{select:{amount:!0,canceledAt:!0,createdAt:!0,from:!0,id:!0,location:{select:{address:!0,emoji:!0,latitude:!0,longitude:!0,name:!0}},orderId:!0,price:!0,to:!0}},id:!0},where:{id:e}}),r=new E({events:t.event.map(a=>{let m=new Intl.NumberFormat("de-DE",{currency:"EUR",style:"currency"}).format(a.price/100),l=d.CONFIRMED;return a.canceledAt&&(l=d.CANCELLED),{alarms:[{trigger:600,type:I.display}],created:a.createdAt,description:`${a.amount}x
|
|
3
|
+
${m}`,end:a.to,id:a.id,location:{address:a.location.address,geo:a.location.latitude&&a.location.longitude?{lat:a.location.latitude,lon:a.location.longitude}:void 0,title:a.location.name},start:a.from,status:l,summary:`${a.location.emoji||"\u{1F374}"} ${a.location.name}`,timestamp:a.createdAt,url:`https://share.toogoodtogo.com/receipts/details/${a.orderId}`}}),name:"TGTG",ttl:3600});return this.updateUserLastSeen(e),r.toString()}static generatePrefix(e=0){return e>100?U():e<10?f():g()}static async generateUserPage(e){let[t,r]=await Promise.all([this.getUser(e),w(i.src("templates/user.html"),"utf-8")]);return r.replace(/\${CALENDAR_URL}/g,`${i.baseUrl}/${t.id}/calendar.ical`).replace(/\${EMAIL_ADDRESS}/g,`${t.prefix}${i.baseMail}`)}static async getUser(e){let t=await o.user.findUniqueOrThrow({where:{id:e}});return this.updateUserLastSeen(t.id),t}static async isHealthy(){let e=await o.mail.count({where:{createdAt:{lt:y().subtract(30,"minutes").toDate()}}});if(e>0)throw new Error(`There are ${e} unhandled mails in the queue!`)}static updateUserLastSeen(e){o.user.update({data:{lastSeenAt:new Date},where:{id:e}}).catch(t=>{console.log(t)})}};var n=class c{app;server;constructor(){this.app=p(),this.app.use(C()),this.setupRoutes(),this.server=this.app.listen(process.env.PORT||8080),console.log(`tgtg-ical v${i.version} listening on port ${process.env.PORT||8080}`),process.on("SIGINT",()=>this.stop()),process.on("SIGTERM",()=>this.stop()),u.runCleanup().then(()=>console.log("Initial cleanup succeeded.")).catch(e=>{console.log("Initial cleanup failed:"),console.error(e),process.exit(1)})}static run(){new c}handleError(e,t){if(e&&typeof e=="object"&&"code"in e&&e.code==="P2025"){t.sendStatus(404);return}console.log(e),t.sendStatus(500)}setupRoutes(){this.app.get("/ping",(e,t)=>{t.send("pong")}),this.app.get("/",(e,t)=>{if("userId"in e.cookies&&e.cookies.userId){t.redirect("/"+e.cookies.userId);return}s.createUser().then(r=>{t.cookie("userId",r.id),t.redirect("/"+r.id)}).catch(r=>this.handleError(r,t))}),this.app.get("/_health",(e,t)=>{s.isHealthy().then(()=>t.sendStatus(204)).catch(r=>this.handleError(r,t))}),this.app.use(p.static(i.src("./assets"))),this.app.get("/:userId",(e,t)=>{t.format({"application/json":()=>{s.getUser(e.params.userId).then(r=>t.send(r)).catch(r=>this.handleError(r,t))},"text/html":()=>{s.generateUserPage(e.params.userId).then(r=>{t.cookie("userId",e.params.userId),t.send(r)}).catch(r=>this.handleError(r,t))}})}),this.app.get("/:userId/calendar.ical",(e,t)=>{s.generateCalendar(e.params.userId).then(r=>{t.set("Content-Type","text/calendar"),t.send(r)}).catch(r=>this.handleError(r,t))})}async stop(){await new Promise(e=>this.server.close(e)),await o.$disconnect(),process.exit()}};n.run();
|
|
4
4
|
//# sourceMappingURL=start.js.map
|
package/dist/start.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bin/start.ts","../src/lib/server.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';\n\nimport cookieParser from 'cookie-parser';\nimport express, { type Express, type Response } from 'express';\nimport { Server } from 'http';\n\nimport Config from '../lib/config.js';\nimport prisma from '../lib/db.js';\nimport Parser from '../lib/parser.js';\nimport ServerLib from '../lib/server.js';\n\nclass AppServer {\n private app: Express;\n\n private server: Server;\n constructor() {\n this.app = express();\n this.app.use(cookieParser());\n\n this.setupRoutes();\n this.server = this.app.listen(process.env.PORT || 8080);\n console.log(\n `tgtg-ical v${Config.version} listening on port ${process.env.PORT || 8080}`,\n );\n\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\n\n Parser.runCleanup()\n .then(() => console.log('Initial cleanup succeeded.'))\n .catch((error) => {\n console.log('Initial cleanup failed:');\n console.error(error);\n process.exit(1);\n });\n }\n\n static run() {\n new AppServer();\n }\n\n handleError(error: PrismaClientKnownRequestError | unknown, res: Response) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'P2025'\n ) {\n res.sendStatus(404);\n return;\n }\n\n console.log(error);\n res.sendStatus(500);\n }\n\n setupRoutes() {\n this.app.get('/ping', (req, res) => {\n res.send('pong');\n });\n\n this.app.get('/', (req, res) => {\n if ('userId' in req.cookies && req.cookies.userId) {\n res.redirect('/' + req.cookies.userId);\n return;\n }\n\n ServerLib.createUser()\n .then((user) => {\n res.cookie('userId', user.id);\n res.redirect('/' + user.id);\n })\n .catch((error) => this.handleError(error, res));\n });\n\n this.app.get('/_health', (req, res) => {\n ServerLib.isHealthy()\n .then(() => res.sendStatus(204))\n .catch((error) => this.handleError(error, res));\n });\n\n this.app.use(express.static(Config.src('./assets')));\n\n this.app.get('/:userId', (req, res) => {\n res.format({\n 'application/json': () => {\n ServerLib.getUser(req.params.userId)\n .then((json) => res.send(json))\n .catch((error) => this.handleError(error, res));\n },\n 'text/html': () => {\n ServerLib.generateUserPage(req.params.userId)\n .then((html) => {\n res.cookie('userId', req.params.userId);\n res.send(html);\n })\n .catch((error) => this.handleError(error, res));\n },\n });\n });\n\n this.app.get('/:userId/calendar.ical', (req, res) => {\n ServerLib.generateCalendar(req.params.userId)\n .then((ical) => {\n res.set('Content-Type', 'text/calendar');\n res.send(ical);\n })\n .catch((error) => this.handleError(error, res));\n });\n }\n\n async stop() {\n await new Promise((cb) => this.server.close(cb));\n await prisma.$disconnect();\n\n process.exit();\n }\n}\n\nAppServer.run();\n","import { generateName, generateNameWithNumber } from '@criblinc/docker-names';\nimport { User } from '@prisma/client';\nimport { readFile } from 'fs/promises';\nimport { ICalAlarmType, ICalCalendar, ICalEventStatus } from 'ical-generator';\nimport moment from 'moment-timezone';\nimport { randomUUID } from 'node:crypto';\n\nimport Config from './config.js';\nimport prisma from './db.js';\nimport { DEFAULT_EMOJI } from './emoji.js';\n\nexport default class ServerLib {\n static async createUser() {\n let user: undefined | User;\n\n // eslint-disable-next-line no-constant-condition\n for (let c = 0; true; c++) {\n const prefix = this.generatePrefix(c);\n try {\n user = await prisma.user.create({\n data: {\n prefix,\n },\n });\n if (user) {\n break;\n }\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'P2002'\n ) {\n continue;\n }\n\n throw error;\n }\n }\n if (!user) {\n throw new Error('User not created');\n }\n\n return user;\n }\n\n static async generateCalendar(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n select: {\n event: {\n select: {\n amount: true,\n canceledAt: true,\n createdAt: true,\n from: true,\n id: true,\n location: {\n select: {\n address: true,\n emoji: true,\n latitude: true,\n longitude: true,\n name: true,\n },\n },\n orderId: true,\n price: true,\n to: true,\n },\n },\n id: true,\n },\n where: {\n id: userId,\n },\n });\n\n const cal = new ICalCalendar({\n events: user.event.map((event) => {\n const price = new Intl.NumberFormat('de-DE', {\n currency: 'EUR',\n style: 'currency',\n }).format(event.price / 100);\n\n let status: ICalEventStatus = ICalEventStatus.CONFIRMED;\n if (event.canceledAt) {\n status = ICalEventStatus.CANCELLED;\n }\n\n return {\n alarms: [{ trigger: 600, type: ICalAlarmType.display }],\n created: event.createdAt,\n description: `${event.amount}x\\n${price}`,\n end: event.to,\n id: event.id,\n location: {\n address: event.location.address,\n geo:\n event.location.latitude && event.location.longitude\n ? {\n lat: event.location.latitude,\n lon: event.location.longitude,\n }\n : undefined,\n title: event.location.name,\n },\n start: event.from,\n status,\n summary: `${event.location.emoji || DEFAULT_EMOJI} ${event.location.name}`,\n timestamp: event.createdAt,\n url: `https://share.toogoodtogo.com/receipts/details/${event.orderId}`,\n };\n }),\n name: 'TGTG',\n ttl: 60 * 60,\n });\n\n this.updateUserLastSeen(userId);\n return cal.toString();\n }\n\n static generatePrefix(c = 0) {\n if (c > 100) {\n return randomUUID();\n }\n\n if (c < 10) {\n return generateName();\n }\n\n return generateNameWithNumber();\n }\n\n static async generateUserPage(userId: string) {\n const [user, html] = await Promise.all([\n this.getUser(userId),\n readFile(Config.src('templates/user.html'), 'utf-8'),\n ]);\n\n return html\n .replace(\n /\\${CALENDAR_URL}/g,\n `${Config.baseUrl}/${user.id}/calendar.ical`,\n )\n .replace(/\\${EMAIL_ADDRESS}/g, `${user.prefix}${Config.baseMail}`);\n }\n\n static async getUser(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n where: {\n id: userId,\n },\n });\n\n this.updateUserLastSeen(user.id);\n return user;\n }\n\n static async isHealthy() {\n const c = await prisma.mail.count({\n where: {\n createdAt: {\n lt: moment().subtract(30, 'minutes').toDate(),\n },\n },\n });\n if (c > 0) {\n throw new Error(`There are ${c} unhandled mails in the queue!`);\n }\n }\n\n static updateUserLastSeen(userId: string) {\n prisma.user\n .update({\n data: { lastSeenAt: new Date() },\n where: { id: userId },\n })\n .catch((error) => {\n console.log(error);\n });\n }\n}\n"],"mappings":";wDAKA,OAAOA,MAAkB,gBACzB,OAAOC,MAA8C,UCNrD,OAAS,gBAAAC,EAAc,0BAAAC,MAA8B,yBACrD,MAAqB,iBACrB,OAAS,YAAAC,MAAgB,cACzB,OAAS,iBAAAC,EAAe,gBAAAC,EAAc,mBAAAC,MAAuB,iBAC7D,OAAOC,MAAY,kBACnB,OAAS,cAAAC,MAAkB,SAM3B,IAAqBC,EAArB,KAA+B,CAC3B,aAAa,YAAa,CACtB,IAAIC,EAGJ,QAASC,EAAI,GAASA,IAAK,CACvB,IAAMC,EAAS,KAAK,eAAeD,CAAC,EACpC,GAAI,CAMA,GALAD,EAAO,MAAMG,EAAO,KAAK,OAAO,CAC5B,KAAM,CACF,OAAAD,CACJ,CACJ,CAAC,EACGF,EACA,KAER,OAASI,EAAO,CACZ,GACIA,GACA,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAM,OAAS,QAEf,SAGJ,MAAMA,CACV,CACJ,CACA,GAAI,CAACJ,EACD,MAAM,IAAI,MAAM,kBAAkB,EAGtC,OAAOA,CACX,CAEA,aAAa,iBAAiBK,EAAgB,CAC1C,IAAML,EAAO,MAAMG,EAAO,KAAK,kBAAkB,CAC7C,OAAQ,CACJ,MAAO,CACH,OAAQ,CACJ,OAAQ,GACR,WAAY,GACZ,UAAW,GACX,KAAM,GACN,GAAI,GACJ,SAAU,CACN,OAAQ,CACJ,QAAS,GACT,MAAO,GACP,SAAU,GACV,UAAW,GACX,KAAM,EACV,CACJ,EACA,QAAS,GACT,MAAO,GACP,GAAI,EACR,CACJ,EACA,GAAI,EACR,EACA,MAAO,CACH,GAAIE,CACR,CACJ,CAAC,EAEKC,EAAM,IAAIC,EAAa,CACzB,OAAQP,EAAK,MAAM,IAAKQ,GAAU,CAC9B,IAAMC,EAAQ,IAAI,KAAK,aAAa,QAAS,CACzC,SAAU,MACV,MAAO,UACX,CAAC,EAAE,OAAOD,EAAM,MAAQ,GAAG,EAEvBE,EAA0BC,EAAgB,UAC9C,OAAIH,EAAM,aACNE,EAASC,EAAgB,WAGtB,CACH,OAAQ,CAAC,CAAE,QAAS,IAAK,KAAMC,EAAc,OAAQ,CAAC,EACtD,QAASJ,EAAM,UACf,YAAa,GAAGA,EAAM,MAAM;AAAA,EAAMC,CAAK,GACvC,IAAKD,EAAM,GACX,GAAIA,EAAM,GACV,SAAU,CACN,QAASA,EAAM,SAAS,QACxB,IACIA,EAAM,SAAS,UAAYA,EAAM,SAAS,UACpC,CACI,IAAKA,EAAM,SAAS,SACpB,IAAKA,EAAM,SAAS,SACxB,EACA,OACV,MAAOA,EAAM,SAAS,IAC1B,EACA,MAAOA,EAAM,KACb,OAAAE,EACA,QAAS,GAAGF,EAAM,SAAS,OAASK,CAAa,IAAIL,EAAM,SAAS,IAAI,GACxE,UAAWA,EAAM,UACjB,IAAK,kDAAkDA,EAAM,OAAO,EACxE,CACJ,CAAC,EACD,KAAM,OACN,IAAK,GAAK,EACd,CAAC,EAED,YAAK,mBAAmBH,CAAM,EACvBC,EAAI,SAAS,CACxB,CAEA,OAAO,eAAeL,EAAI,EAAG,CACzB,OAAIA,EAAI,IACGa,EAAW,EAGlBb,EAAI,GACGc,EAAa,EAGjBC,EAAuB,CAClC,CAEA,aAAa,iBAAiBX,EAAgB,CAC1C,GAAM,CAACL,EAAMiB,CAAI,EAAI,MAAM,QAAQ,IAAI,CACnC,KAAK,QAAQZ,CAAM,EACnBa,EAASC,EAAO,IAAI,qBAAqB,EAAG,OAAO,CACvD,CAAC,EAED,OAAOF,EACF,QACG,oBACA,GAAGE,EAAO,OAAO,IAAInB,EAAK,EAAE,gBAChC,EACC,QAAQ,qBAAsB,GAAGA,EAAK,MAAM,GAAGmB,EAAO,QAAQ,EAAE,CACzE,CAEA,aAAa,QAAQd,EAAgB,CACjC,IAAML,EAAO,MAAMG,EAAO,KAAK,kBAAkB,CAC7C,MAAO,CACH,GAAIE,CACR,CACJ,CAAC,EAED,YAAK,mBAAmBL,EAAK,EAAE,EACxBA,CACX,CAEA,aAAa,WAAY,CACrB,IAAMC,EAAI,MAAME,EAAO,KAAK,MAAM,CAC9B,MAAO,CACH,UAAW,CACP,GAAIiB,EAAO,EAAE,SAAS,GAAI,SAAS,EAAE,OAAO,CAChD,CACJ,CACJ,CAAC,EACD,GAAInB,EAAI,EACJ,MAAM,IAAI,MAAM,aAAaA,CAAC,gCAAgC,CAEtE,CAEA,OAAO,mBAAmBI,EAAgB,CACtCF,EAAO,KACF,OAAO,CACJ,KAAM,CAAE,WAAY,IAAI,IAAO,EAC/B,MAAO,CAAE,GAAIE,CAAO,CACxB,CAAC,EACA,MAAOD,GAAU,CACd,QAAQ,IAAIA,CAAK,CACrB,CAAC,CACT,CACJ,EDxKA,IAAMiB,EAAN,MAAMC,CAAU,CACJ,IAEA,OACR,aAAc,CACV,KAAK,IAAMC,EAAQ,EACnB,KAAK,IAAI,IAAIC,EAAa,CAAC,EAE3B,KAAK,YAAY,EACjB,KAAK,OAAS,KAAK,IAAI,OAAO,QAAQ,IAAI,MAAQ,IAAI,EACtD,QAAQ,IACJ,cAAcC,EAAO,OAAO,sBAAsB,QAAQ,IAAI,MAAQ,IAAI,EAC9E,EAEA,QAAQ,GAAG,SAAU,IAAM,KAAK,KAAK,CAAC,EACtC,QAAQ,GAAG,UAAW,IAAM,KAAK,KAAK,CAAC,EAEvCC,EAAO,WAAW,EACb,KAAK,IAAM,QAAQ,IAAI,4BAA4B,CAAC,EACpD,MAAOC,GAAU,CACd,QAAQ,IAAI,yBAAyB,EACrC,QAAQ,MAAMA,CAAK,EACnB,QAAQ,KAAK,CAAC,CAClB,CAAC,CACT,CAEA,OAAO,KAAM,CACT,IAAIL,CACR,CAEA,YAAYK,EAAgDC,EAAe,CACvE,GACID,GACA,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAM,OAAS,QACjB,CACEC,EAAI,WAAW,GAAG,EAClB,MACJ,CAEA,QAAQ,IAAID,CAAK,EACjBC,EAAI,WAAW,GAAG,CACtB,CAEA,aAAc,CACV,KAAK,IAAI,IAAI,QAAS,CAACC,EAAKD,IAAQ,CAChCA,EAAI,KAAK,MAAM,CACnB,CAAC,EAED,KAAK,IAAI,IAAI,IAAK,CAACC,EAAKD,IAAQ,CAC5B,GAAI,WAAYC,EAAI,SAAWA,EAAI,QAAQ,OAAQ,CAC/CD,EAAI,SAAS,IAAMC,EAAI,QAAQ,MAAM,EACrC,MACJ,CAEAC,EAAU,WAAW,EAChB,KAAMC,GAAS,CACZH,EAAI,OAAO,SAAUG,EAAK,EAAE,EAC5BH,EAAI,SAAS,IAAMG,EAAK,EAAE,CAC9B,CAAC,EACA,MAAOJ,GAAU,KAAK,YAAYA,EAAOC,CAAG,CAAC,CACtD,CAAC,EAED,KAAK,IAAI,IAAI,WAAY,CAACC,EAAKD,IAAQ,CACnCE,EAAU,UAAU,EACf,KAAK,IAAMF,EAAI,WAAW,GAAG,CAAC,EAC9B,MAAOD,GAAU,KAAK,YAAYA,EAAOC,CAAG,CAAC,CACtD,CAAC,EAED,KAAK,IAAI,IAAIL,EAAQ,OAAOE,EAAO,IAAI,UAAU,CAAC,CAAC,EAEnD,KAAK,IAAI,IAAI,WAAY,CAACI,EAAKD,IAAQ,CACnCA,EAAI,OAAO,CACP,mBAAoB,IAAM,CACtBE,EAAU,QAAQD,EAAI,OAAO,MAAM,EAC9B,KAAMG,GAASJ,EAAI,KAAKI,CAAI,CAAC,EAC7B,MAAOL,GAAU,KAAK,YAAYA,EAAOC,CAAG,CAAC,CACtD,EACA,YAAa,IAAM,CACfE,EAAU,iBAAiBD,EAAI,OAAO,MAAM,EACvC,KAAMI,GAAS,CACZL,EAAI,OAAO,SAAUC,EAAI,OAAO,MAAM,EACtCD,EAAI,KAAKK,CAAI,CACjB,CAAC,EACA,MAAON,GAAU,KAAK,YAAYA,EAAOC,CAAG,CAAC,CACtD,CACJ,CAAC,CACL,CAAC,EAED,KAAK,IAAI,IAAI,yBAA0B,CAACC,EAAKD,IAAQ,CACjDE,EAAU,iBAAiBD,EAAI,OAAO,MAAM,EACvC,KAAMK,GAAS,CACZN,EAAI,IAAI,eAAgB,eAAe,EACvCA,EAAI,KAAKM,CAAI,CACjB,CAAC,EACA,MAAOP,GAAU,KAAK,YAAYA,EAAOC,CAAG,CAAC,CACtD,CAAC,CACL,CAEA,MAAM,MAAO,CACT,MAAM,IAAI,QAASO,GAAO,KAAK,OAAO,MAAMA,CAAE,CAAC,EAC/C,MAAMC,EAAO,YAAY,EAEzB,QAAQ,KAAK,CACjB,CACJ,EAEAf,EAAU,IAAI","names":["cookieParser","express","generateName","generateNameWithNumber","readFile","ICalAlarmType","ICalCalendar","ICalEventStatus","moment","randomUUID","ServerLib","user","c","prefix","db_default","error","userId","cal","ICalCalendar","event","price","status","ICalEventStatus","ICalAlarmType","DEFAULT_EMOJI","randomUUID","generateName","generateNameWithNumber","html","readFile","config_default","moment","AppServer","_AppServer","express","cookieParser","config_default","Parser","error","res","req","ServerLib","user","json","html","ical","cb","db_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/bin/start.ts","../src/lib/server.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';\n\nimport cookieParser from 'cookie-parser';\nimport express, { type Express, type Response } from 'express';\nimport { Server } from 'http';\n\nimport Config from '../lib/config.js';\nimport prisma from '../lib/db.js';\nimport Parser from '../lib/parser.js';\nimport ServerLib from '../lib/server.js';\n\nclass AppServer {\n private app: Express;\n\n private server: Server;\n constructor() {\n this.app = express();\n this.app.use(cookieParser());\n\n this.setupRoutes();\n this.server = this.app.listen(process.env.PORT || 8080);\n console.log(\n `tgtg-ical v${Config.version} listening on port ${process.env.PORT || 8080}`,\n );\n\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\n\n Parser.runCleanup()\n .then(() => console.log('Initial cleanup succeeded.'))\n .catch((error) => {\n console.log('Initial cleanup failed:');\n console.error(error);\n process.exit(1);\n });\n }\n\n static run() {\n new AppServer();\n }\n\n handleError(error: PrismaClientKnownRequestError | unknown, res: Response) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'P2025'\n ) {\n res.sendStatus(404);\n return;\n }\n\n console.log(error);\n res.sendStatus(500);\n }\n\n setupRoutes() {\n this.app.get('/ping', (req, res) => {\n res.send('pong');\n });\n\n this.app.get('/', (req, res) => {\n if ('userId' in req.cookies && req.cookies.userId) {\n res.redirect('/' + req.cookies.userId);\n return;\n }\n\n ServerLib.createUser()\n .then((user) => {\n res.cookie('userId', user.id);\n res.redirect('/' + user.id);\n })\n .catch((error) => this.handleError(error, res));\n });\n\n this.app.get('/_health', (req, res) => {\n ServerLib.isHealthy()\n .then(() => res.sendStatus(204))\n .catch((error) => this.handleError(error, res));\n });\n\n this.app.use(express.static(Config.src('./assets')));\n\n this.app.get('/:userId', (req, res) => {\n res.format({\n 'application/json': () => {\n ServerLib.getUser(req.params.userId)\n .then((json) => res.send(json))\n .catch((error) => this.handleError(error, res));\n },\n 'text/html': () => {\n ServerLib.generateUserPage(req.params.userId)\n .then((html) => {\n res.cookie('userId', req.params.userId);\n res.send(html);\n })\n .catch((error) => this.handleError(error, res));\n },\n });\n });\n\n this.app.get('/:userId/calendar.ical', (req, res) => {\n ServerLib.generateCalendar(req.params.userId)\n .then((ical) => {\n res.set('Content-Type', 'text/calendar');\n res.send(ical);\n })\n .catch((error) => this.handleError(error, res));\n });\n }\n\n async stop() {\n await new Promise((cb) => this.server.close(cb));\n await prisma.$disconnect();\n\n process.exit();\n }\n}\n\nAppServer.run();\n","import { generateName, generateNameWithNumber } from '@criblinc/docker-names';\nimport { User } from '@prisma/client';\nimport { readFile } from 'fs/promises';\nimport { ICalAlarmType, ICalCalendar, ICalEventStatus } from 'ical-generator';\nimport moment from 'moment-timezone';\nimport { randomUUID } from 'node:crypto';\n\nimport Config from './config.js';\nimport prisma from './db.js';\nimport { DEFAULT_EMOJI } from './emoji.js';\n\nexport default class ServerLib {\n static async createUser() {\n let user: undefined | User;\n\n // eslint-disable-next-line no-constant-condition\n for (let c = 0; true; c++) {\n const prefix = this.generatePrefix(c);\n try {\n user = await prisma.user.create({\n data: {\n prefix,\n },\n });\n if (user) {\n break;\n }\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'P2002'\n ) {\n continue;\n }\n\n throw error;\n }\n }\n if (!user) {\n throw new Error('User not created');\n }\n\n return user;\n }\n\n static async generateCalendar(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n select: {\n event: {\n select: {\n amount: true,\n canceledAt: true,\n createdAt: true,\n from: true,\n id: true,\n location: {\n select: {\n address: true,\n emoji: true,\n latitude: true,\n longitude: true,\n name: true,\n },\n },\n orderId: true,\n price: true,\n to: true,\n },\n },\n id: true,\n },\n where: {\n id: userId,\n },\n });\n\n const cal = new ICalCalendar({\n events: user.event.map((event) => {\n const price = new Intl.NumberFormat('de-DE', {\n currency: 'EUR',\n style: 'currency',\n }).format(event.price / 100);\n\n let status: ICalEventStatus = ICalEventStatus.CONFIRMED;\n if (event.canceledAt) {\n status = ICalEventStatus.CANCELLED;\n }\n\n return {\n alarms: [{ trigger: 600, type: ICalAlarmType.display }],\n created: event.createdAt,\n description: `${event.amount}x\\n${price}`,\n end: event.to,\n id: event.id,\n location: {\n address: event.location.address,\n geo:\n event.location.latitude && event.location.longitude\n ? {\n lat: event.location.latitude,\n lon: event.location.longitude,\n }\n : undefined,\n title: event.location.name,\n },\n start: event.from,\n status,\n summary: `${event.location.emoji || DEFAULT_EMOJI} ${event.location.name}`,\n timestamp: event.createdAt,\n url: `https://share.toogoodtogo.com/receipts/details/${event.orderId}`,\n };\n }),\n name: 'TGTG',\n ttl: 60 * 60,\n });\n\n this.updateUserLastSeen(userId);\n return cal.toString();\n }\n\n static generatePrefix(c = 0) {\n if (c > 100) {\n return randomUUID();\n }\n\n if (c < 10) {\n return generateName();\n }\n\n return generateNameWithNumber();\n }\n\n static async generateUserPage(userId: string) {\n const [user, html] = await Promise.all([\n this.getUser(userId),\n readFile(Config.src('templates/user.html'), 'utf-8'),\n ]);\n\n return html\n .replace(\n /\\${CALENDAR_URL}/g,\n `${Config.baseUrl}/${user.id}/calendar.ical`,\n )\n .replace(/\\${EMAIL_ADDRESS}/g, `${user.prefix}${Config.baseMail}`);\n }\n\n static async getUser(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n where: {\n id: userId,\n },\n });\n\n this.updateUserLastSeen(user.id);\n return user;\n }\n\n static async isHealthy() {\n const c = await prisma.mail.count({\n where: {\n createdAt: {\n lt: moment().subtract(30, 'minutes').toDate(),\n },\n },\n });\n if (c > 0) {\n throw new Error(`There are ${c} unhandled mails in the queue!`);\n }\n }\n\n static updateUserLastSeen(userId: string) {\n prisma.user\n .update({\n data: { lastSeenAt: new Date() },\n where: { id: userId },\n })\n .catch((error) => {\n console.log(error);\n });\n }\n}\n"],"mappings":"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","names":["cookieParser","express","generateName","generateNameWithNumber","readFile","ICalAlarmType","ICalCalendar","ICalEventStatus","moment","randomUUID","ServerLib","user","c","prefix","db_default","error","userId","cal","ICalCalendar","event","price","status","ICalEventStatus","ICalAlarmType","randomUUID","generateName","generateNameWithNumber","html","readFile","config_default","moment","AppServer","_AppServer","express","cookieParser","config_default","Parser","error","res","req","ServerLib","user","json","html","ical","cb","db_default"]}
|
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@semantic-release/changelog": "^6.0.3",
|
|
26
26
|
"@semantic-release/exec": "^7.1.0",
|
|
27
27
|
"@semantic-release/git": "^10.0.1",
|
|
28
|
-
"@semantic-release/npm": "^13.1.
|
|
28
|
+
"@semantic-release/npm": "^13.1.2",
|
|
29
29
|
"@types/cookie-parser": "^1.4.10",
|
|
30
30
|
"@types/express": "^5.0.5",
|
|
31
31
|
"@types/he": "^1.2.3",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"semantic-release": "^25.0.2",
|
|
48
48
|
"semantic-release-license": "^1.0.3",
|
|
49
49
|
"source-map-support": "^0.5.21",
|
|
50
|
-
"tsup": "^8.5.
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
51
|
"tsx": "^4.20.6",
|
|
52
52
|
"typedoc": "^0.28.14",
|
|
53
53
|
"typescript": "^5.9.3",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"test": "mocha"
|
|
89
89
|
},
|
|
90
90
|
"type": "module",
|
|
91
|
-
"version": "3.0.9-develop.
|
|
91
|
+
"version": "3.0.9-develop.5"
|
|
92
92
|
}
|
package/dist/chunk-QGN7VGAF.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{init as I}from"@sentry/node";import{readFileSync as A}from"fs";import{dirname as k,resolve as g}from"path";import{fileURLToPath as x}from"url";I({dsn:process.env.SENTRY_DSN||"https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9"});var u=k(x(import.meta.url));u.endsWith("/dist")?u=g(u,"..","src"):u=g(u,"..");function v(l){return g(u,l||"")}var D;try{D=JSON.parse(A(v("../package.json"),"utf8")).version}catch(l){console.log("Failed getting tgtg-ical version:"),console.error(l)}var c={baseMail:process.env.BASE_MAIL||"@tgtg-ical.sebbo.net",baseUrl:process.env.BASE_URL||"https://tgtg-ical.sebbo.net",src:v,version:D};import{PrismaClient as P}from"@prisma/client";var z=new P,i=z;import{captureException as B}from"@sentry/node";import E from"he";import{simpleParser as T}from"mailparser";import n from"moment-timezone";var O="\u{1F374}",b={"\u{1F32D}":[/Ikea/],"\u{1F32E}":[/Enchilada/,/Besitos/],"\u{1F354}":[/Burger/,/McDonald[‘|']?s/,/Burger King/,/Hans im Glück/,/Peter Pane/],"\u{1F355}":[/Domino‘s/,/Pizza Hut/,/L[‘|']Osteria/,/Call a Pizza/,/Smiley[‘|']s Pizza/],"\u{1F357}":[/KFC/],"\u{1F35D}":[/LaTagliatella/,/Vapiano/],"\u{1F364}":[/Nordsee/],"\u{1F369}":[/Donut/,/Dunkin/],"\u{1F36A}":[/Starbucks/],"\u{1F950}":[/LeCroBag/],"\u{1F956}":[/Bäckerei/,/Back/,/BackWerk/,/Kamps/,/Kamps Backstuben/,/Junge Die Bäckerei/,/Back-Factory/],"\u{1F957}":[/dean ?& ?david/],"\u{1F968}":[/Brezel/,/Ditsch/],"\u{1F969}":[/Steakhouse/,/Block House/,/Jim Block/],"\u{1F96A}":[/Sandwich/,/Tank ?& ?Rast/,/T&R Raststätten/,/Autohöfe/,/Aral/,/Shell/,/Jet Tank/,/Subway/,/Caf[é|e] bonjour/,/Total Deutschland/,/Esso:? Snack ?& ?Shop/],"\u{1F6D2}":[/Edeka/,/Netto/,/Rewe/,/Penny/,/Lidl/,/Kaufland/,/Aldi/,/dm/,/Rossmann/,/Globus/,/Metro/,/Norma/,/Tegut/]};function w(l){for(let t in b)if(!!b[t].map(a=>new RegExp(a,"i")).find(a=>a.test(l)))return t;return null}var M=class{static async geocode(t){let r=await fetch("https://nominatim.openstreetmap.org/search?format=json&limit=1&q="+encodeURIComponent(t),{headers:{Referer:c.baseUrl,"User-Agent":`tgtg-ical/${c.version} (${c.baseUrl})`}});if(!r.ok)throw new Error("Geocoding failed: "+r.statusText);await new Promise(a=>setTimeout(a,1e3));let e=await r.json();return!Array.isArray(e)||e.length===0?{latitude:null,longitude:null}:{latitude:parseFloat(e[0].lat),longitude:parseFloat(e[0].lon)}}static async handleMail(t){try{let r=await this.parseMail(t.raw);r&&await this.applyParsedMail(r),await i.mail.delete({where:{id:t.id}})}catch(r){let e=B(r);await i.mail.update({data:{error:r instanceof Error?r.stack:String(r),erroredAt:new Date,errorId:e,version:c.version},where:{id:t.id}})}}static async inhaleMail(t){let r=await i.mail.create({data:{raw:t}});await this.handleMail(r)}static async parseMail(t,r=c.baseMail){let e=await T(t,{skipHtmlToText:!0,skipImageLinks:!0,skipTextLinks:!0,skipTextToHtml:!0});if(!e.from?.value[0].address?.endsWith("toogoodtogo.com"))throw new Error("Not a TGTG email!");let a=(Array.isArray(e.to)?e.to:[e.to]).map(o=>o?.value).flat().filter(o=>!!o).map(o=>o.address).find(o=>o?.endsWith(r));if(!a){let o=e.headers.get("received"),d=new RegExp(`([\\w-]+${c.baseMail})`,"i");Array.isArray(o)&&o.forEach(s=>{let m=(typeof s=="string"?s:s.value).match(d);m&&(a=m[1])})}if(!a)throw new Error("No recipient found!");if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let o=this.parseOrderMail(e);return{to:a,...o}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let o=this.parseChangeMail(e);return{to:a,...o}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let o=this.parseCancellationMail(e);if(o)return{to:a,...o}}if(e.headers.get("x-pm-tag")==="invoice"){let o=this.parseInvoiceMail(e);if(o)return{to:a,...o}}if(!(!e.headers.get("x-pm-tag")&&!e.headers.get("x-pm-message-id")))throw e.headers.get("x-pm-tag")?new Error(`Unsupported email type: ${e.headers.get("x-pm-tag")}`):new Error("Not implemented!")}static async runCleanup(){let t=await i.mail.findMany({orderBy:{erroredAt:"asc"},take:10,where:{OR:[{error:null},{version:null},{version:{not:c.version}}]}});for(let r of t)await this.handleMail(r);await i.user.deleteMany({where:{OR:[{lastSeenAt:{lt:n().subtract(8,"weeks").toDate()}},{createdAt:{lt:n().subtract(3,"hours").toDate()},lastSeenAt:{equals:i.user.fields.createdAt}}]}}),await i.event.deleteMany({where:{to:{lt:n().subtract(4,"weeks").toDate()}}}),await i.mail.deleteMany({where:{createdAt:{lt:n().subtract(2,"weeks").toDate()}}})}static async applyParsedMail(t){let r=await this.findUser(t.to);if(t.type==="order"){let e=await this.getLocation(t.location);await i.event.upsert({create:{amount:t.amount,from:t.time.from.toDate(),location:{connect:{id:e.id}},orderedAt:t.time.order.toDate(),orderId:t.orderId,price:t.price,to:t.time.to.toDate(),user:{connect:{id:r.id}}},update:{amount:t.amount,from:t.time.from.toDate(),location:{connect:{id:e.id}},price:t.price,to:t.time.to.toDate()},where:{orderId:t.orderId,userId:r.id}})}else if(t.type==="change")await i.event.update({data:{from:t.time.from.toDate(),to:t.time.to.toDate()},where:{orderId:t.orderId,userId:r.id}});else if(t.type==="cancel")await i.event.update({data:{canceledAt:t.cancelledAt.toDate()},where:{orderId:t.orderId,userId:r.id}});else if(t.type==="invoice")await i.event.update({data:{invoicedAt:t.invoicedAt.toDate()},where:{orderId:t.orderId,userId:r.id}});else throw new Error("Unknown email type!")}static async findUser(t){if(!t)throw new Error("Did not found a valid recipient!");let r=t.split("@")[0],e=await i.user.findUnique({where:{prefix:r}});if(!e)throw new Error(`User with email prefix ${r} not found!`);return e}static async getLocation(t){let r=await i.location.findFirst({where:{address:t.address,name:t.name}});if(!r){let e=w(t.name),{latitude:a,longitude:o}=await this.geocode(t.address);r=await i.location.create({data:{address:t.address,emoji:e,latitude:a,longitude:o,name:t.name}})}return r}static parseCancellationMail(t){let e=(t.subject||"").match(/\((\w+)\)/);if(e)return{cancelledAt:n(t.date),orderId:e[1],type:"cancel"}}static parseChangeMail(t){let r=[(t.html||"").match(/https:\/\/share.toogoodtogo.com\/receipts\/details\/(\w+)/),(t.html||"").match(/(\d{1,2}\.\d{2}\.\d{2}) zwischen (\d{1,2}:\d{2}) und (\d{1,2}:\d{2})(?: Uhr)? (\w+)+ \(/)];if(!r[0])throw new Error("Order ID not found!");if(!r[1])throw new Error("Date / Time not found!");let e=r[1][4];e==="MEZ"&&(e="MET");let a=n.tz(r[1][1]+" "+r[1][2],"DD.MM.YY HH:mm",e),o=n.tz(r[1][1]+" "+r[1][3],"DD.MM.YY HH:mm",e);return{orderId:r[0][1].trim(),time:{from:a,to:o},type:"change"}}static parseInvoiceMail(t){let e=(t.html||"").match(/Die Rechnung für deine Bestellung (\w+)/);if(e)return{invoicedAt:n(t.date),orderId:e[1],type:"invoice"};throw new Error("Order Id not found!")}static parseOrderMail(t){let r=t.html||"",e=[r.match(/\/order\/([^/]+)\//),r.match(/Wir bestätigen hiermit deine Bestellung bei ([^(.<]+)/),r.match(/<span>Du kannst deine Bestellung am (\d{1,2}\.\d{2}\.\d{2}) zwischen (\d{1,2}:\d{2}) und (\d{1,2}:\d{2}) Uhr (\w+)[^:]+: (.+).<\/span><\/div>/),r.match(/<span>Du kannst deine Bestellung zwischen (\d{1,2}\.\d{1,2}), (\d{1,2}:\d{2}) und (\d{1,2}:\d{2})[^:]+: (.+).<\/span><\/div>/),r.match(/<b>Datum:<\/b>\s+<span>(\d{1,2}\.\d{2}\.\d{2})<\/span>/),r.match(/Abholzeit:<\/b>\s+<span>(\d{1,2}:\d{2}) - (\d{1,2}:\d{2}) (\w+)/),r.match(/Anzahl: (\d+)/),r.match(/Anzahl:<\/b>\s+<span>(\d+)/),r.match(/Gesamtpreis: ([\d,.]+)[^\d,.]/),r.match(/Gesamtpreis:<\/b>\s+<span>([\d,.]+)[^\d,.]/),r.match(/Standort:<\/b>\s+<span>([^<]+)/)];if(!e[0])throw new Error("Order ID not found!");if(!e[1])throw new Error("Location name not found!");if(!e[2]&&!e[3]&&!(e[4]&&e[5]))throw new Error("Date, time and address not found (1)!");let a="MET";e[2]&&(a=e[2][4]),e[5]&&(a=e[5][3]),a==="MEZ"&&(a="MET");let o=n(t.date),d,s,p;if(e[2]&&(d=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",a),s=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",a),p=e[2][5].trim()),e[3]){let y=o.year();d=n.tz(e[3][1]+"."+y+" "+e[3][2],"DD.MM.YYYY HH:mm",a),s=n.tz(e[3][1]+"."+y+" "+e[3][3],"DD.MM.YYYY HH:mm",a),p=e[3][4].trim(),d.isBefore(o)&&d.add(1,"year"),s.isBefore(d)&&s.add(1,"year")}if(e[4]&&e[5]&&e[10]&&(d=n.tz(e[4][1]+" "+e[5][1],"DD.MM.YY HH:mm",a),s=n.tz(e[4][1]+" "+e[5][2],"DD.MM.YY HH:mm",a),p=e[10][1].trim()),!d||!s||!p)throw new Error("Date, time or address not found (2)!");let m=0;if(e[6])m=parseInt(e[6][1],10);else if(e[7])m=parseInt(e[7][1],10);else throw new Error("Amount not found!");if(isNaN(m))throw new Error("Amount (1) is not a number!");let f=0;if(e[8])f=parseInt(e[8][1].replace(/[.|,]/g,""));else if(e[9])f=parseInt(e[9][1].replace(/[.|,]/g,""));else throw new Error("Price not found!");if(isNaN(f))throw new Error("Price is not a number!");let h=E.decode(e[1][1].trim());return h.endsWith("!")&&(h=h.slice(0,-1)),{amount:m,location:{address:E.decode(p),name:h},orderId:e[0][1].trim(),price:f,time:{from:d,order:o,to:s},type:"order"}}};export{c as a,i as b,O as c,M as d};
|
|
2
|
-
//# sourceMappingURL=chunk-QGN7VGAF.js.map
|