Quét QR code hiện trên trang web
Chưa có tài khoản nào
Để trống để dùng API mặc định của ứng dụng.
Đây là mật khẩu bạn tự tạo và phải nhớ. App không lưu mật khẩu này, chỉ dùng để mã hóa dữ liệu local và cloud.
Xuất và khôi phục file JSON gồm cấu hình app, danh sách tài khoản và tombstone xóa. Master Password không được lưu vào file.
Nhập mật khẩu đã tạo để mở dữ liệu local hoặc tải dữ liệu từ Cloud.
Dữ liệu lưu trên Google Sheet là chuỗi đã mã hóa. Không sửa trực tiếp cột data trong Sheet.
const CLOUD_SHEET = 'Cloud';
const BACKUP_SHEET = 'Backups';
const MAX_BACKUPS = 50;
const MIN_DATA_LENGTH = 80;
function getSheet(name, header) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(name);
if (!sheet) sheet = ss.insertSheet(name);
if (sheet.getLastRow() === 0) sheet.appendRow(header);
return sheet;
}
function getCloudSheet() {
return getSheet(CLOUD_SHEET, ['key', 'data', 'timestamp']);
}
function getBackupSheet() {
return getSheet(BACKUP_SHEET, ['backup_at', 'data', 'timestamp']);
}
function jsonResponse(payload) {
return ContentService
.createTextOutput(JSON.stringify(payload))
.setMimeType(ContentService.MimeType.JSON);
}
function backupCurrentCloud() {
const cloud = getCloudSheet();
if (cloud.getLastRow() < 2) return;
const row = cloud.getRange(2, 1, 1, 3).getValues()[0];
const currentData = row[1] || '';
if (!currentData) return;
const backups = getBackupSheet();
backups.insertRowBefore(2);
backups.getRange(2, 1, 1, 3).setValues([[
new Date(),
currentData,
row[2] || Date.now()
]]);
const extraRows = backups.getLastRow() - 1 - MAX_BACKUPS;
if (extraRows > 0) {
backups.deleteRows(MAX_BACKUPS + 2, extraRows);
}
}
function doGet() {
const sheet = getCloudSheet();
const data = sheet.getRange(2, 2).getValue() || '';
const timestamp = Number(sheet.getRange(2, 3).getValue() || 0);
return jsonResponse({ success: true, data, timestamp });
}
function doPost(e) {
try {
const body = JSON.parse((e.postData && e.postData.contents) || '{}');
if (!body.data) throw new Error('Missing data');
if (String(body.data).length < MIN_DATA_LENGTH) {
throw new Error('Refuse short payload to avoid overwriting backup with empty data');
}
const sheet = getCloudSheet();
if (sheet.getLastRow() < 2) sheet.appendRow(['cloud', '', '']);
backupCurrentCloud();
sheet.getRange(2, 1, 1, 3).setValues([[
'cloud',
body.data,
body.timestamp || Date.now()
]]);
return jsonResponse({ success: true });
} catch (error) {
return jsonResponse({ success: false, error: error.message });
}
}
Bạn có chắc chắn muốn thực hiện hành động này?