Skip to content

Instantly share code, notes, and snippets.

@Markkop
Created June 4, 2024 23:27
Show Gist options
  • Save Markkop/6d87a189622e7288eeb098d4b3527c63 to your computer and use it in GitHub Desktop.
Save Markkop/6d87a189622e7288eeb098d4b3527c63 to your computer and use it in GitHub Desktop.
gmail-to-zoho-invoice-generator.js
// ==UserScript==
// @name Invoice Generator for Gmail
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Generate invoices automatically from Gmail content and send to Zoho
// @author Mark Kop
// @match https://mail.google.com/*
// @match https://oauth.pstmn.io/v1/callback
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @connect api.openai.com
// @connect www.googleapis.com
// @connect accounts.zoho.com
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const openAiApiKey = '';
const clientId = '';
const clientSecret = '';
const redirectUri = 'https://oauth.pstmn.io/v1/callback';
const authUrl = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=ZohoInvoice.fullaccess.all`;
const IDS = {
ORGANIZATION_ID: '',
CUSTOMER_ID: '',
ITEM_ID: ''
};
function extractEmailContent() {
console.log('Extracting email content...');
const emailBody = document.querySelector('.a3s');
const emailContent = emailBody ? emailBody.innerText : null;
console.log('Email content:', emailContent);
return emailContent;
}
function generateInvoiceContent(emailContent) {
console.log('Generating invoice content...');
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: "https://api.openai.com/v1/chat/completions",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${openAiApiKey}`
},
data: JSON.stringify({
model: "gpt-4-1106-preview",
messages: [{
role: "system",
content: `Generate JSON with the following keys: customer_id (use ${IDS.CUSTOMER_ID} for this field), date (use format ${new Date().toISOString().split('T')[0]}), line_items (array of objects with keys: item_id (use ${IDS.ITEM_ID}), quantity (use 1), rate (use the value in dollars, number only)).`
}, {
role: "user",
content: emailContent
}],
max_tokens: 512,
response_format: { type: "json_object" }
}),
onload: function(response) {
console.log('OpenAI API response:', response);
const data = JSON.parse(response.responseText);
resolve(data.choices[0].message.content);
},
onerror: function(err) {
console.error('Error during OpenAI API request:', err);
reject(err);
}
});
});
}
function createZohoInvoice(invoiceData) {
console.log('Creating Zoho invoice...');
const token = GM_getValue('GM_zoho_token');
if (!token) {
console.error('Zoho token is missing. Please authorize first.');
initiateOAuthFlow();
return;
}
console.log('Zoho token:', token);
console.log('Invoice data to be sent to Zoho:', invoiceData);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: "https://invoice.zoho.com/api/v3/invoices",
headers: {
"Authorization": `Zoho-oauthtoken ${token}`,
"Content-Type": "application/json",
"X-com-zoho-invoice-organizationid": IDS.ORGANIZATION_ID || ''
},
data: JSON.stringify(JSON.parse(invoiceData)),
onload: function(response) {
console.log('Zoho API response:', response);
const responseData = JSON.parse(response.responseText);
if (response.status === 401) {
console.error('Not authorized. Please check your Zoho token.');
reject('Not authorized');
} else {
resolve(responseData);
}
},
onerror: function(err) {
console.error('Error during Zoho API request:', err);
reject(err);
}
});
});
}
function initiateOAuthFlow() {
console.log('Initiating OAuth flow...');
window.open(authUrl, 'authPopup', 'width=500,height=500');
}
function exchangeCodeForToken(code) {
console.log('Exchanging code for token...');
GM_xmlhttpRequest({
method: "POST",
url: "https://accounts.zoho.com/oauth/v2/token",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: `code=${code}&redirect_uri=${encodeURIComponent(redirectUri)}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=authorization_code`,
onload: function(response) {
console.log('Token exchange response:', response);
const responseData = JSON.parse(response.responseText);
GM_setValue('GM_zoho_token', responseData.access_token);
processInvoice();
},
onerror: function(err) {
console.error('Failed to exchange code for token:', err);
}
});
}
async function processInvoice() {
console.log('Processing invoice...');
const token = GM_getValue('GM_zoho_token');
if (!token) {
console.error('Zoho token is missing. Please authorize first.');
initiateOAuthFlow();
return;
}
const emailContent = extractEmailContent();
if (!emailContent) {
alert('No email content found.');
return;
}
try {
const invoiceDetails = await generateInvoiceContent(emailContent);
console.log('Generated invoice details:', invoiceDetails);
const zohoInvoiceData = await createZohoInvoice(invoiceDetails);
console.log('Zoho invoice data:', zohoInvoiceData);
} catch (error) {
console.error('Error:', error);
alert('An error occurred. Check the console for details.');
}
}
function addButton() {
console.log('Adding button to the page...');
const button = document.createElement('button');
button.innerText = 'Generate Invoice';
button.style.position = 'fixed';
button.style.bottom = '10px';
button.style.right = '10px';
button.style.zIndex = 1000;
button.onclick = () => {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
if (code) {
console.log('Authorization code found:', code);
exchangeCodeForToken(code);
} else {
processInvoice();
}
};
document.body.appendChild(button);
console.log('Button added.');
}
if (window.location.href.startsWith('https://oauth.pstmn.io/v1/callback')) {
addButton();
} else {
window.addEventListener('load', addButton);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment