有没有办法获取我的Stack Exchange统计信息?

时间:2019-08-08 23:36:13

标签: javascript c# api web-services stackexchange-api

我正在重新创建Stack Exchange提供的火炬图像,并且重新创建的响应速度更快,因为我可以将鼠标悬停在网站图标上并显示给定Stack Exchange域的统计信息。我目前必须手动更新我计划每月约两次的数据,除非有一种方法可以通过Web服务或类似服务直接从Stack Exchange加载数据。

请记住以下几点:

  • 我将在ASP.NET Web应用程序中托管它,以便使用C#API。
  • Web服务也将是完美的,因为我可以从JavaScript调用它们。
  • 我需要提供任何服务的文档链接。

以下是我目前的手动创建,以防您好奇或不知道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,但是我无法迁移它。

1 个答案:

答案 0 :(得分:3)

我最近发现Stack Exchange确实为这些类型的事物提供了API。我强烈建议在使用前先阅读其documentation中的API。为了完成我在这里提出的任务,我需要利用以下API调用:

我同时利用了这两个调用来重新创建Stack Exchange风格,以防万一,您不知道这是什么风格:

profile for PerpetualJ on Stack Exchange, a network of free, community-driven Q&A sites

首先,我编写了一组简单的方法来处理对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个以上的请求,则新请求将被丢弃。

从这里开始,实现非常简单,并且是一个很棒的学习过程!

/ users / {ids}

  

获取在{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();
    });
}

/ users / {ids} / associated

  

根据用户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>

祝您未来一切顺利!