Paste the script below, save, then
Deploy → New deployment → Web app → Execute as Me → Access: Anyone → Deploy → Authorize
⚠ Drive permission required: Script Editor mein Project Settings (⚙) → Edit appsscript.json open karein aur oauthScopes add karein:
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/script.external_request"]
Ya fir: Script save karne ke baad pehli baar manually run karein taaki Google Drive permission mile.
// ═══════════════════════════════════════════════════
// TJ Pro — Google Apps Script Backend
// Handles: Data sync + Image Upload + GOOGLEFINANCE LTP
// ═══════════════════════════════════════════════════
// IMPORTANT: appsscript.json mein yeh scopes add karein:
// "oauthScopes": [
// "https://www.googleapis.com/auth/spreadsheets",
// "https://www.googleapis.com/auth/drive",
// "https://www.googleapis.com/auth/script.external_request"
// ]
function doGet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var action = e && e.parameter && e.parameter.action;
// ── FETCH LTP via GOOGLEFINANCE ──
if (action === 'ltp') {
var syms = (e.parameter.symbols || '').split(',').filter(Boolean);
var pf = ss.getSheetByName('PriceFeed') || ss.insertSheet('PriceFeed');
pf.clearContents();
syms.forEach(function(s, i) {
pf.getRange(i + 1, 1).setValue(s.trim());
pf.getRange(i + 1, 2).setFormula(
'=IFERROR(GOOGLEFINANCE("' + s.trim() + '","price"),0)'
);
});
SpreadsheetApp.flush();
Utilities.sleep(3000); // wait for GOOGLEFINANCE to resolve
var prices = {};
syms.forEach(function(s, i) {
prices[s.trim()] = pf.getRange(i + 1, 2).getValue();
});
return ContentService
.createTextOutput(JSON.stringify({ ok: true, prices: prices }))
.setMimeType(ContentService.MimeType.JSON);
}
// ── GET FULL DATA ──
var sh = ss.getSheetByName('TJ_Data') || ss.insertSheet('TJ_Data');
var val = sh.getRange('A1').getValue();
if (!val) val = '{"positions":[],"capital":0,"journal":[]}';
return ContentService
.createTextOutput(val)
.setMimeType(ContentService.MimeType.JSON);
}
/**
* @OnlyCurrentDoc
*/
// Required scopes — Apps Script will request these on first run/redeploy:
// https://www.googleapis.com/auth/spreadsheets
// https://www.googleapis.com/auth/drive
// https://www.googleapis.com/auth/script.external_request
function doPost(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// ── READ DATA: try form-encoded first, then raw body ──
var raw = '';
try {
// form-encoded: data=
if (e.parameter && e.parameter.data) {
raw = e.parameter.data;
} else if (e.postData && e.postData.contents) {
raw = e.postData.contents;
}
} catch(ex) {
raw = '';
}
if (!raw) {
return ContentService
.createTextOutput('{"ok":false,"error":"no data received"}')
.setMimeType(ContentService.MimeType.JSON);
}
// ── CHECK FOR uploadImage ACTION ──
try {
var req = JSON.parse(raw);
if (req.action === 'uploadImage') {
try {
var imgBytes = Utilities.base64Decode(req.imageData);
var blob = Utilities.newBlob(imgBytes, req.mimeType || 'image/png', req.fileName || ('img_' + Date.now() + '.png'));
// Store in a folder named "TradeJournal_Images" in Drive root
var folderName = 'TradeJournal_Images';
var folders = DriveApp.getFoldersByName(folderName);
var folder = folders.hasNext() ? folders.next() : DriveApp.createFolder(folderName);
var file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
// Return direct viewable URL
var fileId = file.getId();
var url = 'https://drive.google.com/uc?export=view&id=' + fileId;
return ContentService
.createTextOutput(JSON.stringify({ success: true, url: url }))
.setMimeType(ContentService.MimeType.JSON);
} catch(imgEx) {
return ContentService
.createTextOutput(JSON.stringify({ success: false, error: imgEx.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
} catch(parseEx) {
// Not JSON or not uploadImage — fall through to normal data save
}
// ── SAVE FULL JSON to TJ_Data ──
var sh = ss.getSheetByName('TJ_Data') || ss.insertSheet('TJ_Data');
sh.getRange('A1').setValue(raw);
// ── WRITE READABLE TABLE to Positions sheet ──
try {
var d = JSON.parse(raw);
var pos = d.positions || [];
var rs = ss.getSheetByName('Positions') || ss.insertSheet('Positions');
rs.clearContents();
if (pos.length > 0) {
var headers = [[
'Ticker','Name','Status','Setup',
'Open Date','Close Date','Qty','Avg Buy',
'LTP','SL','TSL','Cost Basis',
'Realised P/L','Exit Rule',
'Buy Chart Link','Sell Chart Link'
]];
rs.getRange(1, 1, 1, headers[0].length).setValues(headers);
var rows = pos.map(function(p) {
// Safe value helper — converts null/undefined to ''
function sv(v) {
if (v === null || v === undefined) return '';
return v;
}
// Buy chart link: first entry with a tvlink
var buyTV = '';
if (p.entries && p.entries.length > 0) {
for (var i = 0; i < p.entries.length; i++) {
if (p.entries[i] && p.entries[i].tvlink) {
buyTV = p.entries[i].tvlink; break;
}
}
}
// Sell chart link: last exit with a tvlink
var sellTV = '';
if (p.exits && p.exits.length > 0) {
for (var j = p.exits.length - 1; j >= 0; j--) {
if (p.exits[j] && p.exits[j].tvlink) {
sellTV = p.exits[j].tvlink; break;
}
}
}
return [
sv(p.ticker), sv(p.name), sv(p.status), sv(p.setup),
sv(p.openDate), sv(p.closeDate),
sv(p.totalQty), sv(p.avgBuy),
sv(p.currentPrice), sv(p.stoploss), sv(p.tsl),
sv(p.costBasis), sv(p.realizedPnL), sv(p.exitRule),
buyTV, sellTV
];
});
rs.getRange(2, 1, rows.length, headers[0].length).setValues(rows);
}
// ── Also write Journal entries ──
var jData = d.journal || [];
if (jData.length > 0) {
var js = ss.getSheetByName('Journal') || ss.insertSheet('Journal');
js.clearContents();
var jHeaders = [['Date','Linked Trade','Notes']];
js.getRange(1,1,1,3).setValues(jHeaders);
var jRows = jData.map(function(j) {
return [j.date||'', j.tradeId||'', j.body||''];
});
js.getRange(2,1,jRows.length,3).setValues(jRows);
}
} catch(ex) {
// Log error to a Debug sheet for troubleshooting
try {
var dbg = ss.getSheetByName('Debug') || ss.insertSheet('Debug');
dbg.appendRow([new Date(), ex.toString(), raw.substring(0,500)]);
} catch(e2) {}
}
return ContentService
.createTextOutput('{"ok":true}')
.setMimeType(ContentService.MimeType.JSON);
}