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 readFile = promisify(fs.readFile)
|
||||||
const base64url = require('base64url')
|
const base64url = require('base64url')
|
||||||
const process = require('process')
|
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.
|
// If modifying these scopes, delete token.json.
|
||||||
const SCOPES = ['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/gmail.compose'];
|
const SCOPES = ['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/gmail.compose'];
|
||||||
const USER = process.env['USER']
|
const USER = process.env['USER']
|
||||||
|
const HOME = process.env['HOME']
|
||||||
|
|
||||||
const UserCapitalized = USER.charAt(0).toUpperCase() + USER.slice(1)
|
const UserCapitalized = USER.charAt(0).toUpperCase() + USER.slice(1)
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
tokenPath: 'token.json',
|
|
||||||
fileId: "1CqUglyFNoEWL0lx-e7arqUGOOZEYW__JjcwBmNaXE6E",
|
fileId: "1CqUglyFNoEWL0lx-e7arqUGOOZEYW__JjcwBmNaXE6E",
|
||||||
template: `${UserCapitalized} ${(new Date()).getFullYear()} Template`,
|
template: `${UserCapitalized} ${(new Date()).getFullYear()} Template`,
|
||||||
to: "billing@serokell.io",
|
to: "billing@serokell.io",
|
||||||
destdir: "payslips"
|
destdir: `${HOME}/serokell/invoices/payslips`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authAll() {
|
async function authAll() {
|
||||||
|
@ -36,7 +39,7 @@ async function authorize(credentials) {
|
||||||
// Check if we have previously stored a token.
|
// Check if we have previously stored a token.
|
||||||
let token
|
let token
|
||||||
try {
|
try {
|
||||||
token = await readFile(config.tokenPath)
|
token = await pass_get("skl-auto-payslip")
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return getAccessToken(oAuth2Client)
|
return getAccessToken(oAuth2Client)
|
||||||
}
|
}
|
||||||
|
@ -53,6 +56,21 @@ async function ask(question) {
|
||||||
rl.close()
|
rl.close()
|
||||||
return answer
|
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) {
|
async function getAccessToken(oAuth2Client) {
|
||||||
const authUrl = oAuth2Client.generateAuthUrl({
|
const authUrl = oAuth2Client.generateAuthUrl({
|
||||||
|
@ -63,10 +81,10 @@ async function getAccessToken(oAuth2Client) {
|
||||||
const code = await ask('Enter the code from that page here: ')
|
const code = await ask('Enter the code from that page here: ')
|
||||||
const token = (await oAuth2Client.getToken(code)).tokens
|
const token = (await oAuth2Client.getToken(code)).tokens
|
||||||
oAuth2Client.setCredentials(token);
|
oAuth2Client.setCredentials(token);
|
||||||
await promisify(fs.writeFile)(config.tokenPath, JSON.stringify(token))
|
await pass_insert('skl-auto-payslip', JSON.stringify(token))
|
||||||
console.log('Token stored to', config.tokenPath);
|
|
||||||
return oAuth2Client
|
return oAuth2Client
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvoiceDocument {
|
class InvoiceDocument {
|
||||||
constructor(auth, fileId) {
|
constructor(auth, fileId) {
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
|
@ -171,16 +189,20 @@ The total is ${total} to ${iban} (same as always). Thanks!`
|
||||||
}
|
}
|
||||||
const datefmt = date =>
|
const datefmt = date =>
|
||||||
`${date.getFullYear()}-${(""+(date.getMonth()+1)).padStart(2, '0')}-${(""+date.getDate()).padStart(2, '0')}`
|
`${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
|
async export(sheetname, pdfOut) { // save sheet as pdf
|
||||||
console.log("Exporting", sheetname, "to", pdfOut)
|
console.log("Exporting", sheetname, "to", pdfOut)
|
||||||
const sheetId = await this.doc.sheetByName(sheetname)
|
const sheetId = await this.doc.sheetByName(sheetname)
|
||||||
return this.doc.sheetAsPdf(pdfOut, sheetId)
|
return this.doc.sheetAsPdf(pdfOut, sheetId)
|
||||||
},
|
}
|
||||||
async create(sheetname, template=config.template) { // copy sheet from template
|
async create(sheetname, template=config.template) { // copy sheet from template
|
||||||
console.log("Creating", sheetname, "from", template)
|
console.log("Creating", sheetname, "from", template)
|
||||||
return this.doc.copyTo(template, sheetname)
|
return this.doc.copyTo(template, sheetname)
|
||||||
},
|
}
|
||||||
async update(sheetname, startDate, endDate, hours) { // set values in sheet
|
async update(sheetname, startDate, endDate, hours) { // set values in sheet
|
||||||
console.log("Setting data in", sheetname)
|
console.log("Setting data in", sheetname)
|
||||||
hours = +hours
|
hours = +hours
|
||||||
|
@ -194,7 +216,7 @@ const methods = {
|
||||||
D16: values.regular, D17: values.overwork,
|
D16: values.regular, D17: values.overwork,
|
||||||
B9: values.startDate, D9: values.endDate
|
B9: values.startDate, D9: values.endDate
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
async draft(sheetname) { // export pdf+create gmail draft
|
async draft(sheetname) { // export pdf+create gmail draft
|
||||||
const pdfname = `${config.destdir}/${sheetname}.pdf`
|
const pdfname = `${config.destdir}/${sheetname}.pdf`
|
||||||
try {
|
try {
|
||||||
|
@ -207,7 +229,7 @@ const methods = {
|
||||||
const iban = paid.replace(/^Paid to /, '')
|
const iban = paid.replace(/^Paid to /, '')
|
||||||
console.log(paid, total)
|
console.log(paid, total)
|
||||||
composeDraft(sheetname, iban, total, pdfname, this.auth)
|
composeDraft(sheetname, iban, total, pdfname, this.auth)
|
||||||
},
|
}
|
||||||
async auto(hours) { // just do everything
|
async auto(hours) { // just do everything
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const day = date.getDate()
|
const day = date.getDate()
|
||||||
|
@ -244,31 +266,9 @@ const methods = {
|
||||||
}
|
}
|
||||||
await this.update(sheetname, startDate, endDate, hours)
|
await this.update(sheetname, startDate, endDate, hours)
|
||||||
await this.draft(sheetname)
|
await this.draft(sheetname)
|
||||||
},
|
}
|
||||||
async authenticate() { // just auth
|
async authenticate() { // just auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
module.exports = App
|
||||||
const sig = fn =>
|
module.exports.authAll = authAll
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
|
"bin": "bin/skl-auto-payslip.js",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
Loading…
Reference in New Issue