add bin/ and store secret in path
parent
4d5c11ce24
commit
67bdb9f84c
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env node
|
||||
const App = require('../index.js')
|
||||
const sig = fn =>
|
||||
fn.toString().split('\n')[0].replace(/^async /, '').replace(/ \{/, '')
|
||||
|
||||
const methods = App.prototype
|
||||
// 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 {
|
||||
App.authAll().then((auth) => {
|
||||
const app = new App(auth)
|
||||
app[method](...args)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (method && method != 'help') console.log("unknown subcall", method)
|
||||
console.log("valid methods:")
|
||||
for(const realMethod of Object.getOwnPropertyNames(methods)) {
|
||||
if (realMethod == 'constructor') continue
|
||||
if (methods[realMethod])
|
||||
console.log(" ", sig(methods[realMethod]))
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
with import <nixpkgs> {};
|
||||
with callPackage (fetchTarball https://github.com/serokell/nix-npm-buildpackage/archive/master.tar.gz) {};
|
||||
buildNpmPackage { src = ./.; }
|
70
index.js
70
index.js
|
@ -8,19 +8,22 @@ const {promisify} = require('util')
|
|||
const readFile = promisify(fs.readFile)
|
||||
const base64url = require('base64url')
|
||||
const process = require('process')
|
||||
const path = require('path')
|
||||
const exec = promisify(require('child_process').exec)
|
||||
const {spawn} = require('child_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 HOME = process.env['HOME']
|
||||
|
||||
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"
|
||||
destdir: `${HOME}/serokell/invoices/payslips`
|
||||
}
|
||||
|
||||
async function authAll() {
|
||||
|
@ -36,7 +39,7 @@ async function authorize(credentials) {
|
|||
// Check if we have previously stored a token.
|
||||
let token
|
||||
try {
|
||||
token = await readFile(config.tokenPath)
|
||||
token = await pass_get("skl-auto-payslip")
|
||||
} catch(e) {
|
||||
return getAccessToken(oAuth2Client)
|
||||
}
|
||||
|
@ -53,6 +56,21 @@ async function ask(question) {
|
|||
rl.close()
|
||||
return answer
|
||||
}
|
||||
function pass_insert(name, val) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pass = spawn("pass", ['insert', 'skl-auto-payslip', '-m'], {stdio: ['pipe', 'inherit', 'inherit']})
|
||||
pass.stdin.write(val)
|
||||
pass.stdin.end()
|
||||
pass.on('close', (code) => {
|
||||
if (code != 0) reject(`pass exited with code ${code}`)
|
||||
else { resolve() }
|
||||
})
|
||||
})
|
||||
}
|
||||
async function pass_get(name) {
|
||||
const {stdout, stderr} = await exec("pass skl-auto-payslip")
|
||||
return stdout
|
||||
}
|
||||
|
||||
async function getAccessToken(oAuth2Client) {
|
||||
const authUrl = oAuth2Client.generateAuthUrl({
|
||||
|
@ -63,10 +81,10 @@ async function getAccessToken(oAuth2Client) {
|
|||
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);
|
||||
await pass_insert('skl-auto-payslip', JSON.stringify(token))
|
||||
return oAuth2Client
|
||||
}
|
||||
|
||||
class InvoiceDocument {
|
||||
constructor(auth, fileId) {
|
||||
Object.assign(this, {
|
||||
|
@ -171,16 +189,20 @@ The total is ${total} to ${iban} (same as always). Thanks!`
|
|||
}
|
||||
const datefmt = date =>
|
||||
`${date.getFullYear()}-${(""+(date.getMonth()+1)).padStart(2, '0')}-${(""+date.getDate()).padStart(2, '0')}`
|
||||
const methods = {
|
||||
class App {
|
||||
constructor(auth) {
|
||||
this.auth = auth
|
||||
this.doc = new InvoiceDocument(auth, config.fileId)
|
||||
}
|
||||
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
|
||||
|
@ -194,7 +216,7 @@ const methods = {
|
|||
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 {
|
||||
|
@ -207,7 +229,7 @@ const methods = {
|
|||
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()
|
||||
|
@ -244,31 +266,9 @@ const methods = {
|
|||
}
|
||||
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)
|
||||
}
|
||||
module.exports = App
|
||||
module.exports.authAll = authAll
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"bin": "bin/skl-auto-payslip.js",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
|
Loading…
Reference in New Issue