7 changed files with 607 additions and 0 deletions
@ -0,0 +1 @@
|
||||
credentials.json filter=git-crypt diff=git-crypt |
Binary file not shown.
@ -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 |
@ -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) |
||||
} |
@ -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==" |
||||
} |
||||
} |
||||
} |
@ -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" |
||||
} |
||||
} |
Loading…
Reference in new issue