mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-21 16:58:37 +00:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ade53ae7 | ||
|
|
9a51386b7e | ||
|
|
03ee96057f | ||
|
|
a23bca7960 | ||
|
|
6b4ae6a4d7 | ||
|
|
5c80fd158d | ||
|
|
885800c539 | ||
|
|
920d4b9ed6 | ||
|
|
2d02bd609e | ||
|
|
e658ae33b1 | ||
|
|
379018866b | ||
|
|
1ad1e8b792 | ||
|
|
c3dde4c822 | ||
|
|
b40dc2e572 | ||
|
|
70bdd34d66 | ||
|
|
56a6e10189 | ||
|
|
41f8a0a474 | ||
|
|
92f347e28b | ||
|
|
1a1914540c | ||
|
|
449e4f9511 | ||
|
|
959bf98039 | ||
|
|
017c5eb7ef | ||
|
|
9e575584a8 | ||
|
|
19da8c6d11 | ||
|
|
3d19a69c60 | ||
|
|
f6a8d16c85 | ||
|
|
f13991c2da | ||
|
|
40a3d6558d | ||
|
|
fea76a3dda | ||
|
|
1087c01a2c | ||
|
|
fd49ff5483 | ||
|
|
0f5d9a665c | ||
|
|
ad63432645 | ||
|
|
f36681ea18 | ||
|
|
ce94035d98 | ||
|
|
7583e50cf3 | ||
|
|
687de60476 | ||
|
|
e181eb354b | ||
|
|
4e5ddefac9 | ||
|
|
e588ba3d2c | ||
|
|
6d087a9ac9 | ||
|
|
529d366b6c | ||
|
|
4657d00d11 | ||
|
|
3a5edab651 | ||
|
|
d662d9dd6a | ||
|
|
e508e99d14 | ||
|
|
fdc705e955 | ||
|
|
38f48841bd | ||
|
|
2ebce59ee7 | ||
|
|
adefa6446d | ||
|
|
f18c3b301e | ||
|
|
b79265e74a | ||
|
|
50853d8d7e | ||
|
|
7e084bf50b | ||
|
|
60a02a4c6a | ||
|
|
fcfdbdd220 | ||
|
|
ac10f32431 | ||
|
|
5d7e0290d7 | ||
|
|
f170f16919 | ||
|
|
96d9ea6056 | ||
|
|
d8bf424ac3 | ||
|
|
95d2860afd | ||
|
|
a75ed7047b | ||
|
|
d627570cc9 | ||
|
|
4e22d7fcd1 | ||
|
|
bb05f4c67a | ||
|
|
238838b0fd | ||
|
|
ae8413b72b | ||
|
|
027e301420 | ||
|
|
1a6c5a3cff | ||
|
|
378a87bc86 | ||
|
|
27635d260b | ||
|
|
8f99620598 | ||
|
|
bb285512d1 | ||
|
|
88690d8c09 | ||
|
|
0ff442e3e1 | ||
|
|
fedc3268b6 | ||
|
|
fc0d0abaaf | ||
|
|
95df9057c2 | ||
|
|
ed6e35da85 | ||
|
|
64f424e474 | ||
|
|
7b67755932 | ||
|
|
a5f7e7988c | ||
|
|
80ed0e66bb | ||
|
|
5529a8e1f0 | ||
|
|
496bea5ac5 | ||
|
|
52f3a86255 | ||
|
|
7d205cfa42 | ||
|
|
c4f47c56da | ||
|
|
546440d9dc |
@@ -10,3 +10,9 @@ mono:
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/df82484f12510c3f2516
|
||||
on_success: always # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: never # options: [always|never|change] default: always
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
VisualStudioVersion = 14.0.25123.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAuth", "SteamAuth\SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigGenerator", "ConfigGenerator\ConfigGenerator.csproj", "{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
|
||||
@@ -27,10 +25,6 @@ Global
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FA/@EntryIndexedValue">FA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FS/@EntryIndexedValue">FS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PIN/@EntryIndexedValue">PIN</s:String>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace ArchiSteamFarm {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
// Only custom below, different than ones available as user_notification_type
|
||||
Items = 255
|
||||
Items = 254
|
||||
}
|
||||
|
||||
internal readonly HashSet<ENotification> Notifications;
|
||||
@@ -71,6 +71,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
JobID = jobID;
|
||||
|
||||
if (msg.notifications.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notifications = new HashSet<ENotification>();
|
||||
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
|
||||
Notifications.Add((ENotification) notification.user_notification_type);
|
||||
@@ -85,9 +89,7 @@ namespace ArchiSteamFarm {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg.count_new_items > 0) {
|
||||
Notifications = new HashSet<ENotification> {
|
||||
ENotification.Items
|
||||
};
|
||||
Notifications = new HashSet<ENotification> { ENotification.Items };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ArchiSteamFarm</RootNamespace>
|
||||
<AssemblyName>ArchiSteamFarm</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<TargetFrameworkProfile />
|
||||
@@ -109,6 +109,9 @@
|
||||
<Compile Include="JSON\GitHub.cs" />
|
||||
<Compile Include="JSON\Steam.cs" />
|
||||
<Compile Include="Logging.cs" />
|
||||
<Compile Include="MobileAuthenticator.cs" />
|
||||
<Compile Include="Mono.cs" />
|
||||
<Compile Include="ObsoleteSteamGuardAccount.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Trading.cs" />
|
||||
@@ -144,12 +147,6 @@
|
||||
<ItemGroup>
|
||||
<Content Include="cirno.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SteamAuth\SteamAuth.csproj">
|
||||
<Project>{5ad0934e-f6c4-4ae5-83af-c788313b2a87}</Project>
|
||||
<Name>SteamAuth</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace ArchiSteamFarm {
|
||||
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
|
||||
private readonly WebBrowser WebBrowser;
|
||||
|
||||
internal bool Ready { get; private set; }
|
||||
|
||||
private ulong SteamID;
|
||||
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
|
||||
|
||||
internal static void Init() {
|
||||
@@ -62,17 +65,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
int index = hashName.IndexOf('-');
|
||||
if (index < 1) {
|
||||
Logging.LogNullError(nameof(index));
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint appID;
|
||||
if (uint.TryParse(hashName.Substring(0, index), out appID)) {
|
||||
return appID;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(appID));
|
||||
return 0;
|
||||
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
|
||||
}
|
||||
|
||||
private static Steam.Item.EType GetItemType(string name) {
|
||||
@@ -117,18 +114,17 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser = new WebBrowser(bot.BotName);
|
||||
}
|
||||
|
||||
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
internal void OnDisconnected() => Ready = false;
|
||||
|
||||
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
Logging.LogNullError(nameof(steamClient) + " || " + nameof(webAPIUserNonce), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong steamID = steamClient.SteamID;
|
||||
if (steamID == 0) {
|
||||
return false;
|
||||
}
|
||||
SteamID = steamClient.SteamID;
|
||||
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString()));
|
||||
|
||||
// Generate an AES session key
|
||||
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
|
||||
@@ -155,7 +151,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
authResult = iSteamUserAuth.AuthenticateUser(
|
||||
steamid: steamID,
|
||||
steamid: SteamID,
|
||||
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
||||
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
@@ -182,10 +178,12 @@ namespace ArchiSteamFarm {
|
||||
string steamLoginSecure = authResult["tokensecure"].Value;
|
||||
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
|
||||
|
||||
if (!UnlockParentalAccount(parentalPin).Result) {
|
||||
// Unlock Steam Parental if needed
|
||||
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ready = true;
|
||||
LastSessionRefreshCheck = DateTime.Now;
|
||||
return true;
|
||||
}
|
||||
@@ -239,6 +237,89 @@ namespace ArchiSteamFarm {
|
||||
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> GetConfirmations(string deviceID, string confirmationHash, uint time) {
|
||||
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
|
||||
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(string deviceID, string confirmationHash, uint time, uint confirmationID) {
|
||||
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0)) {
|
||||
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/mobileconf/details/" + confirmationID + "?p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
|
||||
|
||||
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Steam.ConfirmationDetails response;
|
||||
|
||||
try {
|
||||
response = JsonConvert.DeserializeObject<Steam.ConfirmationDetails>(json);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogNullError(nameof(response), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
response.ConfirmationID = confirmationID;
|
||||
return response;
|
||||
}
|
||||
|
||||
internal async Task<bool> HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
|
||||
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
|
||||
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
|
||||
|
||||
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Steam.ConfirmationResponse response;
|
||||
|
||||
try {
|
||||
response = JsonConvert.DeserializeObject<Steam.ConfirmationResponse>(json);
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
return response.Success;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(response), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return null;
|
||||
@@ -284,17 +365,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal Dictionary<uint, string> GetOwnedGames(ulong steamID) {
|
||||
if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
|
||||
Logging.LogNullError("steamID || SteamApiKey", Bot.BotName);
|
||||
//Logging.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
Logging.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
|
||||
iPlayerService.Timeout = Timeout;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
|
||||
iPlayerService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
try {
|
||||
response = iPlayerService.GetOwnedGames(
|
||||
steamid: steamID,
|
||||
@@ -316,7 +395,7 @@ namespace ArchiSteamFarm {
|
||||
foreach (KeyValue game in response["games"].Children) {
|
||||
uint appID = (uint) game["appid"].AsUnsignedLong();
|
||||
if (appID == 0) {
|
||||
Logging.LogNullError(nameof(appID));
|
||||
Logging.LogNullError(nameof(appID), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -326,19 +405,93 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal uint GetServerTime() {
|
||||
KeyValue response = null;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
|
||||
iTwoFactorService.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
response = iTwoFactorService.QueryTime(
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response != null) {
|
||||
return (uint) response["server_time"].AsUnsignedLong();
|
||||
}
|
||||
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal async Task<byte?> GetTradeHoldDuration(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
Logging.LogNullError(nameof(tradeID), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
|
||||
|
||||
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
string text = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(text)) {
|
||||
Logging.LogNullError(nameof(text), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
|
||||
if (index < 0) {
|
||||
Logging.LogNullError(nameof(index), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
index += 20;
|
||||
text = text.Substring(index);
|
||||
|
||||
index = text.IndexOf(';');
|
||||
if (index < 0) {
|
||||
Logging.LogNullError(nameof(index), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
text = text.Substring(0, index);
|
||||
|
||||
byte holdDuration;
|
||||
if (byte.TryParse(text, out holdDuration)) {
|
||||
return holdDuration;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(holdDuration), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
|
||||
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
|
||||
Logging.LogNullError("SteamApiKey", Bot.BotName);
|
||||
//Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
try {
|
||||
response = iEconService.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
@@ -370,13 +523,18 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
uint appID = 0;
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string hashName = description["market_hash_name"].Value;
|
||||
if (!string.IsNullOrEmpty(hashName)) {
|
||||
appID = GetAppIDFromMarketHashName(hashName);
|
||||
}
|
||||
|
||||
if (appID == 0) {
|
||||
appID = (uint) description["appid"].AsUnsignedLong();
|
||||
}
|
||||
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string descriptionType = description["type"].Value;
|
||||
if (!string.IsNullOrEmpty(descriptionType)) {
|
||||
type = GetItemType(descriptionType);
|
||||
@@ -393,16 +551,14 @@ namespace ArchiSteamFarm {
|
||||
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
|
||||
};
|
||||
|
||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
||||
Steam.Item steamItem = new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
foreach (Steam.Item steamItem in trade["items_to_give"].Children.Select(item => new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
})) {
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
@@ -412,16 +568,14 @@ namespace ArchiSteamFarm {
|
||||
tradeOffer.ItemsToGive.Add(steamItem);
|
||||
}
|
||||
|
||||
foreach (KeyValue item in trade["items_to_receive"].Children) {
|
||||
Steam.Item steamItem = new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
};
|
||||
|
||||
foreach (Steam.Item steamItem in trade["items_to_receive"].Children.Select(item => new Steam.Item {
|
||||
AppID = (uint) item["appid"].AsUnsignedLong(),
|
||||
ContextID = item["contextid"].AsUnsignedLong(),
|
||||
AssetID = item["assetid"].AsUnsignedLong(),
|
||||
ClassID = item["classid"].AsUnsignedLong(),
|
||||
InstanceID = item["instanceid"].AsUnsignedLong(),
|
||||
Amount = (uint) item["amount"].AsUnsignedLong()
|
||||
})) {
|
||||
Tuple<uint, Steam.Item.EType> description;
|
||||
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
|
||||
steamItem.RealAppID = description.Item1;
|
||||
@@ -466,17 +620,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
|
||||
Logging.LogNullError("tradeID || SteamApiKey", Bot.BotName);
|
||||
//Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
try {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
tradeofferid: tradeID.ToString(),
|
||||
@@ -489,24 +641,24 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
if (response != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
|
||||
internal async Task<HashSet<Steam.Item>> GetMyInventory(bool tradable) {
|
||||
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
|
||||
|
||||
ushort nextPage = 0;
|
||||
uint currentPage = 0;
|
||||
while (true) {
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
|
||||
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
|
||||
|
||||
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
|
||||
if (jObject == null) {
|
||||
@@ -537,13 +689,27 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
uint appID = 0;
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string hashName = description["market_hash_name"].ToString();
|
||||
if (!string.IsNullOrEmpty(hashName)) {
|
||||
appID = GetAppIDFromMarketHashName(hashName);
|
||||
}
|
||||
|
||||
if (appID == 0) {
|
||||
string appIDString = description["appid"].ToString();
|
||||
if (string.IsNullOrEmpty(appIDString)) {
|
||||
Logging.LogNullError(nameof(appIDString), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!uint.TryParse(appIDString, out appID)) {
|
||||
Logging.LogNullError(nameof(appID), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Steam.Item.EType type = Steam.Item.EType.Unknown;
|
||||
|
||||
string descriptionType = description["type"].ToString();
|
||||
if (!string.IsNullOrEmpty(descriptionType)) {
|
||||
type = GetItemType(descriptionType);
|
||||
@@ -559,11 +725,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
foreach (JToken item in items) {
|
||||
|
||||
Steam.Item steamItem;
|
||||
|
||||
try {
|
||||
steamItem = JsonConvert.DeserializeObject<Steam.Item>(item.ToString());
|
||||
steamItem = item.ToObject<Steam.Item>();
|
||||
} catch (JsonException e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
continue;
|
||||
@@ -588,12 +753,17 @@ namespace ArchiSteamFarm {
|
||||
break; // OK, last page
|
||||
}
|
||||
|
||||
if (ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) {
|
||||
continue;
|
||||
uint nextPage;
|
||||
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage)) {
|
||||
Logging.LogNullError(nameof(nextPage), Bot.BotName);
|
||||
break;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(nextPage), Bot.BotName);
|
||||
break;
|
||||
if (nextPage <= currentPage) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentPage = nextPage;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -668,7 +838,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/badges?p=" + page;
|
||||
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
@@ -683,7 +853,7 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
string request = SteamCommunityURL + "/my/gamecards/" + appID;
|
||||
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,11 +25,12 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
// ReSharper disable once ClassCannotBeInstantiated
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
internal sealed class BotConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool Enabled { get; private set; } = false;
|
||||
@@ -82,9 +83,6 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool DistributeKeys { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
||||
|
||||
|
||||
@@ -23,13 +23,15 @@
|
||||
*/
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using SteamAuth;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class BotDatabase {
|
||||
[JsonProperty]
|
||||
private string _LoginKey;
|
||||
|
||||
internal string LoginKey {
|
||||
get {
|
||||
return _LoginKey;
|
||||
@@ -44,7 +46,28 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal SteamGuardAccount SteamGuardAccount {
|
||||
[JsonProperty]
|
||||
private MobileAuthenticator _MobileAuthenticator;
|
||||
|
||||
internal MobileAuthenticator MobileAuthenticator {
|
||||
get {
|
||||
return _MobileAuthenticator;
|
||||
}
|
||||
set {
|
||||
if (_MobileAuthenticator == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_MobileAuthenticator = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Converter code will be removed soon
|
||||
[JsonProperty]
|
||||
private ObsoleteSteamGuardAccount _SteamGuardAccount;
|
||||
|
||||
internal ObsoleteSteamGuardAccount SteamGuardAccount {
|
||||
get {
|
||||
return _SteamGuardAccount;
|
||||
}
|
||||
@@ -58,12 +81,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
private string _LoginKey;
|
||||
|
||||
[JsonProperty]
|
||||
private SteamGuardAccount _SteamGuardAccount;
|
||||
|
||||
private string FilePath;
|
||||
|
||||
internal static BotDatabase Load(string filePath) {
|
||||
|
||||
@@ -31,10 +31,14 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer {
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
|
||||
|
||||
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
|
||||
@@ -42,9 +46,10 @@ namespace ArchiSteamFarm {
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool ManualMode { get; private set; }
|
||||
|
||||
private bool NowFarming;
|
||||
private bool KeepFarming, NowFarming;
|
||||
|
||||
internal CardsFarmer(Bot bot) {
|
||||
if (bot == null) {
|
||||
@@ -55,7 +60,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if ((Timer == null) && (Program.GlobalConfig.IdleFarmingPeriod > 0)) {
|
||||
Timer = new Timer(
|
||||
async e => await CheckGamesForFarming().ConfigureAwait(false),
|
||||
e => CheckGamesForFarming(),
|
||||
null,
|
||||
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod), // Delay
|
||||
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
|
||||
@@ -102,12 +107,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// This is the last moment for final check if we can farm
|
||||
if (Bot.PlayingBlocked) {
|
||||
Logging.LogGenericInfo("But account is currently occupied, so farming is stopped!");
|
||||
Logging.LogGenericInfo("But account is currently occupied, so farming is stopped!", Bot.BotName);
|
||||
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
return;
|
||||
}
|
||||
|
||||
NowFarming = true;
|
||||
KeepFarming = NowFarming = true;
|
||||
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
|
||||
|
||||
do {
|
||||
@@ -170,26 +175,44 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
|
||||
KeepFarming = false;
|
||||
FarmResetEvent.Set();
|
||||
|
||||
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
|
||||
for (byte i = 0; (i < 5) && NowFarming; i++) {
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (NowFarming) {
|
||||
Logging.LogGenericWarning("Timed out!", Bot.BotName);
|
||||
}
|
||||
|
||||
FarmResetEvent.Reset();
|
||||
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
|
||||
Bot.OnFarmingStopped();
|
||||
FarmingSemaphore.Release();
|
||||
}
|
||||
|
||||
internal async Task RestartFarming() {
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
internal void OnNewItemsNotification() {
|
||||
if (!NowFarming) {
|
||||
return;
|
||||
}
|
||||
|
||||
FarmResetEvent.Set();
|
||||
}
|
||||
|
||||
internal async Task OnNewGameAdded() {
|
||||
if (!NowFarming) {
|
||||
// If we're not farming yet, obviously it's worth it to make a check
|
||||
StartFarming().Forget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
|
||||
// If we have Complex algorithm and some games to boost, it's also worth to make a check
|
||||
// That's because we would check for new games after our current round anyway
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
StartFarming().Forget();
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||
@@ -218,22 +241,25 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
byte maxPages = 1;
|
||||
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
|
||||
if ((htmlNodeCollection != null) && (htmlNodeCollection.Count > 0)) {
|
||||
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]");
|
||||
if (htmlNode != null) {
|
||||
string lastPage = htmlNode.InnerText;
|
||||
if (!string.IsNullOrEmpty(lastPage)) {
|
||||
if (!byte.TryParse(lastPage, out maxPages)) {
|
||||
maxPages = 1; // Should never happen
|
||||
}
|
||||
if (string.IsNullOrEmpty(lastPage)) {
|
||||
Logging.LogNullError(nameof(lastPage), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) {
|
||||
Logging.LogNullError(nameof(maxPages), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm.Clear();
|
||||
|
||||
CheckPage(htmlDocument);
|
||||
|
||||
if (maxPages <= 1) {
|
||||
if (maxPages == 1) {
|
||||
return GamesToFarm.Count > 0;
|
||||
}
|
||||
|
||||
@@ -336,12 +362,12 @@ namespace ArchiSteamFarm {
|
||||
CheckPage(htmlDocument);
|
||||
}
|
||||
|
||||
private async Task CheckGamesForFarming() {
|
||||
private void CheckGamesForFarming() {
|
||||
if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
StartFarming().Forget();
|
||||
}
|
||||
|
||||
private async Task<bool?> ShouldFarm(uint appID) {
|
||||
@@ -356,12 +382,29 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
|
||||
if (htmlNode != null) {
|
||||
return !htmlNode.InnerText.Contains("No card drops");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
|
||||
return null;
|
||||
string progress = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(progress)) {
|
||||
Logging.LogNullError(nameof(progress), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte cardsRemaining = 0;
|
||||
|
||||
Match match = Regex.Match(progress, @"\d+");
|
||||
if (match.Success) {
|
||||
if (!byte.TryParse(match.Value, out cardsRemaining)) {
|
||||
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
|
||||
return cardsRemaining > 0;
|
||||
}
|
||||
|
||||
private bool FarmMultiple() {
|
||||
@@ -423,22 +466,28 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(appID);
|
||||
DateTime endFarmingDate = DateTime.Now.AddHours(Program.GlobalConfig.MaxFarmingTime);
|
||||
|
||||
bool success = true;
|
||||
|
||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
for (ushort farmingTime = 0; (farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime) && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
|
||||
|
||||
while (keepFarming.GetValueOrDefault(true) && (DateTime.Now < endFarmingDate)) {
|
||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.Now;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
success = false;
|
||||
break;
|
||||
FarmResetEvent.Reset();
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
|
||||
GamesToFarm[appID] += timePlayed;
|
||||
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
|
||||
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
||||
@@ -459,19 +508,25 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
|
||||
DateTime startFarmingPeriod = DateTime.Now;
|
||||
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
success = false;
|
||||
break;
|
||||
FarmResetEvent.Reset();
|
||||
success = KeepFarming;
|
||||
}
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
|
||||
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
|
||||
foreach (uint appID in appIDs) {
|
||||
GamesToFarm[appID] += timePlayed;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
|
||||
maxHour += timePlayed;
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
public void WriteLine(string category, string msg) {
|
||||
if (string.IsNullOrEmpty(category) && string.IsNullOrEmpty(msg)) {
|
||||
Logging.LogNullError(nameof(category) + " && " + nameof(msg));
|
||||
return;
|
||||
}
|
||||
|
||||
lock (FilePath) {
|
||||
try {
|
||||
File.AppendAllText(FilePath, category + " | " + msg + Environment.NewLine);
|
||||
|
||||
@@ -30,7 +30,8 @@ using System.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated"), SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
internal sealed class GlobalConfig {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal enum EUpdateChannel : byte {
|
||||
@@ -42,12 +43,12 @@ namespace ArchiSteamFarm {
|
||||
internal const byte DefaultHttpTimeout = 60;
|
||||
|
||||
private const byte DefaultMaxFarmingTime = 10;
|
||||
private const byte DefaultFarmingDelay = 5;
|
||||
private const byte DefaultFarmingDelay = 15;
|
||||
private const ushort DefaultWCFPort = 1242;
|
||||
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
||||
|
||||
// This is hardcoded blacklist which should not be possible to change
|
||||
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
|
||||
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool Debug { get; private set; } = false;
|
||||
@@ -80,11 +81,14 @@ namespace ArchiSteamFarm {
|
||||
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte LoginLimiterDelay { get; private set; } = 7;
|
||||
internal byte LoginLimiterDelay { get; private set; } = 10;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte InventoryLimiterDelay { get; private set; } = 3;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte GiftsLimiterDelay { get; private set; } = 1;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ForceHttp { get; private set; } = false;
|
||||
|
||||
@@ -158,6 +162,11 @@ namespace ArchiSteamFarm {
|
||||
globalConfig.FarmingDelay = DefaultFarmingDelay;
|
||||
}
|
||||
|
||||
if ((globalConfig.FarmingDelay > 5) && Mono.RequiresWorkaroundForBug41701()) {
|
||||
Logging.LogGenericWarning("Your Mono runtime is affected by bug 41701, FarmingDelay of " + globalConfig.FarmingDelay + " is not possible - value of 5 will be used instead");
|
||||
globalConfig.FarmingDelay = 5;
|
||||
}
|
||||
|
||||
if (globalConfig.HttpTimeout == 0) {
|
||||
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
|
||||
globalConfig.HttpTimeout = DefaultHttpTimeout;
|
||||
|
||||
@@ -28,7 +28,8 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.JSON {
|
||||
internal static class GitHub {
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global"), SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
internal sealed class ReleaseResponse {
|
||||
internal sealed class Asset {
|
||||
[JsonProperty(PropertyName = "name", Required = Required.Always)]
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
@@ -50,7 +52,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal uint AppID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string AppIDString {
|
||||
get {
|
||||
return AppID.ToString();
|
||||
@@ -72,7 +75,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal ulong ContextID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string ContextIDString {
|
||||
get {
|
||||
return ContextID.ToString();
|
||||
@@ -114,7 +118,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string ID {
|
||||
get { return AssetIDString; }
|
||||
set { AssetIDString = value; }
|
||||
@@ -122,7 +127,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal ulong ClassID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string ClassIDString {
|
||||
get {
|
||||
return ClassID.ToString();
|
||||
@@ -142,9 +148,10 @@ namespace ArchiSteamFarm.JSON {
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong InstanceID { get; set; }
|
||||
internal ulong InstanceID { private get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string InstanceIDString {
|
||||
get {
|
||||
return InstanceID.ToString();
|
||||
@@ -166,7 +173,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal uint Amount { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string AmountString {
|
||||
get {
|
||||
return Amount.ToString();
|
||||
@@ -209,7 +217,8 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal ulong TradeOfferID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string TradeOfferIDString {
|
||||
get {
|
||||
return TradeOfferID.ToString();
|
||||
@@ -244,7 +253,7 @@ namespace ArchiSteamFarm.JSON {
|
||||
// Extra
|
||||
internal ulong OtherSteamID64 => OtherSteamID3 == 0 ? 0 : new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
|
||||
|
||||
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && ((item.Type == Item.EType.FoilTradingCard) || (item.Type == Item.EType.TradingCard)));
|
||||
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && (item.Type == Item.EType.TradingCard));
|
||||
|
||||
internal bool IsPotentiallyDupesTradeForUs() {
|
||||
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
|
||||
@@ -321,5 +330,171 @@ namespace ArchiSteamFarm.JSON {
|
||||
[JsonProperty(PropertyName = "them", Required = Required.Always)]
|
||||
internal ItemList ItemsToReceive { get; } = new ItemList();
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
internal sealed class ConfirmationResponse {
|
||||
[JsonProperty(PropertyName = "success", Required = Required.Always)]
|
||||
internal bool Success { get; private set; }
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
|
||||
internal sealed class ConfirmationDetails {
|
||||
internal enum EType : byte {
|
||||
Unknown,
|
||||
Trade,
|
||||
Market,
|
||||
Other
|
||||
}
|
||||
|
||||
internal uint ConfirmationID { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "success", Required = Required.Always)]
|
||||
internal bool Success { get; private set; }
|
||||
|
||||
private EType _Type;
|
||||
private EType Type {
|
||||
get {
|
||||
if (_Type != EType.Unknown) {
|
||||
return _Type;
|
||||
}
|
||||
|
||||
if (HtmlDocument == null) {
|
||||
return EType.Unknown;
|
||||
}
|
||||
|
||||
HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']");
|
||||
if (testNode != null) {
|
||||
_Type = EType.Market;
|
||||
return _Type;
|
||||
}
|
||||
|
||||
testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']");
|
||||
if (testNode != null) {
|
||||
_Type = EType.Trade;
|
||||
return _Type;
|
||||
}
|
||||
|
||||
_Type = EType.Other;
|
||||
return _Type;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong _TradeOfferID;
|
||||
internal ulong TradeOfferID {
|
||||
get {
|
||||
if (_TradeOfferID != 0) {
|
||||
return _TradeOfferID;
|
||||
}
|
||||
|
||||
if ((Type != EType.Trade) || (HtmlDocument == null)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(nameof(htmlNode));
|
||||
return 0;
|
||||
}
|
||||
|
||||
string id = htmlNode.GetAttributeValue("id", null);
|
||||
if (string.IsNullOrEmpty(id)) {
|
||||
Logging.LogNullError(nameof(id));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int index = id.IndexOf('_');
|
||||
if (index < 0) {
|
||||
Logging.LogNullError(nameof(index));
|
||||
return 0;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (id.Length <= index) {
|
||||
Logging.LogNullError(nameof(id.Length));
|
||||
return 0;
|
||||
}
|
||||
|
||||
id = id.Substring(index);
|
||||
if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) {
|
||||
return _TradeOfferID;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(_TradeOfferID));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong _OtherSteamID64;
|
||||
internal ulong OtherSteamID64 {
|
||||
get {
|
||||
if (_OtherSteamID64 != 0) {
|
||||
return _OtherSteamID64;
|
||||
}
|
||||
|
||||
if ((Type != EType.Trade) || (OtherSteamID3 == 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
_OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
|
||||
return _OtherSteamID64;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(PropertyName = "html")]
|
||||
private string HTML;
|
||||
#pragma warning restore 649
|
||||
|
||||
private uint _OtherSteamID3;
|
||||
private uint OtherSteamID3 {
|
||||
get {
|
||||
if (_OtherSteamID3 != 0) {
|
||||
return _OtherSteamID3;
|
||||
}
|
||||
|
||||
if ((Type != EType.Trade) || (HtmlDocument == null)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(nameof(htmlNode));
|
||||
return 0;
|
||||
}
|
||||
|
||||
string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null);
|
||||
if (string.IsNullOrEmpty(miniProfile)) {
|
||||
Logging.LogNullError(nameof(miniProfile));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (uint.TryParse(miniProfile, out _OtherSteamID3) && (_OtherSteamID3 != 0)) {
|
||||
return _OtherSteamID3;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(_OtherSteamID3));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private HtmlDocument _HtmlDocument;
|
||||
private HtmlDocument HtmlDocument {
|
||||
get {
|
||||
if (_HtmlDocument != null) {
|
||||
return _HtmlDocument;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(HTML)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
_HtmlDocument = new HtmlDocument();
|
||||
_HtmlDocument.LoadHtml(WebUtility.HtmlDecode(HTML));
|
||||
return _HtmlDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
|
||||
Log("[!] StackTrace:" + Environment.NewLine + exception.StackTrace);
|
||||
|
||||
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message + Environment.NewLine + "StackTrace:" + Environment.NewLine + exception.StackTrace);
|
||||
if (exception.InnerException != null) {
|
||||
exception = exception.InnerException;
|
||||
continue;
|
||||
@@ -123,7 +121,8 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG"), SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[Conditional("DEBUG")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
LogNullError(nameof(message), botName);
|
||||
|
||||
294
ArchiSteamFarm/MobileAuthenticator.cs
Normal file
294
ArchiSteamFarm/MobileAuthenticator.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.JSON;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class MobileAuthenticator {
|
||||
internal sealed class Confirmation {
|
||||
internal readonly uint ID;
|
||||
internal readonly ulong Key;
|
||||
internal readonly Steam.ConfirmationDetails.EType Type;
|
||||
|
||||
internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) {
|
||||
if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) {
|
||||
throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type));
|
||||
}
|
||||
|
||||
ID = id;
|
||||
Key = key;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly byte[] TokenCharacters = { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
|
||||
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
private static short SteamTimeDifference;
|
||||
|
||||
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
|
||||
|
||||
[JsonProperty(PropertyName = "shared_secret", Required = Required.DisallowNull)]
|
||||
private string SharedSecret;
|
||||
|
||||
[JsonProperty(PropertyName = "identity_secret", Required = Required.DisallowNull)]
|
||||
private string IdentitySecret;
|
||||
|
||||
[JsonProperty(PropertyName = "device_id")]
|
||||
private string DeviceID;
|
||||
|
||||
private Bot Bot;
|
||||
|
||||
internal static MobileAuthenticator LoadFromSteamGuardAccount(ObsoleteSteamGuardAccount sga) {
|
||||
if (sga != null) {
|
||||
return new MobileAuthenticator {
|
||||
SharedSecret = sga.SharedSecret,
|
||||
IdentitySecret = sga.IdentitySecret,
|
||||
DeviceID = sga.DeviceID
|
||||
};
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(sga));
|
||||
return null;
|
||||
}
|
||||
|
||||
private MobileAuthenticator() {
|
||||
|
||||
}
|
||||
|
||||
internal void Init(Bot bot) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException(nameof(bot));
|
||||
}
|
||||
|
||||
Bot = bot;
|
||||
}
|
||||
|
||||
internal void CorrectDeviceID(string deviceID) {
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
Logging.LogNullError(nameof(deviceID), Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceID = deviceID;
|
||||
}
|
||||
|
||||
internal async Task<bool> HandleConfirmation(Confirmation confirmation, bool accept) {
|
||||
if (confirmation == null) {
|
||||
Logging.LogNullError(nameof(confirmation), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
string confirmationHash = GenerateConfirmationKey(time, "conf");
|
||||
if (!string.IsNullOrEmpty(confirmationHash)) {
|
||||
return await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
|
||||
if (confirmation == null) {
|
||||
Logging.LogNullError(nameof(confirmation), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
string confirmationHash = GenerateConfirmationKey(time, "conf");
|
||||
if (string.IsNullOrEmpty(confirmationHash)) {
|
||||
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Steam.ConfirmationDetails response = await Bot.ArchiWebHandler.GetConfirmationDetails(DeviceID, confirmationHash, time, confirmation.ID).ConfigureAwait(false);
|
||||
if ((response == null) || !response.Success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
internal async Task<string> GenerateToken() {
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time != 0) {
|
||||
return GenerateTokenForTime(time);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal async Task<HashSet<Confirmation>> GetConfirmations() {
|
||||
uint time = await GetSteamTime().ConfigureAwait(false);
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
string confirmationHash = GenerateConfirmationKey(time, "conf");
|
||||
if (string.IsNullOrEmpty(confirmationHash)) {
|
||||
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetConfirmations(DeviceID, confirmationHash, time).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HtmlNodeCollection confirmationNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']");
|
||||
if (confirmationNodes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<Confirmation> result = new HashSet<Confirmation>();
|
||||
foreach (HtmlNode confirmationNode in confirmationNodes) {
|
||||
string idString = confirmationNode.GetAttributeValue("data-confid", null);
|
||||
if (string.IsNullOrEmpty(idString)) {
|
||||
Logging.LogNullError(nameof(idString), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint id;
|
||||
if (!uint.TryParse(idString, out id) || (id == 0)) {
|
||||
Logging.LogNullError(nameof(id), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
string keyString = confirmationNode.GetAttributeValue("data-key", null);
|
||||
if (string.IsNullOrEmpty(keyString)) {
|
||||
Logging.LogNullError(nameof(keyString), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong key;
|
||||
if (!ulong.TryParse(keyString, out key) || (key == 0)) {
|
||||
Logging.LogNullError(nameof(key), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
HtmlNode descriptionNode = confirmationNode.SelectSingleNode(".//div[@class='mobileconf_list_entry_description']/div");
|
||||
if (descriptionNode == null) {
|
||||
Logging.LogNullError(nameof(descriptionNode), Bot.BotName);
|
||||
continue;
|
||||
}
|
||||
|
||||
Steam.ConfirmationDetails.EType type;
|
||||
|
||||
string description = descriptionNode.InnerText;
|
||||
if (description.Equals("Sell - Market Listing")) {
|
||||
type = Steam.ConfirmationDetails.EType.Market;
|
||||
} else if (description.StartsWith("Trade with ", StringComparison.Ordinal)) {
|
||||
type = Steam.ConfirmationDetails.EType.Trade;
|
||||
} else {
|
||||
type = Steam.ConfirmationDetails.EType.Other;
|
||||
}
|
||||
|
||||
result.Add(new Confirmation(id, key, type));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<uint> GetSteamTime() {
|
||||
if (SteamTimeDifference != 0) {
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
|
||||
}
|
||||
|
||||
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (SteamTimeDifference == 0) {
|
||||
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
|
||||
if (serverTime != 0) {
|
||||
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
|
||||
}
|
||||
}
|
||||
|
||||
TimeSemaphore.Release();
|
||||
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
|
||||
}
|
||||
|
||||
private string GenerateTokenForTime(long time) {
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] sharedSecretArray = Convert.FromBase64String(SharedSecret);
|
||||
byte[] timeArray = new byte[8];
|
||||
|
||||
time /= 30L;
|
||||
|
||||
for (int i = 8; i > 0; i--) {
|
||||
timeArray[i - 1] = (byte) time;
|
||||
time >>= 8;
|
||||
}
|
||||
|
||||
byte[] hashedData;
|
||||
using (HMACSHA1 hmacGenerator = new HMACSHA1(sharedSecretArray, true)) {
|
||||
hashedData = hmacGenerator.ComputeHash(timeArray);
|
||||
}
|
||||
|
||||
byte b = (byte) (hashedData[19] & 0xF);
|
||||
int codePoint = ((hashedData[b] & 0x7F) << 24) | ((hashedData[b + 1] & 0xFF) << 16) | ((hashedData[b + 2] & 0xFF) << 8) | (hashedData[b + 3] & 0xFF);
|
||||
|
||||
byte[] codeArray = new byte[5];
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
codeArray[i] = TokenCharacters[codePoint % TokenCharacters.Length];
|
||||
codePoint /= TokenCharacters.Length;
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(codeArray);
|
||||
}
|
||||
|
||||
private string GenerateConfirmationKey(uint time, string tag = null) {
|
||||
if (time == 0) {
|
||||
Logging.LogNullError(nameof(time), Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] b64Secret = Convert.FromBase64String(IdentitySecret);
|
||||
|
||||
int bufferSize = 8;
|
||||
if (string.IsNullOrEmpty(tag) == false) {
|
||||
bufferSize += Math.Min(32, tag.Length);
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
byte[] timeArray = BitConverter.GetBytes((long) time);
|
||||
if (BitConverter.IsLittleEndian) {
|
||||
Array.Reverse(timeArray);
|
||||
}
|
||||
|
||||
Array.Copy(timeArray, buffer, 8);
|
||||
if (string.IsNullOrEmpty(tag) == false) {
|
||||
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, buffer, 8, bufferSize - 8);
|
||||
}
|
||||
|
||||
byte[] hash;
|
||||
using (HMACSHA1 hmac = new HMACSHA1(b64Secret, true)) {
|
||||
hash = hmac.ComputeHash(buffer);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
ArchiSteamFarm/Mono.cs
Normal file
75
ArchiSteamFarm/Mono.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Mono {
|
||||
internal static bool RequiresWorkaroundForBug41701() {
|
||||
// https://bugzilla.xamarin.com/show_bug.cgi?id=41701
|
||||
Version version = GetMonoVersion();
|
||||
if (version == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return version >= new Version(4, 4);
|
||||
}
|
||||
|
||||
private static Version GetMonoVersion() {
|
||||
Type type = Type.GetType("Mono.Runtime");
|
||||
if (type == null) {
|
||||
return null; // OK, not Mono
|
||||
}
|
||||
|
||||
MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (displayName == null) {
|
||||
Logging.LogNullError(nameof(displayName));
|
||||
return null;
|
||||
}
|
||||
|
||||
string versionString = (string) displayName.Invoke(null, null);
|
||||
if (string.IsNullOrEmpty(versionString)) {
|
||||
Logging.LogNullError(nameof(versionString));
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = versionString.IndexOf(' ');
|
||||
if (index <= 0) {
|
||||
Logging.LogNullError(nameof(index));
|
||||
return null;
|
||||
}
|
||||
|
||||
versionString = versionString.Substring(0, index);
|
||||
|
||||
Version version;
|
||||
if (Version.TryParse(versionString, out version)) {
|
||||
return version;
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(version));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
ArchiSteamFarm/ObsoleteSteamGuardAccount.cs
Normal file
43
ArchiSteamFarm/ObsoleteSteamGuardAccount.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
// TODO: This will be completely removed soon
|
||||
public class ObsoleteSteamGuardAccount {
|
||||
[JsonProperty("shared_secret")]
|
||||
public string SharedSecret { get; set; }
|
||||
|
||||
[JsonProperty("serial_number")]
|
||||
public string SerialNumber { get; set; }
|
||||
|
||||
[JsonProperty("revocation_code")]
|
||||
public string RevocationCode { get; set; }
|
||||
|
||||
[JsonProperty("uri")]
|
||||
public string URI { get; set; }
|
||||
|
||||
[JsonProperty("server_time")]
|
||||
public long ServerTime { get; set; }
|
||||
|
||||
[JsonProperty("account_name")]
|
||||
public string AccountName { get; set; }
|
||||
|
||||
[JsonProperty("token_gid")]
|
||||
public string TokenGID { get; set; }
|
||||
|
||||
[JsonProperty("identity_secret")]
|
||||
public string IdentitySecret { get; set; }
|
||||
|
||||
[JsonProperty("secret_1")]
|
||||
public string Secret1 { get; set; }
|
||||
|
||||
[JsonProperty("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[JsonProperty("device_id")]
|
||||
public string DeviceID { get; set; }
|
||||
|
||||
[JsonProperty("fully_enrolled")]
|
||||
public bool FullyEnrolled { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ namespace ArchiSteamFarm {
|
||||
// We booted successfully so we can now remove old exe file
|
||||
if (File.Exists(oldExeFile)) {
|
||||
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
File.Delete(oldExeFile);
|
||||
@@ -172,7 +172,7 @@ namespace ArchiSteamFarm {
|
||||
if (!updateOverride && !GlobalConfig.AutoUpdates) {
|
||||
Logging.LogGenericInfo("New version is available!");
|
||||
Logging.LogGenericInfo("Consider updating yourself!");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Downloading new version...");
|
||||
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
return;
|
||||
@@ -247,11 +248,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
if (GlobalConfig.AutoRestart) {
|
||||
Logging.LogGenericInfo("Restarting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Restart();
|
||||
} else {
|
||||
Logging.LogGenericInfo("Exiting...");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
// 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("2.0.5.2")]
|
||||
[assembly: AssemblyFileVersion("2.0.5.2")]
|
||||
[assembly: AssemblyVersion("2.1.0.9")]
|
||||
[assembly: AssemblyFileVersion("2.1.0.9")]
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
*/
|
||||
|
||||
using SteamAuth;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -45,7 +44,7 @@ namespace ArchiSteamFarm {
|
||||
internal static async Task LimitInventoryRequestsAsync() {
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
Task.Run(async () => {
|
||||
await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
|
||||
InventorySemaphore.Release();
|
||||
}).Forget();
|
||||
}
|
||||
@@ -87,36 +86,44 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
tradeOffers.RemoveWhere(tradeoffer => tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active);
|
||||
tradeOffers.TrimExcess();
|
||||
|
||||
if (tradeOffers.Count == 0) {
|
||||
return;
|
||||
if (tradeOffers.RemoveWhere(tradeoffer => tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active) > 0) {
|
||||
tradeOffers.TrimExcess();
|
||||
if (tradeOffers.Count == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false);
|
||||
await Bot.AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
|
||||
List<Task<bool>> tasks = tradeOffers.Select(ParseTrade).ToList();
|
||||
bool[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
if (results.Any(result => result)) {
|
||||
HashSet<ulong> tradeIDs = new HashSet<ulong>(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
|
||||
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
private async Task<bool> ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
} else if (Bot.BotConfig.IsBotAccount) {
|
||||
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
|
||||
} else {
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (Bot.BotConfig.IsBotAccount) {
|
||||
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
|
||||
@@ -152,8 +159,19 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// At this point we're sure that STM trade is valid
|
||||
|
||||
// If we're dealing with special cards with short lifespan, accept the trade only if user doesn't have trade holds
|
||||
if (tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
|
||||
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
|
||||
if (holdDuration.GetValueOrDefault() > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now check if it's worth for us to do the trade
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
|
||||
await LimitInventoryRequestsAsync().ConfigureAwait(false);
|
||||
|
||||
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false);
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
return true; // OK, assume that this trade is valid, we can't check our EQ
|
||||
}
|
||||
@@ -183,14 +201,16 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Calculate our value of items to give
|
||||
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
|
||||
Dictionary<ulong, uint> amountMapToGive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) {
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
if (!amountMapToGive.TryGetValue(key, out amount)) {
|
||||
amountsToGive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToGive.Add(amount);
|
||||
amountMapToGive[key] = amount - 1; // We're giving one, so we have one less
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
@@ -198,20 +218,23 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Calculate our value of items to receive
|
||||
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
|
||||
Dictionary<ulong, uint> amountMapToReceive = new Dictionary<ulong, uint>(amountMap);
|
||||
foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) {
|
||||
uint amount;
|
||||
if (!amountMap.TryGetValue(key, out amount)) {
|
||||
if (!amountMapToReceive.TryGetValue(key, out amount)) {
|
||||
amountsToReceive.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
amountsToReceive.Add(amount);
|
||||
amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more
|
||||
}
|
||||
|
||||
// Sort it ascending
|
||||
amountsToReceive.Sort();
|
||||
|
||||
// Check actual difference
|
||||
// We sum only values at proper indexes of giving, because user might be overpaying
|
||||
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
|
||||
|
||||
// Trade is worth for us if the difference is greater than 0
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -34,15 +33,6 @@ namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
|
||||
internal static void Forget(this Task task) { }
|
||||
|
||||
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
|
||||
if (action != null) {
|
||||
return Task.WhenAll(sequence.Select(action));
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(action));
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
|
||||
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
|
||||
Logging.LogNullError(nameof(url) + " || " + nameof(name));
|
||||
@@ -62,13 +52,6 @@ namespace ArchiSteamFarm {
|
||||
return cookies.Count == 0 ? null : (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal static Task SleepAsync(int miliseconds) {
|
||||
if (miliseconds >= 0) {
|
||||
return Task.Delay(miliseconds);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(miliseconds));
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +80,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Most web services expect that UserAgent is set, so we declare it globally
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
|
||||
|
||||
// We should always operate in English language, declare it globally
|
||||
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
|
||||
}
|
||||
|
||||
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,7 +106,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<Uri> UrlHeadToUriRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -122,7 +125,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<byte[]> UrlGetToBytesRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -141,7 +144,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<string> UrlGetToContentRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -160,7 +163,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<HtmlDocument> UrlGetToHtmlDocumentRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<JObject> UrlGetToJObjectRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<XmlDocument> UrlGetToXMLRetry(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -217,7 +220,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal async Task<bool> UrlPostRetry(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -251,7 +254,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<string> UrlGetToContent(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -266,7 +269,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -282,7 +285,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<JObject> UrlGetToJObject(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -308,13 +311,13 @@ namespace ArchiSteamFarm {
|
||||
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<XmlDocument> UrlGetToXML(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -337,7 +340,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<bool> UrlHead(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -351,13 +354,13 @@ namespace ArchiSteamFarm {
|
||||
return await UrlRequest(request, HttpMethod.Head, null, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<Uri> UrlHeadToUri(string request, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -368,7 +371,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -382,13 +385,13 @@ namespace ArchiSteamFarm {
|
||||
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogNullError(nameof(request));
|
||||
Logging.LogNullError(nameof(request), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request) || (httpMethod == null)) {
|
||||
Logging.LogNullError(nameof(request) + " || " + nameof(httpMethod));
|
||||
Logging.LogNullError(nameof(request) + " || " + nameof(httpMethod), Identifier);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -413,7 +416,12 @@ namespace ArchiSteamFarm {
|
||||
|
||||
try {
|
||||
responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
} catch { // Request failed, we don't need to know the exact reason, swallow exception
|
||||
} catch (Exception e) {
|
||||
// This exception is really common, don't bother with it unless debug mode is enabled
|
||||
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
|
||||
Logging.LogGenericException(e, Identifier);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
"SteamOwnerID": 0,
|
||||
"MaxFarmingTime": 10,
|
||||
"IdleFarmingPeriod": 3,
|
||||
"FarmingDelay": 5,
|
||||
"LoginLimiterDelay": 7,
|
||||
"FarmingDelay": 15,
|
||||
"LoginLimiterDelay": 10,
|
||||
"InventoryLimiterDelay": 3,
|
||||
"GiftsLimiterDelay": 1,
|
||||
"ForceHttp": false,
|
||||
"HttpTimeout": 60,
|
||||
"WCFHostname": "localhost",
|
||||
@@ -23,6 +24,7 @@
|
||||
303700,
|
||||
335590,
|
||||
368020,
|
||||
425280
|
||||
425280,
|
||||
480730
|
||||
]
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
"SteamTradeMatcher": false,
|
||||
"ForwardKeysToOtherBots": false,
|
||||
"DistributeKeys": false,
|
||||
"UseAsfAsMobileAuthenticator": false,
|
||||
"ShutdownOnFarmingFinished": false,
|
||||
"SendOnFarmingFinished": false,
|
||||
"SteamTradeToken": null,
|
||||
|
||||
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing
|
||||
|
||||
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
|
||||
|
||||
## Issues
|
||||
|
||||
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is not technical support and all cases that are not suggestions or bug reports should NOT be posted there. You have ASF chat and Steam group for general discussion, questions or technical issues. Please avoid using GitHub issues, unless you indeed want to report a bug or suggest an enhancement. Even prior to doing that, please make sure that you're indeed dealing with a bug, or your suggestion makes sense, preferably by asking on chat/steam group first. Invalid issues will be closed immediately.
|
||||
|
||||
---
|
||||
|
||||
### Bugs
|
||||
|
||||
Posting a log is **mandatory**, regardless if it contains information that is relevant or not. You're allowed to make small modifications such as changing bot names to something more generic, but you should not be doing anything else. You want us to fix the bug you've encountered, then help us instead of making it harder - we're not being paid for that, and we're not forced to fix the bug you've encountered. Include as much relevant info as possible - if bug is reproducable, when it happens, if it's a result of a command - which one, does it happen always or only sometimes, with one account or all of them - everything you consider appropriate, that would help us reproduce the bug and fix it. The more information you include, the higher the chance of bug getting fixed. And this is probably what you want, right?
|
||||
|
||||
---
|
||||
|
||||
### Suggestions
|
||||
|
||||
ASF has rather strict scope - farming Steam cards from Steam games, which means that anything going greatly out of the scope will not be accepted, even if it's considered useful. A good example of that is Steam discovery queue, that provides extra cards during Steam sales - this is out of the scope of ASF as a program, ASF focuses on one task and is doing it efficiently, if you want to create your own bot that does exactly what you want - pay somebody for creating it.
|
||||
|
||||
If your suggestion doesn't go out of the scope of ASF, then explain to us in the issue why you consider it useful, why do you think that adding it to ASF is beneficial for **all users**, not yourself. Why we should spend our time coding it, convince us. If suggestion indeed makes sense, or can be considered practical, most likely we won't have anything against that, but **you** should be the one pointing out advantages, not us.
|
||||
|
||||
---
|
||||
|
||||
## Pull requests
|
||||
|
||||
In general any pull request is welcome and should be accepted, unless there is a strong reason against it. A strong reason includes e.g. a feature going potentially out of the scope of ASF. If you're improving existing codebase, rewriting code to be more efficient, clean, better commented - there is absolutely no reason to reject it. If you want to add missing feature, and you're not sure if it should be included in ASF, it won't hurt to ask before spending your own time.
|
||||
|
||||
Every pull request is carefully examined by our continuous integration system - it won't be accepted if it doesn't compile properly or causes any test to fail. We also expect that you at least barely tested the modification you're trying to add, and not blindly editing the file without even checking if it compiles. Consider the fact that you're not coding it only for yourself, but for thousands of users.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
||||
@@ -30,7 +30,10 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
|
||||
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal sealed class BotConfig : ASFConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Enabled { get; set; } = false;
|
||||
@@ -41,7 +44,8 @@ namespace ConfigGenerator {
|
||||
[JsonProperty]
|
||||
public string SteamLogin { get; set; } = null;
|
||||
|
||||
[JsonProperty, PasswordPropertyText(true)]
|
||||
[JsonProperty]
|
||||
[PasswordPropertyText(true)]
|
||||
public string SteamPassword { get; set; } = null;
|
||||
|
||||
[JsonProperty]
|
||||
@@ -83,9 +87,6 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool DistributeKeys { get; set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool UseAsfAsMobileAuthenticator { get; set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool ShutdownOnFarmingFinished { get; set; } = false;
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ConfigGenerator</RootNamespace>
|
||||
<AssemblyName>ConfigGenerator</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
||||
@@ -30,9 +30,11 @@ using System.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace ConfigGenerator {
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
|
||||
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal sealed class GlobalConfig : ASFConfig {
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
internal enum EUpdateChannel : byte {
|
||||
Unknown,
|
||||
Stable,
|
||||
@@ -40,13 +42,13 @@ namespace ConfigGenerator {
|
||||
}
|
||||
|
||||
private const byte DefaultMaxFarmingTime = 10;
|
||||
private const byte DefaultFarmingDelay = 5;
|
||||
private const byte DefaultFarmingDelay = 15;
|
||||
private const byte DefaultHttpTimeout = 60;
|
||||
private const ushort DefaultWCFPort = 1242;
|
||||
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
|
||||
|
||||
// This is hardcoded blacklist which should not be possible to change
|
||||
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
|
||||
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Debug { get; set; } = false;
|
||||
@@ -79,11 +81,14 @@ namespace ConfigGenerator {
|
||||
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte LoginLimiterDelay { get; set; } = 7;
|
||||
public byte LoginLimiterDelay { get; set; } = 10;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte InventoryLimiterDelay { get; set; } = 3;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public byte GiftsLimiterDelay { get; set; } = 1;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool ForceHttp { get; set; } = false;
|
||||
|
||||
|
||||
28
ConfigGenerator/Properties/Settings.Designer.cs
generated
28
ConfigGenerator/Properties/Settings.Designer.cs
generated
@@ -9,18 +9,18 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ConfigGenerator.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GUI</RootNamespace>
|
||||
<AssemblyName>GUI</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
|
||||
13
README.md
13
README.md
@@ -1,11 +1,14 @@
|
||||
ArchiSteamFarm
|
||||
===================
|
||||
|
||||
[](https://ci.appveyor.com/project/JustArchi/ArchiSteamFarm)
|
||||
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
|
||||
[](https://gitter.im/JustArchi/ArchiSteamFarm)
|
||||
[](https://ci.appveyor.com/project/JustArchi/ArchiSteamFarm)
|
||||
[](https://travis-ci.org/JustArchi/ArchiSteamFarm)
|
||||
[](./LICENSE-2.0.txt)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
|
||||
[](https://github.com/JustArchi/ArchiSteamFarm/releases)
|
||||
|
||||
[](https://www.paypal.me/JustArchi/1usd)
|
||||
[](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
|
||||
---
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace SteamAuth
|
||||
{
|
||||
public static class APIEndpoints
|
||||
{
|
||||
public const string STEAMAPI_BASE = "https://api.steampowered.com";
|
||||
public const string COMMUNITY_BASE = "https://steamcommunity.com";
|
||||
public const string MOBILEAUTH_BASE = STEAMAPI_BASE + "/IMobileAuthService/%s/v0001";
|
||||
public static string MOBILEAUTH_GETWGTOKEN = MOBILEAUTH_BASE.Replace("%s", "GetWGToken");
|
||||
public const string TWO_FACTOR_BASE = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001";
|
||||
public static string TWO_FACTOR_TIME_QUERY = TWO_FACTOR_BASE.Replace("%s", "QueryTime");
|
||||
}
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the linking process for a new mobile authenticator.
|
||||
/// </summary>
|
||||
public class AuthenticatorLinker
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set to register a new phone number when linking. If a phone number is not set on the account, this must be set. If a phone number is set on the account, this must be null.
|
||||
/// </summary>
|
||||
public string PhoneNumber = null;
|
||||
|
||||
/// <summary>
|
||||
/// Randomly-generated device ID. Should only be generated once per linker.
|
||||
/// </summary>
|
||||
public string DeviceID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// After the initial link step, if successful, this will be the SteamGuard data for the account. PLEASE save this somewhere after generating it; it's vital data.
|
||||
/// </summary>
|
||||
public SteamGuardAccount LinkedAccount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the authenticator has been fully finalized.
|
||||
/// </summary>
|
||||
public bool Finalized = false;
|
||||
|
||||
private SessionData _session;
|
||||
private CookieContainer _cookies;
|
||||
|
||||
public AuthenticatorLinker(SessionData session)
|
||||
{
|
||||
this._session = session;
|
||||
this.DeviceID = GenerateDeviceID();
|
||||
|
||||
this._cookies = new CookieContainer();
|
||||
session.AddCookies(_cookies);
|
||||
}
|
||||
|
||||
public LinkResult AddAuthenticator()
|
||||
{
|
||||
bool hasPhone = _hasPhoneAttached();
|
||||
if (hasPhone && PhoneNumber != null)
|
||||
return LinkResult.MustRemovePhoneNumber;
|
||||
if (!hasPhone && PhoneNumber == null)
|
||||
return LinkResult.MustProvidePhoneNumber;
|
||||
|
||||
if (!hasPhone)
|
||||
{
|
||||
if (!_addPhoneNumber())
|
||||
{
|
||||
return LinkResult.GeneralFailure;
|
||||
}
|
||||
}
|
||||
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("access_token", _session.OAuthToken);
|
||||
postData.Add("steamid", _session.SteamID.ToString());
|
||||
postData.Add("authenticator_type", "1");
|
||||
postData.Add("device_identifier", this.DeviceID);
|
||||
postData.Add("sms_phone_id", "1");
|
||||
|
||||
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", postData);
|
||||
if (response == null) return LinkResult.GeneralFailure;
|
||||
|
||||
var addAuthenticatorResponse = JsonConvert.DeserializeObject<AddAuthenticatorResponse>(response);
|
||||
if (addAuthenticatorResponse == null || addAuthenticatorResponse.Response == null)
|
||||
{
|
||||
return LinkResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if (addAuthenticatorResponse.Response.Status == 29)
|
||||
{
|
||||
return LinkResult.AuthenticatorPresent;
|
||||
}
|
||||
|
||||
if (addAuthenticatorResponse.Response.Status != 1)
|
||||
{
|
||||
return LinkResult.GeneralFailure;
|
||||
}
|
||||
|
||||
this.LinkedAccount = addAuthenticatorResponse.Response;
|
||||
LinkedAccount.Session = this._session;
|
||||
LinkedAccount.DeviceID = this.DeviceID;
|
||||
|
||||
return LinkResult.AwaitingFinalization;
|
||||
}
|
||||
|
||||
public FinalizeResult FinalizeAddAuthenticator(string smsCode)
|
||||
{
|
||||
//The act of checking the SMS code is necessary for Steam to finalize adding the phone number to the account.
|
||||
//Of course, we only want to check it if we're adding a phone number in the first place...
|
||||
|
||||
if (!String.IsNullOrEmpty(this.PhoneNumber) && !this._checkSMSCode(smsCode))
|
||||
{
|
||||
return FinalizeResult.BadSMSCode;
|
||||
}
|
||||
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("steamid", _session.SteamID.ToString());
|
||||
postData.Add("access_token", _session.OAuthToken);
|
||||
postData.Add("activation_code", smsCode);
|
||||
int tries = 0;
|
||||
while (tries <= 30)
|
||||
{
|
||||
postData.Set("authenticator_code", LinkedAccount.GenerateSteamGuardCode());
|
||||
postData.Set("authenticator_time", TimeAligner.GetSteamTime().ToString());
|
||||
|
||||
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", postData);
|
||||
if (response == null) return FinalizeResult.GeneralFailure;
|
||||
|
||||
var finalizeResponse = JsonConvert.DeserializeObject<FinalizeAuthenticatorResponse>(response);
|
||||
|
||||
if (finalizeResponse == null || finalizeResponse.Response == null)
|
||||
{
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if (finalizeResponse.Response.Status == 89)
|
||||
{
|
||||
return FinalizeResult.BadSMSCode;
|
||||
}
|
||||
|
||||
if (finalizeResponse.Response.Status == 88)
|
||||
{
|
||||
if (tries >= 30)
|
||||
{
|
||||
return FinalizeResult.UnableToGenerateCorrectCodes;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalizeResponse.Response.Success)
|
||||
{
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if (finalizeResponse.Response.WantMore)
|
||||
{
|
||||
tries++;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.LinkedAccount.FullyEnrolled = true;
|
||||
return FinalizeResult.Success;
|
||||
}
|
||||
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
private bool _checkSMSCode(string smsCode)
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("op", "check_sms_code");
|
||||
postData.Add("arg", smsCode);
|
||||
postData.Add("sessionid", _session.SessionID);
|
||||
|
||||
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
|
||||
if (response == null) return false;
|
||||
|
||||
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
|
||||
return addPhoneNumberResponse.Success;
|
||||
}
|
||||
|
||||
private bool _addPhoneNumber()
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("op", "add_phone_number");
|
||||
postData.Add("arg", PhoneNumber);
|
||||
postData.Add("sessionid", _session.SessionID);
|
||||
|
||||
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
|
||||
if (response == null) return false;
|
||||
|
||||
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
|
||||
return addPhoneNumberResponse.Success;
|
||||
}
|
||||
|
||||
private bool _hasPhoneAttached()
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("op", "has_phone");
|
||||
postData.Add("arg", "null");
|
||||
postData.Add("sessionid", _session.SessionID);
|
||||
|
||||
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
|
||||
if (response == null) return false;
|
||||
|
||||
var hasPhoneResponse = JsonConvert.DeserializeObject<HasPhoneResponse>(response);
|
||||
return hasPhoneResponse.HasPhone;
|
||||
}
|
||||
|
||||
public enum LinkResult
|
||||
{
|
||||
MustProvidePhoneNumber, //No phone number on the account
|
||||
MustRemovePhoneNumber, //A phone number is already on the account
|
||||
AwaitingFinalization, //Must provide an SMS code
|
||||
GeneralFailure, //General failure (really now!)
|
||||
AuthenticatorPresent
|
||||
}
|
||||
|
||||
public enum FinalizeResult
|
||||
{
|
||||
BadSMSCode,
|
||||
UnableToGenerateCorrectCodes,
|
||||
Success,
|
||||
GeneralFailure
|
||||
}
|
||||
|
||||
private class AddAuthenticatorResponse
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
public SteamGuardAccount Response { get; set; }
|
||||
}
|
||||
|
||||
private class FinalizeAuthenticatorResponse
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
public FinalizeAuthenticatorInternalResponse Response { get; set; }
|
||||
|
||||
internal class FinalizeAuthenticatorInternalResponse
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[JsonProperty("server_time")]
|
||||
public long ServerTime { get; set; }
|
||||
|
||||
[JsonProperty("want_more")]
|
||||
public bool WantMore { get; set; }
|
||||
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class HasPhoneResponse
|
||||
{
|
||||
[JsonProperty("has_phone")]
|
||||
public bool HasPhone { get; set; }
|
||||
}
|
||||
|
||||
private class AddPhoneResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
public static string GenerateDeviceID()
|
||||
{
|
||||
using (var sha1 = new SHA1Managed())
|
||||
{
|
||||
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
|
||||
byte[] randomBytes = new byte[8];
|
||||
secureRandom.GetBytes(randomBytes);
|
||||
|
||||
byte[] hashedBytes = sha1.ComputeHash(randomBytes);
|
||||
string random32 = BitConverter.ToString(hashedBytes).Replace("-", "").Substring(0, 32).ToLower();
|
||||
|
||||
return "android:" + SplitOnRatios(random32, new[] { 8, 4, 4, 4, 12 }, "-");
|
||||
}
|
||||
}
|
||||
|
||||
private static string SplitOnRatios(string str, int[] ratios, string intermediate)
|
||||
{
|
||||
string result = "";
|
||||
|
||||
int pos = 0;
|
||||
for (int index = 0; index < ratios.Length; index++)
|
||||
{
|
||||
result += str.Substring(pos, ratios[index]);
|
||||
pos = ratios[index];
|
||||
|
||||
if (index < ratios.Length - 1)
|
||||
result += intermediate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
public class Confirmation
|
||||
{
|
||||
public string ID;
|
||||
public string Key;
|
||||
public string Description;
|
||||
|
||||
public ConfirmationType ConfType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrEmpty(Description)) return ConfirmationType.Unknown;
|
||||
if (Description.StartsWith("Confirm ")) return ConfirmationType.GenericConfirmation;
|
||||
if (Description.StartsWith("Trade with ")) return ConfirmationType.Trade;
|
||||
if (Description.StartsWith("Sell -")) return ConfirmationType.MarketSellTransaction;
|
||||
|
||||
return ConfirmationType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConfirmationType
|
||||
{
|
||||
GenericConfirmation,
|
||||
Trade,
|
||||
MarketSellTransaction,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Joshua Coffey
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,36 +0,0 @@
|
||||
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("SteamAuth")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("SteamAuth")]
|
||||
[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("5ad0934e-f6c4-4ae5-83af-c788313b2a87")]
|
||||
|
||||
// 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")]
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.Net;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
public class SessionData
|
||||
{
|
||||
public string SessionID { get; set; }
|
||||
|
||||
public string SteamLogin { get; set; }
|
||||
|
||||
public string SteamLoginSecure { get; set; }
|
||||
|
||||
public string WebCookie { get; set; }
|
||||
|
||||
public string OAuthToken { get; set; }
|
||||
|
||||
public ulong SteamID { get; set; }
|
||||
|
||||
public void AddCookies(CookieContainer cookies)
|
||||
{
|
||||
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
|
||||
|
||||
cookies.Add(new Cookie("steamid", SteamID.ToString(), "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("steamLogin", SteamLogin, "/", ".steamcommunity.com")
|
||||
{
|
||||
HttpOnly = true
|
||||
});
|
||||
|
||||
cookies.Add(new Cookie("steamLoginSecure", SteamLoginSecure, "/", ".steamcommunity.com")
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = true
|
||||
});
|
||||
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("dob", "", "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("sessionid", this.SessionID, "/", ".steamcommunity.com"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SteamAuth</RootNamespace>
|
||||
<AssemblyName>SteamAuth</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1-beta1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="APIEndpoints.cs" />
|
||||
<Compile Include="AuthenticatorLinker.cs" />
|
||||
<Compile Include="Confirmation.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SessionData.cs" />
|
||||
<Compile Include="SteamGuardAccount.cs" />
|
||||
<Compile Include="SteamWeb.cs" />
|
||||
<Compile Include="TimeAligner.cs" />
|
||||
<Compile Include="UserLogin.cs" />
|
||||
<Compile Include="Util.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
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}") = "SteamAuth", "SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBed", "..\TestBed\TestBed.csproj", "{8A732227-C090-4011-9F0A-51180CFE6271}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,456 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
public class SteamGuardAccount
|
||||
{
|
||||
[JsonProperty("shared_secret")]
|
||||
public string SharedSecret { get; set; }
|
||||
|
||||
[JsonProperty("serial_number")]
|
||||
public string SerialNumber { get; set; }
|
||||
|
||||
[JsonProperty("revocation_code")]
|
||||
public string RevocationCode { get; set; }
|
||||
|
||||
[JsonProperty("uri")]
|
||||
public string URI { get; set; }
|
||||
|
||||
[JsonProperty("server_time")]
|
||||
public long ServerTime { get; set; }
|
||||
|
||||
[JsonProperty("account_name")]
|
||||
public string AccountName { get; set; }
|
||||
|
||||
[JsonProperty("token_gid")]
|
||||
public string TokenGID { get; set; }
|
||||
|
||||
[JsonProperty("identity_secret")]
|
||||
public string IdentitySecret { get; set; }
|
||||
|
||||
[JsonProperty("secret_1")]
|
||||
public string Secret1 { get; set; }
|
||||
|
||||
[JsonProperty("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[JsonProperty("device_id")]
|
||||
public string DeviceID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the authenticator has actually been applied to the account.
|
||||
/// </summary>
|
||||
[JsonProperty("fully_enrolled")]
|
||||
public bool FullyEnrolled { get; set; }
|
||||
|
||||
public SessionData Session { get; set; }
|
||||
|
||||
private static byte[] steamGuardCodeTranslations = new byte[] { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
|
||||
|
||||
public bool DeactivateAuthenticator(int scheme = 2)
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("steamid", this.Session.SteamID.ToString());
|
||||
postData.Add("steamguard_scheme", scheme.ToString());
|
||||
postData.Add("revocation_code", this.RevocationCode);
|
||||
postData.Add("access_token", this.Session.OAuthToken);
|
||||
|
||||
try
|
||||
{
|
||||
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/RemoveAuthenticator/v0001", "POST", postData);
|
||||
var removeResponse = JsonConvert.DeserializeObject<RemoveAuthenticatorResponse>(response);
|
||||
|
||||
if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false;
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateSteamGuardCode()
|
||||
{
|
||||
return GenerateSteamGuardCodeForTime(TimeAligner.GetSteamTime());
|
||||
}
|
||||
|
||||
public string GenerateSteamGuardCodeForTime(long time)
|
||||
{
|
||||
if (this.SharedSecret == null || this.SharedSecret.Length == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] sharedSecretArray = Convert.FromBase64String(this.SharedSecret);
|
||||
byte[] timeArray = new byte[8];
|
||||
|
||||
time /= 30L;
|
||||
|
||||
for (int i = 8; i > 0; i--)
|
||||
{
|
||||
timeArray[i - 1] = (byte)time;
|
||||
time >>= 8;
|
||||
}
|
||||
|
||||
HMACSHA1 hmacGenerator = new HMACSHA1();
|
||||
hmacGenerator.Key = sharedSecretArray;
|
||||
byte[] hashedData = hmacGenerator.ComputeHash(timeArray);
|
||||
byte[] codeArray = new byte[5];
|
||||
try
|
||||
{
|
||||
byte b = (byte)(hashedData[19] & 0xF);
|
||||
int codePoint = (hashedData[b] & 0x7F) << 24 | (hashedData[b + 1] & 0xFF) << 16 | (hashedData[b + 2] & 0xFF) << 8 | (hashedData[b + 3] & 0xFF);
|
||||
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
codeArray[i] = steamGuardCodeTranslations[codePoint % steamGuardCodeTranslations.Length];
|
||||
codePoint /= steamGuardCodeTranslations.Length;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null; //Change later, catch-alls are bad!
|
||||
}
|
||||
return Encoding.UTF8.GetString(codeArray);
|
||||
}
|
||||
|
||||
public Confirmation[] FetchConfirmations()
|
||||
{
|
||||
string url = this.GenerateConfirmationURL();
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
this.Session.AddCookies(cookies);
|
||||
|
||||
string response = SteamWeb.Request(url, "GET", null, cookies);
|
||||
|
||||
/*So you're going to see this abomination and you're going to be upset.
|
||||
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
|
||||
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
|
||||
I'm sorry. */
|
||||
|
||||
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
|
||||
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
|
||||
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
|
||||
|
||||
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
|
||||
{
|
||||
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
|
||||
{
|
||||
throw new WGTokenInvalidException();
|
||||
}
|
||||
|
||||
return new Confirmation[0];
|
||||
}
|
||||
|
||||
MatchCollection confIDs = confIDRegex.Matches(response);
|
||||
MatchCollection confKeys = confKeyRegex.Matches(response);
|
||||
MatchCollection confDescs = confDescRegex.Matches(response);
|
||||
|
||||
List<Confirmation> ret = new List<Confirmation>();
|
||||
for (int i = 0; i < confIDs.Count; i++)
|
||||
{
|
||||
string confID = confIDs[i].Groups[1].Value;
|
||||
string confKey = confKeys[i].Groups[1].Value;
|
||||
string confDesc = confDescs[i].Groups[1].Value;
|
||||
Confirmation conf = new Confirmation()
|
||||
{
|
||||
Description = confDesc,
|
||||
ID = confID,
|
||||
Key = confKey
|
||||
};
|
||||
ret.Add(conf);
|
||||
}
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
public async Task<Confirmation[]> FetchConfirmationsAsync()
|
||||
{
|
||||
string url = this.GenerateConfirmationURL();
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
this.Session.AddCookies(cookies);
|
||||
|
||||
string response = await SteamWeb.RequestAsync(url, "GET", null, cookies);
|
||||
|
||||
/*So you're going to see this abomination and you're going to be upset.
|
||||
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
|
||||
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
|
||||
I'm sorry. */
|
||||
|
||||
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
|
||||
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
|
||||
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
|
||||
|
||||
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
|
||||
{
|
||||
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
|
||||
{
|
||||
throw new WGTokenInvalidException();
|
||||
}
|
||||
|
||||
return new Confirmation[0];
|
||||
}
|
||||
|
||||
MatchCollection confIDs = confIDRegex.Matches(response);
|
||||
MatchCollection confKeys = confKeyRegex.Matches(response);
|
||||
MatchCollection confDescs = confDescRegex.Matches(response);
|
||||
|
||||
List<Confirmation> ret = new List<Confirmation>();
|
||||
for (int i = 0; i < confIDs.Count; i++)
|
||||
{
|
||||
string confID = confIDs[i].Groups[1].Value;
|
||||
string confKey = confKeys[i].Groups[1].Value;
|
||||
string confDesc = confDescs[i].Groups[1].Value;
|
||||
Confirmation conf = new Confirmation()
|
||||
{
|
||||
Description = confDesc,
|
||||
ID = confID,
|
||||
Key = confKey
|
||||
};
|
||||
ret.Add(conf);
|
||||
}
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
public long GetConfirmationTradeOfferID(Confirmation conf)
|
||||
{
|
||||
var confDetails = _getConfirmationDetails(conf);
|
||||
if (confDetails == null || !confDetails.Success) return -1;
|
||||
|
||||
Regex tradeOfferIDRegex = new Regex("<div class=\"tradeoffer\" id=\"tradeofferid_(\\d+)\" >");
|
||||
if(!tradeOfferIDRegex.IsMatch(confDetails.HTML)) return -1;
|
||||
return long.Parse(tradeOfferIDRegex.Match(confDetails.HTML).Groups[1].Value);
|
||||
}
|
||||
|
||||
public bool AcceptConfirmation(Confirmation conf)
|
||||
{
|
||||
return _sendConfirmationAjax(conf, "allow");
|
||||
}
|
||||
|
||||
public bool DenyConfirmation(Confirmation conf)
|
||||
{
|
||||
return _sendConfirmationAjax(conf, "cancel");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool RefreshSession()
|
||||
{
|
||||
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
|
||||
NameValueCollection postData = new NameValueCollection();
|
||||
postData.Add("access_token", this.Session.OAuthToken);
|
||||
|
||||
string response = SteamWeb.Request(url, "POST", postData);
|
||||
if (response == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
|
||||
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
|
||||
return false;
|
||||
|
||||
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
|
||||
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
|
||||
|
||||
this.Session.SteamLogin = token;
|
||||
this.Session.SteamLoginSecure = tokenSecure;
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> RefreshSessionAsync()
|
||||
{
|
||||
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
|
||||
NameValueCollection postData = new NameValueCollection();
|
||||
postData.Add("access_token", this.Session.OAuthToken);
|
||||
|
||||
string response = await SteamWeb.RequestAsync(url, "POST", postData);
|
||||
if (response == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
|
||||
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
|
||||
return false;
|
||||
|
||||
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
|
||||
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
|
||||
|
||||
this.Session.SteamLogin = token;
|
||||
this.Session.SteamLoginSecure = tokenSecure;
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf)
|
||||
{
|
||||
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ID + "?";
|
||||
string queryString = GenerateConfirmationQueryParams("details");
|
||||
url += queryString;
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
this.Session.AddCookies(cookies);
|
||||
string referer = GenerateConfirmationURL();
|
||||
|
||||
string response = SteamWeb.Request(url, "GET", null, cookies, null);
|
||||
if (String.IsNullOrEmpty(response)) return null;
|
||||
|
||||
var confResponse = JsonConvert.DeserializeObject<ConfirmationDetailsResponse>(response);
|
||||
if (confResponse == null) return null;
|
||||
return confResponse;
|
||||
}
|
||||
|
||||
private bool _sendConfirmationAjax(Confirmation conf, string op)
|
||||
{
|
||||
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
|
||||
string queryString = "?op=" + op + "&";
|
||||
queryString += GenerateConfirmationQueryParams(op);
|
||||
queryString += "&cid=" + conf.ID + "&ck=" + conf.Key;
|
||||
url += queryString;
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
this.Session.AddCookies(cookies);
|
||||
string referer = GenerateConfirmationURL();
|
||||
|
||||
string response = SteamWeb.Request(url, "GET", null, cookies, null);
|
||||
if (response == null) return false;
|
||||
|
||||
SendConfirmationResponse confResponse = JsonConvert.DeserializeObject<SendConfirmationResponse>(response);
|
||||
return confResponse.Success;
|
||||
}
|
||||
|
||||
public string GenerateConfirmationURL(string tag = "conf")
|
||||
{
|
||||
string endpoint = APIEndpoints.COMMUNITY_BASE + "/mobileconf/conf?";
|
||||
string queryString = GenerateConfirmationQueryParams(tag);
|
||||
return endpoint + queryString;
|
||||
}
|
||||
|
||||
public string GenerateConfirmationQueryParams(string tag)
|
||||
{
|
||||
if (String.IsNullOrEmpty(DeviceID))
|
||||
throw new ArgumentException("Device ID is not present");
|
||||
|
||||
long time = TimeAligner.GetSteamTime();
|
||||
return "p=" + this.DeviceID + "&a=" + this.Session.SteamID.ToString() + "&k=" + _generateConfirmationHashForTime(time, tag) + "&t=" + time + "&m=android&tag=" + tag;
|
||||
}
|
||||
|
||||
private string _generateConfirmationHashForTime(long time, string tag)
|
||||
{
|
||||
byte[] decode = Convert.FromBase64String(this.IdentitySecret);
|
||||
int n2 = 8;
|
||||
if (tag != null)
|
||||
{
|
||||
if (tag.Length > 32)
|
||||
{
|
||||
n2 = 8 + 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
n2 = 8 + tag.Length;
|
||||
}
|
||||
}
|
||||
byte[] array = new byte[n2];
|
||||
int n3 = 8;
|
||||
while (true)
|
||||
{
|
||||
int n4 = n3 - 1;
|
||||
if (n3 <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
array[n4] = (byte)time;
|
||||
time >>= 8;
|
||||
n3 = n4;
|
||||
}
|
||||
if (tag != null)
|
||||
{
|
||||
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, array, 8, n2 - 8);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HMACSHA1 hmacGenerator = new HMACSHA1();
|
||||
hmacGenerator.Key = decode;
|
||||
byte[] hashedData = hmacGenerator.ComputeHash(array);
|
||||
string encodedData = Convert.ToBase64String(hashedData, Base64FormattingOptions.None);
|
||||
string hash = WebUtility.UrlEncode(encodedData);
|
||||
return hash;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null; //Fix soon: catch-all is BAD!
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Determine how to detect an invalid session.
|
||||
public class WGTokenInvalidException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
private class RefreshSessionDataResponse
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
public RefreshSessionDataInternalResponse Response { get; set; }
|
||||
internal class RefreshSessionDataInternalResponse
|
||||
{
|
||||
[JsonProperty("token")]
|
||||
public string Token { get; set; }
|
||||
|
||||
[JsonProperty("token_secure")]
|
||||
public string TokenSecure { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveAuthenticatorResponse
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
public RemoveAuthenticatorInternalResponse Response { get; set; }
|
||||
|
||||
internal class RemoveAuthenticatorInternalResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class SendConfirmationResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
private class ConfirmationDetailsResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("html")]
|
||||
public string HTML { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
public class SteamWeb
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform a mobile login request
|
||||
/// </summary>
|
||||
/// <param name="url">API url</param>
|
||||
/// <param name="method">GET or POST</param>
|
||||
/// <param name="data">Name-data pairs</param>
|
||||
/// <param name="cookies">current cookie container</param>
|
||||
/// <returns>response body</returns>
|
||||
public static string MobileLoginRequest(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null)
|
||||
{
|
||||
return Request(url, method, data, cookies, headers, APIEndpoints.COMMUNITY_BASE + "/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client");
|
||||
}
|
||||
|
||||
public static string Request(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
|
||||
{
|
||||
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
|
||||
if (method == "GET")
|
||||
{
|
||||
url += (url.Contains("?") ? "&" : "?") + query;
|
||||
}
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.Method = method;
|
||||
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
|
||||
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
|
||||
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
||||
request.Referer = referer;
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
request.Headers.Add(headers);
|
||||
}
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
request.CookieContainer = cookies;
|
||||
}
|
||||
|
||||
if (method == "POST")
|
||||
{
|
||||
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||
request.ContentLength = query.Length;
|
||||
|
||||
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
|
||||
requestStream.Write(query);
|
||||
requestStream.Close();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||||
{
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
|
||||
{
|
||||
string responseData = responseStream.ReadToEnd();
|
||||
return responseData;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> RequestAsync(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
|
||||
{
|
||||
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
|
||||
if (method == "GET")
|
||||
{
|
||||
url += (url.Contains("?") ? "&" : "?") + query;
|
||||
}
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.Method = method;
|
||||
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
|
||||
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
|
||||
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
||||
request.Referer = referer;
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
request.Headers.Add(headers);
|
||||
}
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
request.CookieContainer = cookies;
|
||||
}
|
||||
|
||||
if (method == "POST")
|
||||
{
|
||||
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||
request.ContentLength = query.Length;
|
||||
|
||||
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
|
||||
requestStream.Write(query);
|
||||
requestStream.Close();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync();
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
|
||||
{
|
||||
string responseData = responseStream.ReadToEnd();
|
||||
return responseData;
|
||||
}
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to help align system time with the Steam server time. Not super advanced; probably not taking some things into account that it should.
|
||||
/// Necessary to generate up-to-date codes. In general, this will have an error of less than a second, assuming Steam is operational.
|
||||
/// </summary>
|
||||
public class TimeAligner
|
||||
{
|
||||
private static bool _aligned = false;
|
||||
private static int _timeDifference = 0;
|
||||
|
||||
public static long GetSteamTime()
|
||||
{
|
||||
if (!TimeAligner._aligned)
|
||||
{
|
||||
TimeAligner.AlignTime();
|
||||
}
|
||||
return Util.GetSystemUnixTime() + _timeDifference;
|
||||
}
|
||||
|
||||
public static async Task<long> GetSteamTimeAsync()
|
||||
{
|
||||
if (!TimeAligner._aligned)
|
||||
{
|
||||
await TimeAligner.AlignTimeAsync();
|
||||
}
|
||||
return Util.GetSystemUnixTime() + _timeDifference;
|
||||
}
|
||||
|
||||
public static void AlignTime()
|
||||
{
|
||||
long currentTime = Util.GetSystemUnixTime();
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
string response = client.UploadString(APIEndpoints.TWO_FACTOR_TIME_QUERY, "steamid=0");
|
||||
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
|
||||
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
|
||||
TimeAligner._aligned = true;
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task AlignTimeAsync()
|
||||
{
|
||||
long currentTime = Util.GetSystemUnixTime();
|
||||
WebClient client = new WebClient();
|
||||
try
|
||||
{
|
||||
string response = await client.UploadStringTaskAsync(new Uri(APIEndpoints.TWO_FACTOR_TIME_QUERY), "steamid=0");
|
||||
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
|
||||
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
|
||||
TimeAligner._aligned = true;
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TimeQuery
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
internal TimeQueryResponse Response { get; set; }
|
||||
|
||||
internal class TimeQueryResponse
|
||||
{
|
||||
[JsonProperty("server_time")]
|
||||
public long ServerTime { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles logging the user into the mobile Steam website. Necessary to generate OAuth token and session cookies.
|
||||
/// </summary>
|
||||
public class UserLogin
|
||||
{
|
||||
public string Username;
|
||||
public string Password;
|
||||
public ulong SteamID;
|
||||
|
||||
public bool RequiresCaptcha;
|
||||
public string CaptchaGID = null;
|
||||
public string CaptchaText = null;
|
||||
|
||||
public bool RequiresEmail;
|
||||
public string EmailDomain = null;
|
||||
public string EmailCode = null;
|
||||
|
||||
public bool Requires2FA;
|
||||
public string TwoFactorCode = null;
|
||||
|
||||
public SessionData Session = null;
|
||||
public bool LoggedIn = false;
|
||||
|
||||
private CookieContainer _cookies = new CookieContainer();
|
||||
|
||||
public UserLogin(string username, string password)
|
||||
{
|
||||
this.Username = username;
|
||||
this.Password = password;
|
||||
}
|
||||
|
||||
public LoginResult DoLogin()
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
var cookies = _cookies;
|
||||
string response = null;
|
||||
|
||||
if (cookies.Count == 0)
|
||||
{
|
||||
//Generate a SessionID
|
||||
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
|
||||
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
|
||||
|
||||
NameValueCollection headers = new NameValueCollection();
|
||||
headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");
|
||||
|
||||
SteamWeb.MobileLoginRequest("https://steamcommunity.com/login?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, cookies, headers);
|
||||
}
|
||||
|
||||
postData.Add("username", this.Username);
|
||||
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/getrsakey", "POST", postData, cookies);
|
||||
if (response == null || response.Contains("<BODY>\nAn error occurred while processing your request.")) return LoginResult.GeneralFailure;
|
||||
|
||||
var rsaResponse = JsonConvert.DeserializeObject<RSAResponse>(response);
|
||||
|
||||
if (!rsaResponse.Success)
|
||||
{
|
||||
return LoginResult.BadRSA;
|
||||
}
|
||||
|
||||
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
|
||||
byte[] encryptedPasswordBytes;
|
||||
using (var rsaEncryptor = new RSACryptoServiceProvider())
|
||||
{
|
||||
var passwordBytes = Encoding.ASCII.GetBytes(this.Password);
|
||||
var rsaParameters = rsaEncryptor.ExportParameters(false);
|
||||
rsaParameters.Exponent = Util.HexStringToByteArray(rsaResponse.Exponent);
|
||||
rsaParameters.Modulus = Util.HexStringToByteArray(rsaResponse.Modulus);
|
||||
rsaEncryptor.ImportParameters(rsaParameters);
|
||||
encryptedPasswordBytes = rsaEncryptor.Encrypt(passwordBytes, false);
|
||||
}
|
||||
|
||||
string encryptedPassword = Convert.ToBase64String(encryptedPasswordBytes);
|
||||
|
||||
postData.Clear();
|
||||
postData.Add("username", this.Username);
|
||||
postData.Add("password", encryptedPassword);
|
||||
|
||||
postData.Add("twofactorcode", this.TwoFactorCode ?? "");
|
||||
|
||||
postData.Add("captchagid", this.RequiresCaptcha ? this.CaptchaGID : "-1");
|
||||
postData.Add("captcha_text", this.RequiresCaptcha ? this.CaptchaText : "");
|
||||
|
||||
postData.Add("emailsteamid", (this.Requires2FA || this.RequiresEmail) ? this.SteamID.ToString() : "");
|
||||
postData.Add("emailauth", this.RequiresEmail ? this.EmailCode : "");
|
||||
|
||||
postData.Add("rsatimestamp", rsaResponse.Timestamp);
|
||||
postData.Add("remember_login", "false");
|
||||
postData.Add("oauth_client_id", "DE45CD61");
|
||||
postData.Add("oauth_scope", "read_profile write_profile read_client write_client");
|
||||
postData.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
|
||||
postData.Add("donotcache", Util.GetSystemUnixTime().ToString());
|
||||
|
||||
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/dologin", "POST", postData, cookies);
|
||||
if (response == null) return LoginResult.GeneralFailure;
|
||||
|
||||
var loginResponse = JsonConvert.DeserializeObject<LoginResponse>(response);
|
||||
|
||||
if (loginResponse.Message != null && loginResponse.Message.Contains("Incorrect login"))
|
||||
{
|
||||
return LoginResult.BadCredentials;
|
||||
}
|
||||
|
||||
if (loginResponse.CaptchaNeeded)
|
||||
{
|
||||
this.RequiresCaptcha = true;
|
||||
this.CaptchaGID = loginResponse.CaptchaGID;
|
||||
return LoginResult.NeedCaptcha;
|
||||
}
|
||||
|
||||
if (loginResponse.EmailAuthNeeded)
|
||||
{
|
||||
this.RequiresEmail = true;
|
||||
this.SteamID = loginResponse.EmailSteamID;
|
||||
return LoginResult.NeedEmail;
|
||||
}
|
||||
|
||||
if (loginResponse.TwoFactorNeeded && !loginResponse.Success)
|
||||
{
|
||||
this.Requires2FA = true;
|
||||
return LoginResult.Need2FA;
|
||||
}
|
||||
|
||||
if (loginResponse.Message != null && loginResponse.Message.Contains("too many login failures"))
|
||||
{
|
||||
return LoginResult.TooManyFailedLogins;
|
||||
}
|
||||
|
||||
if (loginResponse.OAuthData == null || loginResponse.OAuthData.OAuthToken == null || loginResponse.OAuthData.OAuthToken.Length == 0)
|
||||
{
|
||||
return LoginResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if (!loginResponse.LoginComplete)
|
||||
{
|
||||
return LoginResult.BadCredentials;
|
||||
}
|
||||
else
|
||||
{
|
||||
var readableCookies = cookies.GetCookies(new Uri("https://steamcommunity.com"));
|
||||
var oAuthData = loginResponse.OAuthData;
|
||||
|
||||
SessionData session = new SessionData();
|
||||
session.OAuthToken = oAuthData.OAuthToken;
|
||||
session.SteamID = oAuthData.SteamID;
|
||||
session.SteamLogin = session.SteamID + "%7C%7C" + oAuthData.SteamLogin;
|
||||
session.SteamLoginSecure = session.SteamID + "%7C%7C" + oAuthData.SteamLoginSecure;
|
||||
session.WebCookie = oAuthData.Webcookie;
|
||||
session.SessionID = readableCookies["sessionid"].Value;
|
||||
this.Session = session;
|
||||
this.LoggedIn = true;
|
||||
return LoginResult.LoginOkay;
|
||||
}
|
||||
}
|
||||
|
||||
private class LoginResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("login_complete")]
|
||||
public bool LoginComplete { get; set; }
|
||||
|
||||
[JsonProperty("oauth")]
|
||||
public string OAuthDataString { get; set; }
|
||||
|
||||
public OAuth OAuthData
|
||||
{
|
||||
get
|
||||
{
|
||||
return OAuthDataString != null ? JsonConvert.DeserializeObject<OAuth>(OAuthDataString) : null;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("captcha_needed")]
|
||||
public bool CaptchaNeeded { get; set; }
|
||||
|
||||
[JsonProperty("captcha_gid")]
|
||||
public string CaptchaGID { get; set; }
|
||||
|
||||
[JsonProperty("emailsteamid")]
|
||||
public ulong EmailSteamID { get; set; }
|
||||
|
||||
[JsonProperty("emailauth_needed")]
|
||||
public bool EmailAuthNeeded { get; set; }
|
||||
|
||||
[JsonProperty("requires_twofactor")]
|
||||
public bool TwoFactorNeeded { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
internal class OAuth
|
||||
{
|
||||
[JsonProperty("steamid")]
|
||||
public ulong SteamID { get; set; }
|
||||
|
||||
[JsonProperty("oauth_token")]
|
||||
public string OAuthToken { get; set; }
|
||||
|
||||
[JsonProperty("wgtoken")]
|
||||
public string SteamLogin { get; set; }
|
||||
|
||||
[JsonProperty("wgtoken_secure")]
|
||||
public string SteamLoginSecure { get; set; }
|
||||
|
||||
[JsonProperty("webcookie")]
|
||||
public string Webcookie { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
private class RSAResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("publickey_exp")]
|
||||
public string Exponent { get; set; }
|
||||
|
||||
[JsonProperty("publickey_mod")]
|
||||
public string Modulus { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")]
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
[JsonProperty("steamid")]
|
||||
public ulong SteamID { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum LoginResult
|
||||
{
|
||||
LoginOkay,
|
||||
GeneralFailure,
|
||||
BadRSA,
|
||||
BadCredentials,
|
||||
NeedCaptcha,
|
||||
Need2FA,
|
||||
NeedEmail,
|
||||
TooManyFailedLogins,
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace SteamAuth
|
||||
{
|
||||
public class Util
|
||||
{
|
||||
public static long GetSystemUnixTime()
|
||||
{
|
||||
return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||
}
|
||||
|
||||
public static byte[] HexStringToByteArray(string hex)
|
||||
{
|
||||
int hexLen = hex.Length;
|
||||
byte[] ret = new byte[hexLen / 2];
|
||||
for (int i = 0; i < hexLen; i += 2)
|
||||
{
|
||||
ret[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
|
||||
</packages>
|
||||
@@ -6,4 +6,11 @@ clone_depth: 10
|
||||
build:
|
||||
project: ArchiSteamFarm.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
verbosity: minimal
|
||||
notifications:
|
||||
- provider: Webhook
|
||||
url: https://webhooks.gitter.im/e/6cc89e76555ee263cc11
|
||||
method: POST
|
||||
on_build_success: true
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tools/NetHook2/SteamKit2.dll
Normal file
BIN
tools/NetHook2/SteamKit2.dll
Normal file
Binary file not shown.
BIN
tools/NetHook2/protobuf-net.dll
Normal file
BIN
tools/NetHook2/protobuf-net.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user