@sebbo2002/tgtg-ical 1.0.3-develop.3 → 1.0.3-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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Sebastian Pekarek
3
+ Copyright (c) 2024 Sebastian Pekarek
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
6
  documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
@@ -1,2 +1,2 @@
1
- import{a as i,b as d,d as u}from"./chunk-HTOHNK4X.js";import{startTransaction as h,captureException as p}from"@sentry/node";import{simpleParser as w}from"mailparser";import f from"he";import n from"moment-timezone";var l=class{static async runCleanup(){let t=await i.mail.findMany({where:{OR:[{error:null},{version:{not:d.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){let r=h({op:"mail",name:"parse mail"});try{let e=await this.parseMail(t.raw);e&&await this.applyParsedMail(e),await i.mail.delete({where:{id:t.id}})}catch(e){let o=p(e);await i.mail.update({where:{id:t.id},data:{error:e instanceof Error?e.stack:String(e),erroredAt:new Date,errorId:o,version:d.version}})}finally{r.finish()}}static async parseMail(t,r=d.baseMail){let e=await w(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 o=(Array.isArray(e.to)?e.to:[e.to]).map(a=>a.value).flat().map(a=>a.address).find(a=>a.endsWith(r));if(!o){let a=e.headers.get("received"),s=new RegExp(`([\\w-]+${d.baseMail})`,"i");Array.isArray(a)&&a.forEach(m=>{let c=m.match(s);c&&(o=c[1])})}if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let a=this.parseOrderMail(e);return{to:o,...a}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let a=this.parseChangeMail(e);return{to:o,...a}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let a=this.parseCancellationMail(e);if(a)return{to:o,...a}}if(e.headers.get("x-pm-tag")==="invoice"){let a=this.parseInvoiceMail(e);if(a)return{to:o,...a}}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(/Anzahl: (\d+)/),r.match(/Gesamtpreis: ([\d,.]+)[^\d,.]/)];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])throw new Error("Date, time and address not found (1)!");if(!e[4])throw new Error("Amount not found!");if(!e[5])throw new Error("Price not found!");let o=e[2]?e[2][4]:"MET";o==="MEZ"&&(o="MET");let a,s;if(e[2]&&(a=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",o),s=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",o)),e[3]&&(a=n.tz(e[3][1]+" "+e[3][2],"DD.MM HH:mm",o),s=n.tz(e[3][1]+" "+e[3][3],"DD.MM HH:mm",o),a.isBefore(n())&&a.add(1,"year"),s.isBefore(a)&&s.add(1,"year")),!e[2]&&!e[3])throw new Error("Date, time and address not found (1)!");let m=parseInt(e[4][1],10);if(isNaN(m))throw new Error("Amount is not a number!");let c=parseInt(e[5][1].replace(/[.|,]/g,""));if(isNaN(c))throw new Error("Price is not a number!");return{type:"order",orderId:e[0][1].trim(),location:{name:f.decode(e[1][1].trim()),address:f.decode(e[2]?e[2][5].trim():e[3][4].trim())},time:{order:n(t.date),from:a,to:s},amount:m,price:c}}static parseChangeMail(t){let r=[(t.subject||"").match(/(\w+)$/),(t.html||"").match(/ am (\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 in subject!");if(!r[1])throw new Error("Date / Time not found!");let e=r[1][4];e==="MEZ"&&(e="MET");let o=n.tz(r[1][1]+" "+r[1][2],"DD.MM.YY HH:mm",e),a=n.tz(r[1][1]+" "+r[1][3],"DD.MM.YY HH:mm",e);return{type:"change",orderId:r[0][1].trim(),time:{from:o,to:a}}}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=u(t.name),{latitude:o,longitude:a}=await this.geocode(t.address);r=await i.location.create({data:{name:t.name,address:t.address,latitude:o,longitude:a,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/${d.version} (${d.baseUrl})`,Referer:d.baseUrl}});if(!r.ok)throw new Error("Geocoding failed: "+r.statusText);await new Promise(o=>setTimeout(o,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{l as a};
2
- //# sourceMappingURL=chunk-F63LK4UL.js.map
1
+ import{a as i,b as d,d as h}from"./chunk-HTOHNK4X.js";import{startTransaction as f,captureException as p}from"@sentry/node";import{simpleParser as w}from"mailparser";import u from"he";import n from"moment-timezone";var l=class{static async runCleanup(){let t=await i.mail.findMany({where:{OR:[{error:null},{version:{not:d.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){let r=f({op:"mail",name:"parse mail"});try{let e=await this.parseMail(t.raw);e&&await this.applyParsedMail(e),await i.mail.delete({where:{id:t.id}})}catch(e){let o=p(e);await i.mail.update({where:{id:t.id},data:{error:e instanceof Error?e.stack:String(e),erroredAt:new Date,errorId:o,version:d.version}})}finally{r.finish()}}static async parseMail(t,r=d.baseMail){let e=await w(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 o=(Array.isArray(e.to)?e.to:[e.to]).map(a=>a.value).flat().map(a=>a.address).find(a=>a.endsWith(r));if(!o){let a=e.headers.get("received"),s=new RegExp(`([\\w-]+${d.baseMail})`,"i");Array.isArray(a)&&a.forEach(m=>{let c=m.match(s);c&&(o=c[1])})}if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let a=this.parseOrderMail(e);return{to:o,...a}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let a=this.parseChangeMail(e);return{to:o,...a}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let a=this.parseCancellationMail(e);if(a)return{to:o,...a}}if(e.headers.get("x-pm-tag")==="invoice"){let a=this.parseInvoiceMail(e);if(a)return{to:o,...a}}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(/Anzahl: (\d+)/),r.match(/Gesamtpreis: ([\d,.]+)[^\d,.]/)];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])throw new Error("Date, time and address not found (1)!");if(!e[4])throw new Error("Amount not found!");if(!e[5])throw new Error("Price not found!");let o=e[2]?e[2][4]:"MET";o==="MEZ"&&(o="MET");let a,s;if(e[2]&&(a=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",o),s=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",o)),e[3]&&(a=n.tz(e[3][1]+" "+e[3][2],"DD.MM HH:mm",o),s=n.tz(e[3][1]+" "+e[3][3],"DD.MM HH:mm",o),a.isBefore(n())&&a.add(1,"year"),s.isBefore(a)&&s.add(1,"year")),!e[2]&&!e[3])throw new Error("Date, time and address not found (1)!");let m=parseInt(e[4][1],10);if(isNaN(m))throw new Error("Amount is not a number!");let c=parseInt(e[5][1].replace(/[.|,]/g,""));if(isNaN(c))throw new Error("Price is not a number!");return{type:"order",orderId:e[0][1].trim(),location:{name:u.decode(e[1][1].trim()),address:u.decode(e[2]?e[2][5].trim():e[3][4].trim())},time:{order:n(t.date),from:a,to:s},amount:m,price:c}}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 o=n.tz(r[1][1]+" "+r[1][2],"DD.MM.YY HH:mm",e),a=n.tz(r[1][1]+" "+r[1][3],"DD.MM.YY HH:mm",e);return{type:"change",orderId:r[0][1].trim(),time:{from:o,to:a}}}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=h(t.name),{latitude:o,longitude:a}=await this.geocode(t.address);r=await i.location.create({data:{name:t.name,address:t.address,latitude:o,longitude:a,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/${d.version} (${d.baseUrl})`,Referer:d.baseUrl}});if(!r.ok)throw new Error("Geocoding failed: "+r.statusText);await new Promise(o=>setTimeout(o,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{l as a};
2
+ //# sourceMappingURL=chunk-VVNPXVKY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/parser.ts"],"sourcesContent":["import { Mail, User, Location } from '@prisma/client';\nimport { startTransaction, captureException } from '@sentry/node';\nimport { 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: { 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 // @todo Sentry Transaction (https://docs.sentry.io/platforms/node/#verify)\n const transaction = startTransaction({\n op: 'mail',\n name: 'parse mail'\n });\n\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 finally {\n transaction.finish();\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 .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 match = r.match(regexp);\n if(match) {\n to = match[1];\n }\n });\n }\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(/Anzahl: (\\d+)/),\n html.match(/Gesamtpreis: ([\\d,.]+)[^\\d,.]/)\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]) {\n throw new Error('Date, time and address not found (1)!');\n }\n if(!matches[4]) {\n throw new Error('Amount not found!');\n }\n if(!matches[5]) {\n throw new Error('Price not found!');\n }\n\n let timezone = matches[2] ? matches[2][4] : 'MET';\n if(timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n let from: moment.Moment | undefined;\n let to: moment.Moment | 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 }\n if(matches[3]) {\n from = moment.tz(matches[3][1] + ' ' + matches[3][2], 'DD.MM HH:mm', timezone);\n to = moment.tz(matches[3][1] + ' ' + matches[3][3], 'DD.MM HH:mm', timezone);\n\n if(from.isBefore(moment())) {\n from.add(1, 'year');\n }\n if(to.isBefore(from)) {\n to.add(1, 'year');\n }\n }\n\n if(!matches[2] && !matches[3]) {\n throw new Error('Date, time and address not found (1)!');\n }\n\n const amount = parseInt(matches[4][1], 10);\n if(isNaN(amount)) {\n throw new Error('Amount is not a number!');\n }\n\n const price = parseInt(matches[5][1].replace(/[.|,]/g, ''));\n if(isNaN(price)) {\n throw new Error('Price is not a number!');\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(matches[2] ? matches[2][5].trim() : matches[3][4].trim())\n },\n time: {\n order: moment(email.date),\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"],"mappings":"sDACA,OAAS,oBAAAA,EAAkB,oBAAAC,MAAwB,eACnD,OAAqB,gBAAAC,MAAoB,aACzC,OAAOC,MAAQ,KAGf,OAAOC,MAAY,kBA2CnB,IAAqBC,EAArB,KAA4B,CACxB,aAAoB,YAA4B,CAC5C,IAAMC,EAAQ,MAAMC,EAAO,KAAK,SAAS,CACrC,MAAO,CACH,GAAI,CACA,CAAE,MAAO,IAAK,EACd,CAAE,QAAS,CAAE,IAAKC,EAAO,OAAQ,CAAE,CACvC,CACJ,EACA,QAAS,CACL,UAAW,KACf,EACA,KAAM,EACV,CAAC,EAED,QAAUC,KAAQH,EACd,MAAM,KAAK,WAAWG,CAAI,EAI9B,MAAMF,EAAO,KAAK,WAAW,CACzB,MAAO,CACH,GAAI,CACA,CACI,WAAY,CACR,GAAIG,EAAO,EAAE,SAAS,EAAG,OAAO,EAAE,OAAO,CAC7C,CACJ,EACA,CACI,WAAY,CACR,OAAQH,EAAO,KAAK,OAAO,SAC/B,EACA,UAAW,CACP,GAAIG,EAAO,EAAE,SAAS,EAAG,OAAO,EAAE,OAAO,CAC7C,CACJ,CACJ,CACJ,CACJ,CAAC,EAGD,MAAMH,EAAO,MAAM,WAAW,CAC1B,MAAO,CACH,GAAI,CACA,GAAIG,EAAO,EAAE,SAAS,EAAG,OAAO,EAAE,OAAO,CAC7C,CACJ,CACJ,CAAC,EAGD,MAAMH,EAAO,KAAK,WAAW,CACzB,MAAO,CACH,UAAW,CACP,GAAIG,EAAO,EAAE,SAAS,EAAG,OAAO,EAAE,OAAO,CAC7C,CACJ,CACJ,CAAC,CACL,CAEA,aAAoB,WAAYC,EAA8B,CAC1D,IAAMF,EAAO,MAAMF,EAAO,KAAK,OAAO,CAClC,KAAM,CACF,IAAKI,CACT,CACJ,CAAC,EAED,MAAM,KAAK,WAAWF,CAAI,CAC9B,CAEA,aAAoB,WAAWA,EAAY,CAEvC,IAAMG,EAAcC,EAAiB,CACjC,GAAI,OACJ,KAAM,YACV,CAAC,EAED,GAAI,CACA,IAAMC,EAAS,MAAM,KAAK,UAAUL,EAAK,GAAG,EACzCK,GACC,MAAM,KAAK,gBAAgBA,CAAM,EAGrC,MAAMP,EAAO,KAAK,OAAO,CACrB,MAAO,CACH,GAAIE,EAAK,EACb,CACJ,CAAC,CACL,OACMM,EAAO,CACT,IAAMC,EAAUC,EAAiBF,CAAK,EACtC,MAAMR,EAAO,KAAK,OAAO,CACrB,MAAO,CACH,GAAIE,EAAK,EACb,EACA,KAAM,CACF,MAAOM,aAAiB,MAAQA,EAAM,MAAQ,OAAOA,CAAK,EAC1D,UAAW,IAAI,KACf,QAAAC,EACA,QAASR,EAAO,OACpB,CACJ,CAAC,CACL,QACA,CACII,EAAY,OAAO,CACvB,CACJ,CAEA,aAAoB,UAAUH,EAAcS,EAAkBV,EAAO,SAAsC,CAEvG,IAAMG,EAAQ,MAAMQ,EAAaV,EAAM,CACnC,eAAgB,GAChB,eAAgB,GAChB,eAAgB,GAChB,cAAe,EACnB,CAAC,EAED,GAAG,CAACE,EAAM,KAAK,MAAM,CAAC,EAAE,QAAQ,SAAS,iBAAiB,EACtD,MAAM,IAAI,MAAM,mBAAmB,EAGvC,IAAIS,GAA0B,MAAM,QAAQT,EAAM,EAAE,EAAIA,EAAM,GAAK,CAACA,EAAM,EAAE,GACvE,IAAIS,GAAMA,EAAG,KAAK,EAClB,KAAK,EACL,IAAIC,GAAWA,EAAQ,OAAO,EAC9B,KAAKA,GAAWA,EAAQ,SAASH,CAAe,CAAC,EAEtD,GAAG,CAACE,EAAI,CACJ,IAAME,EAAWX,EAAM,QAAQ,IAAI,UAAU,EACvCY,EAAS,IAAI,OAAO,WAAWf,EAAO,QAAQ,IAAK,GAAG,EACzD,MAAM,QAAQc,CAAQ,GACrBA,EAAS,QAAQE,GAAK,CAClB,IAAMC,EAAQD,EAAE,MAAMD,CAAM,EACzBE,IACCL,EAAKK,EAAM,CAAC,EAEpB,CAAC,CAET,CAGA,GAAGd,EAAM,QAAQ,IAAI,UAAU,IAAM,yBAA0B,CAC3D,IAAMe,EAAQ,KAAK,eAAef,CAAK,EACvC,MAAO,CACH,GAAAS,EACA,GAAGM,CACP,CACJ,CAGA,GAAGf,EAAM,QAAQ,IAAI,UAAU,IAAM,0BAA2B,CAC5D,IAAMgB,EAAS,KAAK,gBAAgBhB,CAAK,EACzC,MAAO,CACH,GAAAS,EACA,GAAGO,CACP,CACJ,CAGA,GAAGhB,EAAM,QAAQ,IAAI,UAAU,IAAM,0BAA2B,CAC5D,IAAMiB,EAAe,KAAK,sBAAsBjB,CAAK,EACrD,GAAGiB,EACC,MAAO,CACH,GAAAR,EACA,GAAGQ,CACP,CAER,CAGA,GAAGjB,EAAM,QAAQ,IAAI,UAAU,IAAM,UAAW,CAC5C,IAAMkB,EAAU,KAAK,iBAAiBlB,CAAK,EAC3C,GAAGkB,EACC,MAAO,CACH,GAAAT,EACA,GAAGS,CACP,CAER,CAGA,GAAG,GAAClB,EAAM,QAAQ,IAAI,UAAU,GAAK,CAACA,EAAM,QAAQ,IAAI,iBAAiB,GAKzE,MAAGA,EAAM,QAAQ,IAAI,UAAU,EACrB,IAAI,MAAM,2BAA2BA,EAAM,QAAQ,IAAI,UAAU,CAAC,EAAE,EAGxE,IAAI,MAAM,kBAAkB,CACtC,CAEA,aAAqB,SAASS,EAA2B,CACrD,GAAG,CAACA,EACA,MAAM,IAAI,MAAM,kCAAkC,EAGtD,IAAMU,EAASV,EAAG,MAAM,GAAG,EAAE,CAAC,EACxBW,EAAO,MAAMxB,EAAO,KAAK,WAAW,CACtC,MAAO,CAAE,OAAAuB,CAAO,CACpB,CAAC,EACD,GAAG,CAACC,EACA,MAAM,IAAI,MAAM,0BAA0BD,CAAM,aAAa,EAGjE,OAAOC,CACX,CAEA,OAAe,eAAepB,EAA8B,CACxD,IAAMqB,EAAOrB,EAAM,MAAQ,GACrBsB,EAAU,CACZD,EAAK,MAAM,qBAAqB,EAChCA,EAAK,MAAM,wDAAwD,EACnEA,EAAK,MAAM,+IAA+I,EAC1JA,EAAK,MAAM,8HAA8H,EACzIA,EAAK,MAAM,eAAe,EAC1BA,EAAK,MAAM,+BAA+B,CAC9C,EACA,GAAG,CAACC,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,qBAAqB,EAEzC,GAAG,CAACA,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,0BAA0B,EAE9C,GAAG,CAACA,EAAQ,CAAC,GAAK,CAACA,EAAQ,CAAC,EACxB,MAAM,IAAI,MAAM,uCAAuC,EAE3D,GAAG,CAACA,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,mBAAmB,EAEvC,GAAG,CAACA,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,kBAAkB,EAGtC,IAAIC,EAAWD,EAAQ,CAAC,EAAIA,EAAQ,CAAC,EAAE,CAAC,EAAI,MACzCC,IAAa,QACZA,EAAW,OAGf,IAAIC,EACAf,EAkBJ,GAhBGa,EAAQ,CAAC,IACRE,EAAOzB,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,iBAAkBC,CAAQ,EAChFd,EAAKV,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,iBAAkBC,CAAQ,GAE/ED,EAAQ,CAAC,IACRE,EAAOzB,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,cAAeC,CAAQ,EAC7Ed,EAAKV,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,cAAeC,CAAQ,EAExEC,EAAK,SAASzB,EAAO,CAAC,GACrByB,EAAK,IAAI,EAAG,MAAM,EAEnBf,EAAG,SAASe,CAAI,GACff,EAAG,IAAI,EAAG,MAAM,GAIrB,CAACa,EAAQ,CAAC,GAAK,CAACA,EAAQ,CAAC,EACxB,MAAM,IAAI,MAAM,uCAAuC,EAG3D,IAAMG,EAAS,SAASH,EAAQ,CAAC,EAAE,CAAC,EAAG,EAAE,EACzC,GAAG,MAAMG,CAAM,EACX,MAAM,IAAI,MAAM,yBAAyB,EAG7C,IAAMC,EAAQ,SAASJ,EAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,SAAU,EAAE,CAAC,EAC1D,GAAG,MAAMI,CAAK,EACV,MAAM,IAAI,MAAM,wBAAwB,EAG5C,MAAO,CACH,KAAM,QACN,QAASJ,EAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,EAC5B,SAAU,CACN,KAAMK,EAAG,OAAOL,EAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,EACpC,QAASK,EAAG,OAAOL,EAAQ,CAAC,EAAIA,EAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,EAAIA,EAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAC/E,EACA,KAAM,CACF,MAAOvB,EAAOC,EAAM,IAAI,EACxB,KAAAwB,EACA,GAAAf,CACJ,EACA,OAAAgB,EACA,MAAAC,CACJ,CACJ,CAEA,OAAe,gBAAgB1B,EAA+B,CAC1D,IAAMsB,EAAU,EACXtB,EAAM,MAAQ,IAAI,MAAM,2DAA2D,GACnFA,EAAM,MAAQ,IAAI,MAAM,yFAAyF,CACtH,EACA,GAAG,CAACsB,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,qBAAqB,EAEzC,GAAG,CAACA,EAAQ,CAAC,EACT,MAAM,IAAI,MAAM,wBAAwB,EAG5C,IAAIC,EAAWD,EAAQ,CAAC,EAAE,CAAC,EACxBC,IAAa,QACZA,EAAW,OAGf,IAAMC,EAAOzB,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,iBAAkBC,CAAQ,EAChFd,EAAKV,EAAO,GAAGuB,EAAQ,CAAC,EAAE,CAAC,EAAI,IAAMA,EAAQ,CAAC,EAAE,CAAC,EAAG,iBAAkBC,CAAQ,EAEpF,MAAO,CACH,KAAM,SACN,QAASD,EAAQ,CAAC,EAAE,CAAC,EAAE,KAAK,EAC5B,KAAM,CACF,KAAAE,EACA,GAAAf,CACJ,CACJ,CACJ,CAEA,OAAe,sBAAsBT,EAAiD,CAElF,IAAMc,GADUd,EAAM,SAAW,IACX,MAAM,WAAW,EACvC,GAAGc,EACC,MAAO,CACH,KAAM,SACN,QAASA,EAAM,CAAC,EAChB,YAAaf,EAAOC,EAAM,IAAI,CAClC,CAER,CAEA,OAAe,iBAAiBA,EAAgC,CAE5D,IAAMc,GADOd,EAAM,MAAQ,IACR,MAAM,yCAAyC,EAClE,GAAGc,EACC,MAAO,CACH,KAAM,UACN,QAASA,EAAM,CAAC,EAChB,WAAYf,EAAOC,EAAM,IAAI,CACjC,EAGJ,MAAM,IAAI,MAAM,qBAAqB,CACzC,CAEA,aAAqB,gBAAgBA,EAA+B,CAChE,IAAMoB,EAAO,MAAM,KAAK,SAASpB,EAAM,EAAE,EAEzC,GAAGA,EAAM,OAAS,QAAS,CACvB,IAAM4B,EAAW,MAAM,KAAK,YAAY5B,EAAM,QAAQ,EAEtD,MAAMJ,EAAO,MAAM,OAAO,CACtB,MAAO,CACH,QAASI,EAAM,QACf,OAAQoB,EAAK,EACjB,EACA,OAAQ,CACJ,QAASpB,EAAM,QACf,UAAWA,EAAM,KAAK,MAAM,OAAO,EACnC,KAAMA,EAAM,KAAK,KAAK,OAAO,EAC7B,GAAIA,EAAM,KAAK,GAAG,OAAO,EACzB,OAAQA,EAAM,OACd,MAAOA,EAAM,MACb,KAAM,CACF,QAAS,CACL,GAAIoB,EAAK,EACb,CACJ,EACA,SAAU,CACN,QAAS,CACL,GAAIQ,EAAS,EACjB,CACJ,CACJ,EACA,OAAQ,CACJ,KAAM5B,EAAM,KAAK,KAAK,OAAO,EAC7B,GAAIA,EAAM,KAAK,GAAG,OAAO,EACzB,OAAQA,EAAM,OACd,MAAOA,EAAM,MACb,SAAU,CACN,QAAS,CACL,GAAI4B,EAAS,EACjB,CACJ,CACJ,CACJ,CAAC,CACL,SACQ5B,EAAM,OAAS,SACnB,MAAMJ,EAAO,MAAM,OAAO,CACtB,MAAO,CACH,QAASI,EAAM,QACf,OAAQoB,EAAK,EACjB,EACA,KAAM,CACF,KAAMpB,EAAM,KAAK,KAAK,OAAO,EAC7B,GAAIA,EAAM,KAAK,GAAG,OAAO,CAC7B,CACJ,CAAC,UAEIA,EAAM,OAAS,SACpB,MAAMJ,EAAO,MAAM,OAAO,CACtB,MAAO,CACH,QAASI,EAAM,QACf,OAAQoB,EAAK,EACjB,EACA,KAAM,CACF,WAAYpB,EAAM,YAAY,OAAO,CACzC,CACJ,CAAC,UAEIA,EAAM,OAAS,UACpB,MAAMJ,EAAO,MAAM,OAAO,CACtB,MAAO,CACH,QAASI,EAAM,QACf,OAAQoB,EAAK,EACjB,EACA,KAAM,CACF,WAAYpB,EAAM,WAAW,OAAO,CACxC,CACJ,CAAC,MAGD,OAAM,IAAI,MAAM,qBAAqB,CAE7C,CAEA,aAAqB,YAAY6B,EAAiD,CAC9E,IAAID,EAAW,MAAMhC,EAAO,SAAS,UAAU,CAC3C,MAAO,CACH,KAAMiC,EAAM,KACZ,QAASA,EAAM,OACnB,CACJ,CAAC,EACD,GAAG,CAACD,EAAU,CACV,IAAME,EAAQC,EAASF,EAAM,IAAI,EAC3B,CAAE,SAAAG,EAAU,UAAAC,CAAU,EAAI,MAAM,KAAK,QAAQJ,EAAM,OAAO,EAChED,EAAW,MAAMhC,EAAO,SAAS,OAAO,CACpC,KAAM,CACF,KAAMiC,EAAM,KACZ,QAASA,EAAM,QACf,SAAAG,EACA,UAAAC,EACA,MAAAH,CACJ,CACJ,CAAC,CACL,CAEA,OAAOF,CACX,CAEA,aAAoB,QAAQlB,EAAyG,CACjI,IAAMwB,EAAW,MAAM,MAAM,oEAAsE,mBAAmBxB,CAAO,EAAG,CAC5H,QAAS,CACL,aAAc,aAAab,EAAO,OAAO,KAAKA,EAAO,OAAO,IAC5D,QAAWA,EAAO,OACtB,CACJ,CAAC,EACD,GAAG,CAACqC,EAAS,GACT,MAAM,IAAI,MAAM,qBAAuBA,EAAS,UAAU,EAI9D,MAAM,IAAI,QAAQC,GAAW,WAAWA,EAAS,GAAI,CAAC,EAEtD,IAAMC,EAAO,MAAMF,EAAS,KAAK,EACjC,MAAG,CAAC,MAAM,QAAQE,CAAI,GAAKA,EAAK,SAAW,EAChC,CACH,SAAU,KACV,UAAW,IACf,EAGG,CACH,SAAU,WAAWA,EAAK,CAAC,EAAE,GAAG,EAChC,UAAW,WAAWA,EAAK,CAAC,EAAE,GAAG,CACrC,CACJ,CACJ","names":["startTransaction","captureException","simpleParser","he","moment","Parser","mails","db_default","config_default","mail","moment","email","transaction","startTransaction","parsed","error","errorId","captureException","baseMailPostfix","simpleParser","to","address","received","regexp","r","match","order","change","cancellation","invoice","prefix","user","html","matches","timezone","from","amount","price","he","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data"]}
package/dist/cleanup.d.ts CHANGED
File without changes
package/dist/cleanup.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as r}from"./chunk-F63LK4UL.js";import"./chunk-HTOHNK4X.js";r.runCleanup().catch(e=>{console.error(e),process.exit(1)});
2
+ import{a as r}from"./chunk-VVNPXVKY.js";import"./chunk-HTOHNK4X.js";r.runCleanup().catch(e=>{console.error(e),process.exit(1)});
3
3
  //# sourceMappingURL=cleanup.js.map
File without changes
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as r}from"./chunk-F63LK4UL.js";import"./chunk-HTOHNK4X.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{a as r}from"./chunk-VVNPXVKY.js";import"./chunk-HTOHNK4X.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.d.ts CHANGED
File without changes
package/dist/start.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import{a as i,b as o,c as d}from"./chunk-HTOHNK4X.js";import p from"express";import y from"cookie-parser";import{randomUUID as h}from"crypto";import{generateName as f,generateNameWithNumber as g}from"@criblinc/docker-names";import{ICalAlarmType as w,ICalCalendar as E,ICalEventStatus as l}from"ical-generator";import I 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 i.user.create({data:{prefix:r}}),e)break}catch(a){if(a.code==="P2002")continue;throw a}}if(!e)throw new Error("User not created");return e}static generatePrefix(e=0){return e>100?h():e<10?f():g()}static async getUser(e){let t=await i.user.findUniqueOrThrow({where:{id:e}});return this.updateUserLastSeen(t.id),t}static updateUserLastSeen(e){i.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(o.src("templates/user.html"),"utf-8")]);return r.replace(/\${CALENDAR_URL}/g,`${o.baseUrl}/${t.id}/calendar.ical`).replace(/\${EMAIL_ADDRESS}/g,`${t.prefix}${o.baseMail}`)}static async generateCalendar(e){let t=await i.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 m=new Intl.NumberFormat("de-DE",{style:"currency",currency:"EUR"}).format(a.price/100),u=l.CONFIRMED;return a.canceledAt&&(u=l.CANCELLED),{id:a.id,start:a.from,end:a.to,timestamp:a.createdAt,summary:`${a.location.emoji||d} ${a.location.name}`,description:`${a.amount}x
3
- ${m}`,url:`https://share.toogoodtogo.com/receipts/details/${a.orderId}`,status:u,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:w.display,trigger:600}]}})});return this.updateUserLastSeen(e),r.toString()}static async isHealthy(){let e=await i.mail.count({where:{createdAt:{gte:I().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=p(),this.app.use(y()),this.setupRoutes(),this.server=this.app.listen(process.env.PORT||8080),process.on("SIGINT",()=>this.stop()),process.on("SIGTERM",()=>this.stop())}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(o.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(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 i.$disconnect(),process.exit()}};n.run();
3
+ ${m}`,url:`https://share.toogoodtogo.com/receipts/details/${a.orderId}`,status:u,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:w.display,trigger:600}]}})});return this.updateUserLastSeen(e),r.toString()}static async isHealthy(){let e=await i.mail.count({where:{createdAt:{lt:I().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=p(),this.app.use(y()),this.setupRoutes(),this.server=this.app.listen(process.env.PORT||8080),process.on("SIGINT",()=>this.stop()),process.on("SIGTERM",()=>this.stop())}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(o.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(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 i.$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 express, { Express, Response } from 'express';\nimport cookieParser from 'cookie-parser';\nimport {Server} from 'http';\n\nimport prisma from '../lib/db.js';\nimport ServerLib from '../lib/server.js';\nimport Config from '../lib/config';\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';\n\n\nclass AppServer {\n static run() {\n new AppServer();\n }\n\n private app: Express;\n private server: Server;\n\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\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\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 '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 'application/json': () => {\n ServerLib.getUser(req.params.userId)\n .then(json => res.send(json))\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 handleError(error: PrismaClientKnownRequestError | unknown, res: Response) {\n if(typeof error === 'object' && 'code' in error && error.code === 'P2025') {\n res.sendStatus(404);\n return;\n }\n\n console.log(error);\n res.sendStatus(500);\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 prisma from './db.js';\nimport { randomUUID } from 'node:crypto';\nimport { generateName, generateNameWithNumber } from '@criblinc/docker-names';\nimport { User } from '@prisma/client';\nimport { ICalAlarmType, ICalCalendar, ICalEventStatus } from 'ical-generator';\nimport { DEFAULT_EMOJI } from './emoji.js';\nimport moment from 'moment-timezone';\nimport { readFile } from 'fs/promises';\nimport Config from './config.js';\n\nexport default class ServerLib {\n static async createUser() {\n let user: User | undefined;\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 }\n catch(error) {\n if(error.code === 'P2002') {\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 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 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 updateUserLastSeen(userId: string) {\n prisma.user.update({\n where: { id: userId },\n data: { lastSeenAt: new Date() }\n }).catch(error => {\n console.log(error);\n });\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(/\\${CALENDAR_URL}/g, `${Config.baseUrl}/${user.id}/calendar.ical`)\n .replace(/\\${EMAIL_ADDRESS}/g, `${user.prefix}${Config.baseMail}`);\n }\n\n static async generateCalendar(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n where: {\n id: userId\n },\n select: {\n id: true,\n event: {\n select: {\n id: true,\n orderId: true,\n from: true,\n to: true,\n amount: true,\n price: true,\n location: {\n select: {\n name: true,\n address: true,\n emoji: true,\n latitude: true,\n longitude: true\n }\n },\n createdAt: true,\n canceledAt: true\n }\n }\n }\n });\n\n const cal = new ICalCalendar({\n name: 'TGTG',\n ttl: 60 * 60,\n events: user.event.map(event => {\n const price = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })\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 id: event.id,\n start: event.from,\n end: event.to,\n timestamp: event.createdAt,\n summary: `${event.location.emoji || DEFAULT_EMOJI} ${event.location.name}`,\n description: `${event.amount}x\\n${price}`,\n url: `https://share.toogoodtogo.com/receipts/details/${event.orderId}`,\n status,\n created: event.createdAt,\n location: {\n title: event.location.name,\n address: event.location.address,\n geo: event.location.latitude && event.location.longitude ? {\n lat: event.location.latitude,\n lon: event.location.longitude\n } : undefined\n },\n alarms: [\n {type: ICalAlarmType.display, trigger: 600},\n ]\n };\n })\n });\n\n this.updateUserLastSeen(userId);\n return cal.toString();\n }\n\n static async isHealthy() {\n const c = await prisma.mail.count({\n where: {\n createdAt: {\n gte: 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"],"mappings":"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","names":["express","cookieParser","randomUUID","generateName","generateNameWithNumber","ICalAlarmType","ICalCalendar","ICalEventStatus","moment","readFile","ServerLib","user","c","prefix","db_default","error","randomUUID","generateName","generateNameWithNumber","userId","html","readFile","config_default","cal","ICalCalendar","event","price","status","ICalEventStatus","DEFAULT_EMOJI","ICalAlarmType","moment","AppServer","_AppServer","express","cookieParser","req","res","ServerLib","user","error","config_default","html","json","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 express, { Express, Response } from 'express';\nimport cookieParser from 'cookie-parser';\nimport {Server} from 'http';\n\nimport prisma from '../lib/db.js';\nimport ServerLib from '../lib/server.js';\nimport Config from '../lib/config';\nimport type { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';\n\n\nclass AppServer {\n static run() {\n new AppServer();\n }\n\n private app: Express;\n private server: Server;\n\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\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\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 '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 'application/json': () => {\n ServerLib.getUser(req.params.userId)\n .then(json => res.send(json))\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 handleError(error: PrismaClientKnownRequestError | unknown, res: Response) {\n if(typeof error === 'object' && 'code' in error && error.code === 'P2025') {\n res.sendStatus(404);\n return;\n }\n\n console.log(error);\n res.sendStatus(500);\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 prisma from './db.js';\nimport { randomUUID } from 'node:crypto';\nimport { generateName, generateNameWithNumber } from '@criblinc/docker-names';\nimport { User } from '@prisma/client';\nimport { ICalAlarmType, ICalCalendar, ICalEventStatus } from 'ical-generator';\nimport { DEFAULT_EMOJI } from './emoji.js';\nimport moment from 'moment-timezone';\nimport { readFile } from 'fs/promises';\nimport Config from './config.js';\n\nexport default class ServerLib {\n static async createUser() {\n let user: User | undefined;\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 }\n catch(error) {\n if(error.code === 'P2002') {\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 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 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 updateUserLastSeen(userId: string) {\n prisma.user.update({\n where: { id: userId },\n data: { lastSeenAt: new Date() }\n }).catch(error => {\n console.log(error);\n });\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(/\\${CALENDAR_URL}/g, `${Config.baseUrl}/${user.id}/calendar.ical`)\n .replace(/\\${EMAIL_ADDRESS}/g, `${user.prefix}${Config.baseMail}`);\n }\n\n static async generateCalendar(userId: string) {\n const user = await prisma.user.findUniqueOrThrow({\n where: {\n id: userId\n },\n select: {\n id: true,\n event: {\n select: {\n id: true,\n orderId: true,\n from: true,\n to: true,\n amount: true,\n price: true,\n location: {\n select: {\n name: true,\n address: true,\n emoji: true,\n latitude: true,\n longitude: true\n }\n },\n createdAt: true,\n canceledAt: true\n }\n }\n }\n });\n\n const cal = new ICalCalendar({\n name: 'TGTG',\n ttl: 60 * 60,\n events: user.event.map(event => {\n const price = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })\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 id: event.id,\n start: event.from,\n end: event.to,\n timestamp: event.createdAt,\n summary: `${event.location.emoji || DEFAULT_EMOJI} ${event.location.name}`,\n description: `${event.amount}x\\n${price}`,\n url: `https://share.toogoodtogo.com/receipts/details/${event.orderId}`,\n status,\n created: event.createdAt,\n location: {\n title: event.location.name,\n address: event.location.address,\n geo: event.location.latitude && event.location.longitude ? {\n lat: event.location.latitude,\n lon: event.location.longitude\n } : undefined\n },\n alarms: [\n {type: ICalAlarmType.display, trigger: 600},\n ]\n };\n })\n });\n\n this.updateUserLastSeen(userId);\n return cal.toString();\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"],"mappings":"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","names":["express","cookieParser","randomUUID","generateName","generateNameWithNumber","ICalAlarmType","ICalCalendar","ICalEventStatus","moment","readFile","ServerLib","user","c","prefix","db_default","error","randomUUID","generateName","generateNameWithNumber","userId","html","readFile","config_default","cal","ICalCalendar","event","price","status","ICalEventStatus","DEFAULT_EMOJI","ICalAlarmType","moment","AppServer","_AppServer","express","cookieParser","req","res","ServerLib","user","error","config_default","html","json","ical","cb","db_default"]}
package/package.json CHANGED
@@ -9,43 +9,43 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@criblinc/docker-names": "^1.2.1",
12
- "@prisma/client": "^5.1.1",
13
- "@sentry/node": "^7.64.0",
12
+ "@prisma/client": "^5.8.0",
13
+ "@sentry/node": "^7.93.0",
14
14
  "cookie-parser": "^1.4.6",
15
15
  "express": "^4.18.2",
16
16
  "he": "^1.2.0",
17
- "ical-generator": "^5.0.0",
18
- "mailparser": "^3.6.5",
19
- "moment-timezone": "^0.5.43"
17
+ "ical-generator": "^6.0.1",
18
+ "mailparser": "^3.6.6",
19
+ "moment-timezone": "^0.5.44"
20
20
  },
21
21
  "description": "A small server that receives mails from TGTG, parses them and generates an iCal feed from them.",
22
22
  "devDependencies": {
23
- "@qiwi/semantic-release-gh-pages-plugin": "^5.2.8",
24
- "@sebbo2002/semantic-release-docker": "^4.0.0",
23
+ "@qiwi/semantic-release-gh-pages-plugin": "^5.2.12",
24
+ "@sebbo2002/semantic-release-docker": "^4.0.2",
25
25
  "@semantic-release/changelog": "^6.0.3",
26
26
  "@semantic-release/exec": "^6.0.3",
27
27
  "@semantic-release/git": "^10.0.1",
28
- "@semantic-release/npm": "^10.0.4",
29
- "@types/express": "^4.17.17",
30
- "@types/he": "^1.2.2",
31
- "@types/mailparser": "^3.4.0",
32
- "@types/mocha": "^10.0.1",
33
- "@types/node": "^20.5.0",
34
- "@typescript-eslint/eslint-plugin": "^6.4.0",
35
- "@typescript-eslint/parser": "^6.4.0",
36
- "c8": "^8.0.1",
37
- "eslint": "^8.47.0",
38
- "eslint-plugin-jsonc": "^2.9.0",
28
+ "@semantic-release/npm": "^11.0.2",
29
+ "@types/express": "^4.17.21",
30
+ "@types/he": "^1.2.3",
31
+ "@types/mailparser": "^3.4.4",
32
+ "@types/mocha": "^10.0.6",
33
+ "@types/node": "^20.10.8",
34
+ "@typescript-eslint/eslint-plugin": "^6.18.1",
35
+ "@typescript-eslint/parser": "^6.18.1",
36
+ "c8": "^9.0.0",
37
+ "eslint": "^8.56.0",
38
+ "eslint-plugin-jsonc": "^2.11.2",
39
39
  "esm": "^3.2.25",
40
40
  "license-checker": "^25.0.1",
41
41
  "mocha": "^10.2.0",
42
42
  "mochawesome": "^7.1.3",
43
- "prisma": "^5.1.1",
43
+ "prisma": "^5.8.0",
44
44
  "semantic-release-license": "^1.0.3",
45
45
  "source-map-support": "^0.5.21",
46
- "ts-node": "^10.9.1",
47
- "tsup": "^7.2.0",
48
- "typescript": "^5.1.6"
46
+ "ts-node": "^10.9.2",
47
+ "tsup": "^8.0.1",
48
+ "typescript": "^5.3.3"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=18.0.0"
@@ -78,5 +78,5 @@
78
78
  "test": "mocha"
79
79
  },
80
80
  "type": "module",
81
- "version": "1.0.3-develop.3"
81
+ "version": "1.0.3-develop.5"
82
82
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/parser.ts"],"sourcesContent":["import { Mail, User, Location } from '@prisma/client';\nimport { startTransaction, captureException } from '@sentry/node';\nimport { 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: { 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 // @todo Sentry Transaction (https://docs.sentry.io/platforms/node/#verify)\n const transaction = startTransaction({\n op: 'mail',\n name: 'parse mail'\n });\n\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 finally {\n transaction.finish();\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 .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 match = r.match(regexp);\n if(match) {\n to = match[1];\n }\n });\n }\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(/Anzahl: (\\d+)/),\n html.match(/Gesamtpreis: ([\\d,.]+)[^\\d,.]/)\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]) {\n throw new Error('Date, time and address not found (1)!');\n }\n if(!matches[4]) {\n throw new Error('Amount not found!');\n }\n if(!matches[5]) {\n throw new Error('Price not found!');\n }\n\n let timezone = matches[2] ? matches[2][4] : 'MET';\n if(timezone === 'MEZ') {\n timezone = 'MET';\n }\n\n let from: moment.Moment | undefined;\n let to: moment.Moment | 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 }\n if(matches[3]) {\n from = moment.tz(matches[3][1] + ' ' + matches[3][2], 'DD.MM HH:mm', timezone);\n to = moment.tz(matches[3][1] + ' ' + matches[3][3], 'DD.MM HH:mm', timezone);\n\n if(from.isBefore(moment())) {\n from.add(1, 'year');\n }\n if(to.isBefore(from)) {\n to.add(1, 'year');\n }\n }\n\n if(!matches[2] && !matches[3]) {\n throw new Error('Date, time and address not found (1)!');\n }\n\n const amount = parseInt(matches[4][1], 10);\n if(isNaN(amount)) {\n throw new Error('Amount is not a number!');\n }\n\n const price = parseInt(matches[5][1].replace(/[.|,]/g, ''));\n if(isNaN(price)) {\n throw new Error('Price is not a number!');\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(matches[2] ? matches[2][5].trim() : matches[3][4].trim())\n },\n time: {\n order: moment(email.date),\n from,\n to\n },\n amount,\n price\n };\n }\n\n private static parseChangeMail(email: ParsedMail): ChangeMail {\n const matches = [\n (email.subject || '').match(/(\\w+)$/),\n (email.html || '').match(/ am (\\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 in subject!');\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"],"mappings":"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","names":["startTransaction","captureException","simpleParser","he","moment","Parser","mails","db_default","config_default","mail","moment","email","transaction","startTransaction","parsed","error","errorId","captureException","baseMailPostfix","simpleParser","to","address","received","regexp","r","match","order","change","cancellation","invoice","prefix","user","html","matches","timezone","from","amount","price","he","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data"]}