mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 06:20:34 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e6723a7e2 | ||
|
|
9cc7e30c98 | ||
|
|
cbf212aff5 | ||
|
|
ab4630c191 | ||
|
|
e1e68d2d6a | ||
|
|
b594af5c44 | ||
|
|
d243bf260f | ||
|
|
af956f22e9 | ||
|
|
aac72d4c5b | ||
|
|
dc98d794b8 | ||
|
|
831856cf15 | ||
|
|
686c0aaf8b | ||
|
|
9eadf805b5 | ||
|
|
3b8315073b | ||
|
|
bd028ba459 | ||
|
|
49344751af | ||
|
|
97724c95a7 | ||
|
|
627c4576f5 | ||
|
|
a90b5adaf5 | ||
|
|
1c74fddbac | ||
|
|
4380679df6 | ||
|
|
e3030dccdb | ||
|
|
e6509ae1a3 | ||
|
|
2bc202dd3a | ||
|
|
b9e8823bf1 | ||
|
|
e7cd488435 | ||
|
|
4c09d95b9a | ||
|
|
1fe5cfff49 | ||
|
|
57014aab6d | ||
|
|
4a5bff1b84 | ||
|
|
5f0ce543ae | ||
|
|
25094592c9 | ||
|
|
8f75042b54 | ||
|
|
34c1016218 | ||
|
|
7351d07518 | ||
|
|
5a8701444a | ||
|
|
8db29fa5a7 | ||
|
|
056b793262 | ||
|
|
a4383cdb89 | ||
|
|
d5514422b6 | ||
|
|
3627a01f59 | ||
|
|
0dc131b79b | ||
|
|
8d9fbce2ed | ||
|
|
a01e718e28 | ||
|
|
9bed5f013a | ||
|
|
2cf2df62c5 | ||
|
|
9bac3915da | ||
|
|
93191f9066 | ||
|
|
ebc8c1674b | ||
|
|
5bc6af718e | ||
|
|
579d9e1cbf | ||
|
|
c7d0fb1aac | ||
|
|
1f4a4cc6b7 | ||
|
|
612ab87626 | ||
|
|
951d58161f | ||
|
|
d68bb01174 | ||
|
|
da411b81b9 | ||
|
|
82c2d3ec15 | ||
|
|
788f5c94a2 | ||
|
|
4d37a775cf | ||
|
|
766a638f0d | ||
|
|
12aa933355 | ||
|
|
9bc76ca1fe | ||
|
|
339a56dc80 | ||
|
|
d16228b2a5 | ||
|
|
99dbca1b36 | ||
|
|
943bc7e3d9 | ||
|
|
9535479602 | ||
|
|
90ade53ae7 | ||
|
|
9a51386b7e | ||
|
|
03ee96057f | ||
|
|
a23bca7960 | ||
|
|
6b4ae6a4d7 | ||
|
|
5c80fd158d | ||
|
|
885800c539 | ||
|
|
920d4b9ed6 | ||
|
|
2d02bd609e | ||
|
|
e658ae33b1 | ||
|
|
379018866b |
@@ -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"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></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>
|
||||
52
ArchiSteamFarm/ArchiServiceInstaller.cs
Normal file
52
ArchiSteamFarm/ArchiServiceInstaller.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
166
ArchiSteamFarm/CryptoHelper.cs
Normal file
166
ArchiSteamFarm/CryptoHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
34
ArchiSteamFarm/SharedInfo.cs
Normal file
34
ArchiSteamFarm/SharedInfo.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"FarmingDelay": 15,
|
||||
"LoginLimiterDelay": 10,
|
||||
"InventoryLimiterDelay": 3,
|
||||
"GiftsLimiterDelay": 1,
|
||||
"MaxTradeHoldDuration": 15,
|
||||
"ForceHttp": false,
|
||||
"HttpTimeout": 60,
|
||||
"WCFHostname": "localhost",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"StartOnLaunch": true,
|
||||
"SteamLogin": null,
|
||||
"SteamPassword": null,
|
||||
"PasswordFormat": 0,
|
||||
"SteamParentalPIN": "0",
|
||||
"SteamApiKey": null,
|
||||
"SteamMasterID": 0,
|
||||
|
||||
@@ -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
30
CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing
|
||||
|
||||
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
|
||||
|
||||
## Issues
|
||||
|
||||
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is not technical support and all cases that are not suggestions or bug reports should NOT be posted there. You have **[ASF chat](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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
27
docs/CodeStyle.vssettings
Normal file
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
BIN
packages/HtmlAgilityPack.1.4.9.4/HtmlAgilityPack.1.4.9.4.nupkg
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/HtmlAgilityPack.1.4.9.4.nupkg
vendored
Normal file
Binary file not shown.
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net20/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net20/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net40-client/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net40-client/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net40/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net40/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net45/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/Net45/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/NetCore45/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/NetCore45/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
@@ -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
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/sl5/HtmlAgilityPack.dll
vendored
Normal file
BIN
packages/HtmlAgilityPack.1.4.9.4/lib/sl5/HtmlAgilityPack.dll
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
packages/HtmlAgilityPack.1.4.9/lib/sl3-wp/_._
vendored
BIN
packages/HtmlAgilityPack.1.4.9/lib/sl3-wp/_._
vendored
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.
BIN
packages/Newtonsoft.Json.9.0.1/Newtonsoft.Json.9.0.1.nupkg
vendored
Normal file
BIN
packages/Newtonsoft.Json.9.0.1/Newtonsoft.Json.9.0.1.nupkg
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tools/LibZ/libz.exe
Normal file
BIN
tools/LibZ/libz.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user