我正在重新创建Stack Exchange提供的火炬图像,并且重新创建的响应速度更快,因为我可以将鼠标悬停在网站图标上并显示给定Stack Exchange域的统计信息。我目前必须手动更新我计划每月约两次的数据,除非有一种方法可以通过Web服务或类似服务直接从Stack Exchange加载数据。
请记住以下几点:
以下是我目前的手动创建,以防您好奇或不知道SE风格是什么,尽管确实需要清理并提高效率。
var siteNames = [ 'Stack Exchange',
'Puzzling',
'Stack Overflow',
'Software Engineering',
'Mathematics',
'Physical Fitness' ]
var reps = [ '6.2k', '4.3k', '954', '410', '224', '220' ];
var golds = [ '1', '0', '0', '1', '0', '0' ];
var silvers = [ '14', '7', '4', '2', '1', '0' ];
var bronzes = [ '98', '50', '20', '10', '8', '10' ];
function getSiteStats(siteID) {
document.getElementById("site-name").innerText = siteNames[siteID];
document.getElementById("rep").innerText = reps[siteID];
document.getElementById("gold").innerText = golds[siteID];
document.getElementById("silver").innerText = silvers[siteID];
document.getElementById("bronze").innerText = bronzes[siteID];
}
function resetSiteStats() {
getSiteStats(0);
}
html, body {
margin: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #6aa4ed;
background-image: linear-gradient(45deg, #6aa4ed, #141d33);
background-image: -webkit-linear-gradient(45deg, #6aa4ed, #141d33);
}
h1, h5 {
color: #fff;
font-family: Arial, Helvetica, sans-serif;
font-weight: 100;
text-align: center;
margin: 0;
}
h1 {
font-size: 10vh;
}
h5 {
margin-bottom: 10px;
}
.flair {
padding: 15px;
background-color: #fff;
border-radius: 5px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
display: flex;
}
.flair img {
width: 40px;
height: 40px;
margin: 5px;
cursor: pointer;
}
.flair .profile {
width: 175px;
height: 175px;
margin: 0;
margin-right: 15px;
box-shadow: 2px 2px 4px rgba(12,13,14,0.5);
cursor: default;
}
.flair a {
color: #37f;
text-decoration: none;
margin: 5px;
}
.flair a:hover {
color: #15a;
}
.flair ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.flair ul > li {
display: inline-block;
margin: 5px;
}
.flair p {
margin: 0;
margin-left: 5px;
}
.badge div {
display: inline-block;
height: 7px;
width: 7px;
border-radius: 50%;
transform: translateY(-3px) translateX(3px);
}
.gold {
background-color: #fc0;
}
.silver {
background-color: #ccc;
}
.bronze {
background-color: #da6;
}
<h1>Stack Exchange Flair</h1>
<h5>Not Mobile Friendly (Yet)</h5>
<h5>Hover Over Site Icons</h5>
<div class="flair">
<img class="profile" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/blue.jpg" />
<div class="account">
<a href="#">PerpetualJ</a>
<p id="site-name">Stack Exchange</p>
<ul>
<li><strong id="rep">6.2k</strong></li>
<li>
<div class="badge">
<div class="gold"></div>
<span id="gold">1</span>
</div>
</li>
<li>
<div class="badge">
<div class="silver"></div>
<span id="silver">14</span>
</div>
</li>
<li>
<div class="badge">
<div class="bronze"></div>
<span id="bronze">98</span>
</div>
</li>
</ul>
<ul>
<li onmouseover="getSiteStats(1);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/puzzling/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(2);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(3);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/softwareengineering/img/icon-48.png"/></li>
<li onmouseover="getSiteStats(4);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/math/img/apple-touch-icon.png"/></li>
<li onmouseover="getSiteStats(5);" onmouseout="resetSiteStats();"><img src="https://cdn.sstatic.net/Sites/fitness/img/icon-48.png?v=f5a02f85db94"/></li>
</ul>
<p>How fast do you have to slap a chicken to cook it?</p>
</div>
</div>
我是否可以通过某种方式调用API,Web服务或类似服务,以使我能够获取给定Stack Exchange网站的当前统计信息?
此外,我宁愿不进行任何类型的网页抓取或类似操作。我希望它来自合法的Stack Exchange服务。
注意:如果它属于meta,请告诉我,以便可以迁移。
主题:根据help center,该问题被视为主题:
我们认为最好的Stack Overflow问题中包含一些源代码,但是如果您的问题通常涵盖……
程序员常用的
- 软件工具;并且是
- 一个实际的,可回答的问题,是软件开发所独有的
...那么您来对地方了!
鉴于以上引用,API是程序员常用的工具,并且通过询问Stack Exchange是否有一个工具,此问题是一个切实可行的问题。但是,我确实认为这可能更适合Meta,但是我无法迁移它。
答案 0 :(得分:3)
我最近发现Stack Exchange确实为这些类型的事物提供了API。我强烈建议在使用前先阅读其documentation中的API。为了完成我在这里提出的任务,我需要利用以下API调用:
我同时利用了这两个调用来重新创建Stack Exchange风格,以防万一,您不知道这是什么风格:
首先,我编写了一组简单的方法来处理对API的请求:
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
在这里,需要key
来 帮助 防止throttling后续请求过多:
每个应用程序都受基于IP的并发请求限制的约束。如果单个IP每秒发出30个以上的请求,则新请求将被丢弃。
从这里开始,实现非常简单,并且是一个很棒的学习过程!
获取在
{ids}
中以ID标识的用户。通常,从其他来源(例如
/questions
)获取用户ID后,将调用此方法来获取用户个人资料。
{ids}
最多可以包含100个以分号分隔的ID。
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
根据用户
account_ids
中的{ids}
返回用户的所有关联帐户。
{ids}
最多可以包含100个以分号分隔的ID。
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
/* Definitions */
var CardType = { Wheel: "wheel", Card: "card", Box: "box" }
var userCard = {
username: '',
profileImageUrl: '',
reputation: 0,
badges: {
gold: 0,
silver: 0,
bronze: 0
},
siteUrls: []
}
/* Initial Calls */
var accountID = '13342919';
generateCard('user-flair-wheel', accountID, CardType.Wheel);
/* Required Events */
function showSitename(tooltipID, siteName) {
var tooltip = document.getElementById(tooltipID);
tooltip.innerHTML = siteName.replace('Stack Exchange', '');
tooltip.classList.add('active');
}
function hideSitename(tooltipID) {
document.getElementById(tooltipID).classList.remove('active');
}
/* UI Generation Functions */
function generateCard(containerid, accountid, cardType) {
getAssociatedAccounts(accountID, function() {
var className = cardType.toString().toLowerCase();
var container = document.getElementById(containerid);
container.classList.add("flair");
container.classList.add(className);
// Build the card.
addProfile(container);
addScores(container, className);
addSites(container, className);
container.innerHTML += '<div id="' + containerid +
'-tooltip" class="se-tooltip"></div>';
});
}
function addProfile(container) {
container.innerHTML += '<img class="user-image" src="' +
userCard.profileImageUrl + '"/>';
container.innerHTML += '<h1 class="username display-4">' +
userCard.username + '</h1>';
}
function addScores(container, cardType) {
var badges = '<ul class="badges">';
badges += '<li><i class="fas fa-trophy"></i> <span id="reputation-' +
cardType + '">' + userCard.reputation + '</span></li>';
badges += '<li><span id="gold-badges-' + cardType + '">' +
userCard.badges.gold + '</span></li>';
badges += '<li><span id="silver-badges-' + cardType + '">' +
userCard.badges.silver + '</span></li>';
badges += '<li><span id="bronze-badges-' + cardType + '">' +
userCard.badges.bronze + '</span></li>';
badges += '</ul>';
container.innerHTML += badges;
}
function addSites(container, cardType) {
var sites = '<ul id="sites-' + cardType + '" class="sites">';
for (var i = 0; i < userCard.siteUrls.length; i++) {
var site = '<li>';
var siteLinkSplit = userCard.siteUrls[i].split('|');
site += '<a href="' + siteLinkSplit[0] + '">';
var tooltipID = container.id +'-tooltip';
var linkElement = '<a href="' + siteLinkSplit[0] + '"';
linkElement += ' onmouseover="showSitename(\'' + tooltipID + '\',\'' + siteLinkSplit[2] + '\')"';
linkElement += ' onmouseout="hideSitename(\'' + tooltipID + '\');"';
site += linkElement + '>';
site += '<img src="' + (siteLinkSplit[1] == '<IMG>' ? '#' : siteLinkSplit[1]) + '"/></a></li>';
sites += site;
}
sites += '</ul>';
container.innerHTML += sites;
}
/* Stack Exchange API Based Functions */
function getAssociatedAccounts(accountID, callback) {
let url = 'users/' + accountID + '/associated';
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
var accounts = sortAccountsByReputation(response.items);
var accountsProcessed = 0;
for (let i = 0; i < accounts.length; i++) {
let siteName = accounts[i].site_url.replace('https://', '');
siteName = siteName.replace('.stackexchange', '');
siteName = siteName.replace('.com', '');
getAssociatedAccountDetails(accounts[i].user_id, siteName, accounts[i].site_name, function() {
if (++accountsProcessed >= accounts.length)
callback();
});
}
});
}
function getAssociatedAccountDetails(userID, siteName, fullSiteName, callback) {
let url = 'users/' + userID +'?order=desc&sort=reputation&site=' + siteName;
getSEWebServiceResponse(url, function(response) {
if (!response.items)
return;
let account = response.items[0];
userCard.reputation += account.reputation;
userCard.badges.gold += account.badge_counts.gold;
userCard.badges.silver += account.badge_counts.silver;
userCard.badges.bronze += account.badge_counts.bronze;
if (userCard.siteUrls.length < 7) {
var siteProfileCombo = account.link + '|<IMG>|' + fullSiteName;
siteProfileCombo = siteProfileCombo.replace('<IMG>', getSiteIcon(siteName));
userCard.siteUrls.push(siteProfileCombo);
}
if (userCard.username.length < 1)
userCard.username = account.display_name;
if (userCard.profileImageUrl.length < 1)
userCard.profileImageUrl = account.profile_image;
callback();
});
}
/* Helper Functions */
function getSEWebServiceResponse(request, callback) {
let apiRoot = 'https://api.stackexchange.com/2.2/';
let key = 'key=s29XM)Eqn2x3YxhjLgFwBQ((';
if (request.indexOf('?') >= 0)
key = '&' + key;
else
key = '?' + key;
getWebServiceResponse(apiRoot + request + key, function(response) { callback(response); });
}
function getWebServiceResponse(requestUrl, callback) {
let request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status < 200 || request.status >= 400)
callback("An unexpected error occurred.");
else
callback(JSON.parse(this.response));
};
request.send();
}
function sortAccountsByReputation(accounts) {
return accounts.sort(function(a, b) { return b.reputation - a.reputation; });
}
function getSiteIcon(siteName) {
if (siteName == "meta")
return 'https://meta.stackexchange.com/content/Sites/stackexchangemeta/img/icon-48.png';
return 'https://cdn.sstatic.net/Sites/' + siteName + '/img/apple-touch-icon.png';
}
/* Flair Styles */
.flair {
position: relative;
margin: 15px;
}
.flair > .se-tooltip {
position: absolute;
left: 50%;
transform: translate(-50%);
width: 250px;
bottom: 50px;
opacity: 0;
background-color: #fff;
color: #555;
text-shadow: none;
border-radius: 25px;
padding: 5px 10px;
box-shadow: 2px 2px 3px #0005;
}
.flair > .se-tooltip.active {
bottom: 10px;
opacity: 1;
}
/* Flair Wheel Styles */
.flair.wheel {
width: 200px;
height: 250px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-shadow: 1px 1px 2px #0005;
}
.flair.wheel .user-image {
width: 100px;
height: 100px;
border-radius: 50%;
box-shadow: 2px 2px 3px #0005;
}
.flair.wheel .username {
font-size: 30px;
margin: 0;
}
.flair.wheel .badges > li > span { position: relative; }
.flair.wheel .badges > li:first-of-type > i { color: #5c9; }
.flair.wheel .badges > li:not(:first-of-type) > span::before {
content: '';
position: absolute;
top: 50%;
left: -15px;
transform: translateY(-40%);
width: 10px;
height: 10px;
border-radius: 50%;
}
.flair.wheel .badges > li:nth-child(2) > span::before { background-color: #fb3; }
.flair.wheel .badges > li:nth-child(3) > span::before { background-color: #aaa; }
.flair.wheel .badges > li:nth-child(4) > span::before { background-color: #c95; }
.flair.wheel .sites {
position: absolute;
top: 10px;
left: 0;
width: 100%;
height: 55%;
}
.flair.wheel .sites > li { position: absolute; }
.flair.wheel .sites > li > a > img {
width: 35px;
height: 35px;
background-color: #fffa;
border-radius: 50%;
padding: 2px;
box-shadow: 2px 2px 3px #0005;
cursor: pointer;
transition: 0.3s cubic-bezier(0.5, -2.5, 1.0, 1.2) all;
z-index: 1;
}
.flair.wheel .sites > li > a:hover > img {
width: 40px;
height: 40px;
background-color: #fff;
}
.flair.wheel .sites > li:nth-child(1) {
top: -15px;
left: 50%;
transform: translate(-50%);
}
.flair.wheel .sites > li:nth-child(2) {
top: 0px;
left: 15%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(3) {
top: 0px;
left: 70%;
transform: translate(-20%);
}
.flair.wheel .sites > li:nth-child(4) {
top: 45%;
left: 80%;
transform: translate(-20%, -50%);
}
.flair.wheel .sites > li:nth-child(5) {
top: 45%;
left: -5px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(6) {
top: 79%;
left: 3px;
transform: translateY(-50%);
}
.flair.wheel .sites > li:nth-child(7) {
top: 79%;
right: 3px;
transform: translateY(-50%);
}
/* To Organize in a Row instead of Column */
.user-flair-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
/* Global Styles */
ul {
padding: 0;
listy-style-type: none;
}
ul > li {
display: inline-block;
padding: 0 10px;
}
/* Template Overrides */
html, body {
margin: 0;
height: 100%;
background-color: #333 !important;
background-image: linear-gradient(45deg, #333, #555) !important;
background-image: -webkit-linear-gradient(45deg, #333, #555) !important;
}
.primary-content {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.primary-content > .lead { font-size: 25px; }
<link href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2940219/PerpetualJ.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" rel="stylesheet"/>
<div id="primary-content" class="primary-content">
<div class="user-flair-container">
<div id="user-flair-wheel"></div>
</div>
</div>
祝您未来一切顺利!