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();

}