' +
'
' +
escapeHtml(source) +
(
published
? ' · ' +
escapeHtml(published)
: ''
) +
'
' +
'
' +
escapeHtml(article.title) +
'
' +
'
' +
escapeHtml(summary) +
'
' +
'
' +
'Read the article →' +
'' +
'
';
});
return (
'' +
'
' +
escapeHtml(
CONFIG.NEWSLETTER_TITLE
) +
'
' +
'
' +
escapeHtml(dateRange.start) +
' - ' +
escapeHtml(dateRange.end) +
'
' +
'
' +
'Weekly news about ' +
escapeHtml(CONFIG.QUERY) +
'' +
'
' +
storiesHtml +
'
' +
'News retrieved using the Webz.io News Search API.' +
'
' +
'
'
);
}
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, ''');
}
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.'
);
}