Something that was missing from the site when I signed up was the ability to figure out where your reputation points were coming from. If you had several questions or answers, it was hard to keep track of which ones were contributing to your reputation score. Another user of the site noticed this deficiency before I did, and created a Python script that will tell you what has changed with your account since the last time your ran the script. The script requires Python and SQLite. This seemed like something could be done pretty easily using PowerShell, so I put together a similar script that does pretty much the same thing:
[Reflection.Assembly]::LoadWithPartialName( "System.Web" ) | Out-Null
$webClient = New-Object Net.WebClient
$profile = $webClient.DownloadString(
"http://stackoverflow.com/users/$userNumber/myProfile.html" )
$userRegex = 'User (?<User>.*?) - Stack Overflow'
$user = [regex]::Match( $profile, $userRegex ).Groups[ "User" ].Value
$reputationRegex = '<div[^>]+class="summarycount">' +
'[^\d]+(?<Reputation>[,\d]+)</div>' +
'[^<]*<div[^>]+>Reputation'
$reputation = [regex]::Match(
$profile, $reputationRegex ).Groups[ "Reputation" ].Value -replace ","
$badgeRegex = '<div[^>]+class="summarycount"[^>]*>' +
'[^\d]+(?<Badges>\d+)</div>[^<]*</td>' +
'[^<]*<td[^>]*>[^<]*<h1[^>]*>Badges'
$badges = [regex]::Match(
$profile, $badgeRegex ).Groups[ "Badges" ].Value -replace ","
$profileXmlPath = Join-Path ( Get-Location ) "Profile_$userNumber.xml"
if ( !( Test-Path $profileXmlPath ) )
{
Out-File -FilePath $profileXmlPath -InputObject @"
<Profile>
<User>$user</User>
<Reputation>$reputation</Reputation>
<Badges>$badges</Badges>
<Questions />
</Profile>
"@
}
[xml]$profileXml = Get-Content $profileXmlPath
$existingQuestions = $profileXml.Profile.Questions.Clone()
if ( $profileXml.Profile[ "Questions" ].Question )
{
$profileXml.Profile.Questions.RemoveAll()
}
function Process-Questions( [string] $questionRegex, [string] $questionType )
{
$questionChangeHash = @{}
foreach ( $questionMatch in [regex]::Matches(
$profile, $questionRegex, 'SingleLine' ) )
{
$id = $questionMatch.Groups[ "ID" ].Value
$question = $questionMatch.Groups[ "Question" ].Value
$votes = $questionMatch.Groups[ "Votes" ].Value
$questionNode = $existingQuestions.Question |
Where-Object { $_.ID -eq $id }
if ( $questionNode )
{
if ( [int]$votes -gt $questionNode.Votes )
{
$questionChangeHash[ $id ] =
"+$( [int]$votes - $questionNode.Votes )"
}
elseif ( [int]$votes -lt $questionNode.Votes )
{
$questionChangeHash[ $id ] = [int]$votes - $questionNode.Votes
}
$questionNode.Votes = $votes
$profileXml.Profile[ "Questions" ].AppendChild(
$questionNode ) | Out-Null
}
else
{
& {
$script:questionNode = $profileXml.CreateElement( "Question" )
$script:questionNode.AppendChild(
$profileXml.CreateElement( "ID" ) )
$script:questionNode.ID = $id
$script:questionNode.AppendChild(
$profileXml.CreateElement( "Type" ) )
$script:questionNode.Type = $questionType
$script:questionNode.AppendChild(
$profileXml.CreateElement( "Question" ) )
$script:questionNode.Question = $question
$script:questionNode.AppendChild(
$profileXml.CreateElement( "Votes" ) )
$script:questionNode.Votes = $votes
$profileXml.Profile[ "Questions" ].AppendChild(
$script:questionNode )
} | Out-Null
$questionChangeHash[ $id ] = "(New)"
}
}
$screenWidth = $Host.UI.RawUI.WindowSize.Width
$elipsis = "..."
if ( $questionChangeHash.Keys.Count -gt 0 )
{
$profileXml.Profile.Questions.Question |
Where-Object { $_.Type -eq $questionType } |
Select-Object `
@{ Name = "$( $questionType )s";
Expression = {
$question =
[System.Web.HttpUtility]::HtmlDecode( $_.Question )
$questionMaxLength =
$screenWidth - ( " Votes Change ".Length )
if ( $question.Length -gt $questionMaxLength )
{
$question = "{0}$elipsis" -f
$question.SubString(
0, $questionMaxLength - $elipsis.Length )
}
$question
}
},
Votes,
@{ Name = "Change";
Expression = {
$questionChangeHash[ $_.ID ]
}
} | Format-Table -AutoSize
}
else
{
$profileXml.Profile.Questions.Question |
Where-Object { $_.Type -eq $questionType } |
Select-Object `
@{ Name = "$( $questionType )s";
Expression = {
$question =
[System.Web.HttpUtility]::HtmlDecode( $_.Question )
$questionMaxLength =
$screenWidth - ( " Votes ".Length )
if ( $question.Length -gt $questionMaxLength )
{
$question = "{0}$elipsis" -f
$question.SubString(
0, $questionMaxLength - $elipsis.Length )
}
$question
}
},
Votes | Format-Table -AutoSize
}
}
$questionRegex = '<div class="question-summary narrow"[^>]+>' +
'.*?<div class="mini-counts">(?<Votes>[\d,-]+).*?' +
'<div class="summary">[^<]*<h3>[^<]*' +
'<a\s*href="/questions/(?<ID>\d+)[^>]+>(?<Question>[^<]+)'
Process-Questions $questionRegex "Question"
$answerRegex = '<div class="answer-summary">' +
'<a[^>]+><div class="answer-votes[^>]+>(?<Votes>[\d,-]+)' +
'</div></a><div class="answer-link">' +
'<a\s*href="/questions/(?<ID>\d+)[^>]+>(?<Question>[^<]+)'
Process-Questions $answerRegex "Answer"
$reputationChange = ""
$badgeChange = ""
if ( [int]$reputation -gt $profileXml.Profile.Reputation )
{
$reputationChange =
"+$( [int]$reputation - $profileXml.Profile.Reputation )"
}
elseif ( [int]$reputation -lt $profileXml.Profile.Reputation )
{
$reputationChange = $reputation - $profileXml.Profile.Reputation
}
if ( [int]$badges -gt $profileXml.Profile.Badges )
{
$badgeChange = "+$( [int]$badges - $profileXml.Profile.Badges )"
}
elseif ( [int]$badges -lt $profileXml.Profile.Badges )
{
$badgeChange = [int]$badges - $profileXml.Profile.Badges
}
$profileXml.Profile.User = $user
$profileXml.Profile.Reputation = $reputation
$profileXml.Profile.Badges = $badges
$profileXml.Profile |
Select-Object User,
@{ Name = "Reputation";
Expression = {
if ( $reputationChange )
{
"{0} ({1})" -f
$_.Reputation, $reputationChange
}
else
{
$_.Reputation
}
}
},
@{ Name = "Badges";
Expression = {
if ( $badgeChange )
{
"{0} ({1})" -f $_.Badges, $badgeChange
}
else
{
$_.Badges
}
}
} | Format-List
$profileXml.Save( $profileXmlPath )
The results are displayed in the typical PowerShell way:
Answers Votes
------- -----
What is the one programming skill you have always wanted to master but haven't had time? 30
Is there a meaningful correlation between spelling and programming ability? 6
Factorial Algorithms in different languages 6
Modal popups - usability 6
How can I uninstall an application using PowerShell? 5
Function pointers in C - address operator "unnecessary" 5
Getting developers fired up about development 5
Rule you know you should follow but don't 4
"Hidden Secrets" of the Visual Studio .NET debugger? 4
C++ Restrict Template Function 4
Should a novice programmer spend time learning to write "desktop" applications these days,... 4
Parsing a log file with regular expressions 3
Shortcut for commenting CSS in VS 2008 3
How do I perform string operations on variables in a for loop? 2
Expose an event handler to VBScript users of my COM object 2
Where do "pure virtual function call" crashes come from? 2
Why do C# and VB have Generics? What benefit do they provide? Generics, FTW 2
regular expression to replace two (or more) consecutive characters by only one? 2
Anyone using a third-party Windows registry editor that they would recommend to others? 2
Transparent form on the desktop 1
Test if a Font is installed 1
Using what I've learned from stackoverflow. (HTML Scraper) 1
How to detect the presence of a default recording device in the system? 1
How do I add Debug Breakpoints to lines displayed in a "Find Results" window in Visual Studio 0
Regex Question - One or more spaces outside of a quote enclosed block of text 0
User : Jeff Hillman
Reputation : 790
Badges : 9
StackOverflow.com has recently added a feature on the user account page that provides some information about how the user's reputation has changed, but it still doesn't give you the detail that these scripts provide.
This is just another example to me of how easy PowerShell can make tasks like this. This isn't likely to be useful for a long time, but it was fun. PowerShell is fun.