Compare commits

...

79 Commits

Author SHA1 Message Date
JustArchi
4e6723a7e2 Partially revert 9cc7e30
Indeed LibZ seems to not being functional on Mono, so we keep using ILRepack for Mono, and LibZ for Windows
Final binary is compiled on Windows anyway, and it seems to work properly on both Windows and Mono, so this is purely for making people using self-compiled binaries happy
2016-07-02 23:40:57 +02:00
JustArchi
9cc7e30c98 Switch from ILRepack to LibZ
We tried ILMerge, we tried ILRepack, so now maybe LibZ will do it's thing?
The objective is solving false-positives of AVs, it might get reverted if there is no improvement
2016-07-02 23:31:37 +02:00
JustArchi
cbf212aff5 Don't launch all parallel tasks immediately
It seems that the runtime is actually smarter than I thought, and can optimize tasks creation for maximum performance better than we'd do
2016-07-02 22:05:29 +02:00
JustArchi
ab4630c191 Add CodeStyle settings
In case somebody would like to stick with mine
2016-07-02 05:50:22 +02:00
JustArchi
e1e68d2d6a Enable greed mode
Barely anybody donates 😢
2016-07-02 05:46:23 +02:00
JustArchi
b594af5c44 Bump 2016-07-02 05:30:05 +02:00
JustArchi
d243bf260f ILRepack bump 2016-07-02 02:22:52 +02:00
JustArchi
af956f22e9 Allow empty descriptions 2016-07-01 16:34:32 +02:00
JustArchi
aac72d4c5b Avoid code duplication 2016-07-01 16:33:27 +02:00
JustArchi
dc98d794b8 Misc 2016-06-30 18:20:26 +02:00
JustArchi
831856cf15 Misc 2016-06-30 18:13:22 +02:00
Łukasz Domeradzki
686c0aaf8b Update CONTRIBUTING.md 2016-06-30 11:48:04 +02:00
JustArchi
9eadf805b5 Bump 2016-06-30 09:46:32 +02:00
JustArchi
3b8315073b Fix regressions 2016-06-30 09:33:22 +02:00
JustArchi
bd028ba459 Packages update 2016-06-29 21:33:38 +02:00
JustArchi
49344751af Add missing copyright notice 2016-06-29 21:24:05 +02:00
JustArchi
97724c95a7 Bump 2016-06-29 20:42:53 +02:00
JustArchi
627c4576f5 Misc 2016-06-29 20:35:33 +02:00
JustArchi
a90b5adaf5 Further hardening 2016-06-29 20:33:52 +02:00
JustArchi
1c74fddbac Further hardening 2016-06-29 20:20:38 +02:00
JustArchi
4380679df6 JSON code review 2016-06-29 20:18:47 +02:00
JustArchi
e3030dccdb Fix GetTradeHoldDuration() sometimes failing
Forgot to ensure the session is active
2016-06-29 09:11:59 +02:00
JustArchi
e6509ae1a3 Use timeouts instead 2016-06-28 23:25:18 +02:00
JustArchi
2bc202dd3a Revert "Respond with Error on Volvo fuckup, #266"
This reverts commit 5bc6af718e.

https://www.steamgifts.com/go/comment/6Fc717c
2016-06-28 22:57:54 +02:00
JustArchi
b9e8823bf1 Bump 2016-06-28 10:58:16 +02:00
JustArchi
e7cd488435 Give up for now 2016-06-28 10:54:39 +02:00
JustArchi
4c09d95b9a Let's test something else 2016-06-28 10:44:07 +02:00
JustArchi
1fe5cfff49 Fix broken Mono after #268 2016-06-28 10:36:28 +02:00
JustArchi
57014aab6d Warn also on empty (invalid) cryptkey 2016-06-28 09:26:11 +02:00
JustArchi
4a5bff1b84 Add --cryptkey, #267 2016-06-28 09:24:43 +02:00
JustArchi
5f0ce543ae Bump 2016-06-28 08:03:48 +02:00
JustArchi
25094592c9 Misc 2016-06-28 07:50:44 +02:00
JustArchi
8f75042b54 Silence resharper over obsolete code that will be removed soon 2016-06-28 07:21:03 +02:00
JustArchi
34c1016218 Misc 2016-06-28 07:18:39 +02:00
JustArchi
7351d07518 Improve ArchiService
- Correct DisplayName
- Implement proper Shutdown sequence instead of 20 seconds timeout-kill
- Misc
2016-06-28 07:14:51 +02:00
JustArchi
5a8701444a Formatting + code review 2016-06-28 06:58:59 +02:00
Łukasz Domeradzki
8db29fa5a7 Merge pull request #268 from justin-gerhardt/master
adding ability to run as a windows service
2016-06-28 06:28:54 +02:00
Justin Gerhardt
056b793262 adding ability to run as a windows service 2016-06-28 00:09:42 -04:00
JustArchi
a4383cdb89 Implement superior ProtectedDataForCurrentUser encryption 2016-06-28 05:24:30 +02:00
JustArchi
d5514422b6 Kill Base64 encryption
It doesn't make any sense when AES is available, not to mention that it's not even proper encryption...
2016-06-28 04:52:00 +02:00
JustArchi
3627a01f59 Correct example.json, default is 0 2016-06-28 04:36:31 +02:00
JustArchi
0dc131b79b Misc 2016-06-28 04:34:51 +02:00
JustArchi
8d9fbce2ed Add support for encrypted passwords, closes #267 2016-06-28 04:32:48 +02:00
JustArchi
a01e718e28 Misc 2016-06-27 21:28:06 +02:00
JustArchi
9bed5f013a Misc 2016-06-27 21:21:22 +02:00
JustArchi
2cf2df62c5 This is no longer required 2016-06-27 21:17:23 +02:00
JustArchi
9bac3915da Move MaxGamesPlayedConcurrently logic higher 2016-06-27 21:15:30 +02:00
JustArchi
93191f9066 Add logic for MaxGamesPlayedConcurrently
This is a limit introduced by Steam Network, limit for Steam Client is 30, but we don't have to respect that one
2016-06-27 21:09:58 +02:00
JustArchi
ebc8c1674b Prefer fully synchronous code during saving
We must ensure no race condition during lock statement and exiting lock clause to run async code - Thread.Sleep() is bad, but we never expect to reach that point in normal conditions, and in faulty ones, it improves the chances of things succeeding in next try
2016-06-27 19:05:24 +02:00
JustArchi
5bc6af718e Respond with Error on Volvo fuckup, #266 2016-06-27 18:56:11 +02:00
JustArchi
579d9e1cbf What the fuck VS 2016-06-27 18:48:25 +02:00
JustArchi
c7d0fb1aac Fix formatting 2016-06-27 18:46:50 +02:00
JustArchi
1f4a4cc6b7 Database saving hardening, #265 2016-06-27 18:44:48 +02:00
JustArchi
612ab87626 Bump 2016-06-27 02:31:25 +02:00
JustArchi
951d58161f Use SharedInfo + Add ConfigGenerator version check 2016-06-27 02:07:27 +02:00
JustArchi
d68bb01174 Add MaxTradeHoldDuration 2016-06-27 01:45:41 +02:00
JustArchi
da411b81b9 Bump 2016-06-27 01:01:55 +02:00
JustArchi
82c2d3ec15 Never register bot with broken database, closes #265 2016-06-27 00:38:38 +02:00
JustArchi
788f5c94a2 Reduce likeness of accepting/rejecting outdated trades 2016-06-26 23:01:30 +02:00
JustArchi
4d37a775cf Add case-insensitivity to commands 2016-06-26 22:38:03 +02:00
JustArchi
766a638f0d Misc 2016-06-26 20:41:21 +02:00
JustArchi
12aa933355 Misc 2016-06-26 20:29:56 +02:00
JustArchi
9bc76ca1fe Misc 2016-06-25 07:03:39 +02:00
JustArchi
339a56dc80 Bump 2016-06-25 04:10:13 +02:00
JustArchi
d16228b2a5 Steam talks more crap than I thought 2016-06-25 04:05:30 +02:00
JustArchi
99dbca1b36 Code review 2016-06-25 03:56:49 +02:00
JustArchi
943bc7e3d9 Further optimize confirmations related to trading
Thanks to that we're in fact asking for confirmations only if we accepted something, request succeeded, and we lost items
2016-06-25 03:52:02 +02:00
JustArchi
9535479602 Bump 2016-06-24 22:29:30 +02:00
JustArchi
90ade53ae7 Compilation fix 2016-06-24 22:27:49 +02:00
JustArchi
9a51386b7e Bump 2016-06-24 22:27:22 +02:00
JustArchi
03ee96057f Add GiftsLimiterDelay 2016-06-24 22:26:52 +02:00
JustArchi
a23bca7960 Actually handle gift only one time regardless 2016-06-24 20:54:21 +02:00
JustArchi
6b4ae6a4d7 Do not attempt to handle the same gift more than one time 2016-06-24 20:27:23 +02:00
JustArchi
5c80fd158d Misc 2016-06-24 19:26:21 +02:00
JustArchi
885800c539 Closes #263 2016-06-24 16:58:49 +02:00
JustArchi
920d4b9ed6 Misc 2016-06-24 05:41:32 +02:00
JustArchi
2d02bd609e Add CONTRIBUTING.md 2016-06-24 05:39:32 +02:00
JustArchi
e658ae33b1 Misc 2016-06-24 03:17:11 +02:00
JustArchi
379018866b Bump 2016-06-24 02:40:44 +02:00
86 changed files with 8525 additions and 10020 deletions

