/* global CardService, GmailApp, UrlFetchApp */ 'use strict'; var API_BASE = 'https://cleanlinks.thewebdexter.com'; var LOGO_URL = 'https://cleanlinks.thewebdexter.com/icons/cleanlinks-icon-512.png'; // ── Entry points ────────────────────────────────────────────────────────────── // Fired automatically when the user opens any email — scans immediately function buildContextualUI(e) { try { var messageId = e.gmail.messageId; var accessToken = e.gmail.accessToken; GmailApp.setCurrentMessageAccessToken(accessToken); var body = GmailApp.getMessageById(messageId).getBody(); var links = extractLinks(body); if (!links.length) { return [buildNoLinksCard()]; } var results = cleanAll(links); return [buildResultsCard(results)]; } catch (err) { return [buildErrorCard(err.message)]; } } // Shown when no email is open function buildHomePage() { return CardService.newCardBuilder() .setHeader(header('Ready')) .addSection(CardService.newCardSection() .addWidget(CardService.newTextParagraph() .setText('Open any email — CleanLinks scans every link automatically and removes tracking parameters.'))) .build(); } // Re-scan button action function doRescan(e) { var messageId = e.gmail.messageId; var accessToken = e.gmail.accessToken; GmailApp.setCurrentMessageAccessToken(accessToken); var body = GmailApp.getMessageById(messageId).getBody(); var links = extractLinks(body); var results = links.length ? cleanAll(links) : []; return CardService.newActionResponseBuilder() .setNavigation(CardService.newNavigation() .updateCard(links.length ? buildResultsCard(results) : buildNoLinksCard())) .build(); } // ── Core: parallel clean ────────────────────────────────────────────────────── function cleanAll(links) { var requests = links.map(function(url) { return { url: API_BASE + '/api/clean?url=' + encodeURIComponent(url), muteHttpExceptions: true }; }); var responses = UrlFetchApp.fetchAll(requests); return responses.map(function(resp, i) { try { var data = JSON.parse(resp.getContentText()); return data.success ? data : bare(links[i]); } catch (_) { return bare(links[i]); } }); } // ── Card builders ───────────────────────────────────────────────────────────── function buildResultsCard(results) { var cleaned = results.filter(function(r) { return r.removed > 0 || r.wasUnwrapped; }); var totalTrackers = results.reduce(function(s, r) { return s + (r.removed || 0); }, 0); var totalLinks = results.length; var subtitle = cleaned.length > 0 ? 'Removed ' + totalTrackers + ' tracker' + (totalTrackers !== 1 ? 's' : '') + ' from ' + cleaned.length + ' of ' + totalLinks + ' links' : 'All ' + totalLinks + ' links are already clean'; var builder = CardService.newCardBuilder() .setHeader(header(subtitle)); if (cleaned.length > 0) { var section = CardService.newCardSection() .setHeader('Clean URLs — open or copy'); cleaned.forEach(function(item) { section.addWidget(linkWidget(item)); }); builder.addSection(section); } builder.addSection(CardService.newCardSection() .addWidget(CardService.newTextButton() .setText('Re-scan') .setOnClickAction(CardService.newAction().setFunctionName('doRescan')))); return builder.build(); } function buildNoLinksCard() { return CardService.newCardBuilder() .setHeader(header('No links found')) .addSection(CardService.newCardSection() .addWidget(CardService.newTextParagraph() .setText('This email contains no http/https links.'))) .build(); } function buildErrorCard(message) { return CardService.newCardBuilder() .setHeader(header('CleanLinks')) .addSection(CardService.newCardSection() .addWidget(CardService.newTextParagraph() .setText('Could not read this email: ' + message))) .build(); } // ── Helpers ─────────────────────────────────────────────────────────────────── function linkWidget(item) { var url = item.cleaned || item.original; var label = ''; if (item.removed > 0) label = item.removed + ' tracker' + (item.removed !== 1 ? 's' : '') + ' removed'; if (item.wasUnwrapped && item.unwrappedVia) label += (label ? ' · ' : '') + 'unwrapped from ' + item.unwrappedVia; return CardService.newDecoratedText() .setTopLabel(hostname(url)) .setText(trunc(url, 55)) .setBottomLabel(label || 'already clean') .setButton(CardService.newTextButton() .setText('Open') .setOpenLink(CardService.newOpenLink() .setUrl(url) .setOpenAs(CardService.OpenAs.FULL_SIZE) .setOnClose(CardService.OnClose.NOTHING))); } function header(subtitle) { return CardService.newCardHeader() .setTitle('CleanLinks') .setSubtitle(subtitle) .setImageUrl(LOGO_URL) .setImageStyle(CardService.ImageStyle.CIRCLE); } function bare(url) { return { original: url, cleaned: url, removed: 0, wasUnwrapped: false }; } function extractLinks(html) { var links = [], seen = {}; var re = /href=["'](https?:\/\/[^"'<>\s]+)["']/gi; var m; while ((m = re.exec(html)) !== null) { if (!seen[m[1]]) { seen[m[1]] = true; links.push(m[1]); } } return links; } function hostname(url) { try { return url.match(/^https?:\/\/([^\/]+)/)[1]; } catch (_) { return url; } } function trunc(s, n) { return s.length > n ? s.slice(0, n - 1) + '…' : s; }