├── Code.gs └── README.md /Code.gs: -------------------------------------------------------------------------------- 1 | // source: https://github.com/qoomon/gmail-rss-feed 2 | // Version: 1.0.1 3 | 4 | const {doGet} = (() => { 5 | // --------------- config --------------------- 6 | const rssFeedGmailRootLabel = 'RSS Feed' 7 | const rssFeedMaxItems = 50 8 | 9 | // --------------- entrypoint --------------------- 10 | function doGet(event) { 11 | Logger.log("[EVENT]") 12 | Logger.log('Parameters:', event.parameter) 13 | const response = doGetRssFeedXml(event) 14 | Logger.log('[RESPONSE]') 15 | Logger.log('MimeType:', response.getMimeType()) 16 | Logger.log('Content...') 17 | Logger.log(response.getContent()) 18 | return response 19 | } 20 | 21 | function doGetRssFeedXml(event) { 22 | // --------------- input parameters--------------------- 23 | 24 | const rssFeedName = event.parameter['gmail-rss-feed'] 25 | if (!rssFeedName) { 26 | return ContentService.createTextOutput("400 - 'feed' parameter missing") 27 | } 28 | 29 | const rssFeedTitle = event.parameter['title'] 30 | 31 | const isMultiAuthor = isAnyOf(event.parameter['multi-author'], ['', 'true']) 32 | 33 | // --------------- gather rss mails--------------------- 34 | 35 | const gmailFeedLabel = GmailApp.getUserLabelByName(`${rssFeedGmailRootLabel}/${rssFeedName}`) 36 | if (!gmailFeedLabel) { 37 | return ContentService.createTextOutput(`400 - '${rssFeedName}' feed not found`) 38 | } 39 | 40 | const gmailFeedMessages = gmailFeedLabel.getThreads(0, rssFeedMaxItems) 41 | .map(thread => thread.getMessages()[0]) 42 | 43 | // --------------- create rss feed --------------------- 44 | 45 | const rssFeed = { 46 | title: rssFeedTitle || `${rssFeedName} - Gmail RSS Feed`, 47 | description: `${rssFeedName} - Gmail RSS Feed`, 48 | link: `https://mail.google.com/#label/${gmailFeedLabel.getName()}`, 49 | image: 'https://ssl.gstatic.com/ui/v1/icons/mail/favicon.ico', 50 | atomLink: ScriptApp.getService().getUrl(), 51 | items: gmailFeedMessages.map(gmailMessage => ({ 52 | guid: gmailMessage.getId(), 53 | author: gmailMessage.getFrom(), 54 | title: (isMultiAuthor ? `${getSenderDisplayName(gmailMessage.getFrom())} - `: "") 55 | + gmailMessage.getSubject(), 56 | link: `https://mail.google.com/mail/u/0/#label/${gmailFeedLabel.getName()}/${gmailMessage.getId()}`, 57 | description: gmailMessage.getBody(), 58 | pubDate: gmailMessage.getDate() 59 | })) 60 | } 61 | 62 | const rssFeedXmlElement = createRssFeedXmlElement(rssFeed) 63 | const rssFeedXmlDocument = XmlService.createDocument(rssFeedXmlElement) 64 | const rssFeedXmlText = XmlService.getPrettyFormat().format(rssFeedXmlDocument) 65 | return ContentService.createTextOutput(rssFeedXmlText) 66 | .setMimeType(ContentService.MimeType.RSS) 67 | } 68 | 69 | function createRssFeedXmlElement(rssObject) { 70 | const rss = XmlService.parse("").detachRootElement() 71 | return rss.addContent(xmlElement('channel', channel => { 72 | channel.addContent(xmlElement('title').setText(rssObject.title)) 73 | channel.addContent(xmlElement('description').setText(rssObject.description)) 74 | channel.addContent(xmlElement('link').setText(rssObject.link)) 75 | if (rssObject.image) { 76 | channel.addContent(xmlElement('image') 77 | .addContent(xmlElement('url').setText(rssObject.image)) 78 | .addContent(xmlElement('title').setText(rssObject.title)) 79 | .addContent(xmlElement('link').setText(rssObject.link))) 80 | } 81 | if (rssObject.atomLink) { 82 | channel.addContent(XmlService.createElement('link') 83 | .setNamespace(rss.getNamespace('atom')) 84 | .setAttribute('href', rssObject.atomLink) 85 | .setAttribute('rel', 'self') 86 | .setAttribute('type', 'application/rss+xml')) 87 | } 88 | rssObject.items.forEach(itemObject => { 89 | channel.addContent(createRssFeedItemXmlElement(itemObject)) 90 | }) 91 | })) 92 | } 93 | 94 | function createRssFeedItemXmlElement(itemObject) { 95 | return xmlElement('item', item => { 96 | item.addContent(xmlElement('title').setText(itemObject.title)) 97 | item.addContent(xmlElement('link').setText(itemObject.link)) 98 | item.addContent(xmlElement('description') 99 | .addContent(XmlService.createCdata(itemObject.description))) 100 | if (itemObject.pubDate) { 101 | item.addContent(xmlElement('pubDate') 102 | .setText(Utilities.formatDate(itemObject.pubDate, 'UTC', "EEE, dd MMM yyyy HH:mm:ss Z"))) 103 | } 104 | if (itemObject.guid) { 105 | item.addContent(xmlElement('guid').setText(itemObject.guid)) 106 | } 107 | if (itemObject.author) { 108 | item.addContent(xmlElement('author').setText(itemObject.author)) 109 | } 110 | }) 111 | } 112 | 113 | function xmlElement(name, modifier) { 114 | const element = XmlService.createElement(name) 115 | if (modifier) { 116 | modifier(element) 117 | } 118 | return element 119 | } 120 | 121 | function isAnyOf(value, ...validValues) { 122 | validValues.includes(value) 123 | } 124 | 125 | function getSenderDisplayName(sender) { 126 | return sender.trim().match(/^(?[^<]+)<.*>/)?.groups?.name?.trim() 127 | ?? sender.trim().match(/^<(?[^>]*)>/)?.groups?.name?.trim() 128 | ?? sender.trim() 129 | } 130 | 131 | return {doGet} 132 | })(); 133 | 134 | // --------------- test --------------------- 135 | 136 | function doGet_sample_event() { 137 | doGet({ 138 | parameter: { 139 | 'gmail-rss-feed': 'Newsletter', 140 | 'multi-account': 'true' 141 | } 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gmail RSS Feed 2 | A [Google Scripts](https://script.google.com) Web App to dynamicaly generate an RSS feed based on labeled emails (`RSS Feed/`) 3 | 4 | ### Installation 5 | * Create a blank project on https://script.google.com/ 6 | * Copy content of [`Code.gs`](Code.gs) into your new project 7 | * Run the script once by clicking Menu `Run > doGet` 8 | * You will be asked to grant this script access to your Gmail account 9 | * Accept the request 10 | * Create a first version by clicking Menu `File > Manage Versions` 11 | * Deploy this script by clicking Menu `Publish > Deploy as web app` 12 | * Select `Execute the app as : me` 13 | * Select `Who has access to the app : Anonyone, even anonymous` 14 | * Copy `Current web app URL` e.g. `https://script.google.com/macros/s//exec` 15 | * ⚠️ Everyone with this URL is potentialy able to read all mails labled with `RSS Feed/` 16 | * Label your first emails with a nested `RSS` label (`RSS Feed/`) e.g. `RSS Feed/Newsletter` 17 | * Create your feed URL by add following query parameters to `Current web app URL` 18 | * `gmail-rss-feed=` selects rss feed name of coresponding Gmail label (`RSS Feed/`) 19 | * `multi-author` enables author name to be prepand to feed item titles 20 | * Example URL `https://script.google.com/macros/s//exec?gmail-rss-feed=Newsletter&multi-author` 21 | --------------------------------------------------------------------------------