Seamlessly push your Telegram notes to Google Drive
Link your Google Drive account to sync finished notes instantly.
Connect your Telegram account in one click to forward notes, photos, and files automatically:
Launch Telegram BotEnable high-fidelity media previewing and instant audio playback directly inside your Google Sheet window!
myFunction).+ (plus) icon next to Files, select HTML, name the file exactly Sidebar (resulting in Sidebar.html)./**
* Notebox Google Sheets Media Preview Sidebar
* Paste this script into your Spreadsheet's Extensions -> Apps Script editor!
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('🎙️ Notebox Previewer')
.addItem('📱 Open Preview Sidebar', 'showSidebar')
.addToUi();
}
function showSidebar() {
const html = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Notebox Media Preview')
.setWidth(350);
SpreadsheetApp.getUi().showSidebar(html);
}
/**
* Triggered automatically when selecting cells in Google Sheets.
* Extracts voice and media links from the selected row and sends them to the Sidebar player.
*/
function onSelectionChange(e) {
const range = e.range;
const sheet = range.getSheet();
const row = range.getRow();
if (row === 1) return; // Skip header row
// Read voice note cell (Column E / index 5) and media cell (Column F / index 6) formulas
const voiceCell = sheet.getRange(row, 5);
const mediaCell = sheet.getRange(row, 6);
const titleCell = sheet.getRange(row, 3);
const voiceFormula = voiceCell.getFormula();
const mediaFormula = mediaCell.getFormula();
const noteTitle = titleCell.getValue() || "Untitled Note";
const voiceUrl = extractUrlFromFormula(voiceFormula);
const mediaUrl = extractUrlFromFormula(mediaFormula);
// Send data to the active sidebar cache
const userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('selected_row_title', noteTitle);
userProperties.setProperty('selected_row_voice', voiceUrl || "");
userProperties.setProperty('selected_row_media', mediaUrl || "");
}
/**
* Parse standard =HYPERLINK("url", "label") Google Sheets formula to extract the absolute URL target.
*/
function extractUrlFromFormula(formula) {
if (!formula) return null;
const match = formula.match(/=HYPERLINK\(\s*["']([^"']+)["']/i);
if (match && match.length > 1) {
const targetUrl = match[1];
if (targetUrl.includes('?preview=')) {
try {
const urlParamsMatch = targetUrl.match(/[\?&]preview=([^&]+)/);
if (urlParamsMatch && urlParamsMatch.length > 1) {
return decodeURIComponent(urlParamsMatch[1]);
}
} catch (err) {
console.warn("Could not decode preview query param:", err);
}
}
return targetUrl;
}
return null;
}
/**
* Server-side function called by Sidebar HTML polling to check if the selection has changed.
*/
function getActiveRowMedia() {
const userProperties = PropertiesService.getUserProperties();
return {
title: userProperties.getProperty('selected_row_title') || "No selection",
voiceUrl: userProperties.getProperty('selected_row_voice') || "",
mediaUrl: userProperties.getProperty('selected_row_media') || ""
};
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', sans-serif;
background-color: #0b0f19;
color: #f3f4f6;
padding: 1.25rem;
overflow-x: hidden;
min-height: 100vh;
background-image: radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.1) 0px, transparent 50%);
}
.panel {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
padding: 1rem;
margin-bottom: 1rem;
backdrop-filter: blur(10px);
}
h3 { font-size: 1rem; font-weight: 600; margin-bottom: 0.5rem; color: #fff; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
p { font-size: 0.8rem; color: #9ca3af; line-height: 1.4; }
.title-badge { display: inline-block; font-size: 0.75rem; font-weight: 500; background: rgba(59, 130, 246, 0.15); color: #3b82f6; border: 1px solid rgba(59, 130, 246, 0.25); border-radius: 9999px; padding: 2px 8px; margin-bottom: 0.75rem; }
.player-container { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.75rem; width: 100%; }
audio, video { width: 100%; border-radius: 8px; display: block; outline: none; background: #000; }
img { max-width: 100%; border-radius: 8px; max-height: 220px; object-fit: contain; display: block; border: 1px solid rgba(255, 255, 255, 0.08); margin: 0 auto; }
.speed-btn { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); color: #f3f4f6; padding: 2px 8px; font-size: 0.7rem; border-radius: 4px; cursor: pointer; transition: all 0.2s; }
.speed-btn:hover { background: rgba(255, 255, 255, 0.1); }
.speed-btn.active { background: #6366f1; border-color: #6366f1; }
.no-selection { text-align: center; padding: 3rem 1rem; color: #9ca3af; font-size: 0.85rem; border: 1px dashed rgba(255, 255, 255, 0.1); border-radius: 8px; }
</style>
</head>
<body>
<div style="display:flex; flex-direction:column; gap:0.5rem; margin-bottom:1rem;">
<h2 style="font-size:1.15rem; font-weight:700; color:#fff; background: linear-gradient(135deg, #3b82f6, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Notebox Companion</h2>
<p style="font-size:0.75rem; color:#9ca3af;">Click on any row in the spreadsheet to instantly preview voice notes and attachment media in this window.</p>
</div>
<div id="selection-card" style="display: none;">
<div class="panel">
<div id="note-title-badge" class="title-badge">Selected Note</div>
<h3 id="note-title">Meeting updates</h3>
</div>
<!-- Audio/Voice Player block -->
<div id="voice-panel" class="panel" style="display: none;">
<h3 style="font-size:0.85rem; color:#9ca3af; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.75rem;">🎙️ Voice Note Preview</h3>
<div class="player-container">
<audio id="audio-player" controls autoplay></audio>
<div style="display: flex; gap: 0.4rem; align-items: center;">
<span style="font-size: 0.7rem; color: #9ca3af;">Speed:</span>
<button onclick="setSpeed(1.0, this)" class="speed-btn active">1x</button>
<button onclick="setSpeed(1.5, this)" class="speed-btn">1.5x</button>
<button onclick="setSpeed(2.0, this)" class="speed-btn">2x</button>
</div>
</div>
</div>
<!-- Media Player block -->
<div id="media-panel" class="panel" style="display: none;">
<h3 style="font-size:0.85rem; color:#9ca3af; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.75rem;">🔗 Media Attachment</h3>
<div id="media-content" class="player-container">
<!-- Rendered Image or Video -->
</div>
</div>
</div>
<div id="empty-card" class="no-selection">
<svg style="width: 32px; height: 32px; color: rgba(255,255,255,0.15); margin-bottom: 0.5rem;" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
<p>Select any note row in the spreadsheet to play audio or view files immediately.</p>
</div>
<script>
let currentVoiceUrl = "";
let currentMediaUrl = "";
function setSpeed(rate, btn) {
const audio = document.getElementById("audio-player");
if (audio) audio.playbackRate = rate;
document.querySelectorAll(".speed-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
}
function checkSelection() {
google.script.run.withSuccessHandler(function(data) {
if (!data || (data.voiceUrl === "" && data.mediaUrl === "")) {
document.getElementById("selection-card").style.display = "none";
document.getElementById("empty-card").style.display = "block";
const audio = document.getElementById("audio-player");
if (audio) { audio.pause(); audio.src = ""; }
currentVoiceUrl = "";
currentMediaUrl = "";
return;
}
document.getElementById("empty-card").style.display = "none";
document.getElementById("selection-card").style.display = "block";
document.getElementById("note-title").innerText = data.title;
// Check if voice note changed
if (data.voiceUrl !== currentVoiceUrl) {
currentVoiceUrl = data.voiceUrl;
const voicePanel = document.getElementById("voice-panel");
const audio = document.getElementById("audio-player");
if (currentVoiceUrl) {
audio.src = currentVoiceUrl;
audio.load();
voicePanel.style.display = "block";
} else {
audio.pause();
audio.src = "";
voicePanel.style.display = "none";
}
}
// Check if media attachment changed
if (data.mediaUrl !== currentMediaUrl) {
currentMediaUrl = data.mediaUrl;
const mediaPanel = document.getElementById("media-panel");
const mediaContent = document.getElementById("media-content");
if (currentMediaUrl) {
const lowerUrl = currentMediaUrl.split('?')[0].toLowerCase();
const isImage = lowerUrl.endsWith('.jpg') || lowerUrl.endsWith('.jpeg') || lowerUrl.endsWith('.png') || lowerUrl.endsWith('.gif') || lowerUrl.endsWith('.webp');
const isVideo = lowerUrl.endsWith('.mp4') || lowerUrl.endsWith('.mov') || lowerUrl.endsWith('.webm');
if (isImage) {
mediaContent.innerHTML = `<img src="${currentMediaUrl}" alt="Attachment Preview" style="max-width: 100%; border-radius: 8px;">`;
} else if (isVideo) {
mediaContent.innerHTML = `<video controls autoplay src="${currentMediaUrl}"></video>`;
} else {
mediaContent.innerHTML = `
<div style="padding: 1rem; border-radius: 8px; border: 1px dashed rgba(255,255,255,0.1); background: rgba(0,0,0,0.15); text-align: center;">
<p style="font-size:0.75rem; color:#f3f4f6; margin-bottom: 0.25rem;">Non-playable attachment</p>
<a href="${currentMediaUrl}" target="_blank" style="font-size: 0.75rem; color: #3b82f6; text-decoration: none; font-weight: 500;">Open file in new tab</a>
</div>
`;
}
mediaPanel.style.display = "block";
} else {
mediaContent.innerHTML = "";
mediaPanel.style.display = "none";
}
}
}).getActiveRowMedia();
}
// Poll selection states every 1.5 seconds for instant dashboard feel
setInterval(checkSelection, 1500);
</script>
</body>
</html>
1. Acceptance of Terms
By accessing and using Notebox ("the Service"), you agree to comply with and be bound by these Terms of Service.
2. Description of Service
Notebox provides user-initiated synchronization of Telegram voice notes, text messages, and file attachments directly to the user's private Google Drive folder and Google Sheets Master Log.
3. Use of Google User Data
The Service accesses your Google Drive and Google Sheets solely via secure client-side API requests to read, write, and format files in the designated "Notebox" folder. Your Google access tokens are stored strictly locally in your browser's private local storage and are never transmitted to any third-party servers.
4. User Responsibilities
You are responsible for maintaining the confidentiality of your credentials and account settings.
1. Introduction
Your privacy is of paramount importance. This Privacy Policy details how Notebox handles your data and Google user information.
2. Information Collection & Use
We do not operate backend servers or databases to store your Google credentials or drive files. All integrations operate locally and securely in your browser window.
3. Google User Data Policy
Notebox strictly complies with the Google API Services User Data Policy, including the Limited Use requirements. We request authorization only for the minimum necessary scopes:
• .../auth/drive.file: To create the "Notebox" folder and store your notes.
• .../auth/spreadsheets: To log note entries inside the master spreadsheet.
Your Google Drive data is never transferred, shared, or sold to external third parties.
4. Local Storage
Access tokens and folder identifiers are cached locally in your browser's private storage (localStorage) for seamless syncing and are cleared immediately upon logging out.