View File

@@ -1,4 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AES/@EntryIndexedValue">AES</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<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>
@@ -15,4 +20,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WTF/@EntryIndexedValue">WTF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XML/@EntryIndexedValue">XML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,52 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics.CodeAnalysis;
using System.ServiceProcess;
namespace ArchiSteamFarm {
[RunInstaller(true)]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class ArchiServiceInstaller : Installer {
public ArchiServiceInstaller() {
ServiceInstaller serviceInstaller = new ServiceInstaller();
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceInstaller.ServiceName = SharedInfo.ServiceName;
serviceInstaller.DisplayName = SharedInfo.ServiceName;
serviceInstaller.Description = SharedInfo.ServiceDescription;
// Defaulting to only starting when a user starts it, can be easily changed after install
serviceInstaller.StartType = ServiceStartMode.Manual;
// System account, requires admin privilege to install
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
Installers.Add(serviceInstaller);
Installers.Add(serviceProcessInstaller);
}
}
}

View File

@@ -71,12 +71,12 @@
<DelaySign>false</DelaySign>
</PropertyGroup>
<ItemGroup>
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Reference Include="HtmlAgilityPack, Version=1.4.9.4, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9.4\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
@@ -88,19 +88,26 @@
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ArchiHandler.cs" />
<Compile Include="ArchiWebHandler.cs" />
<Compile Include="ArchiServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="ConcurrentEnumerator.cs" />
<Compile Include="ConcurrentHashSet.cs" />
<Compile Include="CryptoHelper.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
@@ -110,17 +117,20 @@
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MobileAuthenticator.cs" />
<Compile Include="Mono.cs" />
<Compile Include="Runtime.cs" />
<Compile Include="ObsoleteSteamGuardAccount.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SharedInfo.cs" />
<Compile Include="Trading.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="WCF.cs" />
<Compile Include="WebBrowser.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="App.config">
<SubType>Designer</SubType>
</None>
<None Include="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -159,8 +169,8 @@
copy "$(TargetDir)config\ASF.json" "$(SolutionDir)out\config"
copy "$(TargetDir)config\example.json" "$(SolutionDir)out\config"
copy "$(TargetDir)config\minimal.json" "$(SolutionDir)out\config"
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF.exe.config"
"$(SolutionDir)tools\LibZ\libz.exe" inject-dll -a "$(TargetDir)$(TargetName).exe" -i "$(TargetDir)*.dll" --move
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(SolutionDir)out/config"

View File

@@ -64,7 +64,7 @@ namespace ArchiSteamFarm {
}
int index = hashName.IndexOf('-');
if (index < 1) {
if (index <= 0) {
return 0;
}
@@ -104,6 +104,53 @@ namespace ArchiSteamFarm {
}
}
private static bool ParseItems(Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions, List<KeyValue> input, HashSet<Steam.Item> output) {
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
Logging.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
return false;
}
foreach (KeyValue item in input) {
uint appID = (uint) item["appid"].AsUnsignedLong();
if (appID == 0) {
Logging.LogNullError(nameof(appID));
return false;
}
ulong contextID = item["contextid"].AsUnsignedLong();
if (contextID == 0) {
Logging.LogNullError(nameof(contextID));
return false;
}
ulong classID = item["classid"].AsUnsignedLong();
if (classID == 0) {
Logging.LogNullError(nameof(classID));
return false;
}
uint amount = (uint) item["amount"].AsUnsignedLong();
if (amount == 0) {
Logging.LogNullError(nameof(amount));
return false;
}
uint realAppID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(classID, out description)) {
realAppID = description.Item1;
type = description.Item2;
}
Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type);
output.Add(steamItem);
}
return true;
}
internal ArchiWebHandler(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
@@ -116,22 +163,22 @@ namespace ArchiSteamFarm {
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);
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce), Bot.BotName);
return false;
}
SteamID = steamClient.SteamID;
SteamID = 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);
byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on
byte[] cryptedSessionKey;
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
cryptedSessionKey = rsa.Encrypt(sessionKey);
}
@@ -140,7 +187,7 @@ namespace ArchiSteamFarm {
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
// AES encrypt the loginkey with our session key
byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
// Do the magic
Logging.LogGenericInfo("Logging in to ISteamUserAuth...", Bot.BotName);
@@ -151,7 +198,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,
@@ -342,19 +389,19 @@ namespace ArchiSteamFarm {
XmlNode appNode = xmlNode.SelectSingleNode("appID");
if (appNode == null) {
Logging.LogNullError(nameof(appNode), Bot.BotName);
continue;
return null;
}
uint appID;
if (!uint.TryParse(appNode.InnerText, out appID)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return null;
}
XmlNode nameNode = xmlNode.SelectSingleNode("name");
if (nameNode == null) {
Logging.LogNullError(nameof(nameNode), Bot.BotName);
continue;
return null;
}
result[appID] = nameNode.InnerText;
@@ -396,7 +443,7 @@ namespace ArchiSteamFarm {
uint appID = (uint) game["appid"].AsUnsignedLong();
if (appID == 0) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return null;
}
result[appID] = game["name"].Value;
@@ -436,6 +483,10 @@ namespace ArchiSteamFarm {
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
@@ -444,8 +495,7 @@ namespace ArchiSteamFarm {
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
if (htmlNode == null) {
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
if (htmlNode == null) { // Trade can be no longer valid
return null;
}
@@ -481,7 +531,7 @@ namespace ArchiSteamFarm {
return null;
}
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
internal HashSet<Steam.TradeOffer> GetActiveTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
return null;
@@ -515,7 +565,7 @@ namespace ArchiSteamFarm {
ulong classID = description["classid"].AsUnsignedLong();
if (classID == 0) {
Logging.LogNullError(nameof(classID), Bot.BotName);
continue;
return null;
}
if (descriptions.ContainsKey(classID)) {
@@ -545,44 +595,44 @@ namespace ArchiSteamFarm {
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) {
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
TradeOfferID = trade["tradeofferid"].AsUnsignedLong(),
OtherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong(),
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
};
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;
steamItem.Type = description.Item2;
}
tradeOffer.ItemsToGive.Add(steamItem);
Steam.TradeOffer.ETradeOfferState state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>();
if (state == Steam.TradeOffer.ETradeOfferState.Unknown) {
Logging.LogNullError(nameof(state));
return null;
}
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;
steamItem.Type = description.Item2;
}
if (state != Steam.TradeOffer.ETradeOfferState.Active) {
continue;
}
tradeOffer.ItemsToReceive.Add(steamItem);
ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong();
if (tradeOfferID == 0) {
Logging.LogNullError(nameof(tradeOfferID));
return null;
}
uint otherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong();
if (otherSteamID3 == 0) {
Logging.LogNullError(nameof(otherSteamID3));
return null;
}
Steam.TradeOffer tradeOffer = new Steam.TradeOffer(tradeOfferID, otherSteamID3, state);
List<KeyValue> itemsToGive = trade["items_to_give"].Children;
if (itemsToGive.Count > 0) {
if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) {
Logging.LogGenericError("Parsing " + nameof(itemsToGive) + " failed!", Bot.BotName);
return null;
}
}
List<KeyValue> itemsToReceive = trade["items_to_receive"].Children;
if (itemsToReceive.Count > 0) {
if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) {
Logging.LogGenericError("Parsing " + nameof(itemsToReceive) + " failed!", Bot.BotName);
return null;
}
}
result.Add(tradeOffer);
@@ -675,13 +725,13 @@ namespace ArchiSteamFarm {
string classIDString = description["classid"].ToString();
if (string.IsNullOrEmpty(classIDString)) {
Logging.LogNullError(nameof(classIDString), Bot.BotName);
continue;
return null;
}
ulong classID;
if (!ulong.TryParse(classIDString, out classID) || (classID == 0)) {
Logging.LogNullError(nameof(classID), Bot.BotName);
continue;
return null;
}
if (descriptionMap.ContainsKey(classID)) {
@@ -699,12 +749,12 @@ namespace ArchiSteamFarm {
string appIDString = description["appid"].ToString();
if (string.IsNullOrEmpty(appIDString)) {
Logging.LogNullError(nameof(appIDString), Bot.BotName);
continue;
return null;
}
if (!uint.TryParse(appIDString, out appID)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return null;
}
}
@@ -731,12 +781,12 @@ namespace ArchiSteamFarm {
steamItem = item.ToObject<Steam.Item>();
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
continue;
return null;
}
if (steamItem == null) {
Logging.LogNullError(nameof(steamItem), Bot.BotName);
continue;
return null;
}
Tuple<uint, Steam.Item.EType> description;
@@ -754,13 +804,9 @@ namespace ArchiSteamFarm {
}
uint nextPage;
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage)) {
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage) || (nextPage <= currentPage)) {
Logging.LogNullError(nameof(nextPage), Bot.BotName);
break;
}
if (nextPage <= currentPage) {
break;
return null;
}
currentPage = nextPage;
@@ -800,13 +846,7 @@ namespace ArchiSteamFarm {
itemID = 0;
}
singleTrade.ItemsToGive.Assets.Add(new Steam.Item {
AppID = Steam.Item.SteamAppID,
ContextID = Steam.Item.SteamContextID,
Amount = item.Amount,
AssetID = item.AssetID
});
singleTrade.ItemsToGive.Assets.Add(new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamContextID, item.AssetID, item.Amount));
itemID++;
}
@@ -839,7 +879,6 @@ namespace ArchiSteamFarm {
}
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -854,7 +893,6 @@ namespace ArchiSteamFarm {
}
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -864,7 +902,6 @@ namespace ArchiSteamFarm {
}
string request = SteamCommunityURL + "/my/inventory";
return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
}

