Fetching iCalUId for an appointment object using office js - outlook

I'm trying to fetch iCalUId for a specific appointment object using office js (for an outlook add-in)
The only ids for an appointment object seems to be itemId which can also be converted to what Microsoft calls the Rest Identifier
is there any way to fetch the iCalUid as well?

Unfortunately we don't have an Office JS API to retrieve the iCalUId. You can, however, make a REST call to retrieve the item from the server and obtain the iCalUId from the JSON response. See this documentation for more details.
We also have a UserVoice page where we track feature requests. Please add a request there. Feature requests are considered when we go through our planning process.

I use Office.context.mailbox.makeEwsRequestAsync since the RESP API for Office JS will be fully decommissioned in November 2022. To make EWS requests using this JS function you have to provide XML files as input with the required request (so called SOAP requests) and the response will be also a XML file which I parse with jQuery.parseXML.
/* The following function gets the ewsId for both Organizer and Attendee,
since they have different means to get it.
Beware that these ewsIds are different between organizer and attendees. */
// Event Outlook ID is available when
// a) Outlook event already existed in user's calendar or
// b) it was already saved by the user in the current session
function getEventOutlookUid (callback) {
if (typeof Office.context.mailbox.item.getItemIdAsync === 'function') { // is Organizer
Office.context.mailbox.item.getItemIdAsync(function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
callback(null, result.value)
} else {
console.warn(`EventOutlookUid unavailable: ${result.error.message}. Probably just a new event`)
callback(null, null)
}
})
} else if (Office.context.mailbox.item.itemId) { // is Attendee
callback(null, Office.context.mailbox.item.itemId)
} else {
callback(Error('Neither Office.context.mailbox.item.getItemIdAsync nor Office.context.mailbox.item.itemId could get Outlook Item UID'))
}
}
/* The following function gets the extra IDs by parsing the XML from the SOAP request.
I use jQuery since it is very easy to parse XML with it. The function returns in callback an object
`{ ewsId: eventOutlookUid, changeKey, UID, GlobalObjectId, ConversationId }` */
function getExtendedIds (callback) {
getEventOutlookUid((err, eventOutlookUid) => {
if (err) {
console.error('Error fetching Outlook UID ' + err.message)
callback(Error(err))
} else {
const soapRequest = generateCalendarUidSoapRequest(eventOutlookUid)
if (validateXML(soapRequest)) {
Office.context.mailbox.makeEwsRequestAsync(soapRequest, function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
// console.log(prettifyXml(result.value))
const res = $.parseXML(result.value)
const changeKey = res.getElementsByTagName('t:ItemId')[0].getAttribute('ChangeKey')
const UID = res.getElementsByTagName('t:UID')[0].textContent
const GlobalObjectId = res.getElementsByTagName('t:GlobalObjectId')[0].textContent
const ConversationId = res.getElementsByTagName('t:ConversationId')[0].getAttribute('Id')
callback(null, { ewsId: eventOutlookUid, changeKey, UID, GlobalObjectId, ConversationId })
}
})
} else {
callback(Error('Invalid XML request'))
}
}
})
}
/* The following function generates the XML SOAP request to get all possible ids */
function generateCalendarUidSoapRequest (itemId) {
const request = '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
' <soap:Header><t:RequestServerVersion Version="Exchange2013" /></soap:Header>' +
' <soap:Body>' +
' <m:GetItem>' +
' <m:ItemShape>' +
' <t:BaseShape>AllProperties</t:BaseShape>' +
' </m:ItemShape >' +
' <t:AdditionalProperties>' +
' <t:FieldURI FieldURI="calendar:UID"/>' +
' <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="3" PropertyType="Binary" />' +
' </t:AdditionalProperties>' +
' <m:ItemIds>' +
' <t:ItemId Id="' + itemId + '" />' +
' </m:ItemIds>' +
' </m:GetItem>' +
' </soap:Body>' +
'</soap:Envelope>'
return request
}
/* These are auxiliary functions to pretiffy
(for console.log and debug) and validate the XML as input */
function prettifyXml (sourceXml) {
const xmlDoc = new DOMParser().parseFromString(sourceXml, 'application/xml')
const xsltDoc = new DOMParser().parseFromString([
// describes how we want to modify the XML - indent everything
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
' <xsl:strip-space elements="*"/>',
' <xsl:template match="para[content-style][not(text())]">', // change to just text() to strip space in text nodes
' <xsl:value-of select="normalize-space(.)"/>',
' </xsl:template>',
' <xsl:template match="node()|#*">',
' <xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>',
' </xsl:template>',
' <xsl:output indent="yes"/>',
'</xsl:stylesheet>'
].join('\n'), 'application/xml')
const xsltProcessor = new XSLTProcessor()
xsltProcessor.importStylesheet(xsltDoc)
const resultDoc = xsltProcessor.transformToDocument(xmlDoc)
const resultXml = new XMLSerializer().serializeToString(resultDoc)
return resultXml
}
function validateXML (xmlString) {
const domParser = new DOMParser()
const dom = domParser.parseFromString(xmlString, 'text/xml')
// print the name of the root element or error message
return dom.documentElement.nodeName !== 'parsererror'
}
getExtendedIds((err, res) => {
if (!err) {
console.log(res) // res: { ewsId, changeKey, GlobalObjectId, ConversationId, UID }
}
})
By calling getExtendedIds you'll have an object with { ewsId, changeKey, GlobalObjectId, ConversationId, UID }
I use GlobalObjectId as a unique Id for the same appointment amongst Organizer and Attendees.

