/* * WEEKLY NEWS NEWSLETTER * Webz.io News Search API + Google Apps Script * * Change only the CONFIG section below. */ const CONFIG = { QUERY: 'Supply chain disruptions affecting European car manufacturers', DAYS_BACK: 7, RESULTS_TO_FETCH: 30, STORIES_TO_INCLUDE: 8, MAX_STORIES_PER_DOMAIN: 2, FILTERS: { language: ['english'], country: [], category: ['Economy, Business and Finance'], sentiment: [], domain: [], exclude_domain: [] }, NEWSLETTER_TITLE: 'Weekly Supply Chain Brief', SENDER_NAME: 'Weekly Supply Chain Brief', TEST_RECIPIENT: 'your-email@example.com', RECIPIENTS: [ 'your-email@example.com' ] }; const WEBZ_ENDPOINT = 'https://api.webz.io/api/news/context'; function testNewsletter() { runNewsletter([CONFIG.TEST_RECIPIENT]); } function sendWeeklyNewsletter() { runNewsletter(CONFIG.RECIPIENTS); } function runNewsletter(recipients) { validateConfiguration(recipients); const data = searchWebz(); const stories = selectStories(data.results || []); if (stories.length === 0) { console.log('No matching articles were found.'); return; } const dateRange = getNewsletterDateRange(); const subject = CONFIG.NEWSLETTER_TITLE + ' - ' + dateRange.start + ' to ' + dateRange.end; const htmlBody = buildHtmlNewsletter(stories, dateRange); const textBody = buildTextNewsletter(stories, dateRange); const remainingQuota = MailApp.getRemainingDailyQuota(); if (remainingQuota < recipients.length) { throw new Error( 'Not enough email quota remaining. Need ' + recipients.length + ', but only ' + remainingQuota + ' recipients remain today.' ); } recipients.forEach(function(email) { MailApp.sendEmail({ to: email, subject: subject, body: textBody, htmlBody: htmlBody, name: CONFIG.SENDER_NAME }); }); console.log( 'Newsletter sent to ' + recipients.length + ' recipient(s).' ); console.log( 'Stories included: ' + stories.length ); if (data.credits_used !== undefined) { console.log( 'Webz.io credits used: ' + data.credits_used ); } } function searchWebz() { const apiKey = PropertiesService .getScriptProperties() .getProperty('WEBZ_API_KEY'); if (!apiKey) { throw new Error( 'WEBZ_API_KEY was not found. ' + 'Add it under Project Settings > Script Properties.' ); } const payload = { query: CONFIG.QUERY, k: CONFIG.RESULTS_TO_FETCH, filters: buildWebzFilters() }; console.log( 'Webz.io request: ' + JSON.stringify(payload) ); const response = UrlFetchApp.fetch( WEBZ_ENDPOINT, { method: 'post', contentType: 'application/json', headers: { Authorization: 'Bearer ' + apiKey }, payload: JSON.stringify(payload), muteHttpExceptions: true } ); const status = response.getResponseCode(); const responseText = response.getContentText(); if (status < 200 || status >= 300) { throw new Error( 'Webz.io returned HTTP ' + status + '\n\n' + responseText ); } try { return JSON.parse(responseText); } catch (error) { throw new Error( 'Webz.io returned invalid JSON: ' + error.message ); } } function buildWebzFilters() { const filters = { published_from: getPublishedFrom() }; addFilterIfNotEmpty( filters, 'language', CONFIG.FILTERS.language ); addFilterIfNotEmpty( filters, 'country', CONFIG.FILTERS.country ); addFilterIfNotEmpty( filters, 'category', CONFIG.FILTERS.category ); addFilterIfNotEmpty( filters, 'sentiment', CONFIG.FILTERS.sentiment ); addFilterIfNotEmpty( filters, 'domain', CONFIG.FILTERS.domain ); addFilterIfNotEmpty( filters, 'exclude_domain', CONFIG.FILTERS.exclude_domain ); return filters; } function addFilterIfNotEmpty(filters, name, values) { if ( Array.isArray(values) && values.length > 0 ) { filters[name] = values; } } function getPublishedFrom() { const date = new Date(); date.setUTCDate( date.getUTCDate() - CONFIG.DAYS_BACK ); return date.toISOString(); } function selectStories(results) { const selected = []; const seenUrls = new Set(); const domainCounts = {}; for (let i = 0; i < results.length; i++) { const result = results[i] || {}; const article = result.article || {}; const metadata = result.metadata || {}; if ( !article.title || !article.url ) { continue; } if ( seenUrls.has(article.url) ) { continue; } const domain = String( metadata.domain || getDomainFromUrl(article.url) || '' ).toLowerCase(); if ( domain && (domainCounts[domain] || 0) >= CONFIG.MAX_STORIES_PER_DOMAIN ) { continue; } seenUrls.add(article.url); if (domain) { domainCounts[domain] = (domainCounts[domain] || 0) + 1; } selected.push(result); if ( selected.length >= CONFIG.STORIES_TO_INCLUDE ) { break; } } return selected; } function buildHtmlNewsletter(stories, dateRange) { let storiesHtml = ''; stories.forEach(function(result) { const article = result.article || {}; const metadata = result.metadata || {}; const source = metadata.domain || getDomainFromUrl(article.url) || 'News source'; const published = formatPublishedDate( article.published_at ); const summary = getStorySummary(result); storiesHtml += '