Skip to content

Commit

Permalink
Initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
tstepanski committed Sep 8, 2015
1 parent 768898b commit 5de3161
Show file tree
Hide file tree
Showing 14 changed files with 582 additions and 0 deletions.
63 changes: 63 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
28 changes: 28 additions & 0 deletions SteamPageParser.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamPageParser", "SteamPageParser\SteamPageParser.csproj", "{4903B432-A0F7-4E1A-9068-2560E29BAE7C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamPageParserRunner", "SteamPageParserRunner\SteamPageParserRunner.csproj", "{7F4EBFF3-90A6-4A5D-9BDA-DBE238A64A31}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4903B432-A0F7-4E1A-9068-2560E29BAE7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4903B432-A0F7-4E1A-9068-2560E29BAE7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4903B432-A0F7-4E1A-9068-2560E29BAE7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4903B432-A0F7-4E1A-9068-2560E29BAE7C}.Release|Any CPU.Build.0 = Release|Any CPU
{7F4EBFF3-90A6-4A5D-9BDA-DBE238A64A31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F4EBFF3-90A6-4A5D-9BDA-DBE238A64A31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F4EBFF3-90A6-4A5D-9BDA-DBE238A64A31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F4EBFF3-90A6-4A5D-9BDA-DBE238A64A31}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions SteamPageParser/AppPackage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace SteamPageParser
{
public class AppPackage
{
private AppPackage(SteamApp associatedSteamApp)
{
AssociatedSteamApp = associatedSteamApp;
}

public string Title { get; internal set; }
public decimal OriginalPrice { get; internal set; }
public decimal CurrentPrice { get; internal set; }

public decimal CurrentPricePercentage => CurrentPrice/OriginalPrice;
public decimal DiscountPercentage => 1 - CurrentPricePercentage;

public SteamApp AssociatedSteamApp { get; }

public static AppPackage NewAppPackage(SteamApp associatedSteamApp) => new AppPackage(associatedSteamApp);
}
}
14 changes: 14 additions & 0 deletions SteamPageParser/InvalidAppException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace SteamPageParser
{
public class InvalidAppException : Exception
{
public InvalidAppException(int appId)
{
AppId = appId;
}

public int AppId { get; }
}
}
88 changes: 88 additions & 0 deletions SteamPageParser/Navigator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace SteamPageParser
{
public static class Navigator
{
public static string SteamAppUrl => @"http://store.steampowered.com/app/";

private static int MaximumAppId => 1000000;
private static int MininumAppId => 0;
private static int MaximumCoresToOccupy => 5;

public static ParallelQuery<SteamApp> GetSteamApps()
{
var appIdsToRun = new List<int>();

for (var i = MininumAppId; i < MaximumAppId; i++)
{
appIdsToRun.Add(i);
}

return appIdsToRun
.AsParallel()
.WithDegreeOfParallelism(MaximumCoresToOccupy)
.Select(async appId => await GetSteamApp(appId))
.Select(sa => sa.Result)
.Where(sa => sa != null);
}

public static async Task<SteamApp> GetSteamApp(int appId)
{
SteamApp app = null;

await Task.Run(() =>
{
try
{
var response = GetResponse(appId);
var html = GetHtml(appId, response);
app = Parser.ParsePage(appId, html);
}
catch (Exception exception)
{
Debug.WriteLine(exception.Message);
}
});

return app;
}

private static string GetHtml(int appId, HttpWebResponse response)
{
if (response.StatusCode != HttpStatusCode.OK) throw new InvalidAppException(appId);

using (var receiveStream = response.GetResponseStream())
{
if (receiveStream == null) throw new InvalidAppException(appId);

var readStream = response.CharacterSet == null
? new StreamReader(receiveStream)
: new StreamReader(receiveStream, Encoding.GetEncoding(response.CharacterSet));

var data = readStream.ReadToEnd();

response.Close();
readStream.Close();

return data;
}
}

private static HttpWebResponse GetResponse(int appId)
{
var request = (HttpWebRequest) WebRequest.Create($"{SteamAppUrl}{appId}/");

return (HttpWebResponse) request.GetResponse();
}
}
}
90 changes: 90 additions & 0 deletions SteamPageParser/Parser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Linq;
using HtmlAgilityPack;

