-
Notifications
You must be signed in to change notification settings - Fork 1
/
backend.gs
188 lines (170 loc) · 5.72 KB
/
backend.gs
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
var OUTPUT_FILE_NAME = 'podcast_feed_file.xml'
/**
* Check whether folder contains files larger than 25 MB.
*
* Such files cannot be scanned by Drive's antivirus scanner [1]. When the user tries to download
* such a file via an "ANYONE_WITH_LINK" URL, they are redirected to a warning page saying the file
* could not be scanned. This warning page prevents podcast clients from automatically downloading
* the file. The fix is to use "public hosting" [2], which does not have this behavior.
*
* [1] https://support.google.com/a/answer/172541?hl=en
* [2] https://support.google.com/drive/answer/2881970?hl=en
*/
function doCheckForLargeFiles(id) {
var files = getAudioVideoFiles(DriveApp.getFolderById(id));
return files.some(function(file) {
return file.getSize() > 25 << 20;
});
}
/**
* Determine whether a given file is audio/video or not.
*/
function isAudioVideoFile(file) {
var topLevelMimeType = file.getMimeType().split('/')[0];
return topLevelMimeType == 'audio' || topLevelMimeType == 'video';
}
/**
* Compare strings (just like cstdlib's strcmp).
*/
function strcmp(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
/**
* Get all audio/video files in a folder, sorted by name.
*/
function getAudioVideoFiles(folder) {
var rv = [];
var contents = folder.getFiles();
while (contents.hasNext()) {
var file = contents.next();
if (isAudioVideoFile(file)) {
rv.push(file);
}
}
rv.sort(function(fileA, fileB) {
return strcmp(fileA.getName(), fileB.getName());
});
return rv;
}
/**
* Get bogus date based on index.
*
* We use this to generate increasing timestamps for files, so that they will be sorted correctly.
*
* @return A string, representing a date |idx| hours after the epoch, in a format suitable for a
* podcast file.
*/
function indexToFakeDate(idx) {
return Utilities.formatDate(new Date(idx * 3600e3), 'GMT', "E, d MMM yyyy HH:mm:ss 'GMT'");
}
/**
* Write output to output file.
*
* If there is a single file in the folder with OUTPUT_FILE_NAME, it is overwritten, otherwise a new
* file is created.
*
* Unfortunately, when overwriting an existing file, the shared link isn't preserved; only the
* public link is preserved during updates.
*/
function writeOutput(folder, output) {
var existingOutFiles = folder.getFilesByName(OUTPUT_FILE_NAME);
var existingOutFilesArr = [];
while (existingOutFiles.hasNext()) {
existingOutFilesArr.push(existingOutFiles.next());
}
if (existingOutFilesArr.length == 1) {
existingOutFilesArr[0].setContent(output);
return existingOutFilesArr[0];
} else {
var outFile = DriveApp.createFile(OUTPUT_FILE_NAME, output);
folder.addFile(outFile);
DriveApp.getRootFolder().removeFile(outFile);
return outFile;
}
}
/**
* Converts the URL returned by file.getUrl() to a direct link.
*
* As an example, the URL
* https://docs.google.com/a/kerrickstaley.com/file/d/FILE_ID/edit?usp=drivesdk
* will be converted to
* https://docs.google.com/a/kerrickstaley.com/uc?id=FILE_ID
*/
function getSharedUrl(file) {
return file.getUrl()
.replace('/file/d/', '/uc?id=')
.replace('/edit?usp=drivesdk', '')
.replace('/view?usp=drivesdk', '');
}
/**
* Get the public URL [1] of |file| in |folder|.
*
* [1] https://support.google.com/drive/answer/2881970?hl=en
*/
function getPublicUrl(folder, file) {
// TODO: escape this
return 'https://host.googledrive.com/host/' + folder.getId() + '/' + file.getName();
}
/**
* Scan a folder for audio/video files and create a podcast feed file.
*/
function doFolder(id, sharePublically) {
var itunesNs = XmlService.getNamespace('itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd')
var folder = DriveApp.getFolderById(id);
var channel = XmlService.createElement('channel');
var title = XmlService.createElement('title').setText(folder.getName());
channel.addContent(title);
// TODO: remove
var subTitle = XmlService.createElement('subtitle', itunesNs)
.setText('Generated by drive-podcast');
channel.addContent(subTitle);
var files = getAudioVideoFiles(folder);
files.forEach(function(file, idx) {
var item = XmlService.createElement('item');
var title = XmlService.createElement('title').setText(file.getName().replace(/\..*?$/, ''));
item.addContent(title);
var pubDate = XmlService.createElement('pubDate').setText(indexToFakeDate(idx));
item.addContent(pubDate);
var enclosure = XmlService.createElement('enclosure');
var url = sharePublically ? getPublicUrl(folder, file) : getSharedUrl(file);
enclosure.setAttribute('url', url);
enclosure.setAttribute('length', file.getSize());
enclosure.setAttribute('type', file.getMimeType());
item.addContent(enclosure);
channel.addContent(item);
});
var rss = XmlService.createElement('rss').setAttribute('version', '2.0');
rss.addContent(channel);
var outText = XmlService.getPrettyFormat().format(XmlService.createDocument(rss));
var trashFiles = folder.getFilesByName(OUTPUT_FILE_NAME);
while (trashFiles.hasNext()) {
trashFiles.next().setTrashed(true);
}
var outFile = writeOutput(folder, outText);
// set sharing permissions
// TODO: do something smarter based on the folder's current permissions
if (sharePublically) {
folder.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
} else {
folder.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
}
return sharePublically ? getPublicUrl(folder, outFile) : getSharedUrl(outFile);
}
/**
* Helper that serves HTML.
*/
function doGet() {
return HtmlService.createHtmlOutputFromFile('index.html');
}
/**
* Helper that returns an OAuth token.
*/
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}