[READ-ONLY] Mirror of https://github.com/probablykasper/yt-email-notifier. macOS menubar app for YouTube upload notification emails
email menubar notifications tray youtube
0

Configure Feed

Select the types of activity you want to include in your feed.

Added video duration to emails, fixed email spacing on mobile

1. Added video duration to emails, which shows up as a box in the bottom-right of the thumbnail
2. Fixed extra spacing appearing above and below the thumbnail in emails

Kasper (Nov 17, 2020, 11:18 PM +0100) 75160127 1a9651d8

+68 -12
+5
package-lock.json
··· 1644 1644 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1645 1645 "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 1646 1646 }, 1647 + "iso8601-duration": { 1648 + "version": "1.2.0", 1649 + "resolved": "https://registry.npmjs.org/iso8601-duration/-/iso8601-duration-1.2.0.tgz", 1650 + "integrity": "sha512-ErTBd++b17E8nmWII1K1uZtBgD1E8RjyvwmxlCjPHNqHMD7gmcMHOw0E8Ro/6+QT4PhHRSnnMo7bxa1vFPkwhg==" 1651 + }, 1647 1652 "isstream": { 1648 1653 "version": "0.1.2", 1649 1654 "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+1
package.json
··· 37 37 "body-parser": "^1.19.0", 38 38 "connect": "^3.7.0", 39 39 "display-notification": "^2.0.0", 40 + "iso8601-duration": "^1.2.0", 40 41 "log4js": "^6.3.0", 41 42 "nedb": "^1.8.0", 42 43 "node-fetch": "^2.6.0",
+62 -12
src/server.js
··· 4 4 const connect = require('connect') 5 5 const serveStatic = require('serve-static') 6 6 const WebSocket = require('ws') 7 + const iso8601duration = require('iso8601-duration') 7 8 const fetch = require('node-fetch') 8 9 const paths = require('./paths.js') 9 10 const logger = require('./logger.js') ··· 63 64 64 65 let intervals = [] 65 66 67 + function parseDuration(isoDur) { 68 + const dur = iso8601duration.parse(isoDur) 69 + let result = dur.seconds 70 + if (dur.seconds < 10) result = '0'+result 71 + result = dur.minutes+':'+result 72 + dur.hours += dur.days*24 73 + if (dur.hours > 0) { 74 + if (dur.minutes < 10) result = '0'+result 75 + result = dur.hours+':'+result 76 + } 77 + return result 78 + } 79 + 66 80 const pLimit = require('p-limit') 67 81 const limit = pLimit(5) 68 82 async function refresh(instance) { ··· 81 95 maxResults: 50, 82 96 }, 83 97 }) 98 + const isoDurations = [] 99 + let idList = '' 100 + for (let i = 0; i < uploads.items.length; i++) { 101 + const video = uploads.items[i] 102 + const id = video.snippet.resourceId.videoId 103 + if (idList !== '') idList += ',' 104 + idList += id 105 + } 106 + if (idList !== '') { 107 + const videos = await fetchYT({ 108 + url: 'https://www.googleapis.com/youtube/v3/videos', 109 + query: { 110 + part: 'contentDetails', 111 + id: idList.trimRight(','), 112 + }, 113 + }) 114 + for (const item of videos.items) { 115 + isoDurations[item.id] = item.contentDetails.duration 116 + } 117 + } 84 118 logger.info(instance.email, channel.name) 85 119 for (let i = uploads.items.length - 1; i >= 0; i--) { 86 120 // weird date situation: ··· 99 133 const video = uploads.items[i] 100 134 const publishedAt = new Date(video.contentDetails.videoPublishedAt) 101 135 if (publishedAt.getTime() >= channel.fromTime) { 136 + const videoId = video.snippet.resourceId.videoId 102 137 const videoDoc = await new Promise((resolve, reject) => { 103 - db.findOne({ _id: video.snippet.resourceId.videoId }, (err, doc) => { 138 + db.findOne({ _id: videoId }, (err, doc) => { 104 139 if (err) reject(err) 105 140 else resolve(doc) 106 141 }) 107 142 }) 108 143 if (!videoDoc) { 144 + const isoDuration = isoDurations[videoId] 145 + if (!isoDuration) { 146 + throw new Error(`isoDuration of video ${videoId} is ${isoDuration}`) 147 + } 109 148 const doc = { 110 - _id: video.snippet.resourceId.videoId, 149 + _id: videoId, 111 150 thumbnails: { 112 151 high: video.snippet.thumbnails.high.url, 113 152 }, 114 153 title: video.snippet.title, 115 154 channelTitle: video.snippet.channelTitle, 116 155 publishedAt: publishedAt.getTime(), 156 + isoDuration: isoDuration, 117 157 } 118 158 if (video.snippet.thumbnails.standard) { 119 159 doc.thumbnails.standard = video.snippet.thumbnails.standard.url ··· 155 195 if (videoDoc.thumbnails.maxres) thumbnailUrl = videoDoc.thumbnails.maxres 156 196 const videoUrl = 'https://youtube.com/watch?v='+videoDoc._id 157 197 const channelUrl = 'https://www.youtube.com/channel/'+channel.id 198 + const durationText = parseDuration(videoDoc.isoDuration) 158 199 const html = ` 159 200 <html> 160 201 <head> 161 202 <title>${htmlEncode(emailTitle)}</title> 162 203 </head> 163 - <body> 164 - <table align="center" width="608" border="0" cellpadding="0" cellspacing="0"><tbody><tr> 165 - <td width="20"></td> 204 + <body style="margin: 0; padding: 0;"> 205 + <table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="width:100%;max-width:636px"><tbody><tr> 206 + <td width="14"></td> 166 207 <td> 167 - <table align="center" width="608" border="0" cellpadding="0" cellspacing="0"> 208 + <table align="center" width="608" border="0" cellpadding="0" cellspacing="0" style="width:100%"> 168 209 <tbody> 169 210 <tr> 170 211 <td> 171 - <a href="${htmlEncode(videoUrl)}" class="nonplayable"> 172 - <table width="608" height="342" style="background-repeat:no-repeat;background-size:100% auto;background-position:center;" background="${htmlEncode(thumbnailUrl)}" border="0" cellpadding="0" cellspacing="0"> 212 + <a style="text-decoration:none" href="${htmlEncode(videoUrl)}" class="nonplayable"> 213 + <table class="imgtable" width="608" style="background-repeat:no-repeat;background-size:cover;background-position:center;width:100%" background="${htmlEncode(thumbnailUrl)}" border="0" cellpadding="0" cellspacing="0"> 173 214 <tbody> 174 215 <tr> 175 - <td width="608" style="max-height:342px;"></td> 216 + <td style="padding-bottom:53%;padding-bottom:calc(56.25% - 24px - 8px)"></td> 217 + </tr> 218 + <tr> 219 + <td height="24" style="text-align:right" valign="bottom"> 220 + <span style="color:#ffffff;font-size:12px;font-family:Roboto,sans-serif;background-color:#212121;border-radius: 2px;padding:2px 4px;display:inline-block">${htmlEncode(durationText)}</span> 221 + </td> 222 + <td width="8"></td> 223 + </tr> 224 + <tr> 225 + <td height="8"></td> 176 226 </tr> 177 227 </tbody> 178 228 </table> ··· 181 231 </tr> 182 232 <tr height="16"></tr> 183 233 <tr> 184 - <table border="0" cellpadding="0" cellspacing="0"> 234 + <td><table border="0" cellpadding="0" cellspacing="0"> 185 235 <tbody> 186 236 <tr> 187 237 <td valign="top"> ··· 206 256 </td> 207 257 </tr> 208 258 </tbody> 209 - </table> 259 + </table></td> 210 260 </tr> 211 261 <tr> 212 262 <td> ··· 221 271 </tbody> 222 272 </table> 223 273 </td> 224 - <td width="20"></td> 274 + <td width="14"></td> 225 275 </tr></tbody></table> 226 276 </body> 227 277 </html>