@sebbo2002/tgtg-ical 1.0.0-develop.0 → 1.0.0-develop.10

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) 2021 Sebastian Pekarek
3
+ Copyright (c) 2023 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
package/README.md CHANGED
@@ -1,10 +1,40 @@
1
- # template
1
+ # tgtg-ical
2
2
 
3
3
  [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
4
4
 
5
5
  A small server that receives mails from TGTG, parses them and generates an iCal feed from them.
6
6
 
7
7
 
8
+ ## 📦 Installation
9
+
10
+ git clone https://github.com/sebbo2002/tgtg-ical.git
11
+ cd ./tgtg-ical
12
+
13
+ echo 'DATABASE_URL="mysql://root@localhost:3306/tgtg-ical"' > .env
14
+
15
+ npm install
16
+ npx prisma migrate deploy
17
+
18
+
19
+ ## 🙋 FAQ
20
+
21
+ ### How does this work?
22
+ With the help of tgtg-ical you can generate a personal email address and a corresponding calendar feed. If you store
23
+ this email address at Too Good To Go as an email address for notifications or forward the messages (e.g. via a filter
24
+ rule), collection appointments will be displayed in the corresponding calendar feed.
25
+
26
+ ### Which languages are supported?
27
+ Currently only German and English are supported. If you want to add another language, feel free to create a pull request.
28
+
29
+ ### How long are emails stored?
30
+ In the best case, the incoming e-mail can be completely analyzed and is then deleted directly. Then only the information
31
+ needed to provide the calendar is stored. If the analysis fails, the email is kept for manual analysis and deleted after
32
+ two weeks at the latest.
33
+
34
+ ### Which databases are supported?
35
+ We use [Prisma](https://www.prisma.io/) as ORM. All databases supported by Prisma should therefore work with tgtg-ical.
36
+
37
+
8
38
  ## 🙆🏼‍♂️ Copyright and license
9
39
 
10
40
  Copyright (c) Sebastian Pekarek under the [MIT license](LICENSE).
@@ -0,0 +1,2 @@
1
+ import{a as o,b as d,d as u}from"./chunk-HTOHNK4X.js";import{startTransaction as f,captureException as h}from"@sentry/node";import{simpleParser as p}from"mailparser";import n from"moment-timezone";var l=class{static async runCleanup(){let t=await o.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 o.user.deleteMany({where:{OR:[{lastSeenAt:{lt:n().subtract(8,"weeks").toDate()}},{lastSeenAt:{equals:o.user.fields.createdAt},createdAt:{lt:n().subtract(3,"hours").toDate()}}]}}),await o.event.deleteMany({where:{to:{lt:n().subtract(4,"weeks").toDate()}}}),await o.mail.deleteMany({where:{createdAt:{lt:n().subtract(2,"weeks").toDate()}}})}static async inhaleMail(t){let r=await o.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 o.mail.delete({where:{id:t.id}})}catch(e){let i=h(e);await o.mail.update({where:{id:t.id},data:{error:e instanceof Error?e.stack:String(e),erroredAt:new Date,errorId:i,version:d.version}})}finally{r.finish()}}static async parseMail(t,r=d.baseMail){let e=await p(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 i=(Array.isArray(e.to)?e.to:[e.to]).map(a=>a.value).flat().map(a=>a.address).find(a=>a.endsWith(r));if(!i){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&&(i=c[1])})}if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let a=this.parseOrderMail(e);return{to:i,...a}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let a=this.parseChangeMail(e);return{to:i,...a}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let a=this.parseCancellationMail(e);if(a)return{to:i,...a}}if(e.headers.get("x-pm-tag")==="invoice"){let a=this.parseInvoiceMail(e);if(a)return{to:i,...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 o.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 i=e[2]?e[2][4]:"MET";i==="MEZ"&&(i="MET");let a,s;if(e[2]&&(a=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",i),s=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",i)),e[3]&&(a=n.tz(e[3][1]+" "+e[3][2],"DD.MM HH:mm",i),s=n.tz(e[3][1]+" "+e[3][3],"DD.MM HH:mm",i),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:e[1][1].trim(),address: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 i=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:i,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 o.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 o.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 o.event.update({where:{orderId:t.orderId,userId:r.id},data:{canceledAt:t.cancelledAt.toDate()}});else if(t.type==="invoice")await o.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 o.location.findFirst({where:{name:t.name,address:t.address}});if(!r){let e=u(t.name),{latitude:i,longitude:a}=await this.geocode(t.address);r=await o.location.create({data:{name:t.name,address:t.address,latitude:i,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(i=>setTimeout(i,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-ZGNCUUSH.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 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: matches[1][1].trim(),\n address: 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","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","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import{a as r}from"./chunk-ZGNCUUSH.js";import"./chunk-HTOHNK4X.js";r.runCleanup().catch(e=>{console.error(e),process.exit(1)});
3
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bin/cleanup.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\n/* istanbul ignore file */\nimport Parser from '../lib/parser.js';\n\nParser.runCleanup()\n .catch(error => {\n console.error(error);\n process.exit(1);\n });\n"],"mappings":";oEAMAA,EAAO,WAAW,EACb,MAAMC,GAAS,CACZ,QAAQ,MAAMA,CAAK,EACnB,QAAQ,KAAK,CAAC,CAClB,CAAC","names":["Parser","error"]}
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as o,b as s,d as f}from"./chunk-HTOHNK4X.js";import{startTransaction as p,captureException as w}from"@sentry/node";import{simpleParser as M}from"mailparser";import n from"moment-timezone";var m=class{static async runCleanup(){let t=await o.mail.findMany({where:{OR:[{error:null},{version:{not:s.version}}]},orderBy:{erroredAt:"asc"},take:10});for(let r of t)await this.handleMail(r);await o.user.deleteMany({where:{OR:[{lastSeenAt:{lt:n().subtract(8,"weeks").toDate()}},{lastSeenAt:{equals:o.user.fields.createdAt}}]}}),await o.event.deleteMany({where:{to:{lt:n().subtract(4,"weeks").toDate()}}}),await o.mail.deleteMany({where:{createdAt:{lt:n().subtract(2,"weeks").toDate()}}})}static async inhaleMail(t){let r=await o.mail.create({data:{raw:t}});await this.handleMail(r)}static async handleMail(t){let r=p({op:"mail",name:"parse mail"});try{let e=await this.parseMail(t.raw);e&&await this.applyParsedMail(e),await o.mail.delete({where:{id:t.id}})}catch(e){let i=w(e);await o.mail.update({where:{id:t.id},data:{error:e instanceof Error?e.stack:String(e),erroredAt:new Date,errorId:i,version:s.version}})}finally{r.finish()}}static async parseMail(t,r=s.baseMail){let e=await M(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 i=(Array.isArray(e.to)?e.to:[e.to]).map(a=>a.value).flat().map(a=>a.address).find(a=>a.endsWith(r));if(!i){let a=e.headers.get("received"),d=new RegExp(`([\\w-]+${s.baseMail})`,"i");Array.isArray(a)&&a.forEach(u=>{let c=u.match(d);c&&(i=c[1])})}if(e.headers.get("x-pm-tag")==="consumer_order_confirm"){let a=this.parseOrderMail(e);return{to:i,...a}}if(e.headers.get("x-pm-tag")==="collection_time_changed"){let a=this.parseChangeMail(e);return{to:i,...a}}if(e.headers.get("x-pm-tag")==="consumer_order_reverted"){let a=this.parseCancellationMail(e);if(a)return{to:i,...a}}if(e.headers.get("x-pm-tag")==="invoice"){let a=this.parseInvoiceMail(e);if(a)return{to:i,...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 o.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 i=e[2]?e[2][4]:"MET";i==="MEZ"&&(i="MET");let a,d;if(e[2]&&(a=n.tz(e[2][1]+" "+e[2][2],"DD.MM.YY HH:mm",i),d=n.tz(e[2][1]+" "+e[2][3],"DD.MM.YY HH:mm",i)),e[3]&&(a=n.tz(e[3][1]+" "+e[3][2],"DD.MM HH:mm",i),d=n.tz(e[3][1]+" "+e[3][3],"DD.MM HH:mm",i),a.isBefore(n())&&a.add(1,"year"),d.isBefore(a)&&d.add(1,"year")),!e[2]&&!e[3])throw new Error("Date, time and address not found (1)!");let u=parseInt(e[4][1],10);if(isNaN(u))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:e[1][1].trim(),address:e[2]?e[2][5].trim():e[3][4].trim()},time:{order:n(t.date),from:a,to:d},amount:u,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 i=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:i,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 o.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 o.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 o.event.update({where:{orderId:t.orderId,userId:r.id},data:{canceledAt:t.cancelledAt.toDate()}});else if(t.type==="invoice")await o.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 o.location.findFirst({where:{name:t.name,address:t.address}});if(!r){let e=f(t.name),{latitude:i,longitude:a}=await this.geocode(t.address);r=await o.location.create({data:{name:t.name,address:t.address,latitude:i,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/${s.version} (${s.baseUrl})`,Referer:s.baseUrl}});if(!r.ok)throw new Error("Geocoding failed: "+r.statusText);await new Promise(i=>setTimeout(i,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)}}};var h="";process.stdin.on("data",l=>{h+=l});process.stdin.on("end",()=>{m.inhaleMail(h).catch(l=>{console.error(l),process.exit(1)})});
2
+ import{a as r}from"./chunk-ZGNCUUSH.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
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/parser.ts","../src/bin/inhale-mail.ts"],"sourcesContent":["import { Mail, User, Location } from '@prisma/client';\nimport { startTransaction, captureException } from '@sentry/node';\nimport { ParsedMail, simpleParser } from 'mailparser';\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 }\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: matches[1][1].trim(),\n address: 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","#!/usr/bin/env node\n'use strict';\n\n/* istanbul ignore file */\nimport Parser from '../lib/parser.js';\n\nlet buffer = '';\nprocess.stdin.on('data', (chunk) => {\n buffer += chunk;\n});\nprocess.stdin.on('end', () => {\n Parser.inhaleMail(buffer)\n .catch(error => {\n console.error(error);\n process.exit(1);\n });\n});\n"],"mappings":"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","names":["startTransaction","captureException","simpleParser","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","location","input","emoji","getEmoji","latitude","longitude","response","resolve","data","buffer","chunk","Parser","error"]}
1
+ {"version":3,"sources":["../src/bin/inhale-mail.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\n/* istanbul ignore file */\nimport Parser from '../lib/parser.js';\n\nlet buffer = '';\nprocess.stdin.on('data', (chunk) => {\n buffer += chunk;\n});\nprocess.stdin.on('end', () => {\n Parser.inhaleMail(buffer)\n .catch(error => {\n console.error(error);\n process.exit(1);\n });\n});\n"],"mappings":";oEAMA,IAAIA,EAAS,GACb,QAAQ,MAAM,GAAG,OAASC,GAAU,CAChCD,GAAUC,CACd,CAAC,EACD,QAAQ,MAAM,GAAG,MAAO,IAAM,CAC1BC,EAAO,WAAWF,CAAM,EACnB,MAAMG,GAAS,CACZ,QAAQ,MAAMA,CAAK,EACnB,QAAQ,KAAK,CAAC,CAClB,CAAC,CACT,CAAC","names":["buffer","chunk","Parser","error"]}
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} unahandled mails in the queue!`)}};import{Prisma as C}from"@prisma/client";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(201)).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(e instanceof C.PrismaClientKnownRequestError&&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:{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();
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 { Prisma } from '@prisma/client';\nimport Config from '../lib/config';\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(201))\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: Prisma.PrismaClientKnownRequestError | unknown, res: Response) {\n if(error instanceof Prisma.PrismaClientKnownRequestError && 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} unahandled 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","Prisma","AppServer","_AppServer","express","cookieParser","req","res","ServerLib","user","error","config_default","html","json","ical","Prisma","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 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"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "author": "Sebastian Pekarek <mail@sebbo.net>",
3
3
  "bin": {
4
- "tgtg-ical-server": "./dist/start.js",
5
- "tgtg-ical-inhale-mail": "./dist/bin/inhale-mail.js"
4
+ "tgtg-ical-inhale-mail": "./dist/bin/inhale-mail.js",
5
+ "tgtg-ical-server": "./dist/start.js"
6
6
  },
7
7
  "bugs": {
8
8
  "url": "https://github.com/sebbo2002/tgtg-ical/issues"
@@ -68,12 +68,13 @@
68
68
  "build": "tsup",
69
69
  "build-all": "./.github/workflows/build.sh",
70
70
  "coverage": "c8 mocha",
71
+ "deploy": "./.github/workflows/deploy.sh",
71
72
  "develop": "ts-node ./src/bin/start.ts",
72
73
  "license-check": "license-checker --production --summary",
73
74
  "lint": "eslint . --ext .ts,.json",
74
- "start": "node ./dist/bin/start.js",
75
+ "start": "node ./dist/start.js",
75
76
  "test": "mocha"
76
77
  },
77
78
  "type": "module",
78
- "version": "1.0.0-develop.0"
79
+ "version": "1.0.0-develop.10"
79
80
  }