Related

How to upload outlook email attachments on my cloud server using Outlook Js Add-in

I am developing outlook javascript add-in using vs2017. I have created a sample application to find attachments from outlook mail item. Here, While getting attachments from Exchange Server, It returns 200 OK.
I have my own cloud application looks like google drive. I want to upload outlook mail attachments on my cloud server using POST API call. API call was running successfully. But I am not able to get file content from the exchange server.
I have added some sample code over here.
Creating a service request
/// <reference path="../App.js" />
var xhr;
var serviceRequest;
(function () {
"use strict";
// The Office initialize function must be run each time a new page is loaded
Office.initialize = function (reason) {
$(document).ready(function () {
app.initialize();
initApp();
});
};
function initApp() {
$("#footer").hide();
if (Office.context.mailbox.item.attachments == undefined) {
var testButton = document.getElementById("testButton");
testButton.onclick = "";
showToast("Not supported", "Attachments are not supported by your Exchange server.");
} else if (Office.context.mailbox.item.attachments.length == 0) {
var testButton = document.getElementById("testButton");
testButton.onclick = "";
showToast("No attachments", "There are no attachments on this item.");
} else {
// Initalize a context object for the app.
// Set the fields that are used on the request
// object to default values.
serviceRequest = new Object();
serviceRequest.attachmentToken = "";
serviceRequest.ewsUrl = Office.context.mailbox.ewsUrl;
serviceRequest.attachments = new Array();
}
};
})();
function testAttachments() {
Office.context.mailbox.getCallbackTokenAsync(attachmentTokenCallback);
};
function attachmentTokenCallback(asyncResult, userContext) {
if (asyncResult.status == "succeeded") {
serviceRequest.attachmentToken = asyncResult.value;
makeServiceRequest();
}
else {
showToast("Error", "Could not get callback token: " + asyncResult.error.message);
}
}
function makeServiceRequest() {
var attachment;
xhr = new XMLHttpRequest();
// Update the URL to point to your service location.
xhr.open("POST", "https://localhost:8080/GetOutlookAttachments/AttachmentExampleService/api/AttachmentService", true);
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onreadystatechange = requestReadyStateChange;
// Translate the attachment details into a form easily understood by WCF.
for (i = 0; i < Office.context.mailbox.item.attachments.length; i++) {
attachment = Office.context.mailbox.item.attachments[i];
attachment = attachment._data$p$0 || attachment.$0_0;
if (attachment !== undefined) {
serviceRequest.attachments[i] = JSON.parse(JSON.stringify(attachment));
}
}
// Send the request. The response is handled in the
// requestReadyStateChange function.
xhr.send(JSON.stringify(serviceRequest));
};
// Handles the response from the JSON web service.
function requestReadyStateChange() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
if (!response.isError) {
// The response indicates that the server recognized
// the client identity and processed the request.
// Show the response.
var names = "<h2>Attachments processed: " + response.attachmentsProcessed + "</h2>";
document.getElementById("names").innerHTML = names;
} else {
showToast("Runtime error", response.message);
}
} else {
if (xhr.status == 404) {
showToast("Service not found", "The app server could not be found.");
} else {
showToast("Unknown error", "There was an unexpected error: " + xhr.status + " -- " + xhr.statusText);
}
}
}
};
// Shows the service response.
function showResponse(response) {
showToast("Service Response", "Attachments processed: " + response.attachmentsProcessed);
}
// Displays a message for 10 seconds.
function showToast(title, message) {
var notice = document.getElementById("notice");
var output = document.getElementById('output');
notice.innerHTML = title;
output.innerHTML = message;
$("#footer").show("slow");
window.setTimeout(function () { $("#footer").hide("slow") }, 10000);
};
Please help me to get attachments from outlook mail and upload on my cloud server.
attachment._data$p$0 gives to attachment metadata. Get id from there and use getAttachmentContentAsync API to get the attachment content Documentation

CRM - Internal Server Error: when working on multiple entities

So I have a custom action (an approval button) that can be triggered either when editing a single entity or for multiple entities when triggered from the entity list view.
The logic essentially removes one entity type and creates a different one with more fields and options available after approval.
The logic works just fine when it is triggered from the Form where there is just a single entity being edited.
However when the same logic is run from the list view and now I am iterating over multiple entities, there is in internal server error when I try to create the record for the new entity type (the one with more options). This makes no sense, I am calling out to a function which already works under a different scenario. And I am creating a new entity, not updating or deleting an existing one so there should be not locks or other concurrency issues.
The the error is gloriously uninformative and I can't see any logs anywhere that would help me debug this. Has anyone run into this before?
Edit
I have enabled CRM Trace Logging for errors (in the registry), however this does not help. This internal server 'Error' does not seem to be errory enough to show up in the logs.
Edit 2
Perhaps some code? The error happens on the SDK.REST.createRecord line, but only when it is run inside the loop of the click handler, when it is run from single entity form, it creates a record without issue.
PCL.EventParticipants = {
EventFormApproveParticipantClick: function (selectedItemIds, entityTypeName) {
debugger;
var anEventRequest,
requestId,
action,
event,
contact,
emailPref,
actualEmail;
console.log('Approval Clicked');
// Do this if we are working on a single event request
if (entityTypeName == null)
{
requestId = Xrm.Page.data.entity.getId();
action = Xrm.Page.data.entity.attributes.get("pcl_action").getValue();
var participant = PCL.EventParticipants.MakeParticipant(
Xrm.Page.data.entity.attributes.get("pcl_contact").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_event").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_name").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_emailpreference").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_selectedemail").getValue()
);
if (PCL.EventParticipants.Act(requestId, action, participant)) {
alert('Approval complete.');
}
return;
}
var opSuccess = true;
// When multiple requests are selected do...
for (var x = 0; x < selectedItemIds.length; x++) {
requestId = selectedItemIds[x];
SDK.REST.retrieveRecord(
requestId,
"pcl_eventrequest",
"pcl_eventrequestId,pcl_Action,pcl_Contact,pcl_Event,pcl_name,pcl_EmailPreference,pcl_SelectedEmail", null,
function (anEventRequest) {
requestId = anEventRequest.pcl_eventrequestId;
action = anEventRequest.pcl_Action.Value;
var participant = PCL.EventParticipants.MakeParticipant(
anEventRequest.pcl_Contact.Id,
anEventRequest.pcl_Event.Id,
anEventRequest.pcl_name,
anEventRequest.pcl_EmailPreference,
anEventRequest.pcl_SelectedEmail
);
if (!PCL.EventParticipants.Act(requestId, action, participant)) {
opSuccess = false;
}
},
function(error) {
alert('Could not retrieve selected event request: ' + requestId + ' Check that it has not been removed from the system. --> ' + error.message);
}, false
);
}
if (opSuccess) {
alert('Approvals completed.');
} else {
alert('One or more Approvals failed.');
}
},
Act: function (requestId, actionValue, participant) {
var opSuccess = false;
if (actionValue == '798330000') {
// Add action
opSuccess = PCL.EventParticipants.CreateEventParticipant(participant);
}
if (actionValue == '798330001') {
// Remove action
opSuccess = PCL.EventParticipants.RemoveEventParticipant(participant);
}
if (opSuccess == false) {
return opSuccess;
}
opSuccess = PCL.EventParticipants.RemoveParticipantRequest(requestId);
return opSuccess
},
CreateEventParticipant: function (eventParticipant) {
var existingParticipant = PCL.EventParticipants.RetrieveEventParticipantLike(eventParticipant.pcl_Event.Id, eventParticipant.pcl_Contact.Id);
if (existingParticipant != null) {
alert('Cannot approve this request. This contact is already participating in the selected event.');
return false;
}
var opSuccess = false;
SDK.REST.createRecord(
eventParticipant,
"pcl_eventparticipant",
function (result) {
opSuccess = true;
},
function(error) {
alert('Could not create event request with contactId: ' + eventParticipant.pcl_Contact.Id + ' and eventId: ' + eventParticipant.pcl_Event.Id + '. --> ' + error.message);
}, false
);
return opSuccess;
}, .....
}
Edit 3
I have modified the SDK.REST to have a 5th parameter which toggles whether or not the operation is synchronous or asynchronous. Passing false at the end of any operation makes the operation synchronous.

node.js(Javascript) google-api-nodejs-client - list messages

My question was voted down, so i am rewriting it hopefully this is more succinct
I am stuck at writing a javascript function to list messages in inbox.
Using - official "google-api-nodejs-client", node.js, electron, (and javascript)
Goal: list messages in gmail inbox
For that to work i need to authorize first then ask for the messages
Authorize
- I copied the code from google node.js quickstart
- this works in electron (well actually node.js as it is a command line script).
Ask for Messages
- Google has an example, i copied it adjusted some parts but doesn't work. I think the example i am working from is not designed for the node.js "google-api-nodejs-client".
- Maybe it needs a different authorize
This is the listmessages function from the google example, i can't seem to figure out how to make this work with the authorize from list labels. This is what i have tried
changing gapi to google
changing userId to 'me'
changing givig it a query
Does not use a client library.
Does not use a client library.
/**
* Retrieve Messages in user's mailbox matching query.
*
* #param {String} userId User's email address. The special value 'me'
* can be used to indicate the authenticated user.
* #param {String} query String used to filter the Messages listed.
* #param {Function} callback Function to call when the request is complete.
*/
function listMessages(userId, query, callback) {
var getPageOfMessages = function(request, result) {
request.execute(function(resp) {
result = result.concat(resp.messages);
var nextPageToken = resp.nextPageToken;
if (nextPageToken) {
request = gapi.client.gmail.users.messages.list({
'userId': userId,
'pageToken': nextPageToken,
'q': query
});
getPageOfMessages(request, result);
} else {
callback(result);
}
});
};
var initialRequest = gapi.client.gmail.users.messages.list({
'userId': userId,
'q': query
});
getPageOfMessages(initialRequest, []);
}
This is the aurhorize function that works to list labels.
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/gmail-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'gmail-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Gmail API.
authorize(JSON.parse(content), listLabels);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
/**
* Lists the labels in the user's account.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listLabels(auth) {
var gmail = google.gmail('v1');
gmail.users.labels.list({
auth: auth,
userId: 'me',
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var labels = response.labels;
if (labels.length == 0) {
console.log('No labels found.');
} else {
console.log('Labels:');
for (var i = 0; i < labels.length; i++) {
var label = labels[i];
console.log('- %s', label.name);
}
}
});
}
The flow of your program should look like this:
Then you can use the api to list messages. (just to get you started):
export function listMessages(oauth2Client, userId, query, callback) {
const gmail = google.gmail('v1');
gmail.users.messages.list({
auth: oauth2Client,
userId: 'me',
}, (err, response) => {
console.log(response);
});
}
Notice the oauth2Client parameter. This is the object you get from your function authorize. Let me know if you have any questions.

Google Geolocation on beforeSave

I'm trying to do Geolocation using Google Maps API on beforeSave event for Parse Cloud. The problem is httpRequest isn't triggered between the process.
Here's my code:
Parse.Cloud.beforeSave('SearchList', function (request, response) {
if ( ! request.object.isNew())
response.success();
var SearchList = Parse.Object.extend('SearchList');
var query = new Parse.Query(SearchList);
query.equalTo('Id', request.object.get('Id'));
query.first({
success: function (entry) {
if (entry) {
var address = request.object.get('Street1') + ', ' + request.object.get('Street2') + ', ' + request.object.get('Street3') + ', ' + request.object.get('Suburb') + ', ' + request.object.get('State') + ', ' + request.object.get('Postcode');
for (var attr in request) {
entry.set(attr, request[attr]);
}
entry.set('Address', address);
if (request.object.get('Type') === 'Bus') {
address = request.object.get('Street1') + ', ' + request.object.get('State');
console.log('Bus Address: ' + address);
Parse.Cloud.httpRequest({
method: 'POST',
url: 'https://maps.googleapis.com/maps/api/geocode/json',
params: {
address : address,
key: 'my-api-key'
},
success: function(httpResponse) {
var response = httpResponse.data;
if (response.status === 'OK') {
var langLat = response.results[0].geometry.location;
console.log(langLat);
entry.set('Location', new Parse.GeoPoint({ latitude: Number(langLat.latitude), longitude: Number(langLat.longitude) }));
entry.save();
response.error('Updated Existing Entry');
}
},
error: function(httpResponse) {
entry.set('Location', null);
entry.save();
response.error('Updated Existing Entry');
}
});
} else {
entry.set('Location', new Parse.GeoPoint({ latitude: Number(request.object.get('Latitude')), longitude: Number(request.object.get('Longitude')) }));
entry.save();
response.error('Updated Existing Entry');
}
} else {
response.success();
}
},
error: function (err) {
response.error('Could not validate uniqueness for the Id object.');
}
});
});
So what my code does is during save, I checked the entry if it already existed in the database. If it exist I simple update its values and then return a response.error telling parseObject.save to not continue to save the data since it already existed, else I return a response.success telling parseObject.save to continue saving the data. So during this process there are some data who don't have lat-long values, so I have to check whether they have lat-long, if they don't I wanted to do a Geolocation query using the entry's address w/ Google Maps API to retrieve the lat-long values.
Any ideas on how I can execute Geolocation using Google Maps API during beforeSave?
Thanks
When you call reaponse success or error you're signalling that the function is complete, so anything after that isn't guaranteed to complete, especially if it's asynchronous.
You should make the asynchronous request and update the passed object and then return success to save it.

Is there a soundcloud artwork url scheme like in the facebook api?

I'd like to display the artwork image of a soundcloud track I have the Track-URL from.
I know that when I extract the track key/id from the URL, I can request the artwork URL via their API and then inject the retrieved URL into a image tag.
What I'd like to know is if its possible to use some kind of URL schema to make soundcloud forward browsers to the correct artwork URL like its possible with facebook profile images.
For example:
Mark Zuckerbergs current profile picture has the URL http://profile.ak.fbcdn.net/hprofile-ak-prn2/t5/202896_4_1782288297_q.jpg
Thats some cryptic stuff because its hosted on a CDN. Soundcloud artwork URLs look pretty much cryptic as well.
Now, when I know marks facebook id/key ("zuck"), I can simply access his profile image like so:
http://graph.facebook.com/zuck/picture
That URL is automatically forwarded to the profile picture URL by the facebook API.
Using this URL schema you abstract away not only the reason for a additional API request, but they also safe processing time on their side.
Is there some functionality like this for soundcloud track artworks?
I wrote an express app that redirects to largest available image on their CDN, given artwork_url.
FixSoundCloudArtworkUrl.js
It uses their naming scheme and enumerates sizes one by one until some image returns status 200.
Source:
'use strict';
var express = require('express'),
app = express();
require('./config/development')(app, express);
require('./config/production')(app, express);
var redis = require('redis'),
request = require('request'),
Promise = require('bluebird');
Promise.promisifyAll(redis.RedisClient.prototype);
var redisSettings = app.set('redis'),
redisClient = redis.createClient(redisSettings.port, redisSettings.host, redisSettings.options);
app.configure(function () {
app.use(express.bodyParser());
app.use(app.router);
});
function sendError(res, status, error) {
if (!(error instanceof Error)) {
error = new Error(JSON.stringify(error));
}
return res
.status(status || 500)
.end(error && error.message || 'Internal Server Error');
}
function generateCacheHeaders() {
var maxAge = 3600 * 24 * 365;
return {
'Cache-Control': 'public,max-age=' + maxAge,
'Expires': new Date(Date.now() + (maxAge * 1000)).toUTCString()
};
}
function getCacheKey(url) {
return 'soundcloud-thumbnail-proxy:' + url;
}
app.get('/*', function (req, res) {
var originalUrl = req.params[0],
cacheKey = getCacheKey(originalUrl),
urls;
// https://developers.soundcloud.com/docs/api/reference#artwork_url
// This is a ridiculous naming scheme, by the way.
urls = [
originalUrl,
originalUrl.replace('-large', '-t500x500'),
originalUrl.replace('-large', '-crop'), // 400x400
originalUrl.replace('-large', '-t300x300'),
originalUrl.replace('-large', '-large') // 100x100
];
return redisClient.getAsync(cacheKey).then(function (cachedUrl) {
if (cachedUrl) {
return cachedUrl;
}
return Promise.reduce(urls, function (resolvedUrl, url) {
if (resolvedUrl) {
return resolvedUrl;
}
return new Promise(function (resolve) {
request.head(url, function (err, response) {
if (!err && response.statusCode === 200) {
resolve(url);
} else {
resolve(null);
}
});
});
}, null);
}).then(function (url) {
if (!url) {
throw new Error('File not found');
}
var headers = generateCacheHeaders();
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
res.setHeader(key, headers[key]);
}
}
res.redirect(url);
redisClient.set(cacheKey, url);
redisClient.expire(cacheKey, 60 * 60 * 24 * 30);
}).catch(function (err) {
sendError(res, 404, err);
});
});
app.get('/crossdomain.xml', function (req, res) {
req.setEncoding('utf8');
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end('<?xml version="1.0" ?><cross-domain-policy><allow-access-from domain="*" /></cross-domain-policy>');
});
redisClient.on('ready', function () {
app.listen(app.set('port'));
});
redisClient.on('error', function () {
throw new Error('Could not connect to Redis');
});
module.exports = app;
No. The only documented way is: API Reference for "/tracks"

Resources