mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-13 07:00:39 +00:00
Compare commits
70 Commits
1.2.4.0
...
1.5.0.0-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0e990f9f3 | ||
|
|
6a7a568e59 | ||
|
|
547be25b57 | ||
|
|
9559bcb3b8 | ||
|
|
049b78eb02 | ||
|
|
d55c734718 | ||
|
|
79b6fc8b17 | ||
|
|
8603e7a579 | ||
|
|
ea1b401228 | ||
|
|
ee73a12440 | ||
|
|
cad47368ac | ||
|
|
50c7d32ed7 | ||
|
|
eab136f635 | ||
|
|
ae829d7942 | ||
|
|
a3ab01bf0e | ||
|
|
4913d6c0a0 | ||
|
|
8c1ffda944 | ||
|
|
8d5d407c90 | ||
|
|
af4117690f | ||
|
|
f02be5753c | ||
|
|
691949eb08 | ||
|
|
42758eadc3 | ||
|
|
319822d1a1 | ||
|
|
948787f8ba | ||
|
|
73b5246a88 | ||
|
|
670091c293 | ||
|
|
1332d12087 | ||
|
|
55633c8d14 | ||
|
|
42f47740db | ||
|
|
aaf10cf8f8 | ||
|
|
3d54772da5 | ||
|
|
1b552b305b | ||
|
|
af93dc5b0e | ||
|
|
63e1598ebf | ||
|
|
aa2cf6dbe4 | ||
|
|
d507b40c97 | ||
|
|
3df34ece61 | ||
|
|
047af8912f | ||
|
|
fdef39887a | ||
|
|
e0d944efc5 | ||
|
|
74e416394a | ||
|
|
35fb7c4e7f | ||
|
|
8d06dd90d8 | ||
|
|
254b0a7773 | ||
|
|
3fc51af261 | ||
|
|
20ea58981d | ||
|
|
f8c1582aeb | ||
|
|
13722ca8f8 | ||
|
|
da3537eba2 | ||
|
|
67f0b0f8e9 | ||
|
|
08a962e688 | ||
|
|
5b711f158d | ||
|
|
0ddcf004c1 | ||
|
|
4997cc48ca | ||
|
|
51bcf73e15 | ||
|
|
d7b21ded96 | ||
|
|
daf4efa054 | ||
|
|
6a12a26612 | ||
|
|
533058aa3f | ||
|
|
804d1260f2 | ||
|
|
78fcf53606 | ||
|
|
ceb641d1fa | ||
|
|
47fdff2993 | ||
|
|
1a96a975f9 | ||
|
|
cd460c5ec5 | ||
|
|
70b7f9d5dd | ||
|
|
f236d6c33f | ||
|
|
356df9098e | ||
|
|
9a8aa1e10d | ||
|
|
7aa985154f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,6 +7,9 @@ ArchiSteamFarm/config/*
|
||||
!ArchiSteamFarm/config/example.xml
|
||||
!ArchiSteamFarm/config/minimal.xml
|
||||
|
||||
# Ignore local debugging log file
|
||||
ArchiSteamFarm/log.txt
|
||||
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -24,18 +24,61 @@
|
||||
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ArchiHandler : ClientMsgHandler {
|
||||
/*
|
||||
____ _ _ _ _
|
||||
/ ___| __ _ | || || |__ __ _ ___ | | __ ___
|
||||
| | / _` || || || '_ \ / _` | / __|| |/ // __|
|
||||
| |___| (_| || || || |_) || (_| || (__ | < \__ \
|
||||
\____|\__,_||_||_||_.__/ \__,_| \___||_|\_\|___/
|
||||
|
||||
*/
|
||||
|
||||
internal sealed class NotificationsCallback : CallbackMsg {
|
||||
internal class Notification {
|
||||
internal enum ENotificationType {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
}
|
||||
|
||||
internal ENotificationType NotificationType { get; set; }
|
||||
}
|
||||
|
||||
internal List<Notification> Notifications { get; private set; }
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notifications = new List<Notification>();
|
||||
foreach (var notification in msg.notifications) {
|
||||
Notifications.Add(new Notification {
|
||||
NotificationType = (Notification.ENotificationType) notification.user_notification_type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class OfflineMessageCallback : CallbackMsg {
|
||||
internal uint OfflineMessages { get; private set; }
|
||||
internal List<uint> Users { get; private set; }
|
||||
internal OfflineMessageCallback(CMsgClientOfflineMessageNotification body) {
|
||||
OfflineMessages = body.offline_messages;
|
||||
Users = body.friends_with_offline_messages;
|
||||
|
||||
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
OfflineMessages = msg.offline_messages;
|
||||
Users = msg.friends_with_offline_messages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +87,7 @@ namespace ArchiSteamFarm {
|
||||
Unknown = -1,
|
||||
OK = 0,
|
||||
AlreadyOwned = 9,
|
||||
RegionLockedKey = 13,
|
||||
RegionLocked = 13,
|
||||
InvalidKey = 14,
|
||||
DuplicatedKey = 15,
|
||||
BaseGameRequired = 24,
|
||||
@@ -53,14 +96,23 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal EResult Result { get; private set; }
|
||||
internal EPurchaseResult PurchaseResult { get; private set; }
|
||||
internal KeyValue ReceiptInfo { get; private set; } = new KeyValue();
|
||||
internal Dictionary<uint, string> Items { get; private set; } = new Dictionary<uint, string>();
|
||||
internal KeyValue ReceiptInfo { get; private set; }
|
||||
internal Dictionary<uint, string> Items { get; private set; }
|
||||
|
||||
internal PurchaseResponseCallback(CMsgClientPurchaseResponse body) {
|
||||
Result = (EResult) body.eresult;
|
||||
PurchaseResult = (EPurchaseResult) body.purchase_result_details;
|
||||
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
|
||||
JobID = jobID;
|
||||
|
||||
using (MemoryStream ms = new MemoryStream(body.purchase_receipt_info)) {
|
||||
if (msg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiptInfo = new KeyValue();
|
||||
Items = new Dictionary<uint, string>();
|
||||
|
||||
Result = (EResult) msg.eresult;
|
||||
PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
|
||||
|
||||
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
|
||||
if (!ReceiptInfo.TryReadAsBinary(ms)) {
|
||||
return;
|
||||
}
|
||||
@@ -72,34 +124,44 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotificationCallback : CallbackMsg {
|
||||
internal enum ENotificationType {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
}
|
||||
/*
|
||||
__ __ _ _ _
|
||||
| \/ | ___ | |_ | |__ ___ __| | ___
|
||||
| |\/| | / _ \| __|| '_ \ / _ \ / _` |/ __|
|
||||
| | | || __/| |_ | | | || (_) || (_| |\__ \
|
||||
|_| |_| \___| \__||_| |_| \___/ \__,_||___/
|
||||
|
||||
internal ENotificationType NotificationType { get; private set; }
|
||||
|
||||
internal NotificationCallback(CMsgClientUserNotifications.Notification body) {
|
||||
NotificationType = (ENotificationType) body.user_notification_type;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
internal void AcceptClanInvite(ulong clanID) {
|
||||
if (clanID == 0 || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = true;
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void DeclineClanInvite(ulong clanID) {
|
||||
if (clanID == 0 || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = false;
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(params uint[] gameIDs) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
foreach (uint gameID in gameIDs) {
|
||||
if (gameID == 0) {
|
||||
@@ -110,10 +172,15 @@ namespace ArchiSteamFarm {
|
||||
game_id = new GameID(gameID),
|
||||
});
|
||||
}
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(ICollection<uint> gameIDs) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
foreach (uint gameID in gameIDs) {
|
||||
if (gameID == 0) {
|
||||
@@ -124,16 +191,32 @@ namespace ArchiSteamFarm {
|
||||
game_id = new GameID(gameID),
|
||||
});
|
||||
}
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
// Will provide result in ClientPurchaseResponse, regardless if success or not
|
||||
internal void RedeemKey(string key) {
|
||||
internal AsyncJob<PurchaseResponseCallback> RedeemKey(string key) {
|
||||
if (string.IsNullOrEmpty(key) || !Client.IsConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey);
|
||||
request.SourceJobID = Client.GetNextJobID();
|
||||
request.Body.key = key;
|
||||
|
||||
Client.Send(request);
|
||||
return new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
|
||||
}
|
||||
|
||||
/*
|
||||
_ _ _ _
|
||||
| | | | __ _ _ __ __| || | ___ _ __ ___
|
||||
| |_| | / _` || '_ \ / _` || | / _ \| '__|/ __|
|
||||
| _ || (_| || | | || (_| || || __/| | \__ \
|
||||
|_| |_| \__,_||_| |_| \__,_||_| \___||_| |___/
|
||||
|
||||
*/
|
||||
|
||||
public sealed override void HandleMsg(IPacketMsg packetMsg) {
|
||||
if (packetMsg == null) {
|
||||
return;
|
||||
@@ -158,7 +241,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientOfflineMessageNotification>(packetMsg);
|
||||
Client.PostCallback(new OfflineMessageCallback(response.Body));
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
|
||||
private void HandlePurchaseResponse(IPacketMsg packetMsg) {
|
||||
@@ -167,7 +254,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
|
||||
Client.PostCallback(new PurchaseResponseCallback(response.Body));
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new PurchaseResponseCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
|
||||
private void HandleUserNotifications(IPacketMsg packetMsg) {
|
||||
@@ -176,9 +267,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
|
||||
foreach (var notification in response.Body.notifications) {
|
||||
Client.PostCallback(new NotificationCallback(notification));
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
@@ -66,7 +66,7 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.1-beta4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\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">
|
||||
@@ -79,6 +79,7 @@
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
@@ -97,9 +98,12 @@
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SteamItem.cs" />
|
||||
<Compile Include="SteamItemList.cs" />
|
||||
<Compile Include="SteamTradeOffer.cs" />
|
||||
<Compile Include="SteamTradeOfferRequest.cs" />
|
||||
<Compile Include="Trading.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
<Compile Include="WCF.cs" />
|
||||
<Compile Include="WebBrowser.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -139,12 +143,20 @@
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent Condition=" '$(OS)' != 'Unix' ">if $(ConfigurationName) == Release (
|
||||
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
|
||||
copy "$(TargetDir)config\example.xml" "$(TargetDir)out\config"
|
||||
copy "$(TargetDir)config\minimal.xml" "$(TargetDir)out\config"
|
||||
"$(SolutionDir)tools\ILMerge.exe" /ndebug /internalize /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll" /target:exe /targetplatform:v4,"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2" /wildcards
|
||||
)</PostBuildEvent>
|
||||
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
|
||||
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
|
||||
copy "$(TargetDir)config\example.xml" "$(TargetDir)out\config"
|
||||
copy "$(TargetDir)config\minimal.xml" "$(TargetDir)out\config"
|
||||
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
|
||||
del "$(TargetDir)out\ASF.exe.config"
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
|
||||
mkdir "$(TargetDir)out" "$(TargetDir)out/config"
|
||||
cp "$(TargetDir)config/example.xml" "$(TargetDir)out/config"
|
||||
cp "$(TargetDir)config/minimal.xml" "$(TargetDir)out/config"
|
||||
mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
|
||||
rm "$(TargetDir)out/ASF.exe.config"
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,7 +21,8 @@
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using HtmlAgilityPack;
|
||||
using SteamKit2;
|
||||
using System;
|
||||
@@ -38,7 +39,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly string ApiKey;
|
||||
private readonly Dictionary<string, string> SteamCookieDictionary = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>();
|
||||
|
||||
private ulong SteamID;
|
||||
private string VanityURL;
|
||||
@@ -46,9 +47,9 @@ namespace ArchiSteamFarm {
|
||||
// This is required because home_process request must be done on final URL
|
||||
private string GetHomeProcess() {
|
||||
if (!string.IsNullOrEmpty(VanityURL)) {
|
||||
return "http://steamcommunity.com/id/" + VanityURL + "/home_process";
|
||||
return "https://steamcommunity.com/id/" + VanityURL + "/home_process";
|
||||
} else if (SteamID != 0) {
|
||||
return "http://steamcommunity.com/profiles/" + SteamID + "/home_process";
|
||||
return "https://steamcommunity.com/profiles/" + SteamID + "/home_process";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -59,20 +60,24 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Unlocking parental account...");
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
{ "pin", parentalPin }
|
||||
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{ "pin", parentalPin }
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", postData, SteamCookieDictionary, "https://steamcommunity.com/").ConfigureAwait(false);
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", data, Cookie, "https://steamcommunity.com/").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Failed!");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<string> setCookieValues;
|
||||
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Failed!");
|
||||
Logging.LogNullError("setCookieValues", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,13 +85,13 @@ namespace ArchiSteamFarm {
|
||||
if (setCookieValue.Contains("steamparental=")) {
|
||||
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14);
|
||||
setCookie = setCookie.Substring(0, setCookie.IndexOf(';'));
|
||||
SteamCookieDictionary.Add("steamparental", setCookie);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Success!");
|
||||
Cookie.Add("steamparental", setCookie);
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Failed!");
|
||||
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);
|
||||
}
|
||||
|
||||
internal ArchiWebHandler(Bot bot, string apiKey) {
|
||||
@@ -125,7 +130,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Send the magic
|
||||
KeyValue authResult;
|
||||
Logging.LogGenericInfo(Bot.BotName, "Logging in to ISteamUserAuth...");
|
||||
Logging.LogGenericInfo("Logging in to ISteamUserAuth...", Bot.BotName);
|
||||
using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
|
||||
iSteamUserAuth.Timeout = Timeout;
|
||||
|
||||
@@ -138,7 +143,7 @@ namespace ArchiSteamFarm {
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -147,16 +152,15 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Success!");
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
|
||||
string steamLogin = authResult["token"].AsString();
|
||||
string steamLoginSecure = authResult["tokensecure"].AsString();
|
||||
|
||||
SteamCookieDictionary.Clear();
|
||||
SteamCookieDictionary.Add("sessionid", sessionID);
|
||||
SteamCookieDictionary.Add("steamLogin", steamLogin);
|
||||
SteamCookieDictionary.Add("steamLoginSecure", steamLoginSecure);
|
||||
SteamCookieDictionary.Add("birthtime", "-473356799"); // ( ͡° ͜ʖ ͡°)
|
||||
Cookie["sessionid"] = sessionID;
|
||||
Cookie["steamLogin"] = steamLogin;
|
||||
Cookie["steamLoginSecure"] = steamLoginSecure;
|
||||
Cookie["birthtime"] = "-473356799";
|
||||
|
||||
await UnlockParentalAccount(parentalPin).ConfigureAwait(false);
|
||||
return true;
|
||||
@@ -167,8 +171,13 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/my/profile", SteamCookieDictionary).ConfigureAwait(false);
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/my/profile", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -179,7 +188,7 @@ namespace ArchiSteamFarm {
|
||||
internal async Task<bool> ReconnectIfNeeded() {
|
||||
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
|
||||
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Reconnecting because our sessionID expired!");
|
||||
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
|
||||
var restart = Task.Run(async () => await Bot.Restart().ConfigureAwait(false));
|
||||
return true;
|
||||
}
|
||||
@@ -192,25 +201,25 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValue response;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
|
||||
// Timeout
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.GetTradeOffers(
|
||||
key: ApiKey,
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
return null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
try {
|
||||
response = iEconService.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -221,7 +230,7 @@ namespace ArchiSteamFarm {
|
||||
accountid_other = trade["accountid_other"].AsInteger(),
|
||||
message = trade["message"].AsString(),
|
||||
expiration_time = trade["expiration_time"].AsInteger(),
|
||||
trade_offer_state = (SteamTradeOffer.ETradeOfferState) trade["trade_offer_state"].AsInteger(),
|
||||
trade_offer_state = trade["trade_offer_state"].AsEnum<SteamTradeOffer.ETradeOfferState>(),
|
||||
items_to_give = new List<SteamItem>(),
|
||||
items_to_receive = new List<SteamItem>(),
|
||||
is_our_offer = trade["is_our_offer"].AsBoolean(),
|
||||
@@ -229,7 +238,7 @@ namespace ArchiSteamFarm {
|
||||
time_updated = trade["time_updated"].AsInteger(),
|
||||
from_real_time_trade = trade["from_real_time_trade"].AsBoolean(),
|
||||
escrow_end_date = trade["escrow_end_date"].AsInteger(),
|
||||
confirmation_method = (SteamTradeOffer.ETradeOfferConfirmationMethod) trade["confirmation_method"].AsInteger()
|
||||
confirmation_method = trade["confirmation_method"].AsEnum<SteamTradeOffer.ETradeOfferConfirmationMethod>()
|
||||
};
|
||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
||||
tradeOffer.items_to_give.Add(new SteamItem {
|
||||
@@ -261,44 +270,64 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task JoinClan(ulong clanID) {
|
||||
internal async Task<bool> JoinClan(ulong clanID) {
|
||||
if (clanID == 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
|
||||
return;
|
||||
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = "http://steamcommunity.com/gid/" + clanID;
|
||||
string request = "https://steamcommunity.com/gid/" + clanID;
|
||||
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "join"}
|
||||
};
|
||||
|
||||
await WebBrowser.UrlPost(request, postData, SteamCookieDictionary).ConfigureAwait(false);
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task LeaveClan(ulong clanID) {
|
||||
internal async Task<bool> LeaveClan(ulong clanID) {
|
||||
if (clanID == 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
|
||||
return;
|
||||
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = GetHomeProcess();
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "leaveGroup"},
|
||||
{"groupId", clanID.ToString()}
|
||||
};
|
||||
|
||||
await WebBrowser.UrlPost(request, postData, SteamCookieDictionary).ConfigureAwait(false);
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
@@ -307,84 +336,176 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
|
||||
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID + "/";
|
||||
string request = referer + "accept";
|
||||
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID;
|
||||
string request = referer + "/accept";
|
||||
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"tradeofferid", tradeID.ToString()}
|
||||
};
|
||||
|
||||
HttpResponseMessage result = await WebBrowser.UrlPost(request, postData, SteamCookieDictionary, referer).ConfigureAwait(false);
|
||||
if (result == null) {
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = result.IsSuccessStatusCode;
|
||||
|
||||
if (!success) {
|
||||
Logging.LogGenericWarning(Bot.BotName, "Request failed, reason: " + result.ReasonPhrase);
|
||||
switch (result.StatusCode) {
|
||||
case HttpStatusCode.InternalServerError:
|
||||
Logging.LogGenericWarning(Bot.BotName, "That might be caused by 7-days trade lock from new device");
|
||||
Logging.LogGenericWarning(Bot.BotName, "Try again in 7 days, declining that offer for now");
|
||||
DeclineTradeOffer(tradeID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||
if (ApiKey == null) {
|
||||
if (tradeID == 0 || ApiKey == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tradeID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValue response;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
|
||||
// Timeout
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
key: ApiKey,
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
try {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<List<SteamItem>> GetInventory() {
|
||||
JObject jObject = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
|
||||
jObject = await WebBrowser.UrlGetToJObject("https://steamcommunity.com/my/inventory/json/753/6", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (jObject == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<SteamItem> result = new List<SteamItem>();
|
||||
|
||||
IEnumerable<JToken> jTokens = jObject.SelectTokens("$.rgInventory.*");
|
||||
foreach (JToken jToken in jTokens) {
|
||||
result.Add(JsonConvert.DeserializeObject<SteamItem>(jToken.ToString()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<bool> SendTradeOffer(List<SteamItem> inventory, ulong partnerID, string token = null) {
|
||||
if (inventory == null || inventory.Count == 0 || partnerID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<SteamTradeOfferRequest> trades = new List<SteamTradeOfferRequest>();
|
||||
|
||||
SteamTradeOfferRequest singleTrade = null;
|
||||
for (ushort i = 0; i < inventory.Count; i++) {
|
||||
if (i % Trading.MaxItemsPerTrade == 0) {
|
||||
if (trades.Count >= Trading.MaxTradesPerAccount) {
|
||||
break;
|
||||
}
|
||||
|
||||
singleTrade = new SteamTradeOfferRequest();
|
||||
trades.Add(singleTrade);
|
||||
}
|
||||
|
||||
SteamItem item = inventory[i];
|
||||
singleTrade.me.assets.Add(new SteamItem() {
|
||||
appid = "753",
|
||||
contextid = "6",
|
||||
amount = item.amount,
|
||||
assetid = item.id
|
||||
});
|
||||
}
|
||||
|
||||
string referer = "https://steamcommunity.com/tradeoffer/new";
|
||||
string request = referer + "/send";
|
||||
|
||||
foreach (SteamTradeOfferRequest trade in trades) {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"partner", partnerID.ToString()},
|
||||
{"tradeoffermessage", "Sent by ASF"},
|
||||
{"json_tradeoffer", JsonConvert.SerializeObject(trade)},
|
||||
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : string.Format("{{ \"trade_offer_access_token\":\"{0}\" }}", token)} // TODO: This should be rewrote
|
||||
};
|
||||
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return response != null; // Steam API doesn't respond with any error code, assume any response is a success
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> GetBadgePage(int page) {
|
||||
if (SteamID == 0 || page == 0) {
|
||||
internal async Task<HtmlDocument> GetBadgePage(byte page) {
|
||||
if (page == 0 || SteamID == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/badges?l=english&p=" + page, SteamCookieDictionary).ConfigureAwait(false);
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
|
||||
if (SteamID == 0 || appID == 0) {
|
||||
if (appID == 0 || SteamID == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await WebBrowser.UrlGetToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", SteamCookieDictionary).ConfigureAwait(false);
|
||||
HtmlDocument htmlDocument = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument("https://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return htmlDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -31,30 +31,35 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Text;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Bot {
|
||||
private const ulong ArchiSCFarmGroup = 103582791440160998;
|
||||
private const ushort CallbackSleep = 500; // In miliseconds
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
||||
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
|
||||
|
||||
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
||||
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 303700, 335590, 368020, 425280 };
|
||||
|
||||
private readonly string ConfigFile, LoginKeyFile, MobileAuthenticatorFile, SentryFile;
|
||||
private readonly Timer SendItemsTimer;
|
||||
|
||||
private readonly CallbackManager CallbackManager;
|
||||
private readonly CardsFarmer CardsFarmer;
|
||||
private readonly SteamApps SteamApps;
|
||||
private readonly SteamFriends SteamFriends;
|
||||
private readonly SteamUser SteamUser;
|
||||
private readonly Trading Trading;
|
||||
|
||||
internal readonly string BotName;
|
||||
internal readonly ArchiHandler ArchiHandler;
|
||||
internal readonly ArchiWebHandler ArchiWebHandler;
|
||||
internal readonly CallbackManager CallbackManager;
|
||||
internal readonly CardsFarmer CardsFarmer;
|
||||
internal readonly SteamClient SteamClient;
|
||||
internal readonly SteamFriends SteamFriends;
|
||||
internal readonly SteamUser SteamUser;
|
||||
internal readonly Trading Trading;
|
||||
|
||||
private bool KeepRunning = true;
|
||||
private bool InvalidPassword = false;
|
||||
@@ -75,8 +80,13 @@ namespace ArchiSteamFarm {
|
||||
internal bool CardDropsRestricted { get; private set; } = false;
|
||||
internal bool FarmOffline { get; private set; } = false;
|
||||
internal bool HandleOfflineMessages { get; private set; } = false;
|
||||
internal bool ForwardKeysToOtherBots { get; private set; } = false;
|
||||
internal bool DistributeKeys { get; private set; } = false;
|
||||
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
|
||||
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
||||
internal bool SendOnFarmingFinished { get; private set; } = false;
|
||||
internal string SteamTradeToken { get; private set; } = "null";
|
||||
internal byte SendTradePeriod { get; private set; } = 0;
|
||||
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>();
|
||||
internal bool Statistics { get; private set; } = true;
|
||||
|
||||
@@ -85,17 +95,18 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.Length != 17 && key.Length != 29) {
|
||||
return false;
|
||||
// Steam keys are offered in many formats: https://support.steampowered.com/kb_article.php?ref=7480-WUSF-3601
|
||||
// It's pointless to implement them all, so we'll just do a simple check if key is supposed to be valid
|
||||
// Every valid key, apart from Prey one has at least two dashes
|
||||
return Utilities.GetCharCountInString(key, '-') >= 2;
|
||||
}
|
||||
|
||||
internal static string GetAnyBotName() {
|
||||
foreach (string botName in Bots.Keys) {
|
||||
return botName;
|
||||
}
|
||||
|
||||
for (byte i = 5; i < key.Length; i += 6) {
|
||||
if (key[i] != '-') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static int GetRunningBotsCount() {
|
||||
@@ -110,6 +121,20 @@ namespace ArchiSteamFarm {
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static void RefreshCMs() {
|
||||
bool initialized = false;
|
||||
while (!initialized) {
|
||||
try {
|
||||
Logging.LogGenericInfo("Refreshing list of CMs...");
|
||||
SteamDirectory.Initialize().Wait();
|
||||
initialized = true;
|
||||
Logging.LogGenericInfo("Success!");
|
||||
} catch (TaskCanceledException) {
|
||||
Logging.LogGenericWarning("Failed! Retrying...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Bot(string botName) {
|
||||
if (Bots.ContainsKey(botName)) {
|
||||
return;
|
||||
@@ -117,10 +142,10 @@ namespace ArchiSteamFarm {
|
||||
|
||||
BotName = botName;
|
||||
|
||||
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
|
||||
LoginKeyFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".key");
|
||||
MobileAuthenticatorFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".auth");
|
||||
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
|
||||
ConfigFile = Path.Combine(Program.ConfigDirectory, BotName + ".xml");
|
||||
LoginKeyFile = Path.Combine(Program.ConfigDirectory, BotName + ".key");
|
||||
MobileAuthenticatorFile = Path.Combine(Program.ConfigDirectory, BotName + ".auth");
|
||||
SentryFile = Path.Combine(Program.ConfigDirectory, BotName + ".bin");
|
||||
|
||||
if (!ReadConfig()) {
|
||||
return;
|
||||
@@ -142,6 +167,9 @@ namespace ArchiSteamFarm {
|
||||
CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
|
||||
CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);
|
||||
|
||||
SteamApps = SteamClient.GetHandler<SteamApps>();
|
||||
CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
|
||||
|
||||
SteamFriends = SteamClient.GetHandler<SteamFriends>();
|
||||
CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
|
||||
CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
|
||||
@@ -160,7 +188,7 @@ namespace ArchiSteamFarm {
|
||||
CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
|
||||
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
|
||||
|
||||
CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
|
||||
CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
|
||||
CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
|
||||
CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
|
||||
|
||||
@@ -168,6 +196,15 @@ namespace ArchiSteamFarm {
|
||||
CardsFarmer = new CardsFarmer(this);
|
||||
Trading = new Trading(this);
|
||||
|
||||
if (SendTradePeriod > 0 && SendItemsTimer == null) {
|
||||
SendItemsTimer = new Timer(
|
||||
async e => await ResponseSendTrade(BotName).ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromHours(SendTradePeriod), // Delay
|
||||
TimeSpan.FromHours(SendTradePeriod) // Period
|
||||
);
|
||||
}
|
||||
|
||||
// Start
|
||||
var handleCallbacks = Task.Run(() => HandleCallbacks());
|
||||
var start = Task.Run(async () => await Start().ConfigureAwait(false));
|
||||
@@ -183,15 +220,15 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
foreach (Confirmation confirmation in await SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) {
|
||||
if (SteamGuardAccount.AcceptConfirmation(confirmation)) {
|
||||
Logging.LogGenericInfo(BotName, "Accepting confirmation: Success!");
|
||||
Logging.LogGenericInfo("Accepting confirmation: Success!", BotName);
|
||||
} else {
|
||||
Logging.LogGenericWarning(BotName, "Accepting confirmation: Failed!");
|
||||
Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName);
|
||||
}
|
||||
}
|
||||
} catch (SteamGuardAccount.WGTokenInvalidException) {
|
||||
Logging.LogGenericWarning(BotName, "Accepting confirmation: Failed!");
|
||||
Logging.LogGenericWarning(BotName, "Confirmation could not be accepted because of invalid token exception");
|
||||
Logging.LogGenericWarning(BotName, "If issue persists, consider removing and readding ASF 2FA");
|
||||
Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName);
|
||||
Logging.LogGenericWarning("Confirmation could not be accepted because of invalid token exception", BotName);
|
||||
Logging.LogGenericWarning("If issue persists, consider removing and readding ASF 2FA", BotName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +237,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericNotice(BotName, "Linking new ASF MobileAuthenticator...");
|
||||
Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName);
|
||||
UserLogin userLogin = new UserLogin(SteamLogin, SteamPassword);
|
||||
LoginResult loginResult;
|
||||
while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) {
|
||||
@@ -209,7 +246,7 @@ namespace ArchiSteamFarm {
|
||||
userLogin.EmailCode = Program.GetUserInput(BotName, Program.EUserInputType.SteamGuard);
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericError(BotName, "Unhandled situation: " + loginResult);
|
||||
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -223,7 +260,7 @@ namespace ArchiSteamFarm {
|
||||
authenticatorLinker.PhoneNumber = Program.GetUserInput(BotName, Program.EUserInputType.PhoneNumber);
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericError(BotName, "Unhandled situation: " + linkResult);
|
||||
Logging.LogGenericError("Unhandled situation: " + linkResult, BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -233,18 +270,18 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
File.WriteAllText(MobileAuthenticatorFile, JsonConvert.SerializeObject(SteamGuardAccount));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(BotName, e);
|
||||
Logging.LogGenericException(e, BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(BotName, Program.EUserInputType.SMS));
|
||||
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
|
||||
Logging.LogGenericError(BotName, "Unhandled situation: " + finalizeResult);
|
||||
Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName);
|
||||
DelinkMobileAuthenticator();
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Successfully linked ASF as new mobile authenticator for this account!");
|
||||
Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName);
|
||||
Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, SteamGuardAccount.RevocationCode);
|
||||
return true;
|
||||
}
|
||||
@@ -299,6 +336,9 @@ namespace ArchiSteamFarm {
|
||||
case "SteamApiKey":
|
||||
SteamApiKey = value;
|
||||
break;
|
||||
case "SteamTradeToken":
|
||||
SteamTradeToken = value;
|
||||
break;
|
||||
case "SteamParentalPIN":
|
||||
SteamParentalPIN = value;
|
||||
break;
|
||||
@@ -320,9 +360,21 @@ namespace ArchiSteamFarm {
|
||||
case "HandleOfflineMessages":
|
||||
HandleOfflineMessages = bool.Parse(value);
|
||||
break;
|
||||
case "ForwardKeysToOtherBots":
|
||||
ForwardKeysToOtherBots = bool.Parse(value);
|
||||
break;
|
||||
case "DistributeKeys":
|
||||
DistributeKeys = bool.Parse(value);
|
||||
break;
|
||||
case "ShutdownOnFarmingFinished":
|
||||
ShutdownOnFarmingFinished = bool.Parse(value);
|
||||
break;
|
||||
case "SendOnFarmingFinished":
|
||||
SendOnFarmingFinished = bool.Parse(value);
|
||||
break;
|
||||
case "SendTradePeriod":
|
||||
SendTradePeriod = byte.Parse(value);
|
||||
break;
|
||||
case "Blacklist":
|
||||
Blacklist.Clear();
|
||||
foreach (string appID in value.Split(',')) {
|
||||
@@ -333,14 +385,14 @@ namespace ArchiSteamFarm {
|
||||
Statistics = bool.Parse(value);
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericWarning(BotName, "Unrecognized config value: " + key + "=" + value);
|
||||
Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value, BotName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(BotName, e);
|
||||
Logging.LogGenericError(BotName, "Your config for this bot instance is invalid, it won't run!");
|
||||
Logging.LogGenericException(e, BotName);
|
||||
Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!", BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -357,7 +409,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Starting...");
|
||||
Logging.LogGenericInfo("Starting...", BotName);
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorAuth == null) {
|
||||
@@ -374,31 +426,37 @@ namespace ArchiSteamFarm {
|
||||
|
||||
await Utilities.SleepAsync(0); // TODO: This is here only to make VS happy, for now
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Stopping...");
|
||||
Logging.LogGenericInfo("Stopping...", BotName);
|
||||
|
||||
SteamClient.Disconnect();
|
||||
}
|
||||
|
||||
internal async Task<bool> Shutdown(string botName = null) {
|
||||
internal async Task Shutdown() {
|
||||
KeepRunning = false;
|
||||
await Stop().ConfigureAwait(false);
|
||||
Bot bot;
|
||||
Bots.TryRemove(BotName, out bot);
|
||||
Program.OnBotShutdown();
|
||||
}
|
||||
|
||||
internal static async Task<bool> Shutdown(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
bot = this;
|
||||
} else {
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bot.KeepRunning = false;
|
||||
await bot.Stop().ConfigureAwait(false);
|
||||
Bots.TryRemove(bot.BotName, out bot);
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Program.OnBotShutdown();
|
||||
await bot.Shutdown().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task OnFarmingFinished() {
|
||||
if (SendOnFarmingFinished) {
|
||||
await ResponseSendTrade(BotName).ConfigureAwait(false);
|
||||
}
|
||||
if (ShutdownOnFarmingFinished) {
|
||||
await Shutdown().ConfigureAwait(false);
|
||||
}
|
||||
@@ -424,183 +482,420 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void ResponseStatus(ulong steamID, string botName = null) {
|
||||
if (steamID == 0) {
|
||||
return;
|
||||
internal static string ResponseStatus(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
bot = this;
|
||||
} else {
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
SendMessage(steamID, "Couldn't find any bot named " + botName + "!");
|
||||
return;
|
||||
}
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
if (bot.CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||
SendMessage(steamID, "Bot " + bot.BotName + " is currently farming appIDs: " + string.Join(", ", bot.CardsFarmer.CurrentGamesFarming) + " and has a total of " + bot.CardsFarmer.GamesToFarm.Count + " games left to farm");
|
||||
}
|
||||
SendMessage(steamID, "Currently " + Bots.Count + " bots are running");
|
||||
return bot.ResponseStatus();
|
||||
}
|
||||
|
||||
private void Response2FA(ulong steamID, string botName = null) {
|
||||
if (steamID == 0) {
|
||||
return;
|
||||
internal string ResponseStatus() {
|
||||
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
|
||||
return "Bot " + BotName + " is currently farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
|
||||
} else {
|
||||
return "Bot " + BotName + " is not farming.";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ResponseStatusAll() {
|
||||
StringBuilder result = new StringBuilder(Environment.NewLine);
|
||||
foreach (Bot bot in Bots.Values) {
|
||||
result.Append(bot.ResponseStatus() + Environment.NewLine);
|
||||
}
|
||||
|
||||
result.Append("Currently " + Bots.Count + " bots are running.");
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
internal static async Task<string> ResponseSendTrade(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
bot = this;
|
||||
if (bot.SteamMasterID == 0) {
|
||||
return "Trade couldn't be send because SteamMasterID is not defined!";
|
||||
}
|
||||
|
||||
string token = null;
|
||||
if (!string.IsNullOrEmpty(bot.SteamTradeToken) && !bot.SteamTradeToken.Equals("null")) {
|
||||
token = bot.SteamTradeToken;
|
||||
}
|
||||
|
||||
List<SteamItem> inventory = await bot.ArchiWebHandler.GetInventory().ConfigureAwait(false);
|
||||
if (inventory == null || inventory.Count == 0) {
|
||||
return "Nothing to send, inventory seems empty!";
|
||||
}
|
||||
|
||||
if (await bot.ArchiWebHandler.SendTradeOffer(inventory, bot.SteamMasterID, token).ConfigureAwait(false)) {
|
||||
await bot.AcceptAllConfirmations().ConfigureAwait(false);
|
||||
return "Trade offer sent successfully!";
|
||||
} else {
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
SendMessage(steamID, "Couldn't find any bot named " + botName + "!");
|
||||
return;
|
||||
}
|
||||
return "Trade offer failed due to error!";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Response2FA(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
if (bot.SteamGuardAccount == null) {
|
||||
SendMessage(steamID, "That bot doesn't have ASF 2FA enabled!");
|
||||
return;
|
||||
return "That bot doesn't have ASF 2FA enabled!";
|
||||
}
|
||||
|
||||
long timeLeft = 30 - TimeAligner.GetSteamTime() % 30;
|
||||
SendMessage(steamID, "2FA Token: " + bot.SteamGuardAccount.GenerateSteamGuardCode() + " (expires in " + timeLeft + " seconds)");
|
||||
return "2FA Token: " + bot.SteamGuardAccount.GenerateSteamGuardCode() + " (expires in " + timeLeft + " seconds)";
|
||||
}
|
||||
|
||||
private void Response2FAOff(ulong steamID, string botName = null) {
|
||||
if (steamID == 0) {
|
||||
return;
|
||||
internal static string Response2FAOff(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
bot = this;
|
||||
} else {
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
SendMessage(steamID, "Couldn't find any bot named " + botName + "!");
|
||||
return;
|
||||
}
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
if (bot.SteamGuardAccount == null) {
|
||||
SendMessage(steamID, "That bot doesn't have ASF 2FA enabled!");
|
||||
return;
|
||||
return "That bot doesn't have ASF 2FA enabled!";
|
||||
}
|
||||
|
||||
if (bot.DelinkMobileAuthenticator()) {
|
||||
SendMessage(steamID, "Done! Bot is no longer using ASF 2FA");
|
||||
return "Done! Bot is no longer using ASF 2FA";
|
||||
} else {
|
||||
SendMessage(steamID, "Something went wrong!");
|
||||
return "Something went wrong during delinking mobile authenticator!";
|
||||
}
|
||||
}
|
||||
|
||||
private void ResponseStart(ulong steamID, string botName) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(botName)) {
|
||||
return;
|
||||
internal async Task<string> ResponseRedeem(string message, bool validate) {
|
||||
StringBuilder response = new StringBuilder();
|
||||
using (StringReader reader = new StringReader(message)) {
|
||||
string key = reader.ReadLine();
|
||||
IEnumerator<Bot> iterator = Bots.Values.GetEnumerator();
|
||||
Bot currentBot = this;
|
||||
while (key != null) {
|
||||
if (currentBot == null) {
|
||||
break;
|
||||
}
|
||||
if (validate && !IsValidCdKey(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArchiHandler.PurchaseResponseCallback result;
|
||||
try {
|
||||
result = await currentBot.ArchiHandler.RedeemKey(key);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, currentBot.BotName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
var purchaseResult = result.PurchaseResult;
|
||||
var items = result.Items;
|
||||
|
||||
switch (purchaseResult) {
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.AlreadyOwned:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.BaseGameRequired:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.RegionLocked:
|
||||
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
|
||||
if (DistributeKeys) {
|
||||
do {
|
||||
if (iterator.MoveNext()) {
|
||||
currentBot = iterator.Current;
|
||||
} else {
|
||||
currentBot = null;
|
||||
}
|
||||
} while (currentBot == this);
|
||||
|
||||
if (!ForwardKeysToOtherBots) {
|
||||
key = reader.ReadLine();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ForwardKeysToOtherBots) {
|
||||
key = reader.ReadLine();
|
||||
break;
|
||||
}
|
||||
|
||||
bool alreadyHandled = false;
|
||||
foreach (Bot bot in Bots.Values) {
|
||||
if (alreadyHandled) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (bot == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArchiHandler.PurchaseResponseCallback otherResult;
|
||||
try {
|
||||
otherResult = await bot.ArchiHandler.RedeemKey(key);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, bot.BotName);
|
||||
break; // We're done with this key
|
||||
}
|
||||
|
||||
if (otherResult == null) {
|
||||
break; // We're done with this key
|
||||
}
|
||||
|
||||
var otherPurchaseResult = otherResult.PurchaseResult;
|
||||
var otherItems = otherResult.Items;
|
||||
|
||||
switch (otherPurchaseResult) {
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
|
||||
alreadyHandled = true; // We're done with this key
|
||||
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
|
||||
break;
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
|
||||
alreadyHandled = true; // This key doesn't work, don't try to redeem it anymore
|
||||
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
|
||||
break;
|
||||
default:
|
||||
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherPurchaseResult + " | Items: " + string.Join("", otherItems));
|
||||
break;
|
||||
}
|
||||
}
|
||||
key = reader.ReadLine();
|
||||
break;
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
|
||||
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
|
||||
if (DistributeKeys) {
|
||||
do {
|
||||
if (iterator.MoveNext()) {
|
||||
currentBot = iterator.Current;
|
||||
} else {
|
||||
currentBot = null;
|
||||
}
|
||||
} while (currentBot == this);
|
||||
}
|
||||
key = reader.ReadLine();
|
||||
break;
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
|
||||
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
|
||||
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
|
||||
if (DistributeKeys && !ForwardKeysToOtherBots) {
|
||||
do {
|
||||
if (iterator.MoveNext()) {
|
||||
currentBot = iterator.Current;
|
||||
} else {
|
||||
currentBot = null;
|
||||
}
|
||||
} while (currentBot == this);
|
||||
}
|
||||
key = reader.ReadLine();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response.Length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.ToString();
|
||||
}
|
||||
|
||||
internal static async Task<string> ResponseRedeem(string botName, string message, bool validate) {
|
||||
if (string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
return await bot.ResponseRedeem(message, validate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<string> ResponseAddLicense(string botName, string game) {
|
||||
if (string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
uint gameID;
|
||||
if (!uint.TryParse(game, out gameID)) {
|
||||
return "Couldn't parse game as a number!";
|
||||
}
|
||||
|
||||
var result = await bot.SteamApps.RequestFreeLicense(gameID);
|
||||
return "Result: " + result.Result + " | Granted apps: " + string.Join(", ", result.GrantedApps) + " " + string.Join(", ", result.GrantedPackages);
|
||||
}
|
||||
|
||||
internal static async Task<string> ResponsePlay(string botName, string game) {
|
||||
if (string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "Couldn't find any bot named " + botName + "!";
|
||||
}
|
||||
|
||||
uint gameID;
|
||||
if (!uint.TryParse(game, out gameID)) {
|
||||
return "Couldn't parse game as a number!";
|
||||
}
|
||||
|
||||
await bot.CardsFarmer.SwitchToManualMode(gameID != 0).ConfigureAwait(false);
|
||||
bot.ArchiHandler.PlayGames(gameID);
|
||||
|
||||
return "Done!";
|
||||
}
|
||||
|
||||
internal static string ResponseStart(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Bots.ContainsKey(botName)) {
|
||||
SendMessage(steamID, "That bot instance is already running!");
|
||||
return;
|
||||
return "That bot instance is already running!";
|
||||
}
|
||||
|
||||
new Bot(botName);
|
||||
if (Bots.ContainsKey(botName)) {
|
||||
SendMessage(steamID, "Done!");
|
||||
return "Done!";
|
||||
} else {
|
||||
SendMessage(steamID, "That bot instance failed to start, make sure that XML config exists and bot is active!");
|
||||
return "That bot instance failed to start, make sure that XML config exists and bot is active!";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResponseStop(ulong steamID, string botName) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(botName)) {
|
||||
return;
|
||||
internal static async Task<string> ResponseStop(string botName) {
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Bots.ContainsKey(botName)) {
|
||||
SendMessage(steamID, "That bot instance is already inactive!");
|
||||
return;
|
||||
Bot bot;
|
||||
if (!Bots.TryGetValue(botName, out bot)) {
|
||||
return "That bot instance is already inactive!";
|
||||
}
|
||||
|
||||
if (await Shutdown(botName).ConfigureAwait(false)) {
|
||||
SendMessage(steamID, "Done!");
|
||||
return "Done!";
|
||||
} else {
|
||||
SendMessage(steamID, "That bot instance failed to shutdown!");
|
||||
return "That bot instance failed to shutdown!";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMessage(ulong steamID, string message) {
|
||||
if (IsValidCdKey(message)) {
|
||||
ArchiHandler.RedeemKey(message);
|
||||
return;
|
||||
internal async Task<string> HandleMessage(string message) {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!message.StartsWith("!")) {
|
||||
return;
|
||||
return await ResponseRedeem(BotName, message, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!message.Contains(" ")) {
|
||||
switch (message) {
|
||||
case "!2fa":
|
||||
Response2FA(steamID);
|
||||
break;
|
||||
return Response2FA(BotName);
|
||||
case "!2faoff":
|
||||
Response2FAOff(steamID);
|
||||
break;
|
||||
return Response2FAOff(BotName);
|
||||
case "!exit":
|
||||
await ShutdownAllBots().ConfigureAwait(false);
|
||||
break;
|
||||
return "Done";
|
||||
case "!restart":
|
||||
await Program.Restart().ConfigureAwait(false);
|
||||
break;
|
||||
return "Done";
|
||||
case "!status":
|
||||
ResponseStatus(steamID);
|
||||
break;
|
||||
return ResponseStatus();
|
||||
case "!statusall":
|
||||
return ResponseStatusAll();
|
||||
case "!stop":
|
||||
await Shutdown().ConfigureAwait(false);
|
||||
break;
|
||||
return await ResponseStop(BotName).ConfigureAwait(false);
|
||||
case "!loot":
|
||||
return await ResponseSendTrade(BotName).ConfigureAwait(false);
|
||||
default:
|
||||
return "Unrecognized command: " + message;
|
||||
}
|
||||
} else {
|
||||
string[] args = message.Split(' ');
|
||||
switch (args[0]) {
|
||||
case "!2fa":
|
||||
Response2FA(steamID, args[1]);
|
||||
break;
|
||||
return Response2FA(args[1]);
|
||||
case "!2faoff":
|
||||
Response2FAOff(steamID, args[1]);
|
||||
break;
|
||||
return Response2FAOff(args[1]);
|
||||
case "!addlicense":
|
||||
if (args.Length > 2) {
|
||||
return await ResponseAddLicense(args[1], args[2]).ConfigureAwait(false);
|
||||
} else {
|
||||
return await ResponseAddLicense(BotName, args[1]).ConfigureAwait(false);
|
||||
}
|
||||
case "!play":
|
||||
if (args.Length > 2) {
|
||||
return await ResponsePlay(args[1], args[2]).ConfigureAwait(false);
|
||||
} else {
|
||||
return await ResponsePlay(BotName, args[1]).ConfigureAwait(false);
|
||||
}
|
||||
case "!redeem":
|
||||
ArchiHandler.RedeemKey(args[1]);
|
||||
break;
|
||||
if (args.Length > 2) {
|
||||
return await ResponseRedeem(args[1], args[2], false).ConfigureAwait(false);
|
||||
} else {
|
||||
return await ResponseRedeem(BotName, args[1], false).ConfigureAwait(false);
|
||||
}
|
||||
case "!start":
|
||||
ResponseStart(steamID, args[1]);
|
||||
break;
|
||||
return ResponseStart(args[1]);
|
||||
case "!stop":
|
||||
await ResponseStop(steamID, args[1]).ConfigureAwait(false);
|
||||
break;
|
||||
return await ResponseStop(args[1]).ConfigureAwait(false);
|
||||
case "!status":
|
||||
ResponseStatus(steamID, args[1]);
|
||||
break;
|
||||
return ResponseStatus(args[1]);
|
||||
case "!loot":
|
||||
return await ResponseSendTrade(args[1]).ConfigureAwait(false);
|
||||
default:
|
||||
return "Unrecognized command: " + args[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMessage(ulong steamID, string message) {
|
||||
if (steamID == 0 || string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessage(steamID, await HandleMessage(message).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
private void OnConnected(SteamClient.ConnectedCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.Result != EResult.OK) {
|
||||
Logging.LogGenericError(BotName, "Unable to connect to Steam: " + callback.Result);
|
||||
Logging.LogGenericError("Unable to connect to Steam: " + callback.Result, BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Connected to Steam!");
|
||||
Logging.LogGenericInfo("Connected to Steam!", BotName);
|
||||
|
||||
if (File.Exists(LoginKeyFile)) {
|
||||
LoginKey = File.ReadAllText(LoginKeyFile);
|
||||
@@ -637,7 +932,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Disconnected from Steam!");
|
||||
Logging.LogGenericInfo("Disconnected from Steam!", BotName);
|
||||
await CardsFarmer.StopFarming().ConfigureAwait(false);
|
||||
|
||||
if (!KeepRunning) {
|
||||
@@ -654,18 +949,18 @@ namespace ArchiSteamFarm {
|
||||
if (!string.IsNullOrEmpty(LoginKey)) { // InvalidPassword means usually that login key has expired, if we used it
|
||||
LoginKey = null;
|
||||
File.Delete(LoginKeyFile);
|
||||
Logging.LogGenericInfo(BotName, "Removed expired login key");
|
||||
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(BotName, "Will retry after 25 minutes...");
|
||||
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
|
||||
}
|
||||
} else if (LoggedInElsewhere) {
|
||||
LoggedInElsewhere = false;
|
||||
Logging.LogGenericWarning(BotName, "Account is being used elsewhere, will try reconnecting in 30 minutes...");
|
||||
Logging.LogGenericWarning("Account is being used elsewhere, will try reconnecting in 30 minutes...", BotName);
|
||||
await Utilities.SleepAsync(30 * 60 * 1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Reconnecting...");
|
||||
Logging.LogGenericInfo("Reconnecting...", BotName);
|
||||
|
||||
// 2FA tokens are expiring soon, use limiter only when we don't have any pending
|
||||
if (TwoFactorAuth == null) {
|
||||
@@ -675,6 +970,12 @@ namespace ArchiSteamFarm {
|
||||
SteamClient.Connect();
|
||||
}
|
||||
|
||||
private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
@@ -798,7 +1099,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Logged off of Steam: " + callback.Result);
|
||||
Logging.LogGenericInfo("Logged off of Steam: " + callback.Result, BotName);
|
||||
|
||||
switch (callback.Result) {
|
||||
case EResult.AlreadyLoggedInElsewhere:
|
||||
@@ -828,10 +1129,10 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
case EResult.InvalidPassword:
|
||||
InvalidPassword = true;
|
||||
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result);
|
||||
Logging.LogGenericWarning("Unable to login to Steam: " + result, BotName);
|
||||
break;
|
||||
case EResult.OK:
|
||||
Logging.LogGenericInfo(BotName, "Successfully logged on!");
|
||||
Logging.LogGenericInfo("Successfully logged on!", BotName);
|
||||
|
||||
if (UseAsfAsMobileAuthenticator && TwoFactorAuth == null && SteamGuardAccount == null) {
|
||||
LinkMobileAuthenticator();
|
||||
@@ -866,16 +1167,16 @@ namespace ArchiSteamFarm {
|
||||
|
||||
Trading.CheckTrades();
|
||||
|
||||
await CardsFarmer.StartFarming().ConfigureAwait(false);
|
||||
var start = Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false));
|
||||
break;
|
||||
case EResult.NoConnection:
|
||||
case EResult.ServiceUnavailable:
|
||||
case EResult.Timeout:
|
||||
case EResult.TryAnotherCM:
|
||||
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result);
|
||||
Logging.LogGenericWarning("Unable to login to Steam: " + result, BotName);
|
||||
break;
|
||||
default: // Unexpected result, shutdown immediately
|
||||
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result);
|
||||
Logging.LogGenericWarning("Unable to login to Steam: " + result, BotName);
|
||||
await Shutdown().ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
@@ -924,15 +1225,22 @@ namespace ArchiSteamFarm {
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNotification(ArchiHandler.NotificationCallback callback) {
|
||||
private void OnNotifications(ArchiHandler.NotificationsCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (callback.NotificationType) {
|
||||
case ArchiHandler.NotificationCallback.ENotificationType.Trading:
|
||||
Trading.CheckTrades();
|
||||
break;
|
||||
bool checkTrades = false;
|
||||
foreach (var notification in callback.Notifications) {
|
||||
switch (notification.NotificationType) {
|
||||
case ArchiHandler.NotificationsCallback.Notification.ENotificationType.Trading:
|
||||
checkTrades = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (checkTrades) {
|
||||
Trading.CheckTrades();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,9 +1262,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var purchaseResult = callback.PurchaseResult;
|
||||
var items = callback.Items;
|
||||
SendMessage(SteamMasterID, "Status: " + purchaseResult + " | Items: " + string.Join("", items));
|
||||
|
||||
if (purchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||
// We will restart CF module to recalculate current status and decide about new optimal approach
|
||||
await CardsFarmer.RestartFarming().ConfigureAwait(false);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -28,14 +28,12 @@ using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage, ISteamSerializable {
|
||||
EMsg ISteamSerializableMessage.GetEMsg() {
|
||||
return EMsg.ClientAcknowledgeClanInvite;
|
||||
}
|
||||
|
||||
internal ulong GroupID = 0;
|
||||
internal bool AcceptInvite = true;
|
||||
|
||||
public CMsgClientClanInviteAction() { }
|
||||
EMsg ISteamSerializableMessage.GetEMsg() {
|
||||
return EMsg.ClientAcknowledgeClanInvite;
|
||||
}
|
||||
|
||||
void ISteamSerializable.Serialize(Stream stream) {
|
||||
if (stream == null) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -42,9 +42,10 @@ namespace ArchiSteamFarm {
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
|
||||
internal readonly ConcurrentDictionary<uint, double> GamesToFarm = new ConcurrentDictionary<uint, double>();
|
||||
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
|
||||
internal readonly List<uint> CurrentGamesFarming = new List<uint>();
|
||||
|
||||
private bool ManualMode = false;
|
||||
private bool NowFarming = false;
|
||||
|
||||
internal CardsFarmer(Bot bot) {
|
||||
@@ -58,13 +59,13 @@ namespace ArchiSteamFarm {
|
||||
);
|
||||
}
|
||||
|
||||
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, double> gamesToFarm) {
|
||||
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||
if (gamesToFarm == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<uint> result = new List<uint>();
|
||||
foreach (KeyValuePair<uint, double> keyValue in gamesToFarm) {
|
||||
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm) {
|
||||
if (keyValue.Value >= 2) {
|
||||
result.Add(keyValue.Key);
|
||||
}
|
||||
@@ -73,7 +74,7 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static uint GetAnyGameToFarm(ConcurrentDictionary<uint, double> gamesToFarm) {
|
||||
internal static uint GetAnyGameToFarm(ConcurrentDictionary<uint, float> gamesToFarm) {
|
||||
if (gamesToFarm == null) {
|
||||
return 0;
|
||||
}
|
||||
@@ -85,14 +86,30 @@ namespace ArchiSteamFarm {
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal bool FarmMultiple(ConcurrentDictionary<uint, double> appIDs) {
|
||||
internal async Task SwitchToManualMode(bool manualMode) {
|
||||
if (ManualMode == manualMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
ManualMode = manualMode;
|
||||
|
||||
if (ManualMode) {
|
||||
Logging.LogGenericInfo("Now running in Manual Farming mode", Bot.BotName);
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
} else {
|
||||
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
|
||||
var start = Task.Run(async () => await StartFarming().ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
|
||||
internal bool FarmMultiple(ConcurrentDictionary<uint, float> appIDs) {
|
||||
if (appIDs.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double maxHour = -1;
|
||||
float maxHour = 0;
|
||||
|
||||
foreach (double hour in appIDs.Values) {
|
||||
foreach (float hour in appIDs.Values) {
|
||||
if (hour > maxHour) {
|
||||
maxHour = hour;
|
||||
}
|
||||
@@ -103,7 +120,7 @@ namespace ArchiSteamFarm {
|
||||
CurrentGamesFarming.Add(appID);
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + string.Join(", ", appIDs.Keys));
|
||||
Logging.LogGenericInfo("Now farming: " + string.Join(", ", appIDs.Keys), Bot.BotName);
|
||||
if (Farm(maxHour, appIDs.Keys)) {
|
||||
CurrentGamesFarming.Clear();
|
||||
return true;
|
||||
@@ -121,9 +138,9 @@ namespace ArchiSteamFarm {
|
||||
CurrentGamesFarming.Clear();
|
||||
CurrentGamesFarming.Add(appID);
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + appID);
|
||||
Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName);
|
||||
if (await Farm(appID).ConfigureAwait(false)) {
|
||||
double hours;
|
||||
float hours;
|
||||
GamesToFarm.TryRemove(appID, out hours);
|
||||
return true;
|
||||
} else {
|
||||
@@ -140,127 +157,30 @@ namespace ArchiSteamFarm {
|
||||
internal async Task StartFarming() {
|
||||
await Semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
if (NowFarming) {
|
||||
Semaphore.Release();
|
||||
if (ManualMode) {
|
||||
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
return;
|
||||
}
|
||||
|
||||
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
|
||||
Semaphore.Release();
|
||||
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
|
||||
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking badges...");
|
||||
|
||||
// Find the number of badge pages
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking page: 1/?");
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWarning(Bot.BotName, "Could not get badges information, will try again later!");
|
||||
Semaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
var maxPages = 1;
|
||||
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
|
||||
if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) {
|
||||
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
|
||||
if (!int.TryParse(htmlNode.InnerText, out maxPages)) {
|
||||
maxPages = 1; // Should never happen
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm.Clear();
|
||||
|
||||
// Find APPIDs we need to farm
|
||||
for (var page = 1; page <= maxPages; page++) {
|
||||
if (page > 1) { // Because we fetched page number 1 already
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking page: " + page + "/" + maxPages);
|
||||
htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='btn_green_white_innerfade btn_small_thin']");
|
||||
if (htmlNodeCollection == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (HtmlNode htmlNode in htmlNodeCollection) {
|
||||
string steamLink = htmlNode.GetAttributeValue("href", null);
|
||||
if (steamLink == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
|
||||
if (appID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Bot.GlobalBlacklist.Contains(appID) || Bot.Blacklist.Contains(appID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We assume that every game has at least 2 hours played, until we actually check them
|
||||
GamesToFarm[appID] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (GamesToFarm.Count == 0) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "No games to farm!");
|
||||
Semaphore.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have restricted card drops, actually do check hours of all games that are left to farm
|
||||
if (Bot.CardDropsRestricted) {
|
||||
foreach (uint appID in GamesToFarm.Keys) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking hours of appID: " + appID);
|
||||
htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='badge_title_stats_playtime']");
|
||||
if (htmlNode == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string hoursString = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(hoursString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hoursString = Regex.Match(hoursString, @"[0-9\.,]+").Value;
|
||||
double hours;
|
||||
|
||||
if (string.IsNullOrEmpty(hoursString)) {
|
||||
hours = 0;
|
||||
} else {
|
||||
hours = double.Parse(hoursString, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
GamesToFarm[appID] = hours;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Farming in progress...");
|
||||
|
||||
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
|
||||
NowFarming = true;
|
||||
Semaphore.Release(); // From this point we allow other calls to shut us down
|
||||
|
||||
// Now the algorithm used for farming depends on whether account is restricted or not
|
||||
if (Bot.CardDropsRestricted) {
|
||||
// If we have restricted card drops, we use complex algorithm, which prioritizes farming solo titles >= 2 hours, then all at once, until any game hits mentioned 2 hours
|
||||
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Complex");
|
||||
if (Bot.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
List<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
|
||||
if (gamesToFarmSolo.Count > 0) {
|
||||
while (gamesToFarmSolo.Count > 0) {
|
||||
uint appID = gamesToFarmSolo[0];
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + appID);
|
||||
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
|
||||
gamesToFarmSolo.Remove(appID);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
@@ -269,20 +189,19 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
} else {
|
||||
if (FarmMultiple(GamesToFarm)) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + string.Join(", ", GamesToFarm.Keys));
|
||||
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we have unrestricted card drops, we use simple algorithm and farm cards one-by-one
|
||||
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Simple");
|
||||
} else { // If we have unrestricted card drops, we use simple algorithm
|
||||
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
|
||||
while (GamesToFarm.Count > 0) {
|
||||
uint appID = GetAnyGameToFarm(GamesToFarm);
|
||||
if (await FarmSolo(appID).ConfigureAwait(false)) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + appID);
|
||||
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -292,7 +211,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
CurrentGamesFarming.Clear();
|
||||
NowFarming = false;
|
||||
Logging.LogGenericInfo(Bot.BotName, "Farming finished!");
|
||||
Logging.LogGenericInfo("Farming finished!", Bot.BotName);
|
||||
await Bot.OnFarmingFinished().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -304,17 +223,156 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Sending signal to stop farming");
|
||||
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
|
||||
FarmResetEvent.Set();
|
||||
for (var i = 0; i < 5 && NowFarming; i++) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Waiting for reaction...");
|
||||
for (byte i = 0; i < 5 && NowFarming; i++) {
|
||||
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
|
||||
await Utilities.SleepAsync(1000).ConfigureAwait(false);
|
||||
}
|
||||
FarmResetEvent.Reset();
|
||||
Logging.LogGenericInfo(Bot.BotName, "Farming stopped!");
|
||||
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
|
||||
Semaphore.Release();
|
||||
}
|
||||
|
||||
private async Task<bool> IsAnythingToFarm() {
|
||||
if (NowFarming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Checking badges...", Bot.BotName);
|
||||
|
||||
// Find the number of badge pages
|
||||
Logging.LogGenericInfo("Checking first page...", Bot.BotName);
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWarning("Could not get badges information, will try again later!", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
byte maxPages = 1;
|
||||
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
|
||||
if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) {
|
||||
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
|
||||
if (!byte.TryParse(htmlNode.InnerText, out maxPages)) {
|
||||
maxPages = 1; // Should never happen
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm.Clear();
|
||||
|
||||
// Find APPIDs we need to farm
|
||||
Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
|
||||
|
||||
List<Task> checkPagesTasks = new List<Task>();
|
||||
for (byte page = 1; page <= maxPages; page++) {
|
||||
if (page == 1) {
|
||||
CheckPage(htmlDocument); // Because we fetched page number 1 already
|
||||
} else {
|
||||
byte currentPage = page; // We need a copy of variable being passed when in for loops
|
||||
checkPagesTasks.Add(Task.Run(async () => await CheckPage(currentPage).ConfigureAwait(false)));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(checkPagesTasks).ConfigureAwait(false);
|
||||
|
||||
if (GamesToFarm.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have restricted card drops, actually do check hours of all games that are left to farm
|
||||
if (Bot.CardDropsRestricted) {
|
||||
List<Task> checkHoursTasks = new List<Task>();
|
||||
Logging.LogGenericInfo("Checking hours...", Bot.BotName);
|
||||
foreach (uint appID in GamesToFarm.Keys) {
|
||||
checkHoursTasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false)));
|
||||
}
|
||||
await Task.WhenAll(checkHoursTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CheckPage(HtmlDocument htmlDocument) {
|
||||
if (htmlDocument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='btn_green_white_innerfade btn_small_thin']");
|
||||
if (htmlNodeCollection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (HtmlNode htmlNode in htmlNodeCollection) {
|
||||
string steamLink = htmlNode.GetAttributeValue("href", null);
|
||||
if (steamLink == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
|
||||
if (appID == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Bot.GlobalBlacklist.Contains(appID) || Bot.Blacklist.Contains(appID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We assume that every game has at least 2 hours played, until we actually check them
|
||||
GamesToFarm[appID] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPage(byte page) {
|
||||
if (page == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CheckPage(htmlDocument);
|
||||
}
|
||||
|
||||
private async Task CheckHours(uint appID) {
|
||||
if (appID == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogNullError("htmlDocument", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='badge_title_stats_playtime']");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError("htmlNode", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
string hoursString = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(hoursString)) {
|
||||
Logging.LogNullError("hoursString", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
hoursString = Regex.Match(hoursString, @"[0-9\.,]+").Value;
|
||||
float hours;
|
||||
|
||||
if (string.IsNullOrEmpty(hoursString)) {
|
||||
hours = 0;
|
||||
} else {
|
||||
hours = float.Parse(hoursString, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
GamesToFarm[appID] = hours;
|
||||
}
|
||||
|
||||
private async Task CheckGamesForFarming() {
|
||||
if (NowFarming || GamesToFarm.Count > 0 || !Bot.SteamClient.IsConnected) {
|
||||
return;
|
||||
@@ -349,7 +407,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
for (ushort farmingTime = 0; farmingTime <= MaxFarmingTime && (!keepFarming.HasValue || keepFarming.Value); farmingTime += StatusCheckSleep) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Still farming: " + appID);
|
||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
|
||||
success = false;
|
||||
break;
|
||||
@@ -358,11 +416,11 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(0);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + appID);
|
||||
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool Farm(double maxHour, ICollection<uint> appIDs) {
|
||||
private bool Farm(float maxHour, ICollection<uint> appIDs) {
|
||||
if (maxHour >= 2) {
|
||||
return true;
|
||||
}
|
||||
@@ -371,15 +429,15 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Still farming: " + string.Join(", ", appIDs));
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
double timePlayed = StatusCheckSleep / 60.0;
|
||||
foreach (KeyValuePair<uint, double> gameToFarm in GamesToFarm) {
|
||||
float timePlayed = StatusCheckSleep / 60.0F;
|
||||
foreach (KeyValuePair<uint, float> gameToFarm in GamesToFarm) {
|
||||
if (!appIDs.Contains(gameToFarm.Key)) {
|
||||
continue;
|
||||
}
|
||||
@@ -391,7 +449,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(0);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + string.Join(", ", appIDs));
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,20 +22,14 @@
|
||||
|
||||
*/
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Debugging {
|
||||
internal static bool IsDebugBuild { get; private set; } = false;
|
||||
#if DEBUG
|
||||
internal static readonly bool IsDebugBuild = true;
|
||||
#else
|
||||
internal static readonly bool IsDebugBuild = false;
|
||||
#endif
|
||||
|
||||
internal static bool IsReleaseBuild { get { return !IsDebugBuild; } }
|
||||
|
||||
static Debugging() {
|
||||
MarkIfDebug();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void MarkIfDebug() {
|
||||
IsDebugBuild = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -31,6 +31,8 @@ namespace ArchiSteamFarm {
|
||||
internal static class Logging {
|
||||
private static readonly object FileLock = new object();
|
||||
|
||||
internal static bool LogToFile { get; set; } = false;
|
||||
|
||||
internal static void Init() {
|
||||
File.Delete(Program.LogFile);
|
||||
}
|
||||
@@ -47,33 +49,45 @@ namespace ArchiSteamFarm {
|
||||
Console.Write(loggedMessage);
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
File.AppendAllText(Program.LogFile, loggedMessage);
|
||||
if (LogToFile) {
|
||||
lock (FileLock) {
|
||||
File.AppendAllText(Program.LogFile, loggedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericException(string botName, Exception exception, [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
|
||||
Log("[!] StackTrace: " + exception.StackTrace);
|
||||
|
||||
Exception innerException = exception.InnerException;
|
||||
if (innerException != null) {
|
||||
LogGenericException(innerException, botName, previousMethodName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWarning(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericNotice(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[*] NOTICE: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogGenericDebug(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -31,7 +31,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Program {
|
||||
internal enum EUserInputType {
|
||||
internal enum EUserInputType : byte {
|
||||
Login,
|
||||
Password,
|
||||
PhoneNumber,
|
||||
@@ -42,19 +42,27 @@ namespace ArchiSteamFarm {
|
||||
TwoFactorAuthentication,
|
||||
}
|
||||
|
||||
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest";
|
||||
internal const string ConfigDirectoryPath = "config";
|
||||
internal enum EMode : byte {
|
||||
Normal, // Standard most common usage
|
||||
Client, // WCF client only
|
||||
Server // Normal + WCF server
|
||||
}
|
||||
|
||||
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest";
|
||||
internal const string ConfigDirectory = "config";
|
||||
internal const string LogFile = "log.txt";
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
private static readonly SemaphoreSlim SteamSemaphore = new SemaphoreSlim(1);
|
||||
private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
private static readonly string ExecutablePath = Assembly.Location;
|
||||
private static readonly AssemblyName AssemblyName = Assembly.GetName();
|
||||
private static readonly object ConsoleLock = new object();
|
||||
//private static readonly string ExeName = AssemblyName.Name + ".exe";
|
||||
private static readonly string ExecutableFile = Assembly.Location;
|
||||
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
|
||||
private static readonly WCF WCF = new WCF();
|
||||
|
||||
internal static readonly string LogFile = Path.Combine(Path.GetDirectoryName(ExecutablePath), "log.txt");
|
||||
internal static readonly string Version = AssemblyName.Version.ToString();
|
||||
internal static readonly string Version = Assembly.GetName().Version.ToString();
|
||||
|
||||
private static EMode Mode;
|
||||
|
||||
internal static bool ConsoleIsBusy { get; private set; } = false;
|
||||
|
||||
@@ -71,17 +79,17 @@ namespace ArchiSteamFarm {
|
||||
|
||||
string localVersion = Version;
|
||||
|
||||
Logging.LogGenericNotice("", "Local version: " + localVersion);
|
||||
Logging.LogGenericNotice("", "Remote version: " + remoteVersion);
|
||||
Logging.LogGenericInfo("Local version: " + localVersion);
|
||||
Logging.LogGenericInfo("Remote version: " + remoteVersion);
|
||||
|
||||
int comparisonResult = localVersion.CompareTo(remoteVersion);
|
||||
if (comparisonResult < 0) {
|
||||
Logging.LogGenericNotice("", "New version is available!");
|
||||
Logging.LogGenericNotice("", "Consider updating yourself!");
|
||||
Logging.LogGenericInfo("New version is available!");
|
||||
Logging.LogGenericInfo("Consider updating yourself!");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false);
|
||||
} else if (comparisonResult > 0) {
|
||||
Logging.LogGenericNotice("", "You're currently using pre-release version!");
|
||||
Logging.LogGenericNotice("", "Be careful!");
|
||||
Logging.LogGenericInfo("You're currently using pre-release version!");
|
||||
Logging.LogGenericInfo("Be careful!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +100,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
internal static async Task Restart() {
|
||||
await Bot.ShutdownAllBots().ConfigureAwait(false);
|
||||
System.Diagnostics.Process.Start(ExecutablePath);
|
||||
System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs()));
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
@@ -142,10 +150,9 @@ namespace ArchiSteamFarm {
|
||||
return result.Trim(); // Get rid of all whitespace characters
|
||||
}
|
||||
|
||||
internal static async void OnBotShutdown() {
|
||||
internal static void OnBotShutdown() {
|
||||
if (Bot.GetRunningBotsCount() == 0) {
|
||||
Logging.LogGenericInfo("Main", "No bots are running, exiting");
|
||||
await Utilities.SleepAsync(5000).ConfigureAwait(false); // This might be the only message user gets, consider giving him some time
|
||||
Logging.LogGenericInfo("No bots are running, exiting");
|
||||
ShutdownResetEvent.Set();
|
||||
}
|
||||
}
|
||||
@@ -155,41 +162,122 @@ namespace ArchiSteamFarm {
|
||||
WebBrowser.Init();
|
||||
}
|
||||
|
||||
private static void ParseArgs(string[] args) {
|
||||
foreach (string arg in args) {
|
||||
switch (arg) {
|
||||
case "--client":
|
||||
Mode = EMode.Client;
|
||||
Logging.LogToFile = false;
|
||||
break;
|
||||
case "--log":
|
||||
Logging.LogToFile = true;
|
||||
break;
|
||||
case "--no-log":
|
||||
Logging.LogToFile = false;
|
||||
break;
|
||||
case "--server":
|
||||
Mode = EMode.Server;
|
||||
WCF.StartServer();
|
||||
break;
|
||||
default:
|
||||
if (arg.StartsWith("--")) {
|
||||
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Mode != EMode.Client) {
|
||||
Logging.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Command sent: \"" + arg + "\"");
|
||||
|
||||
// We intentionally execute this async block synchronously
|
||||
Logging.LogGenericInfo("Response received: \"" + WCF.SendCommand(arg) + "\"");
|
||||
/*
|
||||
Task.Run(async () => {
|
||||
Logging.LogGenericNotice("WCF", "Response received: " + await WCF.SendCommand(arg).ConfigureAwait(false));
|
||||
}).Wait();
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
|
||||
if (sender == null || args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericException((Exception) args.ExceptionObject);
|
||||
}
|
||||
|
||||
private static void Main(string[] args) {
|
||||
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
|
||||
|
||||
Logging.LogGenericInfo("Archi's Steam Farm, version " + Version);
|
||||
Directory.SetCurrentDirectory(ExecutableDirectory);
|
||||
InitServices();
|
||||
|
||||
Logging.LogGenericInfo("Main", "Archi's Steam Farm, version " + Version);
|
||||
|
||||
Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait();
|
||||
|
||||
// Allow loading configs from source tree if it's a debug build
|
||||
if (Debugging.IsDebugBuild) {
|
||||
|
||||
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
|
||||
for (var i = 0; i < 4; i++) {
|
||||
Directory.SetCurrentDirectory("..");
|
||||
if (Directory.Exists(ConfigDirectoryPath)) {
|
||||
if (Directory.Exists(ConfigDirectory)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If config directory doesn't exist after our adjustment, abort all of that
|
||||
if (!Directory.Exists(ConfigDirectory)) {
|
||||
Directory.SetCurrentDirectory(ExecutableDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(ConfigDirectoryPath)) {
|
||||
Logging.LogGenericError("Main", "Config directory doesn't exist!");
|
||||
Console.ReadLine();
|
||||
// By default we're operating on normal mode
|
||||
Mode = EMode.Normal;
|
||||
Logging.LogToFile = true;
|
||||
|
||||
// But that can be overriden by arguments
|
||||
ParseArgs(args);
|
||||
|
||||
// If we ran ASF as a client, we're done by now
|
||||
if (Mode == EMode.Client) {
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait();
|
||||
|
||||
if (!Directory.Exists(ConfigDirectory)) {
|
||||
Logging.LogGenericError("Config directory doesn't exist!");
|
||||
Thread.Sleep(5000);
|
||||
Task.Run(async () => await Exit(1).ConfigureAwait(false)).Wait();
|
||||
}
|
||||
|
||||
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) {
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
Bot.RefreshCMs();
|
||||
|
||||
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
|
||||
string botName = Path.GetFileNameWithoutExtension(configFile);
|
||||
Bot bot = new Bot(botName);
|
||||
if (!bot.Enabled) {
|
||||
Logging.LogGenericInfo(botName, "Not starting this instance because it's disabled in config file");
|
||||
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we got any bots running
|
||||
OnBotShutdown();
|
||||
|
||||
// Wait for signal to shutdown
|
||||
ShutdownResetEvent.WaitOne();
|
||||
|
||||
// We got a signal to shutdown, consider giving user some time to read the message
|
||||
Thread.Sleep(5000);
|
||||
|
||||
// This is over, cleanup only now
|
||||
WCF.StopServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,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.2.4.0")]
|
||||
[assembly: AssemblyFileVersion("1.2.4.0")]
|
||||
[assembly: AssemblyVersion("1.5.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.5.0.0")]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,16 +22,43 @@
|
||||
|
||||
*/
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class SteamItem {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
|
||||
|
||||
[JsonProperty]
|
||||
internal string appid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string contextid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string assetid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string id {
|
||||
get { return assetid; }
|
||||
set { assetid = value; }
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string currencyid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string classid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string instanceid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string amount { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal bool missing { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int pos { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
39
ArchiSteamFarm/SteamItemList.cs
Normal file
39
ArchiSteamFarm/SteamItemList.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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 Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class SteamItemList {
|
||||
[JsonProperty]
|
||||
internal List<SteamItem> assets { get; set; } = new List<SteamItem>();
|
||||
|
||||
[JsonProperty]
|
||||
internal List<string> currency { get; set; } = new List<string>();
|
||||
|
||||
[JsonProperty]
|
||||
internal bool ready { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,12 +22,13 @@
|
||||
|
||||
*/
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class SteamTradeOffer {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
|
||||
internal enum ETradeOfferState {
|
||||
Unknown,
|
||||
Invalid,
|
||||
@@ -49,18 +50,43 @@ namespace ArchiSteamFarm {
|
||||
MobileApp
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string tradeofferid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int accountid_other { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string message { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int expiration_time { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal ETradeOfferState trade_offer_state { get; set; }
|
||||
internal List<SteamItem> items_to_give { get; set; }
|
||||
internal List<SteamItem> items_to_receive { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal List<SteamItem> items_to_give { get; set; } = new List<SteamItem>();
|
||||
|
||||
[JsonProperty]
|
||||
internal List<SteamItem> items_to_receive { get; set; } = new List<SteamItem>();
|
||||
|
||||
[JsonProperty]
|
||||
internal bool is_our_offer { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int time_created { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int time_updated { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal bool from_real_time_trade { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal int escrow_end_date { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal ETradeOfferConfirmationMethod confirmation_method { get; set; }
|
||||
|
||||
// Extra
|
||||
|
||||
41
ArchiSteamFarm/SteamTradeOfferRequest.cs
Normal file
41
ArchiSteamFarm/SteamTradeOfferRequest.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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 Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class SteamTradeOfferRequest {
|
||||
[JsonProperty]
|
||||
internal bool newversion { get; set; } = true;
|
||||
|
||||
[JsonProperty]
|
||||
internal int version { get; set; } = 2;
|
||||
|
||||
[JsonProperty]
|
||||
internal SteamItemList me { get; set; } = new SteamItemList();
|
||||
|
||||
[JsonProperty]
|
||||
internal SteamItemList them { get; set; } = new SteamItemList();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -28,6 +28,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Trading {
|
||||
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 readonly Bot Bot;
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private volatile byte ParsingTasks = 0;
|
||||
@@ -76,10 +79,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.SteamMasterID) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Accepting trade: " + tradeID);
|
||||
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
|
||||
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
|
||||
} else {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Ignoring trade: " + tradeID);
|
||||
Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -56,5 +56,20 @@ namespace ArchiSteamFarm {
|
||||
|
||||
return Regex.Replace(text, @"[^\d]", "");
|
||||
}
|
||||
|
||||
internal static uint GetCharCountInString(string s, char c) {
|
||||
if (string.IsNullOrEmpty(s)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
foreach (char singleChar in s) {
|
||||
if (singleChar == c) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
ArchiSteamFarm/WCF.cs
Normal file
129
ArchiSteamFarm/WCF.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
[ServiceContract]
|
||||
internal interface IWCF {
|
||||
[OperationContract]
|
||||
string HandleCommand(string input);
|
||||
}
|
||||
|
||||
internal class WCF : IWCF {
|
||||
|
||||
private const string URL = "http://localhost:1242/ASF"; // 1242 = 1024 + A(65) + S(83) + F(70)
|
||||
|
||||
private ServiceHost ServiceHost;
|
||||
private Client Client;
|
||||
|
||||
internal void StartServer() {
|
||||
if (ServiceHost != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Starting WCF server...");
|
||||
ServiceHost = new ServiceHost(typeof(WCF));
|
||||
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), URL);
|
||||
|
||||
try {
|
||||
ServiceHost.Open();
|
||||
} catch (AddressAccessDeniedException) {
|
||||
Logging.LogGenericWarning("WCF service could not be started because of AddressAccessDeniedException");
|
||||
Logging.LogGenericWarning("If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions");
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("WCF server ready!");
|
||||
}
|
||||
|
||||
internal void StopServer() {
|
||||
if (ServiceHost == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceHost.Close();
|
||||
ServiceHost = null;
|
||||
}
|
||||
|
||||
internal string SendCommand(string input) {
|
||||
if (Client == null) {
|
||||
Client = new Client(new BasicHttpBinding(), new EndpointAddress(URL));
|
||||
}
|
||||
|
||||
return Client.HandleCommand(input);
|
||||
}
|
||||
|
||||
public string HandleCommand(string input) {
|
||||
if (string.IsNullOrEmpty(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] args = input.Split(' ');
|
||||
|
||||
string botName;
|
||||
|
||||
if (args.Length > 1) { // If we have args[1] provided, use given botName
|
||||
botName = args[1];
|
||||
} else { // If not, just pick first one
|
||||
botName = Bot.GetAnyBotName();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
return "ERROR: Invalid botName: " + botName;
|
||||
}
|
||||
|
||||
Bot bot;
|
||||
if (!Bot.Bots.TryGetValue(botName, out bot)) {
|
||||
return "ERROR: Couldn't find any bot named: " + botName;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Received command: \"" + input + "\"");
|
||||
|
||||
string command = '!' + input;
|
||||
string output = bot.HandleMessage(command).Result; // TODO: This should be asynchronous
|
||||
|
||||
Logging.LogGenericInfo("Answered to command: \"" + input + "\" with: \"" + output + "\"");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Client : ClientBase<IWCF>, IWCF {
|
||||
internal Client(Binding binding, EndpointAddress address) : base(binding, address) { }
|
||||
|
||||
public string HandleCommand(string input) {
|
||||
try {
|
||||
return Channel.HandleCommand(input);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
Copyright 2015 Łukasz "JustArchi" Domeradzki
|
||||
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -34,51 +34,80 @@ using System.Xml;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class WebBrowser {
|
||||
internal const byte HttpTimeout = 180; // In seconds
|
||||
[Flags]
|
||||
internal enum RequestOptions : byte {
|
||||
None = 0,
|
||||
FakeUserAgent = 1 << 0,
|
||||
XMLHttpRequest = 1 << 1
|
||||
}
|
||||
|
||||
private const string FakeUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36";
|
||||
|
||||
internal const byte HttpTimeout = 180; // In seconds, how long we can wait for server's response
|
||||
internal const byte MaxConnections = 20; // Defines maximum number of connections. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
|
||||
internal const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
|
||||
internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility)
|
||||
|
||||
private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version;
|
||||
private static readonly HttpClientHandler HttpClientHandler = new HttpClientHandler { UseCookies = false };
|
||||
private static readonly HttpClient HttpClient = new HttpClient(HttpClientHandler) { Timeout = TimeSpan.FromSeconds(HttpTimeout) };
|
||||
|
||||
internal static void Init() {
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + Program.Version);
|
||||
// Most web services expect that UserAgent is set, so we declare it globally
|
||||
// Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent)
|
||||
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
|
||||
|
||||
// Don't limit maximum number of allowed concurrent connections
|
||||
// It's application's responsibility to handle that stuff
|
||||
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
|
||||
// Set max connection limit from default of 2 to desired value
|
||||
ServicePointManager.DefaultConnectionLimit = MaxConnections;
|
||||
|
||||
// Don't use Expect100Continue, we don't need to do that
|
||||
// Set max idle time from default of 100 seconds (100 * 1000) to desired value
|
||||
ServicePointManager.MaxServicePointIdleTime = MaxIdleTime * 1000;
|
||||
|
||||
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
private static async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request) || httpMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request);
|
||||
|
||||
if (httpMethod == HttpMethod.Post && data != null) {
|
||||
requestMessage.Content = new FormUrlEncodedContent(data);
|
||||
}
|
||||
|
||||
if (cookies != null && cookies.Count > 0) {
|
||||
StringBuilder cookieHeader = new StringBuilder();
|
||||
foreach (KeyValuePair<string, string> cookie in cookies) {
|
||||
cookieHeader.Append(cookie.Key + "=" + cookie.Value + ";");
|
||||
}
|
||||
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
|
||||
}
|
||||
|
||||
if (referer != null) {
|
||||
requestMessage.Headers.Referrer = new Uri(referer);
|
||||
}
|
||||
|
||||
HttpResponseMessage responseMessage;
|
||||
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
|
||||
if (data != null) {
|
||||
try {
|
||||
requestMessage.Content = new FormUrlEncodedContent(data);
|
||||
} catch (UriFormatException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
} catch { // Request failed, we don't need to know the exact reason, swallow exception
|
||||
return null;
|
||||
if (cookies != null && cookies.Count > 0) {
|
||||
StringBuilder cookieHeader = new StringBuilder();
|
||||
foreach (KeyValuePair<string, string> cookie in cookies) {
|
||||
cookieHeader.Append(cookie.Key + "=" + cookie.Value + ";");
|
||||
}
|
||||
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
|
||||
}
|
||||
|
||||
if (referer != null) {
|
||||
requestMessage.Headers.Referrer = new Uri(referer);
|
||||
}
|
||||
|
||||
if (requestOptions.HasFlag(RequestOptions.FakeUserAgent)) {
|
||||
requestMessage.Headers.UserAgent.ParseAdd(FakeUserAgent);
|
||||
}
|
||||
|
||||
if (requestOptions.HasFlag(RequestOptions.XMLHttpRequest)) {
|
||||
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||
}
|
||||
|
||||
try {
|
||||
responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
} catch { // Request failed, we don't need to know the exact reason, swallow exception
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (responseMessage == null || !responseMessage.IsSuccessStatusCode) {
|
||||
@@ -88,28 +117,33 @@ namespace ArchiSteamFarm {
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
internal static async Task<HttpResponseMessage> UrlGet(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<HttpResponseMessage> UrlGet(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Get, null, cookies, referer).ConfigureAwait(false);
|
||||
return await UrlRequest(request, HttpMethod.Get, null, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HttpResponseMessage> UrlPost(string request, Dictionary<string, string> postData = null, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<HttpResponseMessage> UrlPost(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Post, postData, cookies, referer).ConfigureAwait(false);
|
||||
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> HttpResponseToHtmlDocument(HttpResponseMessage httpResponse) {
|
||||
if (httpResponse == null || httpResponse.Content == null) {
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
HttpContent httpContent = httpResponse.Content;
|
||||
if (httpContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await httpContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
@@ -121,38 +155,96 @@ namespace ArchiSteamFarm {
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage responseMessage = await UrlGet(request, cookies, referer).ConfigureAwait(false);
|
||||
if (responseMessage == null || responseMessage.Content == null) {
|
||||
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
HttpContent httpContent = httpResponse.Content;
|
||||
if (httpContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await httpContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<string> UrlPostToContent(string request, Dictionary<string, string> postData = null, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<string> UrlPostToContent(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage responseMessage = await UrlPost(request, postData, cookies, referer).ConfigureAwait(false);
|
||||
if (responseMessage == null || responseMessage.Content == null) {
|
||||
HttpResponseMessage httpResponse = await UrlPost(request, data, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
HttpContent httpContent = httpResponse.Content;
|
||||
if (httpContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await httpContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<string> UrlGetToTitle(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<JObject> UrlPostToJObject(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await UrlGetToHtmlDocument(request, cookies, referer).ConfigureAwait(false);
|
||||
string content = await UrlPostToContent(request, data, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JObject jObject;
|
||||
|
||||
try {
|
||||
jObject = JObject.Parse(content);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return jObject;
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await HttpResponseToHtmlDocument(httpResponse).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> UrlPostToHtmlDocument(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage httpResponse = await UrlPost(request, data, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await HttpResponseToHtmlDocument(httpResponse).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<string> UrlGetToTitle(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HtmlDocument htmlDocument = await UrlGetToHtmlDocument(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -165,25 +257,12 @@ namespace ArchiSteamFarm {
|
||||
return htmlNode.InnerText;
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<JArray> UrlGetToJArray(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await HttpResponseToHtmlDocument(httpResponse).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<JArray> UrlGetToJArray(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
|
||||
string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
@@ -193,19 +272,19 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
jArray = JArray.Parse(content);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException("WebBrowser", e);
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return jArray;
|
||||
}
|
||||
|
||||
internal static async Task<JObject> UrlGetToJObject(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<JObject> UrlGetToJObject(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
|
||||
string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
@@ -215,19 +294,19 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
jObject = JObject.Parse(content);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException("WebBrowser", e);
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return jObject;
|
||||
}
|
||||
|
||||
internal static async Task<XmlDocument> UrlGetToXML(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
internal static async Task<XmlDocument> UrlGetToXML(string request, Dictionary<string, string> cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
|
||||
string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
@@ -237,7 +316,7 @@ namespace ArchiSteamFarm {
|
||||
try {
|
||||
xmlDocument.LoadXml(content);
|
||||
} catch (XmlException e) {
|
||||
Logging.LogGenericException("WebBrowser", e);
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
102
ArchiSteamFarm/config/example.xml
Normal file → Executable file
102
ArchiSteamFarm/config/example.xml
Normal file → Executable file
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<!-- This is full-fledged example config, you may be also interested in minimal.xml for bare minimum one -->
|
||||
|
||||
<!-- This config includes all user-switchable properties that you may want to change on per-bot basis -->
|
||||
<!-- Default values used in config match default ASF values when given config property is not found -->
|
||||
<!-- In other words, if given property is not defined, ASF will assume default pre-programmed value -->
|
||||
|
||||
<!-- Notice, if you use special characters reserved for XML, you should escape them -->
|
||||
<!-- Escape table: [& - &] | [" - "] | [' - '] | [< - <] | [> - >] -->
|
||||
@@ -10,83 +13,136 @@
|
||||
<!-- Type of the property is a tip for you that defines what values you can use -->
|
||||
<!-- bool - Boolean value that can be only "true" or "false" -->
|
||||
<!-- string - Any sequence of characters, unless stated otherwise (keep in mind escape table above), with special treatment of "null" value -->
|
||||
<!-- byte - 8-bit unsigned integer, used mostly for small numbers (0-255) -->
|
||||
<!-- uint - 32-bit unsigned integer, used mostly for steam appID -->
|
||||
<!-- ulong - 64-bit unsigned (long) integer, used mostly for representing steamID64 -->
|
||||
<!-- HashSet(uint) - Comma-separated list of unique 32-bit unsigned integers -->
|
||||
|
||||
<!-- Master switch to turn account on and off, set to "true" after you're done -->
|
||||
<!-- TIP: This bot instance won't run unless below switch is set to "true" -->
|
||||
<!-- This bot instance won't run unless below switch is set to "true" -->
|
||||
<Enabled type="bool" value="false"/>
|
||||
|
||||
<!-- This is your steam login, the one you use for logging in to steam -->
|
||||
<!-- TIP: You can use "null" if you wish to enter login on every startup -->
|
||||
<!-- You can use "null" if you wish to enter login on every startup -->
|
||||
<SteamLogin type="string" value="null"/>
|
||||
|
||||
<!-- This is your steam password, the one you use for logging in to steam -->
|
||||
<!-- TIP: You can use "null" if you wish to enter password on every startup -->
|
||||
<!-- You can use "null" if you wish to enter password on every startup -->
|
||||
<SteamPassword type="string" value="null"/>
|
||||
|
||||
<!-- This is steam nickname, the one you want to use for bot. Can be anything up to 32 characters -->
|
||||
<!-- TIP: You can use "null" if you wish to preserve your actual nickname -->
|
||||
<!-- You can use "null" if you wish to preserve your actual nickname, and this is what you want most likely -->
|
||||
<SteamNickname type="string" value="null"/>
|
||||
|
||||
<!-- This is your bot's API key, get one at https://steamcommunity.com/dev/apikey while logged in as bot, domain doesn't matter -->
|
||||
<!-- TIP: You can use "null", but it will disable all API-based functionalities such as trading -->
|
||||
<!-- This is your bot's API key, get one at https://steamcommunity.com/dev/apikey while logged in as a bot, domain doesn't matter -->
|
||||
<!-- Remember that each account should have unique API key generated through above link, wrong API key will make all API-based functionalities to fail -->
|
||||
<!-- API key is useless for primary accounts, as they're not using trading feature, you can leave it at "null" if you're configuring a primary account -->
|
||||
<!-- When at "null", it will disable all ASF API-based functionalities such as trading -->
|
||||
<SteamApiKey type="string" value="null"/>
|
||||
|
||||
<!-- This is your parental PIN if you use steam parental functionality -->
|
||||
<!-- TIP: Most likely you don't want to change it. You can use "null" if you wish to enter PIN on every startup, 0 means there is no PIN -->
|
||||
<!-- Most likely you don't want to change it. You can use "null" if you wish to enter PIN on every startup, 0 means there is no PIN, and this is probably what you want -->
|
||||
<SteamParentalPIN type="string" value="0"/>
|
||||
|
||||
<!-- This is steamID64 of the bot-master - you, for example "76561198006963719" -->
|
||||
<!-- You can get one e.g. by logging in to http://steamrep.com/ -->
|
||||
<!-- TIP: You can use "0", but bot won't accept steam cd-keys or trades from anybody" -->
|
||||
<!-- If you're configuring primary account, you can safely leave it at "0", as you're master yourself -->
|
||||
<!-- When at "0", bot won't accept any commands, including steam cd-keys or trades" -->
|
||||
<SteamMasterID type="ulong" value="0"/>
|
||||
|
||||
<!-- This is steamID64 of the master clan (group). If defined, bot will join the group and the chat automatically after logging in -->
|
||||
<!-- The easiest way to get one is to check groups on your profile: http://steamcommunity.com/my/profile" -->
|
||||
<!-- Then copying the URL of "Leave group" link, it will look like this: javascript:leaveGroupPrompt('103582791440160998','Archi\'s SC Farm') -->
|
||||
<!-- The steamID64 we're looking for is there, in above example: "103582791440160998" -->
|
||||
<!-- TIP: If you don't have your own farming group, most likely you don't want to change it, 0 means there is no master group defined -->
|
||||
<!-- Remember that joining a group and the chat requires having non-restricted access to steam community, which is not always the case with alts -->
|
||||
<!-- If you don't have your own farming group, most likely you don't want to change it, 0 means there is no master group defined -->
|
||||
<SteamMasterClanID type="ulong" value="0"/>
|
||||
|
||||
<!-- This switch defines if you want to use built-in ASF two-factor-authentication (ASF 2FA) for this account -->
|
||||
<!-- The one and only purpose for this function is automating steam logins and steam trades, so you won't need to enter 2FA codes all the time -->
|
||||
<!-- This however defeats the whole purpose of two-factor-auth, and should be used on alt accounts ONLY -->
|
||||
<!-- You should read full documentation, along with explanation here: https://github.com/JustArchi/ArchiSteamFarm/wiki/Escrow -->
|
||||
<!-- Personally I suggest switching this to "true" ONLY for alts -->
|
||||
<!-- WARNING, this option can potentially LOCK OUT YOUR ACCOUNT! If you don't fully understand this feature, DON'T USE IT! -->
|
||||
<UseAsfAsMobileAuthenticator type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines if the account has card drops restricted -->
|
||||
<!-- Restricted card drops means that the account doesn't receive any steam cards until it plays the game for at least 2 hours -->
|
||||
<!-- As there is no magical way to detect it by ASF, I made this option config-based switch -->
|
||||
<!-- TIP: Based on this parameter, ASF will try to choose the most optimal cards farming algorithm for this account -->
|
||||
<!-- There is no magical way to detect it by ASF, I made this option config-based switch -->
|
||||
<!-- Based on our observations, it looks like most accounts that never issued a refund have non-restricted card drops, and this is what default value assumes -->
|
||||
<!-- However, if you noticed that your cards never drop until you hit 2 hours played mark, consider setting it as "true" -->
|
||||
<!-- Guessing "wrong" won't have any real consequences, ASF will just work in non-optimal way, and everybody wants to drop cards fast, right? -->
|
||||
<!-- Based on this parameter, ASF will try to choose the most optimal cards farming algorithm for this account -->
|
||||
<CardDropsRestricted type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines if the account should stay as "Offline" after logging in to Steam -->
|
||||
<!-- Please note that bot won't be able to respond to any commands when this property is set to "true" -->
|
||||
<!-- TIP: Setting this to "true" may be useful for primary accounts, to not show as online when you're not here -->
|
||||
<!-- This switch defines if the account should stay as "Offline" after logging in to Steam. This has several advantages and disadvantages -->
|
||||
<!-- Personally I find it extremely useful for primary accounts, as your status will remain "Online" and not "In-Game" when ASF is farming -->
|
||||
<!-- Thanks to that, your friends will never ask you again if you're playing or farming the game. Your status will now nicely reflect that -->
|
||||
<!-- However, it's less useful for alt accounts, as you won't be able to interact with bots and check what they're doing - they'll appear as "Offline" -->
|
||||
<!-- Personally I suggest switching this to "true" for primary accounts, and leaving it at "false" for alt accounts -->
|
||||
<!-- Bot won't be able to receive and answer to your commands, unless you set "HandleOfflineMessages" below to "true" as well -->
|
||||
<FarmOffline type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines if bot should handle offline messages when it sees them -->
|
||||
<!-- Basically it should be used only when "FarmOffline" property above is true, so bot can handle offline messages -->
|
||||
<!-- Reading offline messages will also mark them as received, therefore it should not be used if you want to keep them for later -->
|
||||
<!-- Personally I suggest keeping this on "false" for primary accounts, and considering switching to "true" for alts, if "FarmOffline" above is set to true as well -->
|
||||
<HandleOfflineMessages type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines if bot should try to forward key to the other bot in case of failing to redeem on his own account -->
|
||||
<!-- This can be useful if you want to send key to your farm and you don't care which account redeems it -->
|
||||
<!-- When "true", bot will try redeeming "AlreadyOwned", "BaseGameRequired", "OnCooldown" and "RegionLocked" keys on other available accounts -->
|
||||
<!-- By default this option is disabled, as not everyone might want to redeem keys on other configured accounts -->
|
||||
<!-- WARNING: Remember that Steam issues temporary ban on too many failed key activations (OnCooldown status) -->
|
||||
<!-- Therefore, be careful when it comes to multiple keys activations, this option is supposed to help you, not make you lazy -->
|
||||
<ForwardKeysToOtherBots type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines bot's behaviour on getting multiple keys -->
|
||||
<!-- When "false", bot will try to redeem all keys on it's own account, when "true", keys will be distributed on "one bot, one key" basis -->
|
||||
<!-- Keep in mind that "ForwardKeysToOtherBots" property defined above affects this switch as well -->
|
||||
<!-- When "ForwardKeysToOtherBots" is "true", bot will try redeeming "AlreadyOwned", "BaseGameRequired", "OnCooldown" and "RegionLocked" keys on other available accounts -->
|
||||
<!-- If "ForwardKeysToOtherBots" is set to "false", bot will keep unused keys for itself -->
|
||||
<!-- By default this option is disabled, as not everyone might want to alter the multiple keys redeeming behaviour -->
|
||||
<!-- Switch this to "true" only if you prefer to use "one bot, one key" behaviour, instead of default one, which is to redeem all keys only on one account -->
|
||||
<DistributeKeys type="bool" value="false"/>
|
||||
|
||||
<!-- This switch defines if bot should disconnect once farming is finished -->
|
||||
<!-- When no bots are active, ASF will shutdown as well -->
|
||||
<!-- Some people may want to keep their bots 24/7, other disconnect them after job is done -->
|
||||
<!-- Choose yourself what you prefer -->
|
||||
<!-- Keep in mind that when no bots are active, ASF will shutdown as well -->
|
||||
<!-- You may want to disconnect the bot after he's done, if that's the case, set below to "true" -->
|
||||
<!-- However, you may instead want to keep it online for the whole time, in this case, leave it at "false" -->
|
||||
<!-- Even when bot is not farming anything, he'll keep checking badges from time to time, if there is anything new to farm -->
|
||||
<!-- Personally I suggest leaving it at "false", unless you have a reason to close the process after all bots finished farming -->
|
||||
<ShutdownOnFarmingFinished type="bool" value="false"/>
|
||||
|
||||
<!-- Comma-separated list of IDs that should not be considered for farming -->
|
||||
<!-- Default value includes appIDs that are wrongly appearing on the profile, e.g. Monster Summer Sale -->
|
||||
<!-- TIP: Most likely you don't want to change it -->
|
||||
<!-- This switch defines if bot should send you all the items it farmed after farming is finished -->
|
||||
<!-- Remember that in order to use this feature, SteamMasterID must be defined above -->
|
||||
<!-- If SteamMasterID is not a friend of this bot, SteamTradeToken will also be needed to set below -->
|
||||
<SendOnFarmingFinished type="bool" value="false"/>
|
||||
|
||||
<!-- This is a SteamTradeToken of SteamMasterID, which is required if bot doesn't have SteamMasterID on friend list -->
|
||||
<!-- You can get the token here: http://steamcommunity.com/id/me/tradeoffers/privacy while being logged in as SteamMasterID -->
|
||||
<!-- The token has 8 characters and is written in the last part of trade URL link, starting after "&token=" -->
|
||||
<SteamTradeToken type="string" value="null"/>
|
||||
|
||||
<!-- This switch defines if bot should send you trade offer with all farmed cards on regular basis -->
|
||||
<!-- This can become useful if you have lots of games to farm and you don't want to wait for all of them to be farmed -->
|
||||
<!-- However, if you have many bots running, it may become a bit spammy/intrusive, so it's not enabled by default -->
|
||||
<!-- Remember that there is also "SendOnFarmingFinished" switch above, which is far less intrusive and can be used instead -->
|
||||
<!-- This property defines how often bot should send you a trade offer, in hours -->
|
||||
<!-- For example, setting this to "24" will result in a trade offer being sent once per day (24 hours) -->
|
||||
<!-- Default value of "0" disables that feature -->
|
||||
<SendTradePeriod type="byte" value="0"/>
|
||||
|
||||
<!-- This is comma-separated list of IDs that should not be considered for farming -->
|
||||
<!-- Default value includes appIDs that are wrongly appearing on the profile, e.g. Summer Sale, Winter Sale or Monster Summer Game -->
|
||||
<!-- In addition to blacklist defined here, ASF also has global blacklist, which is being updated on as-needed basis, so you don't need to update this entry -->
|
||||
<!-- You probably don't want to change anything here -->
|
||||
<Blacklist type="HashSet(uint)" value="303700,335590,368020,425280"/>
|
||||
|
||||
<!-- This switch enables statistics for me - bot will join Archi's SC Farm group and chat -->
|
||||
<!-- This switch enables statistics for me - bot will join Archi's SC Farm group and chat on steam -->
|
||||
<!-- Consider leaving it at "true", this way I can check how many running bots are active -->
|
||||
<!-- That directly affects my willings to work on the project, as I can see how many users are actually using it -->
|
||||
<!-- TIP: Group link is http://steamcommunity.com/groups/ascfarm -->
|
||||
<!-- Such information directly affects my willings to work on the project, as I can see how many users are actually using it -->
|
||||
<!-- So if you want to see new versions coming up, bugs being fixed, and new features getting implemented, consider leaving it at "true" -->
|
||||
<!-- You can find the group, along with all the statistics here: http://steamcommunity.com/groups/ascfarm -->
|
||||
<Statistics type="bool" value="true"/>
|
||||
</configuration>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<!-- This is minimalistic config to "just make ASF work" -->
|
||||
<!-- For full-fledged config, please take a look at example.xml -->
|
||||
<!-- This is minimalistic config "just to make ASF work". For full-fledged config, please take a look at example.xml -->
|
||||
<!-- Full-fledged config includes some neat features such as offline farming or cards farming algorithm selection -->
|
||||
<!-- ASF will use default values for missing properties, check example.xml to see all of them -->
|
||||
<Enabled type="bool" value="false"/>
|
||||
<SteamLogin type="string" value="null"/>
|
||||
<SteamPassword type="string" value="null"/>
|
||||
|
||||
@@ -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="8.0.1-beta4" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
|
||||
<package id="SteamKit2" version="1.7.0" targetFramework="net452" />
|
||||
</packages>
|
||||
@@ -19,11 +19,7 @@ ASF doesn't require and doesn't interfere in any way with Steam client. In addit
|
||||
|
||||
**Setting up:**
|
||||
|
||||
Each ASF bot is defined in it's own XML config file in `config` directory. ASF comes with included ```example.xml``` config file, on which you should base all of your bots. Simply copy ```example.xml``` to a new file, and edit properties inside. Don't forget to switch ```Enabled``` property to ```true``` once you're done, as this is the master switch which enables configured bot to launch. The most minimalistic setup to make ASF working is changing only ```Enabled```, ```SteamLogin``` and ```SteamPassword``` properties, everything else is more or less optional to enable additional features.
|
||||
|
||||
After you set up all your bots (their configs), you should launch ```ASF.exe```. If your accounts require additional steps to unlock, such as Steam guard code, you'll need to enter those too after ASF tries to launch given bot. If everything ended properly, you should notice in the console output, as well as on your Steam, that all of your bots automatically started cards farming.
|
||||
|
||||
ASF doesn't require and doesn't interfere in any way with Steam client, which means that you can be logged in to Steam client as your primary account, and launch ASF at the same time, for any number of accounts, including your main one (if needed).
|
||||
Detailed setting up instructions are available on **[our wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki/Setting-up)**.
|
||||
|
||||
**Current Commands:**
|
||||
|
||||
|
||||
21
SteamAuth/LICENSE
Normal file
21
SteamAuth/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Joshua Coffey
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -32,7 +32,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.1-beta4\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace SteamAuth
|
||||
public string GenerateConfirmationQueryParams(string tag)
|
||||
{
|
||||
if (String.IsNullOrEmpty(DeviceID))
|
||||
DeviceID = AuthenticatorLinker.GenerateDeviceID();
|
||||
throw new ArgumentException("Device ID is not present");
|
||||
|
||||
long time = TimeAligner.GetSteamTime();
|
||||
return "p=" + this.DeviceID + "&a=" + this.Session.SteamID.ToString() + "&k=" + _generateConfirmationHashForTime(time, tag) + "&t=" + time + "&m=android&tag=" + tag;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="8.0.1-beta4" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
|
||||
</packages>
|
||||
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/Newtonsoft.Json.8.0.2/Newtonsoft.Json.8.0.2.nupkg
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/Newtonsoft.Json.8.0.2.nupkg
vendored
Normal file
Binary file not shown.
BIN
packages/Newtonsoft.Json.8.0.2/lib/net20/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/net20/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -6607,6 +6607,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
@@ -6719,6 +6725,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -7477,6 +7489,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
BIN
packages/Newtonsoft.Json.8.0.2/lib/net35/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/net35/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -5643,6 +5643,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
@@ -5761,6 +5767,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDateTimeOffset">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -6531,6 +6543,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
BIN
packages/Newtonsoft.Json.8.0.2/lib/net40/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/net40/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -5851,6 +5851,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
@@ -5969,6 +5975,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDateTimeOffset">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -6739,6 +6751,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
BIN
packages/Newtonsoft.Json.8.0.2/lib/net45/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/net45/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -2361,6 +2361,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -3062,6 +3068,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDateTimeOffset">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -3562,6 +3574,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
BIN
packages/Newtonsoft.Json.8.0.2/lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/portable-net40+sl5+wp80+win8+wpa81/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -1922,6 +1922,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -2605,6 +2611,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDateTimeOffset">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -3105,6 +3117,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
BIN
packages/Newtonsoft.Json.8.0.2/lib/portable-net45+wp80+win8+wpa81+dnxcore50/Newtonsoft.Json.dll
vendored
Normal file
BIN
packages/Newtonsoft.Json.8.0.2/lib/portable-net45+wp80+win8+wpa81+dnxcore50/Newtonsoft.Json.dll
vendored
Normal file
Binary file not shown.
@@ -2172,6 +2172,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonReader.ReadAsDecimal">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -2855,6 +2861,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Byte"/>[] or a null reference if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>. This method will return <c>null</c> at the end of an array.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonTextReader.ReadAsDateTimeOffset">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
@@ -3355,6 +3367,12 @@
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsBoolean">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.Nullable`1"/>.
|
||||
</summary>
|
||||
<returns>A <see cref="T:System.Nullable`1"/>.</returns>
|
||||
</member>
|
||||
<member name="M:Newtonsoft.Json.JsonValidatingReader.ReadAsString">
|
||||
<summary>
|
||||
Reads the next JSON token from the stream as a <see cref="T:System.String"/>.
|
||||
Binary file not shown.
Binary file not shown.
BIN
tools/ILRepack.exe
Normal file
BIN
tools/ILRepack.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user