This repository was archived by the owner on Dec 31, 2021. It is now read-only.
forked from hermanho/MMM-GooglePhotos
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathnode_helper.js
312 lines (279 loc) · 10.4 KB
/
node_helper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
'use strict'
const fs = require('fs')
const path = require('path')
const https = require('https')
const moment = require('moment')
const GP = require("./GPhotos.js")
const authOption = require("./google_auth.json")
var GPhotos = null
var NodeHelper = require("node_helper")
module.exports = NodeHelper.create({
start: function() {
this.scanInterval = 1000 * 60 * 55 // fixed. no longer needs to be fixed
this.config = {}
this.scanTimer = null
this.albums = []
this.photos = []
this.localPhotoList = []
this.localPhotoPntr = 0
this.queue = null
this.uploadAlbumId
this.initializeTimer = null
},
socketNotificationReceived: function(notification, payload) {
switch(notification) {
case 'INIT':
this.initializeAfterLoading(payload)
break
case 'UPLOAD':
this.upload(payload)
break
case 'IMAGE_LOAD_FAIL':
this.log("Image loading fails. Check your network.:", payload)
this.prepAndSendChunk(Math.ceil(20*60*1000/this.config.updateInterval))// 20min * 60s * 1000ms / updateinterval in ms
break
case 'IMAGE_LOADED':
this.log("Image loaded:", payload)
break
case 'NEED_MORE_PICS':
this.log("Used last pic in list")
this.prepAndSendChunk(Math.ceil(20*60*1000/this.config.updateInterval))// 20min * 60s * 1000ms / updateinterval in ms
break
}
},
log: function(...args) {
if (this.debug) console.log("[GPHOTOS]", ...args)
},
upload: function(path) {
if (!this.uploadAlbumId) {
this.log("No uploadable album exists.")
return
}
const step = async ()=> {
var uploadToken = await GPhotos.upload(path)
if (uploadToken) {
var result = await GPhotos.create(uploadToken, this.uploadAlbumId)
this.log("Upload completed.")
} else {
this.log("Upload Fails.")
}
}
step()
},
initializeAfterLoading: function(config) {
this.config = config
this.debug = (config.debug) ? config.debug : false
if (!this.config.scanInterval || this.config.scanInterval < 1000 * 60 * 10) this.config.scanInterval = 1000 * 60 * 10
GPhotos = new GP({
authOption: authOption,
debug:this.debug
})
this.tryToIntitialize()
},
tryToIntitialize: async function() {
//set timer, in case if fails to retry in 1 min
clearTimeout(this.initializeTimer)
this.initializeTimer = setTimeout(()=>{
this.tryToIntitialize()
}, 1*60*1000)
this.log("Starting Initialization")
this.log("Getting album list")
var albums = await this.getAlbums()
if (config.uploadAlbum) {
var uploadAlbum = albums.find((a)=>{
return (a.title == config.uploadAlbum) ? true : false
})
if (uploadAlbum) {
if (uploadAlbum.hasOwnProperty("shareInfo") && uploadAlbum.isWriteable) {
this.log("Confirmed Uploadable album:", config.uploadAlbum, uploadAlbum.id)
this.uploadAlbumId = uploadAlbum.id
this.sendSocketNotification("UPLOADABLE_ALBUM", config.uploadAlbum)
} else {
this.log("This album is not uploadable:", config.uploadAlbum)
}
} else {
this.log("Can't find uploadable album :", config.uploadAlbum)
}
}
for (var ta of this.config.albums) {
var matched = albums.find((a)=>{
if (ta == a.title) return true
return false
})
var exists = function (albums, album) {
return albums.some(expected => album.id === expected.id)
}
if (!matched) {
this.log(`Can't find "${ta}" in your album list.`)
} else if (!exists(this.albums, matched)) {
this.albums.push(matched)
}
}
this.log("Finish Album scanning. Properly scanned :", this.albums.length)
for (var a of this.albums) {
var url = a.coverPhotoBaseUrl + "=w160-h160-c"
var fpath = path.resolve(__dirname, "cache", a.id)
let file = fs.createWriteStream(fpath)
const request = https.get(url, (response)=>{
response.pipe(file)
})
}
this.log("Initialized")
this.sendSocketNotification("INITIALIZED", this.albums)
//load cached list - if available
fs.readFile(this.path +"/cache/photoListCache.json", 'utf-8', (err,data) => {
if (err) { this.log('unable to load cache', err) }
else {
this.localPhotoList = JSON.parse(data.toString())
this.log("successfully loaded cache of ", this.localPhotoList.length, " photos")
this.prepAndSendChunk(5) //only 5 for extra fast startup
}
})
this.log("Initialization complete!")
clearTimeout(this.initializeTimer)
this.log("Start first scanning.")
this.startScanning()
},
prepAndSendChunk: async function(desiredChunk = 50) {
try {
//find which ones to refresh
if (this.localPhotoPntr < 0 || this.localPhotoPntr >= this.localPhotoList.length ) {this.localPhotoPntr = 0}
var numItemsToRefresh = Math.min(desiredChunk, this.localPhotoList.length - this.localPhotoPntr, 50) //50 is api limit
this.log("num to ref: ", numItemsToRefresh,", DesChunk: ", desiredChunk, ", totalLength: ", this.localPhotoList.length, ", Pntr: ", this.localPhotoPntr)
// refresh them
var list = []
if (numItemsToRefresh > 0){
list = await GPhotos.updateTheseMediaItems(this.localPhotoList.slice(this.localPhotoPntr, this.localPhotoPntr+numItemsToRefresh))
}
if (list.length > 0) {
// update the localList
this.localPhotoList.splice(this.localPhotoPntr, list.length, ...list)
// send updated pics
this.sendSocketNotification("MORE_PICS", list)
// update pointer
this.localPhotoPntr = this.localPhotoPntr + list.length
this.log("refreshed: ", list.length, ", totalLength: ", this.localPhotoList.length,", Pntr: ", this.localPhotoPntr)
this.log("just sent ", list.length, " more pics")
} else {
this.log("couldn't send ", list.length, " pics")
}
} catch (err) {
this.log("failed to refresh and send chunk: ", err)
}
},
getAlbums: function() {
return new Promise((resolve)=>{
const step = async ()=> {
try {
var r = await GPhotos.getAlbums()
resolve(r)
} catch (err) {
this.log(err.toString())
console.log(err)
}
}
step()
})
},
startScanning: function() {
// set up interval, then 1 fail won't stop future scans
this.scanTimer = setInterval(()=>{
this.scanJob()
}, this.scanInterval)
// call for first time
this.scanJob()
},
scanJob: function() {
return new Promise((resolve)=>{
this.log("Start Album scanning")
this.queue = null
const step = async ()=> {
try {
if (this.albums.length > 0) {
this.photos = await this.getImageList()
resolve(true)
} else {
this.log("There is no album to get photos.")
resolve(false)
}
} catch (err) {
this.log(err.toString())
console.log(err)
}
}
step()
})
},
getImageList: function() {
var condition = this.config.condition
var photoCondition = (photo) => {
if (!photo.hasOwnProperty("mediaMetadata")) return false
var data = photo.mediaMetadata
if (data.hasOwnProperty("video")) return false
if (!data.hasOwnProperty("photo")) return false
var ct = moment(data.creationTime)
if (condition.fromDate && moment(condition.fromDate).isAfter(ct)) return false
if (condition.toDate && moment(condition.toDate).isBefore(ct)) return false
if (condition.minWidth && (Number(condition.minWidth) > Number(data.width))) return false
if (condition.minHeight && (Number(condition.minHeight) > Number(data.height))) return false
if (condition.maxWidth && (Number(condition.maxWidth) < Number(data.width))) return false
if (condition.maxHeight && (Number(condition.maxHeight) < Number(data.height))) return false
var whr = Number(data.width) / Number(data.height)
if (condition.minWHRatio && (Number(condition.minWHRatio) > whr)) return false
if (condition.maxWHRatio && (Number(condition.maxWHRatio) < whr)) return false
return true
}
var sort = (a, b) => {
var at = moment(a.mediaMetadata.creationTime)
var bt = moment(b.mediaMetadata.creationTime)
if (at.isBefore(bt) && this.config.sort == "new") return 1
if (at.isAfter(bt) && this.config.sort == "old") return 1
return -1
}
return new Promise((resolve)=>{
const step = async () => {
var photos = []
try {
for (var album of this.albums) {
this.log(`Prepping to get photo list from '${album.title}'`)
var list = await GPhotos.getImageFromAlbum(album.id, photoCondition)
this.log(`Got ${list.length} photo(s) from '${album.title}'`)
photos = photos.concat(list)
}
if (photos.length > 0) {
if (this.config.sort == "new" || this.config.sort == "old") {
photos.sort((a, b) => {
var at = moment(a.mediaMetadata.creationTime)
var bt = moment(b.mediaMetadata.creationTime)
if (at.isBefore(bt) && this.config.sort == "new") return 1
if (at.isAfter(bt) && this.config.sort == "old") return 1
return -1
})
} else {
for (var i = photos.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1))
var t = photos[i]
photos[i] = photos[j]
photos[j] = t
}
}
this.log(`Total indexed photos: ${photos.length}`)
this.localPhotoList = photos
fs.writeFile(this.path +"/cache/photoListCache.json", JSON.stringify(this.localPhotoList, null, 4), (err) => {
if (err) {this.log(err)
} else { this.log('Photo list cache saved') }
})
}
return(photos)
} catch (err) {
this.log(err.toString())
console.log(err)
}
}
resolve(step())
})
},
stop: function() {
clearInterval(this.scanTimer)
},
})