(function () {
var api = {
url: 'https://api.stackexchange.com/2.2/',
endpoint: 'questions/featured',
params: {
site: 'stackoverflow',
pagesize: 100,
order: 'desc',
sort: 'activity',
Filter: '!iCF4LsDYJt6oT_Y1qKBCOv',
key: 'JBN9WXWxwrcnAj7WEEytmQ(('
}
},
sites = {};
function createUrl(ap) {
var ep = ap.url + ap.endpoint +'?',
nv = [];
if (ap.params) {
for(var p in ap.params) {
if (ap.params.hasOwnProperty(p)) {
nv.push(p + '=' + ap.params[p]);
}
}
}
// console.log(ep + nv.join('&'));
return ep + nv.join('&');
}
function createSitesUrl() {
return createUrl(
{ url: api.url,
endpoint: 'sites',
params: {
pagesize: 500,
key: api.params.key,
filter: '!*l7_tSmzy1mF3bw9odQw*8ur' }
});
}
function createUserFeaturedUrl(userid, site) {
return createUrl(
{ url: api.url,
endpoint: 'users/'+ userid + '/questions/featured',
params: {
site: site,
pagesize: 100,
key: api.params.key,
filter: '!*7PmgCF3rY3WihzNKHz4jURFN9X6' }
});
}
function createInfoUrl(site) {
return createUrl(
{ url: api.url,
endpoint: 'info',
params: {
site: site,
key: api.params.key,
filter: '!39qVOEzAjeqnKS5bl7_puf_.GSZJG' }
});
}
function createAccountsUrl(accountid) {
return createUrl(
{ url: api.url,
endpoint: 'users/'+ accountid + '/associated',
params: {
pagesize: 100,
key: api.params.key,
filter: '!w*vp4lVBIX453e.b5Y' }
});
}
function find(id) {
return document.getElementById(id);
}
function buildSite(site) {
var img = document.createElement('img');
img.setAttribute('src', site.icon_url);
img.setAttribute('title', site.name);
return img;
}
function buildProgress(site) {
var li = document.createElement('li');
li.setAttribute('data-site', site);
li.textContent = site;
return li;
}
function buildLink(post, site) {
var link = document.createElement('a');
link.setAttribute('href', site.site_url +'/q/' + post.question_id);
link.setAttribute('title', new Date(post.last_activity_date * 1000));
link.textContent = post.title
+ ' ('
+ 'score:'
+ post.score
+ ', ans:'
+ post.answer_count
+ ' )' ;
return link;
}
function buildQuestion(site, item) {
var cont = document.createElement('div');
cont.appendChild(buildSite(site));
cont.appendChild(buildLink(item, site));
return cont;
}
var queue = [];
var backlog = []; // ms to backoff
var backoffTimer;
var boIsRunning = false;
var isCalled = false;
function get(url, callback) {
queue.push(function() { realget(url, callback); });
if (!isCalled) processQueue();
}
function processQueue() {
console.log('bl ',backlog.length, 'ql' , queue.length);
if (backlog.length > 0) {
if (boIsRunning) {
// backoffTimer running
} else {
console.log('bo set');
backoffTimer = setTimeout(()=>{
clearTimeout(backoffTimer);
boIsRunning = false;
processQueue();
}, backlog.shift());
boIsRunning = true;
}
} else {
if (queue.length > 0) {
isCalled = true;
var action = queue.shift();
if (action != null) {
// prevent CORS error so throttle pre-emptively
backlog.push((queue.length + 100));
action();
}
} else {
backlog.push(100);
isCalled = false;
}
}
}
var errors = 0;
// used when we are good to call
function realget(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', function() {
var data;
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText);
if (data.backoff) backlog.push((data.backoff || 0 ) * 1000);
if (errors > 0 ) errors--;
processQueue();
callback(data);
} else {
console.log('readystate and status ', xhr.readyState, xhr.status);
}
});
xhr.addEventListener('error', function(err) {
console.log('error for ', url, err, xhr.status);
errors++;
err.preventDefault();
document.getElementById('msg').textContent = '(throttled, waiting for ' + (errors * 20) + ' seconds)';
backlog.unshift(errors * 20000);
queue.unshift(function() {
document.getElementById('msg').textContent = 'Sites being processed:';
realget(url, callback);
});
processQueue();
});
xhr.addEventListener('abort', function() {
console.log('abort for ', url, xhr.status);
});
xhr.send();
}
function findProgressItem(site) {
return document.querySelector(
'#prog li[data-site="' +
site.site_url.replace('https://','') +
'"]');
}
function updateProgress(site) {
var item = findProgressItem(site);
item.textContent = null;
item.appendChild(buildSite(site));
}
function loadUserFeatured(userid, site) {
updateProgress(site);
get(createUserFeaturedUrl(userid, site.api_site_parameter), function(data) {
console.log(site);
data.items.forEach( function(item) {
console.log(item);
find('fav').appendChild(buildQuestion(site, item));
});
var item = findProgressItem(site);
item.parentElement.removeChild(item);
});
}
function loadUserFromAccount(item) {
var uid = item.user_id;
var site = item.site_url.replace('https://','');
find('prog').appendChild(buildProgress(site));
if (sites[site]) {
loadUserFeatured(uid, sites[site]);
} else {
get(createInfoUrl(site), function(data) {
sites[site] = data.items[0].site;
loadUserFeatured(uid, sites[site]);
});
}
}
function loadAccounts(accountId) {
get(createAccountsUrl(accountId), function(data) {
var fav = find('fav');
while (fav.firstChild) {
fav.removeChild(fav.firstChild);
}
data.items.forEach( function(item) {
loadUserFromAccount(item);
});
});
}
find('go').addEventListener(
'click',
function() {
loadAccounts(find('accountid').value);
});
}());
img {
height: 1em;
}
<div id="error" style="display:none">
</div>
<div>
<label>Account id:</label><input type="text" id="accountid" />
</div>
<div>
<button id="go">Search</button>
</div>
<p id="msg">Sites being processed:</p>
<ul id="prog"></ul>
<p>Bounties:</p>
<div id="fav">
</div>