From bc769925e9e4bd40906d9742328f234edc1dcc21 Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Sat, 18 May 2019 18:52:53 +0200 Subject: [PATCH] initial commit --- .gitattributes | 1 + .gitignore | 4 + credentials.json | Bin 0 -> 452 bytes fix-readline.js | 12 ++ index.js | 274 ++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 299 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 17 +++ 7 files changed, 607 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 credentials.json create mode 100644 fix-readline.js create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9794a74 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +credentials.json filter=git-crypt diff=git-crypt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..647fa8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pdf +node_modules/ +payslips/ +token.json diff --git a/credentials.json b/credentials.json new file mode 100644 index 0000000000000000000000000000000000000000..830903a4ebb253fcc3baeac5b23d9614e034e97a GIT binary patch literal 452 zcmV;#0XzNxM@dveQdv+`0MWv~X1J{iNo=50ThcU2V(>W+JnSe&3(BnKV5GTcAR^hS zBQ`}}D1axD4rIr?7-Xy zMW;UId$mTL%MvPk7%i^=w8_FYjgGky081ac0o@=BPoTyU)Ts|iNL;)QZ3e1%zU8y0 z-A8Ze&1{*&pQYDOnuRQuX3W;tiJ0#VlPSY(;ax`ZLO|X7WZ8Hb`OW7DZ{wE}p|!whH$@!H8#Opj5LpH8NGJRAJ&~PcOucWd${>K0`j~*{d^T5Bvlz0Cu zFP}eI@|wltdJB8ScE-W#QCG0IdSKB6Q=(g-oFw;?zAX$={q8uev>fGjNY;`njO6{Gol>3^vZpt0EPo|P#%5XRl+66IO| literal 0 HcmV?d00001 diff --git a/fix-readline.js b/fix-readline.js new file mode 100644 index 0000000..45e94d2 --- /dev/null +++ b/fix-readline.js @@ -0,0 +1,12 @@ +const readline = require('readline'); +const {promisify} = require('util'); + +readline.Interface.prototype.question[promisify.custom] = function(prompt) { + return new Promise(resolve => + readline.Interface.prototype.question.call(this, prompt, resolve), + ); +}; +readline.Interface.prototype.questionAsync = promisify( + readline.Interface.prototype.question, +); +module.exports = readline diff --git a/index.js b/index.js new file mode 100644 index 0000000..127d32b --- /dev/null +++ b/index.js @@ -0,0 +1,274 @@ +const readline = require('./fix-readline.js') +const nodemailer = require('nodemailer') +const fs = require('fs'); +const {google} = require('googleapis'); +const r2 = require('r2') +const qs = require('querystring') +const {promisify} = require('util') +const readFile = promisify(fs.readFile) +const base64url = require('base64url') +const process = require('process') + +// If modifying these scopes, delete token.json. +const SCOPES = ['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/gmail.compose']; +const USER = process.env['USER'] + +const UserCapitalized = USER.charAt(0).toUpperCase() + USER.slice(1) + +const config = { + tokenPath: 'token.json', + fileId: "1CqUglyFNoEWL0lx-e7arqUGOOZEYW__JjcwBmNaXE6E", + template: `${UserCapitalized} ${(new Date()).getFullYear()} Template`, + to: "billing@serokell.io", + destdir: "payslips" +} + +async function authAll() { + const content = await readFile('credentials.json') + return authorize(JSON.parse(content)) +} + +async function authorize(credentials) { + const {client_secret, client_id, redirect_uris} = credentials.installed; + const oAuth2Client = new google.auth.OAuth2( + client_id, client_secret, redirect_uris[0]); + + // Check if we have previously stored a token. + let token + try { + token = await readFile(config.tokenPath) + } catch(e) { + return getAccessToken(oAuth2Client) + } + oAuth2Client.setCredentials(JSON.parse(token)); + return oAuth2Client +} + +async function ask(question) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + const answer = await rl.questionAsync(question) + rl.close() + return answer +} + +async function getAccessToken(oAuth2Client) { + const authUrl = oAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES, + }); + console.log('Authorize this app by visiting this url:', authUrl); + const code = await ask('Enter the code from that page here: ') + const token = (await oAuth2Client.getToken(code)).tokens + oAuth2Client.setCredentials(token); + await promisify(fs.writeFile)(config.tokenPath, JSON.stringify(token)) + console.log('Token stored to', config.tokenPath); + return oAuth2Client +} +class InvoiceDocument { + constructor(auth, fileId) { + Object.assign(this, { + auth, fileId, + sheets: google.sheets({version: 'v4', auth}), + }) + } + async sheetByName(sheetname) { + const {sheets} = (await this.sheets.spreadsheets.get({ + spreadsheetId: this.fileId, + includeGridData: false + })).data + const r = sheets.find(({properties: {title}}) => title == sheetname) + return r ? r.properties.sheetId : r + } + async sheetAsPdf(destFile, gid) { + const params = { + format: 'pdf', size: 7, fzr: true, + portrait: true, fitw: false, gridlines: false, + printtitle: false, sheetnames: false, pagenum: "UNDEFINED", + attachment: false, gid, printnotes: false + } + const token = this.auth.credentials.access_token + const dest = fs.createWriteStream(destFile) + // thanks, https://gist.github.com/Spencer-Easton/78f9867a691e549c9c70#gistcomment-2622947 + const res = await r2(`https://docs.google.com/spreadsheets/d/${this.fileId}/export?${qs.stringify(params)}`, + {headers: {"Authorization": `Bearer ${token}`}}).response + res.body.pipe(dest) + return new Promise((resolve, reject) => { + res.body.on('end', resolve) + .on('error', reject) + }) + } + async copyTo(src, dest) { + const templateId = await this.sheetByName(src) + if (!templateId) throw "template sheet not found" + const res = await this.sheets.spreadsheets.sheets.copyTo({ + spreadsheetId: this.fileId, + sheetId: templateId, + resource: { + destinationSpreadsheetId: this.fileId + } + }) + const updres = await this.sheets.spreadsheets.batchUpdate({ + spreadsheetId: this.fileId, + resource: { + requests: [{ + updateSheetProperties: { + fields: "title", + properties: { sheetId: res.data.sheetId, title: dest } + } + }] + } + }) + } + async set(sheetname, vals) { + await this.sheets.spreadsheets.values.batchUpdate({ + spreadsheetId: this.fileId, + resource: { + valueInputOption: "USER_ENTERED", + data: Object.keys(vals).map(range => ({range: `'${sheetname}'!${range}`, values: [[vals[range]]]})) + } + }) + } + async get(sheetname, cell) { + const res = await this.sheets.spreadsheets.values.get({ + spreadsheetId: this.fileId, + range: `'${sheetname}'!${cell}` + }) + return res.data.values + } +} +async function composeDraft(title, iban, total, pdf, auth) { + const gmail = google.gmail({version: 'v1', auth}) + const user = await gmail.users.getProfile({userId: 'me'}) + const email = user.data.emailAddress + const transporter = nodemailer.createTransport({ + streamTransport: true, newline: 'unix' + }) + const body = `Hi! + +The total is ${total} to ${iban} (same as always). Thanks!` + // TODO: check if same as always + const msg = { + from: email, + to: config.to, + subject: `payslip - ${title}`, + text: body, + attachments: [{ + filename: `payslip - ${title}.pdf`, + path: pdf + }] + } + transporter.sendMailAsync = promisify(transporter.sendMail) + const info = await transporter.sendMailAsync(msg) + const pdfStream = fs.createReadStream(pdf) + const draft = await gmail.users.drafts.create({ + userId: 'me', + media: {mimeType: 'message/rfc822', body: info.message} + }) + console.log(`Draft created, please review and send: https://mail.google.com/mail/u/${email}/#drafts?compose=${draft.data.message.id}`) +} +const datefmt = date => + `${date.getFullYear()}-${(""+(date.getMonth()+1)).padStart(2, '0')}-${(""+date.getDate()).padStart(2, '0')}` +const methods = { + async export(sheetname, pdfOut) { // save sheet as pdf + console.log("Exporting", sheetname, "to", pdfOut) + const sheetId = await this.doc.sheetByName(sheetname) + return this.doc.sheetAsPdf(pdfOut, sheetId) + }, + async create(sheetname, template=config.template) { // copy sheet from template + console.log("Creating", sheetname, "from", template) + return this.doc.copyTo(template, sheetname) + }, + async update(sheetname, startDate, endDate, hours) { // set values in sheet + console.log("Setting data in", sheetname) + hours = +hours + const values = { + regular: Math.min(hours, 80), + overwork: Math.max(0, hours-80), + startDate, endDate + } + console.log(values) + this.doc.set(sheetname, { + D16: values.regular, D17: values.overwork, + B9: values.startDate, D9: values.endDate + }) + }, + async draft(sheetname) { // export pdf+create gmail draft + const pdfname = `${config.destdir}/${sheetname}.pdf` + try { + await promisify(fs.mkdir)(config.destdir, {recursive: true}) + } catch(e) { + if (e.code != 'EEXIST') throw e + } + await this.export(sheetname, pdfname) + const [[paid,,,, total]] = await this.doc.get(sheetname, "B33:F33") + const iban = paid.replace(/^Paid to /, '') + console.log(paid, total) + composeDraft(sheetname, iban, total, pdfname, this.auth) + }, + async auto(hours) { // just do everything + const date = new Date() + const day = date.getDate() + let part = 'B' + if (day >= 10 && day < 28) { + part = 'A' + } else if (day < 10) { + // talking about last month + date.setMonth(date.getMonth()-1) + } + const month = date.toLocaleString('en-us', {month: 'short'}) + const sheetname = `${UserCapitalized} ${date.getFullYear()} ${month} ${part}` + let startDate, endDate + if (part == 'A') { + date.setDate(1) + startDate = datefmt(date) + date.setDate(15) + endDate = datefmt(date) + } else { + date.setDate(16) + startDate = datefmt(date) + date.setMonth(date.getMonth()+1, 0) + endDate = datefmt(date) + } + const sheetId = await this.doc.sheetByName(sheetname) + if (!sheetId) { + await this.create(sheetname) + } else { + const answer = await ask(`Overwrite '${sheetname}'? [yN]: `) + if (answer.trim().toLowerCase()[0] != 'y') { + console.log("Aborting!") + return null + } + } + await this.update(sheetname, startDate, endDate, hours) + await this.draft(sheetname) + }, + async authenticate() { // just auth + } +} + +const sig = fn => + fn.toString().split('\n')[0].replace(/^async /, '').replace(/ \{/, '') + +// poor man's CLI +const [,, method, ...args] = process.argv +if (method in methods) { + if (args.length < methods[method].length) { + console.error("not enough arguments for method", sig(methods[method])) + } else { + authAll().then((auth) => { + methods.auth = auth + methods.doc = new InvoiceDocument(auth, config.fileId) + methods[method](...args) + }) + } +} else { + if (method && method != 'help') console.log("unknown subcall", method) + console.log("valid methods:") + for(const realMethod in methods) { + console.log(" ", sig(methods[realMethod])) + } + process.exit(1) +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9ed01cb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,299 @@ +{ + "name": "skl-auto-payslip", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "5.0.1" + } + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "requires": { + "es6-promisify": "5.0.0" + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "2.1.1" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "es6-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "4.2.6" + } + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "3.0.0", + "extend": "3.0.2", + "https-proxy-agent": "2.2.1", + "node-fetch": "2.6.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "1.8.4", + "json-bigint": "0.3.0" + } + }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "1.3.0", + "fast-text-encoding": "1.0.0", + "gaxios": "1.8.4", + "gcp-metadata": "1.0.0", + "gtoken": "2.3.3", + "https-proxy-agent": "2.2.1", + "jws": "3.2.2", + "lru-cache": "5.1.1", + "semver": "5.7.0" + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "0.8.3", + "pify": "4.0.1" + } + }, + "googleapis": { + "version": "39.2.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-39.2.0.tgz", + "integrity": "sha512-66X8TG1B33zAt177sG1CoKoYHPP/B66tEpnnSANGCqotMuY5gqSQO8G/0gqHZR2jRgc5CHSSNOJCnpI0SuDxMQ==", + "requires": { + "google-auth-library": "3.1.2", + "googleapis-common": "0.7.2" + } + }, + "googleapis-common": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-0.7.2.tgz", + "integrity": "sha512-9DEJIiO4nS7nw0VE1YVkEfXEj8x8MxsuB+yZIpOBULFSN9OIKcUU8UuKgSZFU4lJmRioMfngktrbkMwWJcUhQg==", + "requires": { + "gaxios": "1.8.4", + "google-auth-library": "3.1.2", + "pify": "4.0.1", + "qs": "6.7.0", + "url-template": "2.0.8", + "uuid": "3.3.2" + } + }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "1.8.4", + "google-p12-pem": "1.0.4", + "jws": "3.2.2", + "mime": "2.4.3", + "pify": "4.0.1" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "requires": { + "agent-base": "4.2.1", + "debug": "3.2.6" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "7.2.1" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "5.1.2" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "1.4.1", + "safe-buffer": "5.1.2" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "3.0.3" + } + }, + "mime": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", + "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.3.tgz", + "integrity": "sha512-5lv9UKmvTBog+m4AWL8XpZnr3WbNKxYL2M77i903ylY/huJIooSTDHyUWQ/OppFuKQpAGMk6qNtDymSJNRIEIg==" + }, + "nodemailer": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.1.1.tgz", + "integrity": "sha512-/x5MRIh56VyuuhLfcz+DL2SlBARpZpgQIf2A4Ao4hMb69MHSgDIMPwYmFwesGT1lkRDZ0eBSoym5+JoIZ3N+cQ==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "r2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/r2/-/r2-2.0.1.tgz", + "integrity": "sha512-EEmxoxYCe3LHzAUhRIRxdCKERpeRNmlLj6KLUSORqnK6dWl/K5ShmDGZqM2lRZQeqJgF+wyqk0s1M7SWUveNOQ==", + "requires": { + "caseless": "0.12.0", + "node-fetch": "2.6.0", + "typedarray-to-buffer": "3.1.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "1.0.0" + } + }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f0bdf4 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "skl-auto-payslip", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "base64url": "^3.0.1", + "googleapis": "^39.2.0", + "nodemailer": "^6.1.1", + "r2": "^2.0.1" + } +}