namespace SteamPageParser
{
public static class Parser
{
private static string AppTitleClass => "apphub_AppName";
private static string PackageClass => "game_area_purchase_game_wrapper";
private static string PackagePriceXPath => "game_purchase_price price";
private static string PackageOriginalPriceXPath => "discount_original_price";
private static string PackageDiscountPriceXPath => "discount_final_price";
private static string PackageTitle => "h1";
private static string ThousandsSeparator => ",";
private static string CurrencySymbol => "$";

public static SteamApp ParsePage(int appId, string html)
{
if (string.IsNullOrWhiteSpace(html)) throw new ArgumentNullException(nameof(html));

try
{
var app = SteamApp.NewSteamApp(appId, html);

var htmlDocument = new HtmlDocument();

var htmlCleaned = html.Replace("\"", "'");

htmlDocument.LoadHtml(htmlCleaned);

var documentNode = htmlDocument.DocumentNode;

var titleNode = documentNode.SelectSingleNode($"//div[@class='{AppTitleClass}']");

app.Title = titleNode.InnerHtml.Trim();

var packageNodes = documentNode.SelectNodes($"//div[@class='{PackageClass}']").ToArray();

foreach (var packageNode in packageNodes)
{
AddPackage(app, packageNode);
}

return app;
}
catch (Exception)
{
throw new InvalidAppException(appId);
}
}

private static void AddPackage(SteamApp app, HtmlNode packageNode)
{
var package = app.AddNewPackage();

var packageTitleNode = packageNode.SelectSingleNode($"//{PackageTitle}");

package.Title = packageTitleNode.InnerHtml.Replace("Buy ", "").Trim();

var priceNodes = packageNode.SelectNodes($"//div[@class='{PackagePriceXPath}']");

if (priceNodes != null)
{
var priceNode = priceNodes[0];

package.CurrentPrice = ParseNodeWithCurrencyToDecimal(priceNode);

package.OriginalPrice = package.CurrentPrice;
}
else
{
var originalPriceNode = packageNode.SelectSingleNode($"//div[@class='{PackageOriginalPriceXPath}']");

package.OriginalPrice = ParseNodeWithCurrencyToDecimal(originalPriceNode);

var discountPriceNode = packageNode.SelectSingleNode($"//div[@class='{PackageDiscountPriceXPath}']");

package.CurrentPrice = ParseNodeWithCurrencyToDecimal(discountPriceNode);
}
}

private static decimal ParseNodeWithCurrencyToDecimal(HtmlNode node)
{
var stringValue = node.InnerHtml.Replace(CurrencySymbol, "").Replace(ThousandsSeparator, "");

return decimal.Parse(stringValue);
}
}
}
36 changes: 36 additions & 0 deletions SteamPageParser/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SteamPageParser")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SteamPageParser")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4903b432-a0f7-4e1a-9068-2560e29bae7c")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
34 changes: 34 additions & 0 deletions SteamPageParser/SteamApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;

namespace SteamPageParser
{
public class SteamApp
{
private SteamApp(int appId, string html)
{
AppId = appId;
Html = html;

PackageList = new List<AppPackage>();
}

public int AppId { get; }
public string Html { get; }
public string Title { get; internal set; }

private List<AppPackage> PackageList { get; }

public AppPackage[] Packages => PackageList.ToArray();

internal AppPackage AddNewPackage()
{
var newPackage = AppPackage.NewAppPackage(this);

PackageList.Add(newPackage);

return newPackage;
}

public static SteamApp NewSteamApp(int appId, string html) => new SteamApp(appId, html);
}
}
Loading

0 comments on commit 5de3161

Please sign in to comment.