(2nd update, March 21, 2013, 21:58 JST: bug fix and JSON/OPML option added.)
(3rd update, March 21, 2013, 22:17 JST: change names of some keys in JSON.)
Feedly is a great feed reader, but it doesn't seem to have an export function.
So I created a bookmarklet to extract URLs of your feed subscriptions and their categories and output them in JSON/OPML format.
Feedly feeds extractor bookmarklet
This bookmarklet only works at your Feedly index page (http://www.feedly.com/home#index).
After running this code, a text area containing the JSON data will be created at the bottom of the page.
Pretty-printed version:
// Feedly feeds extractor bookmarklet by Toshiyuki Ogura
// Run this code at http://www.feedly.com/home#index
// A text area containing your feed urls in JSON format will be created at the bottom of the page.
javascript:(function(){
if (window.location.href != 'http://www.feedly.com/home#index') {
} else {
function docEvaluateArray (expr, doc, context, resolver) {
doc = doc ? doc : (context ? context.ownerDocument : document);
resolver = resolver ? resolver : null;
context = context ? context : doc;
var result = doc.evaluate(expr, context, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var a = [];
for(var i = 0; i < result.snapshotLength; i++) {
a[i] = result.snapshotItem(i);
}
return a;
}
/*
// This function doesn't work because of HTMLUnknownElement.
function jsonToOPMLString (jsonObj) {
var xmlDeclaration = '<?xml version="1.0" encoding="utf-8"?>';
var opmlElement = document.createElement('opml');
opmlElement.setAttribute('version', '1.0');
var headElement = opmlElement.createElement('head');
headElement.createElement('title');
headElement.createElement('dateCreated');
headElement.createElement('dateModified');
headElement.createElement('ownerName');
headElement.createElement('ownerEmail');
var bodyElement = opmlElement.createElement('body');
for (var i = 0; i < jsonObj.length; i++) {
var categoryOutline = bodyElement.createElement('outline');
categoryOutline.setAttribute('text', jsonObj[i]['categoryName']);
for (var j = 0; j < jsonObj[i]['feeds'].length; j++) {
var feedObj = jsonObj[i]['feeds'][j];
var urlOutline = categoryOutline.createElement('outline');
urlOutline.setAttribute('text', feedObj['title']);
urlOutline.setAttribute('type', 'link');
urlOutline.setAttribute('xmlUrl', feedObj['xmlUrl']);
}
}
return xmlDeclaration + opmlElement.innerHTML;
}
*/
function jsonToOPMLString (jsonObj) {
var newLine = '\n'; // for pretty-printing
function getTagString(tagName, text, attrArray) { // attrArray = [['attr1', 'val1'], ['attr2', 'val2']];
var attrStringArray = [];
if(attrArray) {
for (var i = 0; i < attrArray.length; i++) {
attrStringArray.push(attrArray[i][0] + '="' + attrArray[i][1] + '"');
}
var result = '<' + tagName + ' ' + attrStringArray.join(' ');
} else {
var result = '<' + tagName;
}
if (text) {
if (text[0] == '<') {
return result + '>' + newLine + text + '</' + tagName + '>' + newLine;
} else {
return result + '>' + text + '</' + tagName + '>' + newLine;
}
} else {
return result + '/>' + newLine;
}
}
var categoryArray = [];
for (var i = 0; i < jsonObj.length; i++) {
var feedsArray = [];
for (var j = 0; j < jsonObj[i]['feeds'].length; j++) {
var outlineFeed = getTagString('outline', undefined,
[ [ 'text', jsonObj[i]['feeds'][j]['title'] ],
[ 'title', jsonObj[i]['feeds'][j]['title'] ],
[ 'type', 'rss' ],
[ 'xmlUrl', jsonObj[i]['feeds'][j]['xmlUrl'] ] ]);
feedsArray.push(outlineFeed);
}
var outlineCategory = getTagString('outline', feedsArray.join(''),
[ [ 'text', jsonObj[i]['category'] ],
[ 'title', jsonObj[i]['category'] ] ]);
categoryArray.push(outlineCategory);
}
var titleTag = getTagString('title', 'My Feedly feeds');
var nowTimeString = new Date().toUTCString();
var dateCreatedTag = getTagString('dateCreated', nowTimeString);
var dateModifiedTag = getTagString('dateModified', nowTimeString);
var ownerNameTag = getTagString('ownerName', 'My name');
var ownerEmailTag = getTagString('ownerEmail', 'My email');
var headTag = getTagString('head', titleTag + dateCreatedTag + dateModifiedTag + ownerNameTag + ownerEmailTag);
var bodyTag = getTagString('body', categoryArray.join(''));
var opmlTag = getTagString('opml', headTag + bodyTag);
var xmlDeclaration = '<?xml version="1.0" encoding="utf-8"?>' + newLine;
return xmlDeclaration + opmlTag;
}
var bulk = docEvaluateArray("//div[@id='mainArea']/div[starts-with(@class,'cell')]");
var items = Array.prototype.slice.call(bulk[0].childNodes);
for (var i = 1; i < bulk.length; i++) {
var arrayTemp = Array.prototype.slice.call(bulk[i].childNodes);
items = items.concat(arrayTemp);
}
var groupsArray = [];
var categoryObj = {};
var feedsArray = [];
for (var j = 0; j < items.length; j++) {
if (items[j].nodeName == 'H2') {
if (categoryObj['category'] == undefined) {
categoryObj['category'] = items[j].innerHTML.replace(/^\s*/, '').replace(/\s*$/, '');
} else {
categoryObj['feeds'] = feedsArray;
groupsArray.push(categoryObj);
categoryObj = {};
feedsArray = [];
categoryObj['category'] = items[j].innerHTML.replace(/^\s*/, '').replace(/\s*$/, '');
}
} else if (items[j].nodeName == 'DIV') {
var feedObj = {'title' : items[j].childNodes[2].nodeValue.replace(/^[\s\n]*/g, '').replace(/[\s\n]*$/g, ''),
'xmlUrl' : items[j].getAttribute('data-uri').replace('subscription/feed/','') };
feedsArray.push(feedObj);
}
}
var result = document.createElement('textarea');
result.setAttribute('rows', '10');
result.setAttribute('cols', '100%');
document.querySelector('div#mainBar').appendChild(result);
var choice = confirm('Press OK to get JSON,\nCancel to get OPML');
if (choice) {
result.value = JSON.stringify(groupsArray, undefined, 2);
} else {
result.value = jsonToOPMLString(groupsArray);
}
}
})();