Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * 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',
- RECIPIENTS: [
- ]
- };
- 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 +=
- '<div style="' +
- 'margin:0 0 28px 0;' +
- 'padding:0 0 24px 0;' +
- 'border-bottom:1px solid #e5e5e5;' +
- '">' +
- '<div style="' +
- 'color:#777;' +
- 'font-size:13px;' +
- 'margin-bottom:6px;' +
- '">' +
- escapeHtml(source) +
- (
- published
- ? ' · ' +
- escapeHtml(published)
- : ''
- ) +
- '</div>' +
- '<div style="' +
- 'font-size:19px;' +
- 'font-weight:bold;' +
- 'line-height:1.4;' +
- 'margin-bottom:8px;' +
- '">' +
- escapeHtml(article.title) +
- '</div>' +
- '<div style="' +
- 'font-size:15px;' +
- 'line-height:1.6;' +
- 'margin-bottom:10px;' +
- '">' +
- escapeHtml(summary) +
- '</div>' +
- '<a href="' +
- escapeHtml(article.url) +
- '" style="' +
- 'font-size:14px;' +
- 'font-weight:bold;' +
- '">' +
- 'Read the article →' +
- '</a>' +
- '</div>';
- });
- return (
- '<div style="' +
- 'max-width:650px;' +
- 'margin:0 auto;' +
- 'font-family:Arial,Helvetica,sans-serif;' +
- 'color:#222;' +
- '">' +
- '<div style="' +
- 'font-size:28px;' +
- 'font-weight:bold;' +
- 'margin-bottom:5px;' +
- '">' +
- escapeHtml(
- CONFIG.NEWSLETTER_TITLE
- ) +
- '</div>' +
- '<div style="' +
- 'color:#777;' +
- 'font-size:14px;' +
- 'margin-bottom:28px;' +
- '">' +
- escapeHtml(dateRange.start) +
- ' - ' +
- escapeHtml(dateRange.end) +
- '</div>' +
- '<div style="' +
- 'font-size:16px;' +
- 'line-height:1.6;' +
- 'margin-bottom:30px;' +
- '">' +
- 'Weekly news about <strong>' +
- escapeHtml(CONFIG.QUERY) +
- '</strong>' +
- '</div>' +
- storiesHtml +
- '<div style="' +
- 'color:#999;' +
- 'font-size:12px;' +
- 'margin-top:30px;' +
- '">' +
- 'News retrieved using the Webz.io News Search API.' +
- '</div>' +
- '</div>'
- );
- }
- function buildTextNewsletter(stories, dateRange) {
- let output =
- CONFIG.NEWSLETTER_TITLE +
- '\n' +
- dateRange.start +
- ' - ' +
- dateRange.end +
- '\n\n' +
- 'Weekly news about: ' +
- CONFIG.QUERY +
- '\n\n';
- stories.forEach(
- function(result, index) {
- const article =
- result.article || {};
- const metadata =
- result.metadata || {};
- const source =
- metadata.domain ||
- getDomainFromUrl(
- article.url
- ) ||
- 'News source';
- output +=
- (index + 1) +
- '. ' +
- article.title +
- '\n' +
- source +
- '\n' +
- getStorySummary(result) +
- '\n' +
- article.url +
- '\n\n';
- }
- );
- return output;
- }
- function getStorySummary(result) {
- const article =
- result.article || {};
- const chunk =
- result.chunk || {};
- const text =
- article.summary ||
- chunk.text ||
- '';
- return truncateText(
- text,
- 500
- );
- }
- function truncateText(text, maxLength) {
- text = String(text || '')
- .replace(/\s+/g, ' ')
- .trim();
- if (
- text.length <= maxLength
- ) {
- return text;
- }
- return (
- text
- .substring(
- 0,
- maxLength - 3
- )
- .trim() +
- '...'
- );
- }
- function formatPublishedDate(publishedAt) {
- if (!publishedAt) {
- return '';
- }
- const date =
- new Date(publishedAt);
- if (
- isNaN(date.getTime())
- ) {
- return '';
- }
- return Utilities.formatDate(
- date,
- Session.getScriptTimeZone(),
- 'MMM d, yyyy'
- );
- }
- function getNewsletterDateRange() {
- const end = new Date();
- const start =
- new Date(
- end.getTime() -
- CONFIG.DAYS_BACK *
- 24 *
- 60 *
- 60 *
- 1000
- );
- return {
- start: Utilities.formatDate(
- start,
- Session.getScriptTimeZone(),
- 'MMM d, yyyy'
- ),
- end: Utilities.formatDate(
- end,
- Session.getScriptTimeZone(),
- 'MMM d, yyyy'
- )
- };
- }
- function getDomainFromUrl(url) {
- return String(url || '')
- .replace(/^https?:\/\//i, '')
- .replace(/^www\./i, '')
- .split('/')[0]
- .split('?')[0]
- .split('#')[0];
- }
- function escapeHtml(value) {
- return String(value || '')
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- }
- function validateConfiguration(recipients) {
- if (
- !CONFIG.QUERY ||
- !CONFIG.QUERY.trim()
- ) {
- throw new Error(
- 'CONFIG.QUERY is empty.'
- );
- }
- if (
- !Number.isInteger(
- CONFIG.DAYS_BACK
- ) ||
- CONFIG.DAYS_BACK < 1 ||
- CONFIG.DAYS_BACK > 30
- ) {
- throw new Error(
- 'DAYS_BACK must be an integer between 1 and 30.'
- );
- }
- if (
- !Number.isInteger(
- CONFIG.RESULTS_TO_FETCH
- ) ||
- CONFIG.RESULTS_TO_FETCH < 1 ||
- CONFIG.RESULTS_TO_FETCH > 50
- ) {
- throw new Error(
- 'RESULTS_TO_FETCH must be an integer between 1 and 50.'
- );
- }
- if (
- !Number.isInteger(
- CONFIG.STORIES_TO_INCLUDE
- ) ||
- CONFIG.STORIES_TO_INCLUDE < 1
- ) {
- throw new Error(
- 'STORIES_TO_INCLUDE must be a positive integer.'
- );
- }
- if (
- !Number.isInteger(
- CONFIG.MAX_STORIES_PER_DOMAIN
- ) ||
- CONFIG.MAX_STORIES_PER_DOMAIN < 1
- ) {
- throw new Error(
- 'MAX_STORIES_PER_DOMAIN must be a positive integer.'
- );
- }
- if (
- !Array.isArray(recipients) ||
- recipients.length === 0
- ) {
- throw new Error(
- 'No newsletter recipients were configured.'
- );
- }
- recipients.forEach(
- function(email) {
- if (
- !email ||
- !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(
- email
- )
- ) {
- throw new Error(
- 'Invalid recipient: ' +
- email
- );
- }
- }
- );
- }
- function createWeeklyTrigger() {
- const triggers =
- ScriptApp.getProjectTriggers();
- triggers.forEach(
- function(trigger) {
- if (
- trigger.getHandlerFunction() ===
- 'sendWeeklyNewsletter'
- ) {
- ScriptApp.deleteTrigger(
- trigger
- );
- }
- }
- );
- ScriptApp
- .newTrigger(
- 'sendWeeklyNewsletter'
- )
- .timeBased()
- .everyWeeks(1)
- .onWeekDay(
- ScriptApp.WeekDay.MONDAY
- )
- .atHour(9)
- .create();
- console.log(
- 'Weekly newsletter trigger created.'
- );
- }
Advertisement