@sebbo2002/tgtg-ical 2.0.6-develop.4 → 2.0.6-develop.6

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{PrismaClient as I}from"@prisma/client";var A=new I,i=A;import{init as k}from"@sentry/node";import{dirname as x,resolve as g}from"node:path";import{fileURLToPath as P}from"node:url";import{readFileSync as z}from"node:fs";k({dsn:process.env.SENTRY_DSN||"https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9"});var u=x(P(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(z(v("../package.json"),"utf8")).version}catch(l){console.log("Failed getting tgtg-ical version:"),console.error(l)}var c={baseUrl:process.env.BASE_URL||"https://tgtg-ical.sebbo.net",baseMail:process.env.BASE_MAIL||"@tgtg-ical.sebbo.net",version:D,src:v};import{captureException as B}from"@sentry/node";import{simpleParser as T}from"mailparser";import E from"he";import n from"moment-timezone";var O="\u{1F374}",b={"\u{1F354}":[/Burger/,/McDonald[‘|']?s/,/Burger King/,/Hans im Glück/,/Peter Pane/],"\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{1F355}":[/Domino‘s/,/Pizza Hut/,/L[‘|']Osteria/,/Call a Pizza/,/Smiley[‘|']s Pizza/],"\u{1F357}":[/KFC/],"\u{1F35D}":[/LaTagliatella/,/Vapiano/],"\u{1F364}":[/Nordsee/],"\u{1F969}":[/Steakhouse/,/Block House/,/Jim Block/],"\u{1F32E}":[/Enchilada/,/Besitos/],"\u{1F32D}":[/Ikea/],"\u{1F957}":[/dean ?& ?david/],"\u{1F956}":[/Bäckerei/,/Back/,/BackWerk/,/Kamps/,/Kamps Backstuben/,/Junge Die Bäckerei/,/Back-Factory/],"\u{1F968}":[/Brezel/,/Ditsch/],"\u{1F950}":[/LeCroBag/],"\u{1F36A}":[/Starbucks/],"\u{1F369}":[/Donut/,/Dunkin/],"\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 runCleanup(){let t=await i.mail.findMany({where:{OR:[{error:null},{version:null},{version:{not:c.version}}]},orderBy:{erroredAt:"asc"},take:10});for(let r of t)await this.handleMail(r);await i.user.deleteMany({where:{OR:[{lastSeenAt:{lt:n().subtract(8,"weeks").toDate()}},{lastSeenAt:{equals:i.user.fields.createdAt},createdAt:{lt:n().subtract(3,"hours").toDate()}}]}}),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 inhaleMail(t){let r=await i.mail.create({data:{raw:t}});await this.handleMail(r)}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({where:{id:t.id},data:{error:r instanceof Error?r.stack:String(r),erroredAt:new Date,errorId:e,version:c.version}})}}static async parseMail(t,r=c.baseMail){let e=await T(t,{skipHtmlToText:!0,skipImageLinks:!0,skipTextToHtml:!0,skipTextLinks:!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 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 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)),{type:"order",orderId:e[0][1].trim(),location:{name:h,address:E.decode(p)},time:{order:o,from:d,to:s},amount:m,price:f}}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{type:"change",orderId:r[0][1].trim(),time:{from:a,to:o}}}static parseCancellationMail(t){let e=(t.subject||"").match(/\((\w+)\)/);if(e)return{type:"cancel",orderId:e[1],cancelledAt:n(t.date)}}static parseInvoiceMail(t){let e=(t.html||"").match(/Die Rechnung für deine Bestellung (\w+)/);if(e)return{type:"invoice",orderId:e[1],invoicedAt:n(t.date)};throw new Error("Order Id not found!")}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({where:{orderId:t.orderId,userId:r.id},create:{orderId:t.orderId,orderedAt:t.time.order.toDate(),from:t.time.from.toDate(),to:t.time.to.toDate(),amount:t.amount,price:t.price,user:{connect:{id:r.id}},location:{connect:{id:e.id}}},update:{from:t.time.from.toDate(),to:t.time.to.toDate(),amount:t.amount,price:t.price,location:{connect:{id:e.id}}}})}else if(t.type==="change")await i.event.update({where:{orderId:t.orderId,userId:r.id},data:{from:t.time.from.toDate(),to:t.time.to.toDate()}});else if(t.type==="cancel")await i.event.update({where:{orderId:t.orderId,userId:r.id},data:{canceledAt:t.cancelledAt.toDate()}});else if(t.type==="invoice")await i.event.update({where:{orderId:t.orderId,userId:r.id},data:{invoicedAt:t.invoicedAt.toDate()}});else throw new Error("Unknown email type!")}static async getLocation(t){let r=await i.location.findFirst({where:{name:t.name,address:t.address}});if(!r){let e=w(t.name),{latitude:a,longitude:o}=await this.geocode(t.address);r=await i.location.create({data:{name:t.name,address:t.address,latitude:a,longitude:o,emoji:e}})}return r}static async geocode(t){let r=await fetch("https://nominatim.openstreetmap.org/search?format=json&limit=1&q="+encodeURIComponent(t),{headers:{"User-Agent":`tgtg-ical/${c.version} (${c.baseUrl})`,Referer: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)}}};export{i as a,c as b,O as c,M as d};
2
+ //# sourceMappingURL=chunk-2HK3PBFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/db.ts","../src/lib/config.ts","../src/lib/parser.ts","../src/lib/emoji.ts"],"sourcesContent":["import { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nexport default prisma;\n","import { init } from '@sentry/node';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { readFileSync } from 'node:fs';\n\ninit({\n dsn: process.env.SENTRY_DSN || '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}\ncatch(error) {\n console.log('Failed getting tgtg-ical version:');\n console.error(error);\n}\n\nexport default {\n baseUrl: process.env.BASE_URL || 'https://tgtg-ical.sebbo.net',\n baseMail: process.env.BASE_MAIL || '@tgtg-ical.sebbo.net',\n version,\n src\n};\n","import type { Mail, User, Location } from '@prisma/client';\nimport { captureException } from '@sentry/node';\nimport { type ParsedMail, simpleParser } from 'mailparser';\nimport he from 'he';\nimport prisma from './db.js';\nimport config from './config.js';\nimport moment from 'moment-timezone';\nimport getEmoji from './emoji.js';\nimport Config from './config.js';\n\ntype AnyMail = { to: string; } & (OrderMail | ChangeMail | InvoiceMail | CancellationMail);\n\ninterface OrderMail {\n type: 'order';\n orderId: string;\n location: {\n name: string;\n address: string;\n };\n time: {\n order: moment.Moment;\n from: moment.Moment;\n to: moment.Moment;\n };\n amount: number;\n price: number;\n}\n\ninterface ChangeMail {\n type: 'change';\n orderId: string;\n time: {\n from: moment.Moment;\n to: moment.Moment;\n };\n}\n\ninterface InvoiceMail {\n type: 'invoice';\n orderId: string;\n invoicedAt: moment.Moment;\n}\n\ninterface CancellationMail {\n type: 'cancel';\n orderId: string;\n cancelledAt: moment.Moment;\n}\n\nexport default class Parser {\n public static async runCleanup(): Promise<void> {\n const mails = await prisma.mail.findMany({\n where: {\n OR: [\n { error: null },\n { version: null },\n { version: { not: config.version } }\n ]\n },\n orderBy: {\n erroredAt: 'asc'\n },\n take: 10\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 lastSeenAt: {\n equals: prisma.user.fields.createdAt\n },\n createdAt: {\n lt: moment().subtract(3, 'hours').toDate()\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 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 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 }\n catch(error) {\n const errorId = captureException(error);\n await prisma.mail.update({\n where: {\n id: mail.id\n },\n data: {\n error: error instanceof Error ? error.stack : String(error),\n erroredAt: new Date(),\n errorId,\n version: config.version\n }\n });\n }\n }\n\n public static async parseMail(mail: string, baseMailPostfix = config.baseMail): Promise<AnyMail|undefined> {\n // parse email\n const email = await simpleParser(mail, {\n skipHtmlToText: true,\n skipImageLinks: true,\n skipTextToHtml: true,\n skipTextLinks: 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 = (Array.isArray(email.to) ? email.to : [email.to])\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(!email.headers.get('x-pm-tag') && !email.headers.get('x-pm-message-id')) {\n return undefined;\n }\n\n // Unsupported email\n if(email.headers.get('x-pm-tag')) {\n throw new Error(`Unsupported email type: ${email.headers.get('x-pm-tag')}`);\n }\n\n throw new Error('Not implemented!');\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 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(/<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 html.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>/),\n html.match(/<b>Datum:<\\/b>\\s+<span>(\\d{1,2}\\.\\d{2}\\.\\d{2})<\\/span>/),\n html.match(/Abholzeit:<\\/b>\\s+<span>(\\d{1,2}:\\d{2}) - (\\d{1,2}:\\d{2}) (\\w+)/),\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(matches[2][1] + ' ' + matches[2][2], 'DD.MM.YY HH:mm', timezone);\n to = moment.tz(matches[2][1] + ' ' + matches[2][3], 'DD.MM.YY HH:mm', timezone);\n address = matches[2][5].trim();\n }\n if(matches[3]) {\n const year = now.year();\n from = moment.tz(matches[3][1] + '.' + year + ' ' + matches[3][2], 'DD.MM.YYYY HH:mm', timezone);\n to = moment.tz(matches[3][1] + '.' + year + ' ' + matches[3][3], 'DD.MM.YYYY HH:mm', timezone);\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(matches[4][1] + ' ' + matches[5][1], 'DD.MM.YY HH:mm', timezone);\n to = moment.tz(matches[4][1] + ' ' + matches[5][2], 'DD.MM.YY HH:mm', timezone);\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 }\n else if (matches[7]) {\n amount = parseInt(matches[7][1], 10);\n }\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\n let price = 0;\n if (matches[8]) {\n price = parseInt(matches[8][1].replace(/[.|,]/g, ''));\n }\n else if (matches[9]) {\n price = parseInt(matches[9][1].replace(/[.|,]/g, ''));\n }\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 type: 'order',\n orderId: matches[0][1].trim(),\n location: {\n name,\n address: he.decode(address)\n },\n time: {\n order: now,\n from,\n to\n },\n amount,\n price\n };\n }\n\n private static parseChangeMail(email: ParsedMail): ChangeMail {\n const matches = [\n (email.html || '').match(/https:\\/\\/share.toogoodtogo.com\\/receipts\\/details\\/(\\w+)/),\n (email.html || '').match(/(\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})(?: Uhr)? (\\w+)+ \\(/)\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(matches[1][1] + ' ' + matches[1][2], 'DD.MM.YY HH:mm', timezone);\n const to = moment.tz(matches[1][1] + ' ' + matches[1][3], 'DD.MM.YY HH:mm', timezone);\n\n return {\n type: 'change',\n orderId: matches[0][1].trim(),\n time: {\n from,\n to\n }\n };\n }\n\n private static parseCancellationMail(email: ParsedMail): CancellationMail | undefined {\n const subject = email.subject || '';\n const match = subject.match(/\\((\\w+)\\)/);\n if(match) {\n return {\n type: 'cancel',\n orderId: match[1],\n cancelledAt: moment(email.date)\n };\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 type: 'invoice',\n orderId: match[1],\n invoicedAt: moment(email.date)\n };\n }\n\n throw new Error('Order Id not found!');\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 where: {\n orderId: email.orderId,\n userId: user.id\n },\n create: {\n orderId: email.orderId,\n orderedAt: email.time.order.toDate(),\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n amount: email.amount,\n price: email.price,\n user: {\n connect: {\n id: user.id\n }\n },\n location: {\n connect: {\n id: location.id\n }\n }\n },\n update: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n amount: email.amount,\n price: email.price,\n location: {\n connect: {\n id: location.id\n }\n }\n }\n });\n }\n else if(email.type === 'change') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate()\n }\n });\n }\n else if (email.type === 'cancel') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n canceledAt: email.cancelledAt.toDate()\n }\n });\n }\n else if (email.type === 'invoice') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n invoicedAt: email.invoicedAt.toDate()\n }\n });\n }\n else {\n throw new Error('Unknown email type!');\n }\n }\n\n private static async getLocation(input: OrderMail['location']): Promise<Location> {\n let location = await prisma.location.findFirst({\n where: {\n name: input.name,\n address: input.address\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 name: input.name,\n address: input.address,\n latitude,\n longitude,\n emoji\n }\n });\n }\n\n return location;\n }\n\n public static async geocode(address: string): Promise<{ latitude: number, longitude: number } | { latitude: null, longitude: null }> {\n const response = await fetch('https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' + encodeURIComponent(address), {\n headers: {\n 'User-Agent': `tgtg-ical/${config.version} (${config.baseUrl})`,\n 'Referer': config.baseUrl\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","export const DEFAULT_EMOJI = '🍴';\nexport const EMOJIS: Record<string, RegExp[]> = {\n '🍔': [\n /Burger/,\n /McDonald[‘|']?s/,\n /Burger King/,\n /Hans im Glück/,\n /Peter Pane/,\n\n ],\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 '🍕': [\n /Domino‘s/,\n /Pizza Hut/,\n /L[‘|']Osteria/,\n /Call a Pizza/,\n /Smiley[‘|']s Pizza/,\n\n ],\n '🍗': [\n /KFC/,\n\n ],\n '🍝': [\n /LaTagliatella/,\n /Vapiano/,\n\n ],\n '🍤': [\n /Nordsee/\n ],\n '🥩': [\n /Steakhouse/,\n /Block House/,\n /Jim Block/,\n\n ],\n '🌮': [\n /Enchilada/,\n /Besitos/,\n\n ],\n '🌭': [\n /Ikea/,\n\n ],\n '🥗': [\n /dean ?& ?david/\n ],\n '🥖': [\n /Bäckerei/,\n /Back/,\n /BackWerk/,\n /Kamps/,\n /Kamps Backstuben/,\n /Junge Die Bäckerei/,\n /Back-Factory/,\n\n ],\n '🥨': [\n /Brezel/,\n /Ditsch/,\n\n ],\n '🥐': [\n /LeCroBag/,\n\n ],\n '🍪': [\n /Starbucks/\n ],\n '🍩': [\n /Donut/,\n /Dunkin/,\n\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};\n\nexport default function getEmoji(location: string): string | null {\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":["PrismaClient","prisma","db_default","init","dirname","resolve","fileURLToPath","readFileSync","dir","src","path","version","error","config_default","captureException","simpleParser","he","moment","DEFAULT_EMOJI","EMOJIS","getEmoji","location","emoji","name","regExp","Parser","mails","db_default","config_default","mail","moment","email","parsed","error","errorId","captureException","baseMailPostfix","simpleParser","to","address","received","regexp","r","match","order","change","cancellation","invoice","prefix","user","html","matches","timezone","now","from","year","amount","price","name","he","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data"]}
package/dist/cleanup.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{d as r}from"./chunk-MU72W6UK.js";r.runCleanup().catch(e=>{console.error(e),process.exit(1)});
2
+ import{d as r}from"./chunk-2HK3PBFJ.js";r.runCleanup().catch(e=>{console.error(e),process.exit(1)});
3
3
  //# sourceMappingURL=cleanup.js.map
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{d as r}from"./chunk-MU72W6UK.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)})});
2
+ import{d as r}from"./chunk-2HK3PBFJ.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 o,b as i,c as u,d}from"./chunk-MU72W6UK.js";import m from"express";import C from"cookie-parser";import{randomUUID as f}from"node:crypto";import{generateName as g,generateNameWithNumber as w}from"@criblinc/docker-names";import"@prisma/client";import{ICalAlarmType as I,ICalCalendar as E,ICalEventStatus as p}from"ical-generator";import y from"moment-timezone";import{readFile as U}from"fs/promises";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 generatePrefix(e=0){return e>100?f():e<10?g():w()}static async getUser(e){let t=await o.user.findUniqueOrThrow({where:{id:e}});return this.updateUserLastSeen(t.id),t}static updateUserLastSeen(e){o.user.update({where:{id:e},data:{lastSeenAt:new Date}}).catch(t=>{console.log(t)})}static async generateUserPage(e){let[t,r]=await Promise.all([this.getUser(e),U(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 generateCalendar(e){let t=await o.user.findUniqueOrThrow({where:{id:e},select:{id:!0,event:{select:{id:!0,orderId:!0,from:!0,to:!0,amount:!0,price:!0,location:{select:{name:!0,address:!0,emoji:!0,latitude:!0,longitude:!0}},createdAt:!0,canceledAt:!0}}}}),r=new E({name:"TGTG",ttl:60*60,events:t.event.map(a=>{let h=new Intl.NumberFormat("de-DE",{style:"currency",currency:"EUR"}).format(a.price/100),l=p.CONFIRMED;return a.canceledAt&&(l=p.CANCELLED),{id:a.id,start:a.from,end:a.to,timestamp:a.createdAt,summary:`${a.location.emoji||u} ${a.location.name}`,description:`${a.amount}x
2
+ import{a as o,b as i,c as u,d}from"./chunk-2HK3PBFJ.js";import m from"express";import C from"cookie-parser";import{randomUUID as f}from"node:crypto";import{generateName as g,generateNameWithNumber as w}from"@criblinc/docker-names";import"@prisma/client";import{ICalAlarmType as I,ICalCalendar as E,ICalEventStatus as p}from"ical-generator";import y from"moment-timezone";import{readFile as U}from"fs/promises";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 generatePrefix(e=0){return e>100?f():e<10?g():w()}static async getUser(e){let t=await o.user.findUniqueOrThrow({where:{id:e}});return this.updateUserLastSeen(t.id),t}static updateUserLastSeen(e){o.user.update({where:{id:e},data:{lastSeenAt:new Date}}).catch(t=>{console.log(t)})}static async generateUserPage(e){let[t,r]=await Promise.all([this.getUser(e),U(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 generateCalendar(e){let t=await o.user.findUniqueOrThrow({where:{id:e},select:{id:!0,event:{select:{id:!0,orderId:!0,from:!0,to:!0,amount:!0,price:!0,location:{select:{name:!0,address:!0,emoji:!0,latitude:!0,longitude:!0}},createdAt:!0,canceledAt:!0}}}}),r=new E({name:"TGTG",ttl:60*60,events:t.event.map(a=>{let h=new Intl.NumberFormat("de-DE",{style:"currency",currency:"EUR"}).format(a.price/100),l=p.CONFIRMED;return a.canceledAt&&(l=p.CANCELLED),{id:a.id,start:a.from,end:a.to,timestamp:a.createdAt,summary:`${a.location.emoji||u} ${a.location.name}`,description:`${a.amount}x
3
3
  ${h}`,url:`https://share.toogoodtogo.com/receipts/details/${a.orderId}`,status:l,created:a.createdAt,location:{title:a.location.name,address:a.location.address,geo:a.location.latitude&&a.location.longitude?{lat:a.location.latitude,lon:a.location.longitude}:void 0},alarms:[{type:I.display,trigger:600}]}})});return this.updateUserLastSeen(e),r.toString()}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!`)}};var n=class c{static run(){new c}app;server;constructor(){this.app=m(),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()),d.runCleanup().then(()=>console.log("Initial cleanup succeeded.")).catch(e=>{console.log("Initial cleanup failed:"),console.error(e),process.exit(1)})}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(m.static(i.src("./assets"))),this.app.get("/:userId",(e,t)=>{t.format({"text/html":()=>{s.generateUserPage(e.params.userId).then(r=>{t.cookie("userId",e.params.userId),t.send(r)}).catch(r=>this.handleError(r,t))},"application/json":()=>{s.getUser(e.params.userId).then(r=>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))})}handleError(e,t){if(e&&typeof e=="object"&&"code"in e&&e.code==="P2025"){t.sendStatus(404);return}console.log(e),t.sendStatus(500)}async stop(){await new Promise(e=>this.server.close(e)),await o.$disconnect(),process.exit()}};n.run();
4
4
  //# sourceMappingURL=start.js.map
package/package.json CHANGED
@@ -85,5 +85,5 @@
85
85
  "test": "mocha"
86
86
  },
87
87
  "type": "module",
88
- "version": "2.0.6-develop.4"
88
+ "version": "2.0.6-develop.6"
89
89
  }
@@ -1,2 +0,0 @@
1
- import{PrismaClient as E}from"@prisma/client";var I=new E,n=I;import{init as A}from"@sentry/node";import{dirname as k,resolve as h}from"node:path";import{fileURLToPath as x}from"node:url";import{readFileSync as P}from"node:fs";A({dsn:process.env.SENTRY_DSN||"https://5e4630d58e5f4c778ce22140c53b3684@glitch.sebbo.net/9"});var u=k(x(import.meta.url));u.endsWith("/dist")?u=h(u,"..","src"):u=h(u,"..");function y(l){return h(u,l||"")}var v;try{v=JSON.parse(P(y("../package.json"),"utf8")).version}catch(l){console.log("Failed getting tgtg-ical version:"),console.error(l)}var c={baseUrl:process.env.BASE_URL||"https://tgtg-ical.sebbo.net",baseMail:process.env.BASE_MAIL||"@tgtg-ical.sebbo.net",version:v,src:y};import{captureException as z}from"@sentry/node";import{simpleParser as B}from"mailparser";import b from"he";import i from"moment-timezone";var L="\u{1F374}",D={"\u{1F354}":[/Burger/,/McDonald[‘|']?s/,/Burger King/,/Hans im Glück/,/Peter Pane/],"\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{1F355}":[/Domino‘s/,/Pizza Hut/,/L[‘|']Osteria/,/Call a Pizza/,/Smiley[‘|']s Pizza/],"\u{1F357}":[/KFC/],"\u{1F35D}":[/LaTagliatella/,/Vapiano/],"\u{1F364}":[/Nordsee/],"\u{1F969}":[/Steakhouse/,/Block House/,/Jim Block/],"\u{1F32E}":[/Enchilada/,/Besitos/],"\u{1F32D}":[/Ikea/],"\u{1F957}":[/dean ?& ?david/],"\u{1F956}":[/Bäckerei/,/Back/,/BackWerk/,/Kamps/,/Kamps Backstuben/,/Junge Die Bäckerei/,/Back-Factory/],"\u{1F968}":[/Brezel/,/Ditsch/],"\u{1F950}":[/LeCroBag/],"\u{1F36A}":[/Starbucks/],"\u{1F369}":[/Donut/,/Dunkin/],"\u{1F6D2}":[/Edeka/,/Netto/,/Rewe/,/Penny/,/Lidl/,/Kaufland/,/Aldi/,/dm/,/Rossmann/,/Globus/,/Metro/,/Norma/,/Tegut/]};function g(l){for(let t in D)if(!!D[t].map(a=>new RegExp(a,"i")).find(a=>a.test(l)))return t;return null}var w=class{static async runCleanup(){let t=await n.mail.findMany({where:{OR:[{error:null},{version:null},{version:{not:c.version}}]},orderBy:{erroredAt:"asc"},take:10});for(let r of t)await this.handleMail(r);await n.user.deleteMany({where:{OR:[{lastSeenAt:{lt:i().subtract(8,"weeks").toDate()}},{lastSeenAt:{equals:n.user.fields.createdAt},createdAt:{lt:i().subtract(3,"hours").toDate()}}]}}),await n.event.deleteMany({where:{to:{lt:i().subtract(4,"weeks").toDate()}}}),await n.mail.deleteMany({where:{createdAt:{lt:i().subtract(2,"weeks").toDate()}}})}static async inhaleMail(t){let r=await n.mail.create({data:{raw:t}});await this.handleMail(r)}static async handleMail(t){try{let r=await this.parseMail(t.raw);r&&await this.applyParsedMail(r),await n.mail.delete({where:{id:t.id}})}catch(r){let e=z(r);await n.mail.update({where:{id:t.id},data:{error:r instanceof Error?r.stack:String(r),erroredAt:new Date,errorId:e,version:c.version}})}}static async parseMail(t,r=c.baseMail){let e=await B(t,{skipHtmlToText:!0,skipImageLinks:!0,skipTextToHtml:!0,skipTextLinks:!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 findUser(t){if(!t)throw new Error("Did not found a valid recipient!");let r=t.split("@")[0],e=await n.user.findUnique({where:{prefix:r}});if(!e)throw new Error(`User with email prefix ${r} not found!`);return e}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=i(t.date),d,s,p;if(e[2]&&(d=i.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",a),s=i.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",a),p=e[2][5].trim()),e[3]){let M=o.year();d=i.tz(e[3][1]+"."+M+" "+e[3][2],"DD.MM.YYYY HH:mm",a),s=i.tz(e[3][1]+"."+M+" "+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=i.tz(e[4][1]+" "+e[5][1],"DD.MM.YY HH:mm",a),s=i.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!");return{type:"order",orderId:e[0][1].trim(),location:{name:b.decode(e[1][1].trim()),address:b.decode(p)},time:{order:o,from:d,to:s},amount:m,price:f}}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=i.tz(r[1][1]+" "+r[1][2],"DD.MM.YY HH:mm",e),o=i.tz(r[1][1]+" "+r[1][3],"DD.MM.YY HH:mm",e);return{type:"change",orderId:r[0][1].trim(),time:{from:a,to:o}}}static parseCancellationMail(t){let e=(t.subject||"").match(/\((\w+)\)/);if(e)return{type:"cancel",orderId:e[1],cancelledAt:i(t.date)}}static parseInvoiceMail(t){let e=(t.html||"").match(/Die Rechnung für deine Bestellung (\w+)/);if(e)return{type:"invoice",orderId:e[1],invoicedAt:i(t.date)};throw new Error("Order Id not found!")}static async applyParsedMail(t){let r=await this.findUser(t.to);if(t.type==="order"){let e=await this.getLocation(t.location);await n.event.upsert({where:{orderId:t.orderId,userId:r.id},create:{orderId:t.orderId,orderedAt:t.time.order.toDate(),from:t.time.from.toDate(),to:t.time.to.toDate(),amount:t.amount,price:t.price,user:{connect:{id:r.id}},location:{connect:{id:e.id}}},update:{from:t.time.from.toDate(),to:t.time.to.toDate(),amount:t.amount,price:t.price,location:{connect:{id:e.id}}}})}else if(t.type==="change")await n.event.update({where:{orderId:t.orderId,userId:r.id},data:{from:t.time.from.toDate(),to:t.time.to.toDate()}});else if(t.type==="cancel")await n.event.update({where:{orderId:t.orderId,userId:r.id},data:{canceledAt:t.cancelledAt.toDate()}});else if(t.type==="invoice")await n.event.update({where:{orderId:t.orderId,userId:r.id},data:{invoicedAt:t.invoicedAt.toDate()}});else throw new Error("Unknown email type!")}static async getLocation(t){let r=await n.location.findFirst({where:{name:t.name,address:t.address}});if(!r){let e=g(t.name),{latitude:a,longitude:o}=await this.geocode(t.address);r=await n.location.create({data:{name:t.name,address:t.address,latitude:a,longitude:o,emoji:e}})}return r}static async geocode(t){let r=await fetch("https://nominatim.openstreetmap.org/search?format=json&limit=1&q="+encodeURIComponent(t),{headers:{"User-Agent":`tgtg-ical/${c.version} (${c.baseUrl})`,Referer: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)}}};export{n as a,c as b,L as c,w as d};
2
- //# sourceMappingURL=chunk-MU72W6UK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/db.ts","../src/lib/config.ts","../src/lib/parser.ts","../src/lib/emoji.ts"],"sourcesContent":["import { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nexport default prisma;\n","import { init } from '@sentry/node';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { readFileSync } from 'node:fs';\n\ninit({\n dsn: process.env.SENTRY_DSN || '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}\ncatch(error) {\n console.log('Failed getting tgtg-ical version:');\n console.error(error);\n}\n\nexport default {\n baseUrl: process.env.BASE_URL || 'https://tgtg-ical.sebbo.net',\n baseMail: process.env.BASE_MAIL || '@tgtg-ical.sebbo.net',\n version,\n src\n};\n","import type { Mail, User, Location } from '@prisma/client';\nimport { captureException } from '@sentry/node';\nimport { type ParsedMail, simpleParser } from 'mailparser';\nimport he from 'he';\nimport prisma from './db.js';\nimport config from './config.js';\nimport moment from 'moment-timezone';\nimport getEmoji from './emoji.js';\nimport Config from './config.js';\n\ntype AnyMail = { to: string; } & (OrderMail | ChangeMail | InvoiceMail | CancellationMail);\n\ninterface OrderMail {\n type: 'order';\n orderId: string;\n location: {\n name: string;\n address: string;\n };\n time: {\n order: moment.Moment;\n from: moment.Moment;\n to: moment.Moment;\n };\n amount: number;\n price: number;\n}\n\ninterface ChangeMail {\n type: 'change';\n orderId: string;\n time: {\n from: moment.Moment;\n to: moment.Moment;\n };\n}\n\ninterface InvoiceMail {\n type: 'invoice';\n orderId: string;\n invoicedAt: moment.Moment;\n}\n\ninterface CancellationMail {\n type: 'cancel';\n orderId: string;\n cancelledAt: moment.Moment;\n}\n\nexport default class Parser {\n public static async runCleanup(): Promise<void> {\n const mails = await prisma.mail.findMany({\n where: {\n OR: [\n { error: null },\n { version: null },\n { version: { not: config.version } }\n ]\n },\n orderBy: {\n erroredAt: 'asc'\n },\n take: 10\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 lastSeenAt: {\n equals: prisma.user.fields.createdAt\n },\n createdAt: {\n lt: moment().subtract(3, 'hours').toDate()\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 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 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 }\n catch(error) {\n const errorId = captureException(error);\n await prisma.mail.update({\n where: {\n id: mail.id\n },\n data: {\n error: error instanceof Error ? error.stack : String(error),\n erroredAt: new Date(),\n errorId,\n version: config.version\n }\n });\n }\n }\n\n public static async parseMail(mail: string, baseMailPostfix = config.baseMail): Promise<AnyMail|undefined> {\n // parse email\n const email = await simpleParser(mail, {\n skipHtmlToText: true,\n skipImageLinks: true,\n skipTextToHtml: true,\n skipTextLinks: 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 = (Array.isArray(email.to) ? email.to : [email.to])\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(!email.headers.get('x-pm-tag') && !email.headers.get('x-pm-message-id')) {\n return undefined;\n }\n\n // Unsupported email\n if(email.headers.get('x-pm-tag')) {\n throw new Error(`Unsupported email type: ${email.headers.get('x-pm-tag')}`);\n }\n\n throw new Error('Not implemented!');\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 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(/<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 html.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>/),\n html.match(/<b>Datum:<\\/b>\\s+<span>(\\d{1,2}\\.\\d{2}\\.\\d{2})<\\/span>/),\n html.match(/Abholzeit:<\\/b>\\s+<span>(\\d{1,2}:\\d{2}) - (\\d{1,2}:\\d{2}) (\\w+)/),\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(matches[2][1] + ' ' + matches[2][2], 'DD.MM.YY HH:mm', timezone);\n to = moment.tz(matches[2][1] + ' ' + matches[2][3], 'DD.MM.YY HH:mm', timezone);\n address = matches[2][5].trim();\n }\n if(matches[3]) {\n const year = now.year();\n from = moment.tz(matches[3][1] + '.' + year + ' ' + matches[3][2], 'DD.MM.YYYY HH:mm', timezone);\n to = moment.tz(matches[3][1] + '.' + year + ' ' + matches[3][3], 'DD.MM.YYYY HH:mm', timezone);\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(matches[4][1] + ' ' + matches[5][1], 'DD.MM.YY HH:mm', timezone);\n to = moment.tz(matches[4][1] + ' ' + matches[5][2], 'DD.MM.YY HH:mm', timezone);\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 }\n else if (matches[7]) {\n amount = parseInt(matches[7][1], 10);\n }\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\n let price = 0;\n if (matches[8]) {\n price = parseInt(matches[8][1].replace(/[.|,]/g, ''));\n }\n else if (matches[9]) {\n price = parseInt(matches[9][1].replace(/[.|,]/g, ''));\n }\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\n return {\n type: 'order',\n orderId: matches[0][1].trim(),\n location: {\n name: he.decode(matches[1][1].trim()),\n address: he.decode(address)\n },\n time: {\n order: now,\n from,\n to\n },\n amount,\n price\n };\n }\n\n private static parseChangeMail(email: ParsedMail): ChangeMail {\n const matches = [\n (email.html || '').match(/https:\\/\\/share.toogoodtogo.com\\/receipts\\/details\\/(\\w+)/),\n (email.html || '').match(/(\\d{1,2}\\.\\d{2}\\.\\d{2}) zwischen (\\d{1,2}:\\d{2}) und (\\d{1,2}:\\d{2})(?: Uhr)? (\\w+)+ \\(/)\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(matches[1][1] + ' ' + matches[1][2], 'DD.MM.YY HH:mm', timezone);\n const to = moment.tz(matches[1][1] + ' ' + matches[1][3], 'DD.MM.YY HH:mm', timezone);\n\n return {\n type: 'change',\n orderId: matches[0][1].trim(),\n time: {\n from,\n to\n }\n };\n }\n\n private static parseCancellationMail(email: ParsedMail): CancellationMail | undefined {\n const subject = email.subject || '';\n const match = subject.match(/\\((\\w+)\\)/);\n if(match) {\n return {\n type: 'cancel',\n orderId: match[1],\n cancelledAt: moment(email.date)\n };\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 type: 'invoice',\n orderId: match[1],\n invoicedAt: moment(email.date)\n };\n }\n\n throw new Error('Order Id not found!');\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 where: {\n orderId: email.orderId,\n userId: user.id\n },\n create: {\n orderId: email.orderId,\n orderedAt: email.time.order.toDate(),\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n amount: email.amount,\n price: email.price,\n user: {\n connect: {\n id: user.id\n }\n },\n location: {\n connect: {\n id: location.id\n }\n }\n },\n update: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate(),\n amount: email.amount,\n price: email.price,\n location: {\n connect: {\n id: location.id\n }\n }\n }\n });\n }\n else if(email.type === 'change') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n from: email.time.from.toDate(),\n to: email.time.to.toDate()\n }\n });\n }\n else if (email.type === 'cancel') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n canceledAt: email.cancelledAt.toDate()\n }\n });\n }\n else if (email.type === 'invoice') {\n await prisma.event.update({\n where: {\n orderId: email.orderId,\n userId: user.id\n },\n data: {\n invoicedAt: email.invoicedAt.toDate()\n }\n });\n }\n else {\n throw new Error('Unknown email type!');\n }\n }\n\n private static async getLocation(input: OrderMail['location']): Promise<Location> {\n let location = await prisma.location.findFirst({\n where: {\n name: input.name,\n address: input.address\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 name: input.name,\n address: input.address,\n latitude,\n longitude,\n emoji\n }\n });\n }\n\n return location;\n }\n\n public static async geocode(address: string): Promise<{ latitude: number, longitude: number } | { latitude: null, longitude: null }> {\n const response = await fetch('https://nominatim.openstreetmap.org/search?format=json&limit=1&q=' + encodeURIComponent(address), {\n headers: {\n 'User-Agent': `tgtg-ical/${config.version} (${config.baseUrl})`,\n 'Referer': config.baseUrl\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","export const DEFAULT_EMOJI = '🍴';\nexport const EMOJIS: Record<string, RegExp[]> = {\n '🍔': [\n /Burger/,\n /McDonald[‘|']?s/,\n /Burger King/,\n /Hans im Glück/,\n /Peter Pane/,\n\n ],\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 '🍕': [\n /Domino‘s/,\n /Pizza Hut/,\n /L[‘|']Osteria/,\n /Call a Pizza/,\n /Smiley[‘|']s Pizza/,\n\n ],\n '🍗': [\n /KFC/,\n\n ],\n '🍝': [\n /LaTagliatella/,\n /Vapiano/,\n\n ],\n '🍤': [\n /Nordsee/\n ],\n '🥩': [\n /Steakhouse/,\n /Block House/,\n /Jim Block/,\n\n ],\n '🌮': [\n /Enchilada/,\n /Besitos/,\n\n ],\n '🌭': [\n /Ikea/,\n\n ],\n '🥗': [\n /dean ?& ?david/\n ],\n '🥖': [\n /Bäckerei/,\n /Back/,\n /BackWerk/,\n /Kamps/,\n /Kamps Backstuben/,\n /Junge Die Bäckerei/,\n /Back-Factory/,\n\n ],\n '🥨': [\n /Brezel/,\n /Ditsch/,\n\n ],\n '🥐': [\n /LeCroBag/,\n\n ],\n '🍪': [\n /Starbucks/\n ],\n '🍩': [\n /Donut/,\n /Dunkin/,\n\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};\n\nexport default function getEmoji(location: string): string | null {\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":["PrismaClient","prisma","db_default","init","dirname","resolve","fileURLToPath","readFileSync","dir","src","path","version","error","config_default","captureException","simpleParser","he","moment","DEFAULT_EMOJI","EMOJIS","getEmoji","location","emoji","name","regExp","Parser","mails","db_default","config_default","mail","moment","email","parsed","error","errorId","captureException","baseMailPostfix","simpleParser","to","address","received","regexp","r","match","order","change","cancellation","invoice","prefix","user","html","matches","timezone","now","from","year","amount","price","he","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data"]}