View File

@@ -45,6 +45,7 @@ namespace ArchiSteamFarm {
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
internal readonly string BotName;
@@ -60,6 +61,7 @@ namespace ArchiSteamFarm {
[JsonProperty]
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly SteamApps SteamApps;
private readonly SteamFriends SteamFriends;
private readonly SteamUser SteamUser;
@@ -83,7 +85,7 @@ namespace ArchiSteamFarm {
initialized = true;
} catch (Exception e) {
Logging.LogGenericException(e);
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
}
@@ -112,10 +114,18 @@ namespace ArchiSteamFarm {
return false;
}
private static async Task LimitGiftsRequestsAsync() {
await GiftsSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
GiftsSemaphore.Release();
}).Forget();
}
private static async Task LimitLoginRequestsAsync() {
await LoginSemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
LoginSemaphore.Release();
}).Forget();
}
@@ -125,10 +135,15 @@ namespace ArchiSteamFarm {
throw new ArgumentNullException(nameof(botName));
}
BotName = botName;
if (Bots.ContainsKey(botName)) {
throw new Exception("That bot is already defined!");
}
string botPath = Path.Combine(Program.ConfigDirectory, botName);
BotName = botName;
SentryFile = botPath + ".bin";
BotConfig = BotConfig.Load(botPath + ".json");
if (BotConfig == null) {
Logging.LogGenericError("Your bot config is invalid, refusing to start this bot instance!", botName);
@@ -140,14 +155,6 @@ namespace ArchiSteamFarm {
return;
}
if (Bots.ContainsKey(botName)) {
throw new Exception("That bot is already defined!");
}
Bots[botName] = this;
SentryFile = botPath + ".bin";
BotDatabase = BotDatabase.Load(botPath + ".db");
if (BotDatabase == null) {
Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
@@ -231,13 +238,16 @@ namespace ArchiSteamFarm {
if ((SendItemsTimer == null) && (BotConfig.SendTradePeriod > 0)) {
SendItemsTimer = new Timer(
async e => await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false),
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
null,
TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
);
}
// Register bot as available for ASF
Bots[botName] = this;
if (!BotConfig.StartOnLaunch) {
return;
}
@@ -266,8 +276,7 @@ namespace ArchiSteamFarm {
}
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
List<Task<Steam.ConfirmationDetails>> tasks = confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails).ToList();
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(tasks).ConfigureAwait(false);
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<uint> ignoredConfirmationIDs = new HashSet<uint>();
foreach (Steam.ConfirmationDetails details in detailsResults.Where(details => (details != null) && (
@@ -314,7 +323,7 @@ namespace ArchiSteamFarm {
return false;
}
if (await ArchiWebHandler.Init(SteamClient, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
if (await ArchiWebHandler.Init(SteamClient.SteamID, SteamClient.ConnectedUniverse, callback.Nonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
return true;
}
@@ -322,6 +331,21 @@ namespace ArchiSteamFarm {
return false;
}
internal void Stop() {
if (!KeepRunning) {
return;
}
Logging.LogGenericInfo("Stopping...", BotName);
KeepRunning = false;
if (SteamClient.IsConnected) {
SteamClient.Disconnect();
}
Program.OnBotShutdown();
}
internal void OnFarmingStopped() => ResetGamesPlayed();
internal async Task OnFarmingFinished(bool farmedSomething) {
@@ -329,7 +353,7 @@ namespace ArchiSteamFarm {
if ((farmedSomething || !FirstTradeSent) && BotConfig.SendOnFarmingFinished) {
FirstTradeSent = true;
await ResponseSendTrade(BotConfig.SteamMasterID).ConfigureAwait(false);
await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false);
}
if (BotConfig.ShutdownOnFarmingFinished) {
@@ -348,44 +372,52 @@ namespace ArchiSteamFarm {
}
if (message[0] != '!') {
if (!IsMaster(steamID)) {
return null;
}
return await ResponseRedeem(steamID, message, true).ConfigureAwait(false);
}
if (message.IndexOf(' ') < 0) {
switch (message) {
case "!2fa":
switch (message.ToUpper()) {
case "!2FA":
return await Response2FA(steamID).ConfigureAwait(false);
case "!2fano":
case "!2FANO":
return await Response2FAConfirm(steamID, false).ConfigureAwait(false);
case "!2faok":
case "!2FAOK":
return await Response2FAConfirm(steamID, true).ConfigureAwait(false);
case "!api":
case "!API":
return ResponseAPI(steamID);
case "!exit":
case "!EXIT":
return ResponseExit(steamID);
case "!farm":
case "!FARM":
return await ResponseFarm(steamID).ConfigureAwait(false);
case "!help":
case "!HELP":
return ResponseHelp(steamID);
case "!loot":
return await ResponseSendTrade(steamID).ConfigureAwait(false);
case "!pause":
case "!LOOT":
return await ResponseLoot(steamID).ConfigureAwait(false);
case "!LOOTALL":
return await ResponseLootAll(steamID).ConfigureAwait(false);
case "!PASSWORD":
return ResponsePassword(steamID);
case "!PAUSE":
return await ResponsePause(steamID, true).ConfigureAwait(false);
case "!rejoinchat":
case "!REJOINCHAT":
return ResponseRejoinChat(steamID);
case "!resume":
case "!RESUME":
return await ResponsePause(steamID, false).ConfigureAwait(false);
case "!restart":
case "!RESTART":
return ResponseRestart(steamID);
case "!status":
case "!STATUS":
return ResponseStatus(steamID);
case "!statusall":
case "!STATUSALL":
return ResponseStatusAll(steamID);
case "!stop":
case "!STOP":
return ResponseStop(steamID);
case "!update":
case "!UPDATE":
return await ResponseUpdate(steamID).ConfigureAwait(false);
case "!version":
case "!VERSION":
return ResponseVersion(steamID);
default:
return ResponseUnknown(steamID);
@@ -393,50 +425,52 @@ namespace ArchiSteamFarm {
}
string[] args = message.Split((char[]) null, StringSplitOptions.RemoveEmptyEntries);
switch (args[0]) {
case "!2fa":
switch (args[0].ToUpper()) {
case "!2FA":
return await Response2FA(steamID, args[1]).ConfigureAwait(false);
case "!2fano":
case "!2FANO":
return await Response2FAConfirm(steamID, args[1], false).ConfigureAwait(false);
case "!2faok":
case "!2FAOK":
return await Response2FAConfirm(steamID, args[1], true).ConfigureAwait(false);
case "!addlicense":
case "!ADDLICENSE":
if (args.Length > 2) {
return await ResponseAddLicense(steamID, args[1], args[2]).ConfigureAwait(false);
}
return await ResponseAddLicense(steamID, BotName, args[1]).ConfigureAwait(false);
case "!farm":
case "!FARM":
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
case "!loot":
return await ResponseSendTrade(steamID, args[1]).ConfigureAwait(false);
case "!owns":
case "!LOOT":
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
case "!OWNS":
if (args.Length > 2) {
return await ResponseOwns(steamID, args[1], args[2]).ConfigureAwait(false);
}
return await ResponseOwns(steamID, BotName, args[1]).ConfigureAwait(false);
case "!pause":
case "!PASSWORD":
return ResponsePassword(steamID, args[1]);
case "!PAUSE":
return await ResponsePause(steamID, args[1], true).ConfigureAwait(false);
case "!play":
case "!PLAY":
if (args.Length > 2) {
return await ResponsePlay(steamID, args[1], args[2]).ConfigureAwait(false);
}
return await ResponsePlay(steamID, BotName, args[1]).ConfigureAwait(false);
case "!redeem":
case "!REDEEM":
if (args.Length > 2) {
return await ResponseRedeem(steamID, args[1], args[2], false).ConfigureAwait(false);
}
return await ResponseRedeem(steamID, BotName, args[1], false).ConfigureAwait(false);
case "!resume":
case "!RESUME":
return await ResponsePause(steamID, args[1], false).ConfigureAwait(false);
case "!start":
case "!START":
return await ResponseStart(steamID, args[1]).ConfigureAwait(false);
case "!status":
case "!STATUS":
return ResponseStatus(steamID, args[1]);
case "!stop":
case "!STOP":
return ResponseStop(steamID, args[1]);
default:
return ResponseUnknown(steamID);
@@ -458,17 +492,6 @@ namespace ArchiSteamFarm {
SteamClient.Connect();
}
private void Stop() {
Logging.LogGenericInfo("Stopping...", BotName);
KeepRunning = false;
if (SteamClient.IsConnected) {
SteamClient.Disconnect();
}
Program.OnBotShutdown();
}
private bool IsMaster(ulong steamID) {
if (steamID != 0) {
return (steamID == BotConfig.SteamMasterID) || IsOwner(steamID);
@@ -514,6 +537,43 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Successfully finished importing mobile authenticator!", BotName);
}
private string ResponsePassword(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
return null;
}
if (!IsMaster(steamID)) {
return null;
}
if (string.IsNullOrEmpty(BotConfig.SteamPassword)) {
return "Can't encrypt null password!";
}
return Environment.NewLine +
"[" + CryptoHelper.ECryptoMethod.AES + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword) + Environment.NewLine +
"[" + CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword);
}
private static string ResponsePassword(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
}
Bot bot;
if (Bots.TryGetValue(botName, out bot)) {
return bot.ResponsePassword(steamID);
}
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
}
return null;
}
private async Task<string> ResponsePause(ulong steamID, bool pause) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
@@ -630,7 +690,7 @@ namespace ArchiSteamFarm {
return result.ToString();
}
private async Task<string> ResponseSendTrade(ulong steamID) {
private async Task<string> ResponseLoot(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
return null;
@@ -667,11 +727,12 @@ namespace ArchiSteamFarm {
return "Trade offer failed due to error!";
}
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false);
return "Trade offer sent successfully!";
}
private static async Task<string> ResponseSendTrade(ulong steamID, string botName) {
private static async Task<string> ResponseLoot(ulong steamID, string botName) {
if ((steamID == 0) || string.IsNullOrEmpty(botName)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(botName));
return null;
@@ -679,7 +740,7 @@ namespace ArchiSteamFarm {
Bot bot;
if (Bots.TryGetValue(botName, out bot)) {
return await bot.ResponseSendTrade(steamID).ConfigureAwait(false);
return await bot.ResponseLoot(steamID).ConfigureAwait(false);
}
if (IsOwner(steamID)) {
@@ -689,6 +750,20 @@ namespace ArchiSteamFarm {
return null;
}
private static async Task<string> ResponseLootAll(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID));
return null;
}
if (!IsOwner(steamID)) {
return null;
}
await Task.WhenAll(Bots.Values.Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false);
return "Done!";
}
private async Task<string> Response2FA(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID), BotName);
@@ -795,7 +870,7 @@ namespace ArchiSteamFarm {
// Schedule the task after some time so user can receive response
Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
Program.Exit();
}).Forget();
@@ -849,7 +924,7 @@ namespace ArchiSteamFarm {
return null;
}
return "https://github.com/" + Program.GithubRepo + "/wiki/Commands";
return "https://github.com/" + SharedInfo.GithubRepo + "/wiki/Commands";
}
private async Task<string> ResponseRedeem(ulong steamID, string message, bool validate) {
@@ -880,6 +955,7 @@ namespace ArchiSteamFarm {
} else {
ArchiHandler.PurchaseResponseCallback result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (result == null) {
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: Timeout!");
currentBot = null; // Either bot will be changed, or loop aborted
} else {
switch (result.PurchaseResult) {
@@ -914,6 +990,7 @@ namespace ArchiSteamFarm {
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected)) {
ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (otherResult == null) {
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: Timeout!");
continue;
}
@@ -961,6 +1038,7 @@ namespace ArchiSteamFarm {
if (Bots.TryGetValue(botName, out bot)) {
return await bot.ResponseRedeem(steamID, message, validate).ConfigureAwait(false);
}
if (IsOwner(steamID)) {
return "Couldn't find any bot named " + botName + "!";
}
@@ -997,7 +1075,7 @@ namespace ArchiSteamFarm {
// Schedule the task after some time so user can receive response
Task.Run(async () => {
await Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
Program.Restart();
}).Forget();
@@ -1018,10 +1096,11 @@ namespace ArchiSteamFarm {
foreach (uint gameID in gameIDs) {
SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID);
if (callback == null) {
continue;
result.AppendLine(Environment.NewLine + "Result: Timeout!");
break;
}
result.AppendLine("Result: " + callback.Result + " | Granted apps: " + string.Join(", ", callback.GrantedApps) + " " + string.Join(", ", callback.GrantedPackages));
result.AppendLine(Environment.NewLine + "Result: " + callback.Result + " | Granted apps: " + string.Join(", ", callback.GrantedApps) + " " + string.Join(", ", callback.GrantedPackages));
}
return result.ToString();
@@ -1048,14 +1127,14 @@ namespace ArchiSteamFarm {
foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
uint gameID;
if (!uint.TryParse(game, out gameID)) {
continue;
return "Couldn't parse games list!";
}
gamesToRedeem.Add(gameID);
}
if (gamesToRedeem.Count == 0) {
return "Couldn't parse any games given!";
return "List of games is empty!";
}
return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false);
@@ -1178,14 +1257,18 @@ namespace ArchiSteamFarm {
foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) {
uint gameID;
if (!uint.TryParse(game, out gameID)) {
continue;
return "Couldn't parse games list!";
}
gamesToPlay.Add(gameID);
if (gamesToPlay.Count >= CardsFarmer.MaxGamesPlayedConcurrently) {
break;
}
}
if (gamesToPlay.Count == 0) {
return "Couldn't parse any games given!";
return "List of games is empty!";
}
return await bot.ResponsePlay(steamID, gamesToPlay).ConfigureAwait(false);
@@ -1429,7 +1512,7 @@ namespace ArchiSteamFarm {
if (File.Exists(SentryFile)) {
try {
byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
sentryHash = CryptoHelper.SHAHash(sentryFileContent);
sentryHash = SteamKit2.CryptoHelper.SHAHash(sentryFileContent);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
}
@@ -1496,11 +1579,14 @@ namespace ArchiSteamFarm {
return;
}
ArchiWebHandler.OnDisconnected();
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
ArchiWebHandler.OnDisconnected();
CardsFarmer.OnDisconnected();
Trading.OnDisconnected();
FirstTradeSent = false;
CardsFarmer.StopFarming().Forget();
HandledGifts.ClearAndTrim();
// If we initiated disconnect, do not attempt to reconnect
if (callback.UserInitiated) {
@@ -1514,7 +1600,7 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Removed expired login key", BotName);
} else { // If we didn't use login key, InvalidPassword usually means we got captcha or other network-based throttling
Logging.LogGenericInfo("Will retry after 25 minutes...", BotName);
await Utilities.SleepAsync(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
await Task.Delay(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25
}
}
@@ -1557,8 +1643,12 @@ namespace ArchiSteamFarm {
}
bool acceptedSomething = false;
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => gid != 0)) {
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
HandledGifts.Add(gid);
Logging.LogGenericInfo("Accepting gift: " + gid + "...", BotName);
await LimitGiftsRequestsAsync().ConfigureAwait(false);
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
acceptedSomething = true;
Logging.LogGenericInfo("Success!", BotName);
@@ -1751,7 +1841,7 @@ namespace ArchiSteamFarm {
}
}
if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
if (!await RefreshSession().ConfigureAwait(false)) {
return;
}
@@ -1777,7 +1867,7 @@ namespace ArchiSteamFarm {
Trading.CheckTrades().Forget();
await Utilities.SleepAsync(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
CardsFarmer.StartFarming().Forget();
break;
case EResult.NoConnection:

View File

@@ -27,6 +27,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@@ -44,6 +45,10 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal string SteamPassword { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
[JsonProperty]
internal string SteamParentalPIN { get; set; } = "0";
@@ -124,6 +129,21 @@ namespace ArchiSteamFarm {
return null;
}
// Support encrypted passwords
if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) {
// In worst case password will result in null, which will have to be corrected by user during runtime
botConfig.SteamPassword = CryptoHelper.Decrypt(botConfig.PasswordFormat, botConfig.SteamPassword);
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (botConfig.GamesPlayedWhileIdle.Count <= CardsFarmer.MaxGamesPlayedConcurrently) {
return botConfig;
}
Logging.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
botConfig.GamesPlayedWhileIdle = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
return botConfig;
}

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
@@ -126,11 +127,22 @@ namespace ArchiSteamFarm {
private BotDatabase() { }
internal void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
Logging.LogNullError(nameof(json));
return;
}
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
Logging.LogGenericException(e);
}
Thread.Sleep(1000);
}
}
}

View File

@@ -35,6 +35,8 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer {
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
@@ -180,7 +182,7 @@ namespace ArchiSteamFarm {
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) {
@@ -192,6 +194,8 @@ namespace ArchiSteamFarm {
FarmingSemaphore.Release();
}
internal void OnDisconnected() => StopFarming().Forget();
internal void OnNewItemsNotification() {
if (!NowFarming) {
return;
@@ -295,19 +299,19 @@ namespace ArchiSteamFarm {
string steamLink = farmingNode.GetAttributeValue("href", null);
if (string.IsNullOrEmpty(steamLink)) {
Logging.LogNullError(nameof(steamLink), Bot.BotName);
continue;
return;
}
int index = steamLink.LastIndexOf('/');
if (index < 0) {
Logging.LogNullError(nameof(index), Bot.BotName);
continue;
return;
}
index++;
if (steamLink.Length <= index) {
Logging.LogNullError(nameof(steamLink.Length), Bot.BotName);
continue;
return;
}
steamLink = steamLink.Substring(index);
@@ -315,7 +319,7 @@ namespace ArchiSteamFarm {
uint appID;
if (!uint.TryParse(steamLink, out appID) || (appID == 0)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return;
}
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
@@ -325,13 +329,13 @@ namespace ArchiSteamFarm {
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Logging.LogNullError(nameof(timeNode), Bot.BotName);
continue;
return;
}
string hoursString = timeNode.InnerText;
if (string.IsNullOrEmpty(hoursString)) {
Logging.LogNullError(nameof(hoursString), Bot.BotName);
continue;
return;
}
float hours = 0;
@@ -340,7 +344,7 @@ namespace ArchiSteamFarm {
if (match.Success) {
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Logging.LogNullError(nameof(hours), Bot.BotName);
continue;
return;
}
}
@@ -418,6 +422,10 @@ namespace ArchiSteamFarm {
if (game.Value > maxHour) {
maxHour = game.Value;
}
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
break;
}
}
if (maxHour >= 2) {
@@ -500,10 +508,6 @@ namespace ArchiSteamFarm {
return false;
}
if (maxHour >= 2) {
return true;
}
Bot.ArchiHandler.PlayGames(appIDs);
bool success = true;

View File

@@ -36,14 +36,14 @@ namespace ArchiSteamFarm {
private readonly IEnumerator<T> Enumerator;
private readonly ReaderWriterLockSlim Lock;
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim @lock) {
if ((collection == null) || (@lock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock));
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim rwLock) {
if ((collection == null) || (rwLock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
}
@lock.EnterReadLock();
rwLock.EnterReadLock();
Lock = @lock;
Lock = rwLock;
Enumerator = collection.GetEnumerator();
}

View File

@@ -31,7 +31,7 @@ using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable {
private readonly HashSet<T> HashSet = new HashSet<T>();
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public bool IsReadOnly => false;
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);

View File

@@ -0,0 +1,166 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Security.Cryptography;
using System.Text;
namespace ArchiSteamFarm {
internal static class CryptoHelper {
internal enum ECryptoMethod : byte {
PlainText,
AES,
ProtectedDataForCurrentUser
}
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm");
internal static void SetEncryptionKey(string key) {
if (string.IsNullOrEmpty(key)) {
Logging.LogNullError(nameof(key));
return;
}
EncryptionKey = Encoding.UTF8.GetBytes(key);
}
internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return decrypted;
case ECryptoMethod.AES:
return EncryptAES(decrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return EncryptProtectedDataForCurrentUser(decrypted);
default:
return null;
}
}
internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return encrypted;
case ECryptoMethod.AES:
return DecryptAES(encrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return DecryptProtectedDataForCurrentUser(encrypted);
default:
return null;
}
}
private static string EncryptAES(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
key = sha256.ComputeHash(EncryptionKey);
}
byte[] encryptedData = Encoding.UTF8.GetBytes(decrypted);
encryptedData = SteamKit2.CryptoHelper.SymmetricEncrypt(encryptedData, key);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string DecryptAES(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
key = sha256.ComputeHash(EncryptionKey);
}
byte[] decryptedData = Convert.FromBase64String(encrypted);
decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
try {
byte[] encryptedData = ProtectedData.Protect(
Encoding.UTF8.GetBytes(decrypted),
EncryptionKey, // This is used as salt only
DataProtectionScope.CurrentUser
);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string DecryptProtectedDataForCurrentUser(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
try {
byte[] decryptedData = ProtectedData.Unprotect(
Convert.FromBase64String(encrypted),
EncryptionKey, // This is used as salt only
DataProtectionScope.CurrentUser
);
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
}
}

View File

@@ -86,6 +86,12 @@ namespace ArchiSteamFarm {
[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 byte MaxTradeHoldDuration { get; private set; } = 15;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForceHttp { get; private set; } = false;
@@ -159,7 +165,7 @@ namespace ArchiSteamFarm {
globalConfig.FarmingDelay = DefaultFarmingDelay;
}
if ((globalConfig.FarmingDelay > 5) && Mono.RequiresWorkaroundForBug41701()) {
if ((globalConfig.FarmingDelay > 5) && Runtime.RequiresWorkaroundForMonoBug41701()) {
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;
}

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
@@ -91,11 +92,22 @@ namespace ArchiSteamFarm {
private GlobalDatabase() { }
private void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
Logging.LogNullError(nameof(json));
return;
}
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
Logging.LogGenericException(e);
}
Thread.Sleep(1000);
}
}
}

View File

@@ -22,6 +22,7 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -33,6 +34,7 @@ using SteamKit2;
namespace ArchiSteamFarm.JSON {
internal static class Steam {
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
// Deserialized from JSON (SteamCommunity) and constructed from code
internal const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
@@ -50,7 +52,7 @@ namespace ArchiSteamFarm.JSON {
TradingCard
}
internal uint AppID { get; set; }
internal uint AppID { get; private set; }
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
@@ -61,19 +63,21 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
uint appID;
if (!uint.TryParse(value, out appID) || (appID == 0)) {
Logging.LogNullError(nameof(appID));
return;
}
AppID = result;
AppID = appID;
}
}
internal ulong ContextID { get; set; }
internal ulong ContextID { get; private set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
@@ -84,19 +88,21 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong contextID;
if (!ulong.TryParse(value, out contextID) || (contextID == 0)) {
Logging.LogNullError(nameof(contextID));
return;
}
ContextID = result;
ContextID = contextID;
}
}
internal ulong AssetID { get; set; }
internal ulong AssetID { get; private set; }
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
private string AssetIDString {
@@ -106,15 +112,17 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong assetID;
if (!ulong.TryParse(value, out assetID) || (assetID == 0)) {
Logging.LogNullError(nameof(assetID));
return;
}
AssetID = result;
AssetID = assetID;
}
}
@@ -125,7 +133,7 @@ namespace ArchiSteamFarm.JSON {
set { AssetIDString = value; }
}
internal ulong ClassID { get; set; }
internal ulong ClassID { get; private set; }
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
@@ -136,42 +144,20 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong classID;
if (!ulong.TryParse(value, out classID) || (classID == 0)) {
return;
}
ClassID = result;
ClassID = classID;
}
}
internal ulong InstanceID { private get; set; }
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string InstanceIDString {
get {
return InstanceID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
InstanceID = result;
}
}
internal uint Amount { get; set; }
internal uint Amount { get; private set; }
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
@@ -182,23 +168,59 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
uint amount;
if (!uint.TryParse(value, out amount) || (amount == 0)) {
Logging.LogNullError(nameof(amount));
return;
}
Amount = result;
Amount = amount;
}
}
internal uint RealAppID { get; set; }
internal EType Type { get; set; }
// This constructor is used for constructing items in trades being sent
internal Item(uint appID, ulong contextID, ulong assetID, uint amount) : this(appID, contextID, amount) {
if (assetID == 0) {
throw new ArgumentNullException(nameof(assetID));
}
AssetID = assetID;
}
// This constructor is used for constructing items in trades being received
internal Item(uint appID, ulong contextID, ulong classID, uint amount, uint realAppID, EType type) : this(appID, contextID, amount) {
if (classID == 0) {
throw new ArgumentNullException(nameof(classID));
}
ClassID = classID;
RealAppID = realAppID;
Type = type;
}
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private Item() { }
private Item(uint appID, ulong contextID, uint amount) {
if ((appID == 0) || (contextID == 0) || (amount == 0)) {
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(amount));
}
AppID = appID;
ContextID = contextID;
Amount = amount;
}
}
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
// Constructed from code
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte {
Unknown,
@@ -215,43 +237,39 @@ namespace ArchiSteamFarm.JSON {
OnHold
}
internal ulong TradeOfferID { get; set; }
internal readonly ulong TradeOfferID;
internal readonly ETradeOfferState State;
internal readonly HashSet<Item> ItemsToGive = new HashSet<Item>();
internal readonly HashSet<Item> ItemsToReceive = new HashSet<Item>();
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string TradeOfferIDString {
private readonly uint OtherSteamID3;
private ulong _OtherSteamID64;
internal ulong OtherSteamID64 {
get {
return TradeOfferID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
if (_OtherSteamID64 != 0) {
return _OtherSteamID64;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
if (OtherSteamID3 == 0) {
Logging.LogNullError(nameof(OtherSteamID3));
return 0;
}
TradeOfferID = result;
_OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
return _OtherSteamID64;
}
}
[JsonProperty(PropertyName = "accountid_other", Required = Required.Always)]
internal uint OtherSteamID3 { private get; set; }
internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) {
if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) {
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state));
}
[JsonProperty(PropertyName = "trade_offer_state", Required = Required.Always)]
internal ETradeOfferState State { get; set; }
[JsonProperty(PropertyName = "items_to_give", Required = Required.Always)]
internal HashSet<Item> ItemsToGive { get; } = new HashSet<Item>();
[JsonProperty(PropertyName = "items_to_receive", Required = Required.Always)]
internal HashSet<Item> ItemsToReceive { get; } = new HashSet<Item>();
// Extra
internal ulong OtherSteamID64 => OtherSteamID3 == 0 ? 0 : new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
TradeOfferID = tradeOfferID;
OtherSteamID3 = otherSteamID3;
State = state;
}
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && (item.Type == Item.EType.TradingCard));
@@ -313,34 +331,35 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest {
// Constructed from code
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal HashSet<Item> Assets { get; } = new HashSet<Item>();
internal readonly HashSet<Item> Assets = new HashSet<Item>();
}
[JsonProperty(PropertyName = "newversion", Required = Required.Always)]
internal bool NewVersion { get; } = true;
[JsonProperty(PropertyName = "version", Required = Required.Always)]
internal byte Version { get; } = 2;
[JsonProperty(PropertyName = "me", Required = Required.Always)]
internal ItemList ItemsToGive { get; } = new ItemList();
internal readonly ItemList ItemsToGive = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)]
internal ItemList ItemsToReceive { get; } = new ItemList();
internal readonly ItemList ItemsToReceive = new ItemList();
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse {
// Deserialized from JSON
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
private ConfirmationResponse() { }
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails {
// Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
@@ -348,7 +367,19 @@ namespace ArchiSteamFarm.JSON {
Other
}
internal uint ConfirmationID { get; set; }
private uint _ConfirmationID;
internal uint ConfirmationID {
get { return _ConfirmationID; }
set {
if (value == 0) {
Logging.LogNullError(nameof(value));
return;
}
_ConfirmationID = value;
}
}
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
@@ -495,6 +526,8 @@ namespace ArchiSteamFarm.JSON {
return _HtmlDocument;
}
}
private ConfirmationDetails() { }
}
}
}

View File

@@ -1,4 +1,28 @@
using System;
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
@@ -163,31 +187,31 @@ namespace ArchiSteamFarm {
string idString = confirmationNode.GetAttributeValue("data-confid", null);
if (string.IsNullOrEmpty(idString)) {
Logging.LogNullError(nameof(idString), Bot.BotName);
continue;
return null;
}
uint id;
if (!uint.TryParse(idString, out id) || (id == 0)) {
Logging.LogNullError(nameof(id), Bot.BotName);
continue;
return null;
}
string keyString = confirmationNode.GetAttributeValue("data-key", null);
if (string.IsNullOrEmpty(keyString)) {
Logging.LogNullError(nameof(keyString), Bot.BotName);
continue;
return null;
}
ulong key;
if (!ulong.TryParse(keyString, out key) || (key == 0)) {
Logging.LogNullError(nameof(key), Bot.BotName);
continue;
return null;
}
HtmlNode descriptionNode = confirmationNode.SelectSingleNode(".//div[@class='mobileconf_list_entry_description']/div");
if (descriptionNode == null) {
Logging.LogNullError(nameof(descriptionNode), Bot.BotName);
continue;
return null;
}
Steam.ConfirmationDetails.EType type;

View File

@@ -1,7 +1,13 @@
using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
// TODO: This will be completely removed soon
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class ObsoleteSteamGuardAccount {
[JsonProperty("shared_secret")]
public string SharedSecret { get; set; }

View File

@@ -30,6 +30,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
@@ -61,10 +62,9 @@ namespace ArchiSteamFarm {
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
private const string ASF = "ASF";
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
private const string GithubReleaseURL = "https://api.github.com/repos/" + SharedInfo.GithubRepo + "/releases"; // GitHub API is HTTPS only
private const string GlobalConfigFile = ASF + ".json";
private const string GlobalDatabaseFile = ASF + ".db";
@@ -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,9 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericInfo("Downloading new version...");
Logging.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) {
return;
@@ -247,17 +250,17 @@ 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();
}
}
internal static void Exit(int exitCode = 0) {
WCF.StopServer();
Shutdown();
Environment.Exit(exitCode);
}
@@ -276,7 +279,7 @@ namespace ArchiSteamFarm {
return null;
}
if (GlobalConfig.Headless) {
if (GlobalConfig.Headless || !Runtime.IsUserInteractive) {
Logging.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
return null;
}
@@ -334,6 +337,10 @@ namespace ArchiSteamFarm {
}
internal static void OnBotShutdown() {
if (ShutdownResetEvent.IsSet) {
return;
}
if (Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return;
}
@@ -347,6 +354,19 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set();
}
private static void Shutdown() {
if (ShutdownResetEvent.IsSet) {
return;
}
ShutdownResetEvent.Set();
WCF.StopServer();
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
}
}
private static void InitServices() {
GlobalConfig = GlobalConfig.Load(Path.Combine(ConfigDirectory, GlobalConfigFile));
if (GlobalConfig == null) {
@@ -388,13 +408,18 @@ namespace ArchiSteamFarm {
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
continue;
if (arg.StartsWith("--cryptkey=", StringComparison.Ordinal) && (arg.Length > 11)) {
CryptoHelper.SetEncryptionKey(arg.Substring(11));
} else {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
}
break;
}
if (Mode != EMode.Client) {
Logging.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
continue;
break;
}
Logging.LogGenericInfo("Command sent: " + arg);
@@ -517,13 +542,36 @@ namespace ArchiSteamFarm {
}
private static void Main(string[] args) {
Init(args);
if (Runtime.IsUserInteractive) {
// App
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();
// We got a signal to shutdown
Exit();
} else {
// Service
using (Service service = new Service()) {
ServiceBase.Run(service);
}
}
}
private sealed class Service : ServiceBase {
internal Service() {
ServiceName = SharedInfo.ServiceName;
}
protected override void OnStart(string[] args) => Task.Run(() => {
Init(args);
ShutdownResetEvent.Wait();
Stop();
});
protected override void OnStop() => Shutdown();
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
using ArchiSteamFarm;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -9,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyCopyright("Copyright © ArchiSteamFarm 2015-2016")]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,5 +32,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.1.0.7")]
[assembly: AssemblyFileVersion("2.1.0.7")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]

View File

@@ -26,24 +26,57 @@ 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) {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool IsRunningOnMono => MonoRuntime != null;
private static bool? _IsUserInteractive;
internal static bool IsUserInteractive {
get {
if (_IsUserInteractive.HasValue) {
return _IsUserInteractive.Value;
}
if (Environment.UserInteractive) {
_IsUserInteractive = true;
return true;
}
// If it's non-Mono, we can trust the result
if (!IsRunningOnMono) {
_IsUserInteractive = false;
return false;
}
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
return true;
}
}
internal static bool RequiresWorkaroundForMonoBug41701() {
// Mono only, https://bugzilla.xamarin.com/show_bug.cgi?id=41701
if (!IsRunningOnMono) {
return false;
}
return version >= new Version(4, 4);
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
return false;
}
return monoVersion >= new Version(4, 4);
}
private static Version GetMonoVersion() {
Type type = Type.GetType("Mono.Runtime");
if (type == null) {
return null; // OK, not Mono
if (MonoRuntime == null) {
Logging.LogNullError(nameof(MonoRuntime));
return null;
}
MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName == null) {
Logging.LogNullError(nameof(displayName));
return null;

View File

@@ -0,0 +1,34 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.
*/
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.1.8";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string ServiceName = "ArchiSteamFarm";
internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.";
}
}

View File

@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -31,12 +32,23 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading {
private enum ParseTradeResult : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
Error,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
}
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
private byte ParsingTasks;
@@ -44,7 +56,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();
}
@@ -57,6 +69,8 @@ namespace ArchiSteamFarm {
Bot = bot;
}
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
internal async Task CheckTrades() {
lock (TradesSemaphore) {
if (ParsingTasks >= 2) {
@@ -81,90 +95,106 @@ namespace ArchiSteamFarm {
return;
}
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetActiveTradeOffers();
if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
return;
}
if (tradeOffers.RemoveWhere(tradeoffer => tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active) > 0) {
if (tradeOffers.RemoveWhere(tradeoffer => IgnoredTrades.Contains(tradeoffer.TradeOfferID)) > 0) {
tradeOffers.TrimExcess();
if (tradeOffers.Count == 0) {
return;
}
}
List<Task<bool>> tasks = tradeOffers.Select(ParseTrade).ToList();
bool[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
if (results.Any(result => result)) {
ParseTradeResult[] results = await Task.WhenAll(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
if (results.Any(result => result == ParseTradeResult.AcceptedWithItemLose)) {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
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<bool> ParseTrade(Steam.TradeOffer tradeOffer) {
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return false;
return ParseTradeResult.Error;
}
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
return false;
return ParseTradeResult.Error;
}
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
}
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
switch (result) {
case ParseTradeResult.AcceptedWithItemLose:
case ParseTradeResult.AcceptedWithoutItemLose:
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : ParseTradeResult.Error;
case ParseTradeResult.RejectedPermanently:
case ParseTradeResult.RejectedTemporarily:
if (result == ParseTradeResult.RejectedPermanently) {
if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : ParseTradeResult.Error;
}
if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
}
IgnoredTrades.Add(tradeOffer.TradeOfferID);
}
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return false;
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return result;
default:
return result;
}
}
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return false;
return ParseTradeResult.Error;
}
// Always accept trades when we're not losing anything
if (tradeOffer.ItemsToGive.Count == 0) {
// Unless it's steam fuckup and we're dealing with broken trade
return tradeOffer.ItemsToReceive.Count > 0;
return tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily;
}
// Always accept trades from SteamMasterID
if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
return true;
return ParseTradeResult.AcceptedWithItemLose;
}
// If we don't have SteamTradeMatcher enabled, this is the end for us
if (!Bot.BotConfig.SteamTradeMatcher) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// Decline trade if we're giving more count-wise
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// 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;
// Fetch trade hold duration
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
if (!holdDuration.HasValue) {
// If we can't get trade hold duration, reject trade temporarily
return ParseTradeResult.RejectedTemporarily;
}
// If user has a trade hold, we add extra logic
if (holdDuration.Value > 0) {
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
return ParseTradeResult.RejectedPermanently;
}
}
@@ -173,7 +203,7 @@ namespace ArchiSteamFarm {
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
return ParseTradeResult.AcceptedWithItemLose; // OK, assume that this trade is valid, we can't check our EQ
}
// Get appIDs we're interested in
@@ -185,7 +215,7 @@ namespace ArchiSteamFarm {
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {
return true;
return ParseTradeResult.AcceptedWithItemLose;
}
// Now let's create a map which maps items to their amount in our EQ
@@ -238,7 +268,7 @@ namespace ArchiSteamFarm {
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0
return difference > 0;
return difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily;
}
}
}

View File

@@ -53,14 +53,5 @@ namespace ArchiSteamFarm {
}
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
internal static Task SleepAsync(int miliseconds) {
if (miliseconds >= 0) {
return Task.Delay(miliseconds);
}
Logging.LogNullError(nameof(miliseconds));
return Task.FromResult(true);
}
}
}

View File

@@ -11,6 +11,8 @@
"FarmingDelay": 15,
"LoginLimiterDelay": 10,
"InventoryLimiterDelay": 3,
"GiftsLimiterDelay": 1,
"MaxTradeHoldDuration": 15,
"ForceHttp": false,
"HttpTimeout": 60,
"WCFHostname": "localhost",

View File

@@ -3,6 +3,7 @@
"StartOnLaunch": true,
"SteamLogin": null,
"SteamPassword": null,
"PasswordFormat": 0,
"SteamParentalPIN": "0",
"SteamApiKey": null,
"SteamMasterID": 0,

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
<package id="HtmlAgilityPack" version="1.4.9.4" targetFramework="net461" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
<package id="SteamKit2" version="1.7.0" targetFramework="net452" />
</packages>

30
CONTRIBUTING.md Normal file
View 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](https://gitter.im/JustArchi/ArchiSteamFarm)** and **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** 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.

View File

@@ -35,6 +35,12 @@ namespace ConfigGenerator {
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class BotConfig : ASFConfig {
internal enum ECryptoMethod : byte {
PlainText,
AES,
ProtectedDataForCurrentUser
}
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
@@ -48,6 +54,9 @@ namespace ConfigGenerator {
[PasswordPropertyText(true)]
public string SteamPassword { get; set; } = null;
[JsonProperty(Required = Required.DisallowNull)]
public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText;
[JsonProperty]
public string SteamParentalPIN { get; set; } = "0";

View File

@@ -40,7 +40,7 @@
</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>
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -49,6 +49,9 @@
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ArchiSteamFarm\SharedInfo.cs">
<Link>SharedInfo.cs</Link>
</Compile>
<Compile Include="ASFConfig.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="DialogBox.cs" />
@@ -106,8 +109,8 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-ConfigGenerator.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF-ConfigGenerator.exe.config"
"$(SolutionDir)tools\LibZ\libz.exe" inject-dll -a "$(TargetDir)$(TargetName).exe" -i "$(TargetDir)*.dll" --move
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF-ConfigGenerator.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mono --llvm --server -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-ConfigGenerator.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"

View File

@@ -86,6 +86,12 @@ namespace ConfigGenerator {
[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 byte MaxTradeHoldDuration { get; set; } = 15;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false;

View File

@@ -29,6 +29,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using ArchiSteamFarm;
namespace ConfigGenerator {
internal sealed partial class MainForm : Form {
@@ -196,7 +197,7 @@ namespace ConfigGenerator {
args.Cancel = true;
Tutorial.OnAction(Tutorial.EPhase.Help);
Process.Start("https://github.com/JustArchi/ArchiSteamFarm/wiki/Configuration");
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration");
Tutorial.OnAction(Tutorial.EPhase.HelpFinished);
}
}

View File

@@ -23,10 +23,12 @@
*/
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using ArchiSteamFarm;
namespace ConfigGenerator {
internal static class Program {
@@ -35,8 +37,10 @@ namespace ConfigGenerator {
internal const string GlobalConfigFile = ASF + ".json";
private const string ASFDirectory = "ArchiSteamFarm";
private const string ASFExecutableFile = ASF + ".exe";
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
private static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
/// <summary>
/// The main entry point for the application.
@@ -75,11 +79,30 @@ namespace ConfigGenerator {
}
}
if (Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
Environment.Exit(1);
}
if (!File.Exists(ASFExecutableFile)) {
return;
}
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
FileVersionInfo asfVersionInfo = FileVersionInfo.GetVersionInfo(ASFExecutableFile);
Version asfVersion = new Version(asfVersionInfo.ProductVersion);
if (Version == asfVersion) {
return;
}
Logging.LogGenericErrorWithoutStacktrace(
"Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + Version + Environment.NewLine +
Environment.NewLine +
"Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..."
);
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/releases/tag/" + asfVersion);
Environment.Exit(1);
}

View File

@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.InteropServices;
using ArchiSteamFarm;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -9,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConfigGenerator")]
[assembly: AssemblyCopyright("Copyright © ArchiSteamFarm 2015-2016")]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,5 +32,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("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
</packages>

27
docs/CodeStyle.vssettings Normal file

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -465,11 +465,12 @@
<param name="node">The node to duplicate. May not be <c>null</c>.</param>
<param name="deep">true to recursively clone the subtree under the specified node, false to clone only the node itself.</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes">
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes(System.Int32)">
<summary>
Gets all Descendant nodes for this node and each of child nodes
</summary>
<returns></returns>
<param name="level">The depth level of the node to parse in the html tree</param>
<returns>the current element as an HtmlNode</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodesAndSelf">
<summary>
@@ -477,7 +478,7 @@
</summary>
<returns></returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.Descendants">
<member name="M:HtmlAgilityPack.HtmlNode.Descendants(System.Int32)">
<summary>
Gets all Descendant nodes in enumerated list
</summary>
@@ -616,11 +617,12 @@
<param name="value">The value for the attribute.</param>
<returns>The corresponding attribute instance.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves all the children of the node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">Identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo">
<summary>
@@ -628,11 +630,12 @@
</summary>
<returns>The saved string.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves the current node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.Xml.XmlWriter)">
<summary>
@@ -786,6 +789,11 @@
Represents a complete HTML document.
</summary>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument._maxDepthLevel">
<summary>
Defines the max level we would go deep into the html document
</summary>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument.OptionAddDebuggingAttributes">
<summary>
Adds Debugging attributes to node. Default is false.
@@ -1053,6 +1061,12 @@
</summary>
<param name="writer">The XmlWriter to which you want to save.</param>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.MaxDepthLevel">
<summary>
Defines the max level we would go deep into the html document. If this depth level is exceeded, and exception is
thrown.
</summary>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.CheckSum">
<summary>
Gets the document CRC32 checksum if OptionComputeChecksum was set to true before parsing, 0 otherwise.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/LibZ/libz.exe Normal file

Binary file not shown.