Source
https://script.google.com/d/1RHjbqdEN-WvQxqSFr6hdtECfIqZlEEyCfrxvF4UKnKeVPnRX-dyg0dS7/edit
/**
* Creates an Album with the given name in Picasa.
*
* @param {string} title new Album name
* @param {object} optAdvancedArgs
* @return {Album} newly created album
*/
function createAlbum(title, optAdvancedArgs) {
if (optAdvancedArgs != undefined) {
if (optAdvancedArgs.description == undefined) var description = '';
else var description = optAdvancedArgs.description;
if (optAdvancedArgs.location == undefined) var location = '';
else var location = optAdvancedArgs.location;
if (optAdvancedArgs.visibility != 'public' && optAdvancedArgs.visibility != 'unlisted') var visibility = 'private';
else var visibility = optAdvancedArgs.visibility;
if (optAdvancedArgs.date == undefined) var date = new Date().getTime();
else var date = new Date(optAdvancedArgs.date).getTime();
if (optAdvancedArgs.tags == undefined) var tags = '';
else var tags = optAdvancedArgs.tags;
}
else {
var description = '';
var location = '';
var visibility = 'private';
var date = new Date().getTime();
var tags = '';
}
var xmlOutput = picasaRequest_('post', "\
<entry xmlns='http://www.w3.org/2005/Atom'\
xmlns:media='http://search.yahoo.com/mrss/'\
xmlns:gphoto='http://schemas.google.com/photos/2007'>\
<title type='text'>" + title + "</title>\
<summary type='text'>" + description + "</summary>\
<gphoto:location>" + location + "</gphoto:location>\
<gphoto:access>" + visibility + "</gphoto:access>\
<gphoto:timestamp>" + date + "</gphoto:timestamp>\
<media:group><media:keywords>" + tags + "</media:keywords></media:group>\
<category scheme='http://schemas.google.com/g/2005#kind'\
term='http://schemas.google.com/photos/2007#album'></category>\
</entry>\
");
var album = new PicasaApp.album(xmlOutput.getElement());
return album;
}
/**
* Returns all the Photos that contain the specified query string in your gallery.
*
* @param {string} query the search query to match
* @param {boolean} optCommunity Optional flag indicating community pictures should be returned.
* @return {Photo[]} any photo with title, caption or tag matching the search query
*/
function find(query, optCommunity) {
if (optCommunity) {
var url = 'https://picasaweb.google.com/data/feed/api/all?q=' + query;
var data = UrlFetchApp.fetch(url, googleOAuth_()).getContentText();
var xmlOutput = Xml.parse(data, false);
}
else {
var xmlOutput = picasaRequest_('get', '?kind=photo&q=' + query);
}
var temp = xmlOutput.getElement().getElements('entry');
var photos = [];
for (var i = 0; i < temp.length; i++) {
photos[i] = new PicasaApp.photo(temp[i]);
}
return photos;
}
/**
* Returns all the Albums in Picasa.
*
* @return {Album[]} the Albums in Picasa
*/
function getAlbums() {
var xmlOutput = picasaRequest_('get');
var temp = xmlOutput.getElement().getElements('entry');
var albums = [];
for (var i = 0; i < temp.length; i++) {
albums[i] = new PicasaApp.album(temp[i]);
}
return albums;
}
/**
* Returns the Album at the given id.
*
* @param {string} id the album id
* @return {Album} the Album at the given id
*/
function getAlbumById(id) {
var xmlOutput = picasaRequest_('get', '/albumid/' + id);
var album = new PicasaApp.album(xmlOutput.getElement());
return album;
}
/*
- - - - - - - Class Album - - - - - - -
Members:
- addPhoto()
- deleteAlbum()
- find(query)
- getDatePublished()
- getDescription()
- getId()
- getLastUpdated()
- getLocation()
- getNumberOfPhotos()
- getPhotos()
- getSize()
- getTitle()
- getVisibility()
- setDescription(description)
- setLocation(location)
- setTitle(title)
- setVisibility(visibility)
*/
var PicasaApp = {};
// Namespaces
var gphoto = 'http://schemas.google.com/photos/2007';
PicasaApp.album = function (album) {
this.album = album;
};
var albumClass = PicasaApp.album.prototype;
albumClass.addPhoto = function (blob, optCaption) {
var authorizedFormats = 'image/bmp, image/gif, image/jpeg, image/png, video/3gpp, video/avi, video/quicktime,';
authorizedFormats += ' video/mp4, video/mpeg, video/mpeg4, video/msvideo, video/x-ms-asf, video/x-ms-wmv, video/x-msvideo';
if (authorizedFormats.indexOf(blob.getContentType()) == -1) throw 'Error: wrong format';
if (Math.round(blob.getBytes().length / 1000000) > 5) throw 'Error: 5MB is the size limit';
if (optCaption != undefined && typeof(optCaption)!='string') throw 'Caption should be a string';
var id = this.album.getElement(gphoto, 'id').getText();
var xmlOutput = gasCall(function(){return picasaRequest_('addPhoto', [blob, optCaption], '/albumid/' + id);});
return new PicasaApp.photo(xmlOutput.entry);
};
albumClass.deleteAlbum = function () {
var url = findUrlToUpdateElement_(this.album);
var xmlOutput = picasaRequest_('delete', '', url);
};
albumClass.find = function (query) {
var id = this.album.getElement(gphoto, 'id').getText();
var xmlOutput = picasaRequest_('get', '/albumid/' + id + '?kind=photo&q=' + query);
var temp = xmlOutput.getElement().getElements('entry');
var photos = [];
for (var i = 0; i < temp.length; i++) {
photos[i] = new PicasaApp.photo(temp[i]);
}
return photos;
};
albumClass.getDatePublished = function () {
return new Date(this.album.published.getText());
};
albumClass.getDescription = function () {
return this.album.summary.getText();
};
albumClass.getId = function () {
return this.album.getElement(gphoto, 'id').getText();
};
albumClass.getLastUpdated = function () {
return new Date(this.album.updated.getText());
};
albumClass.getLocation = function () {
return this.album.getElement(gphoto, 'location').getText();
};
albumClass.getNumberOfPhotos = function () {
return this.album.getElement(gphoto, 'numphotos').getText();
};
albumClass.getPhotos = function () {
var id = this.album.getElement(gphoto, 'id').getText();
var xmlOutput = picasaRequest_('get', '/albumid/' + id);
var temp = xmlOutput.getElement().getElements('entry');
var photos = [];
for (var i = 0; i < temp.length; i++) {
photos[i] = new PicasaApp.photo(temp[i]);
}
return photos;
};
albumClass.getSize = function () {
return this.album.getElement(gphoto, 'bytesUsed').getText();
};
albumClass.getTitle = function () {
return this.album.title.getText();
};
albumClass.getVisibility = function () {
return this.album.rights.getText();
};
albumClass.setDescription = function (text) {
var url = findUrlToUpdateElement_(this.album);
var xmlOutput = picasaRequest_('patch', "<summary type='text'>" + text + "</summary>", url);
};
albumClass.setLocation = function (text) {
var url = findUrlToUpdateElement_(this.album);
var xmlOutput = picasaRequest_('patch', "<gphoto:location>" + text + "</gphoto:location>", url);
};
albumClass.setTitle = function (text) {
var url = findUrlToUpdateElement_(this.album);
var xmlOutput = picasaRequest_('patch', "<title type='text'>" + text + "</title>", url);
};
albumClass.setVisibility = function (text) {
if (text != 'public' && text != 'unlisted' && text != 'private') throw 'Error: Visibility must be either ' + 'public or unlisted or private';
var url = findUrlToUpdateElement_(this.album);
var xmlOutput = picasaRequest_('patch', "<gphoto:access>" + text + "</gphoto:access>", url);
};
/*
- - - - - - - Class Photo - - - - - - -
Members:
- deletePhoto()
- getAlbum()
- getBlob()
- getCaption()
- getDatePublished()
- getHeight()
- getLastUpdated()
- getSize()
- getTitle()
- getUrl()
- getWidth()
- setAlbum(album)
- setCaption(caption)
- setTitle(title)
*/
PicasaApp.photo = function (photo) {
this.photo = photo;
};
var photoClass = PicasaApp.photo.prototype;
photoClass.deletePhoto = function () {
var url = findUrlToUpdateElement_(this.photo);
var xmlOutput = picasaRequest_('delete', '', url);
};
photoClass.getAlbum = function () {
var id = this.photo.getElement(gphoto, 'albumid').getText();
var xmlOutput = picasaRequest_('get', '/albumid/' + id);
var album = new PicasaApp.album(xmlOutput.getElement());
return album;
};
photoClass.getBlob = function () {
var title = this.photo.title.getText();
var type = this.photo.getElement('content').getAttribute('type').getValue();
var url = this.photo.getElement('content').getAttribute('src').getValue();
var data = UrlFetchApp.fetch(url, googleOAuth_()).getContent();
return Utilities.newBlob(data, type, title);
};
photoClass.getCaption = function () {
return this.photo.summary.getText();
};
photoClass.getDatePublished = function () {
return new Date(this.photo.published.getText());
};
photoClass.getHeight = function () {
return this.photo.getElement(gphoto, 'height').getText();
};
photoClass.getLastUpdated = function () {
return new Date(this.photo.updated.getText());
};
photoClass.getSize = function () {
return this.photo.getElement(gphoto, 'size').getText();
};
photoClass.getTitle = function () {
return this.photo.title.getText();
};
photoClass.getUrl = function () {
return this.photo.getElement('content').getAttribute('src').getValue();
};
photoClass.getWidth = function () {
return this.photo.getElement(gphoto, 'width').getText();
};
photoClass.setAlbum = function (album) {
var url = findUrlToUpdateElement_(this.photo);
var xmlOutput = picasaRequest_('patch', "<gphoto:albumid>" + album.getId() + "</gphoto:albumid>", url);
};
photoClass.setCaption = function (text) {
var url = findUrlToUpdateElement_(this.photo);
var xmlOutput = picasaRequest_('patch', "<summary type='text'>" + text + "</summary>", url);
};
photoClass.setTitle = function (text) {
var url = findUrlToUpdateElement_(this.photo);
try {
var xmlOutput = picasaRequest_('patch', "<title type='text'>" + text + "</title>", url);
}
catch (e) {
throw 'Error: Photo title should follow this pattern: myPhoto.jpg';
}
};
/* - - - - - - - Utilities - - - - - - - */
function picasaRequest_(method, query, url) {
if (query == undefined) query = '';
var base = "https://picasaweb.google.com/data/feed/api/user/default";
var fetchArgs = googleOAuth_();
fetchArgs.headers = {};
fetchArgs.headers['GData-Version'] = '2';
fetchArgs.method = method;
if (method == 'get') {
url = base + query;
}
else if (method == 'post') {
url = base;
fetchArgs.contentType = 'application/atom+xml';
fetchArgs.payload = query;
}
else if (method == 'delete') {
fetchArgs.headers['If-Match'] = '*';
return UrlFetchApp.fetch(url, fetchArgs).getResponseCode();
}
else if (method == 'patch') {
fetchArgs.method = 'post';
fetchArgs.headers['X-HTTP-Method-Override'] = 'PATCH';
fetchArgs.headers['If-Match'] = '*';
fetchArgs.contentType = 'application/xml';
fetchArgs.payload = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/' ";
fetchArgs.payload += "xmlns:gphoto='http://schemas.google.com/photos/2007'>" + query + "</entry>";
}
else if (method == 'addPhoto') {
fetchArgs.headers['MIME-version'] = "1.0";
fetchArgs.method = 'post';
var boundary = 'END_OF_PART_1234_1234_1234_1234';
var boundaryFull = "\r\n--" + boundary + "\r\n";
fetchArgs.contentType = 'multipart/related; boundary="' + boundary + '"';
var metadataXml = "<entry xmlns='http://www.w3.org/2005/Atom'>";
metadataXml+= "<title>" + query[0].getName() + "</title>";
if (query[1] != undefined) metadataXml+="<summary>" + query[1] + "</summary>";
metadataXml+="<category scheme='http://schemas.google.com/g/2005#kind' ";
metadataXml+=" term='http://schemas.google.com/photos/2007#photo'/>" + "</entry>";
var payload = toBytes_("Media multipart posting" + boundaryFull);
payload = payload.concat(toBytes_("Content-Type: application/atom+xml\r\n\r\n"));
payload = payload.concat(toBytes_(metadataXml));
payload = payload.concat(toBytes_(boundaryFull));
payload = payload.concat(toBytes_("Content-Type: " + query[0].getContentType() + "\r\n\r\n"));
payload = payload.concat(query[0].getBytes());
payload = payload.concat(toBytes_(boundaryFull));
fetchArgs.payload = payload;
url = base + url;
}
var data = UrlFetchApp.fetch(url, fetchArgs).getContentText();
var xmlOutput = Xml.parse(data, false);
return xmlOutput;
}
function findUrlToUpdateElement_(element) {
var links = element.getElements('link');
for (var i = 0; i < links.length; i++) {
if (links[i].getAttribute('rel').getValue() == 'edit') {
var url = links[i].getAttribute('href').getValue();
}
}
return url;
}
function googleOAuth_() {
var oAuthConfig = UrlFetchApp.addOAuthService('lh2');
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope=http://picasaweb.google.com/data/');
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey('anonymous');
oAuthConfig.setConsumerSecret('anonymous');
return {
oAuthServiceName: 'lh2',
oAuthUseToken: 'always'
};
}
/*
Here's Peter Herrmann implementation of the exponential backoff pattern presented
in the Google Developer Blog [1] and covered in the Google Apps developer docs [2].
It will retry with delays of approximately 1, 2, 4, 8 then 16 seconds for a total of
about 32 seconds before gives up and rethrows the last error.
[1] http://googleappsdeveloper.blogspot.com.au/2011/12/documents-list-api-best-practices.html
[2] https://developers.google.com/google-apps/documents-list/#implementing_exponential_backoff
*/
function gasCall(f) {
for (var n = 0; n < 6; n++) {
try {
return f();
}
catch (e) {
if (n == 5) {
throw e;
}
Utilities.sleep((Math.pow(2, n) * 1000) + (Math.round(Math.random() * 1000)));
}
}
}
/*
From slieberman@google.com - Converts a string to bytes.
http://code.google.com/p/google-apps-script-issues/issues/detail?id=1387
*/
function toBytes_(str) {
return Utilities.newBlob(str).getBytes();
}