Compare commits

...

40 Commits

Author SHA1 Message Date
JustArchi
ab531c80df Misc 2016-03-11 02:15:25 +01:00
JustArchi
f20ea0a87f Add FarmingPeriod 2016-03-11 02:07:20 +01:00
JustArchi
3e7f726afb Bump 2016-03-11 00:52:15 +01:00
JustArchi
d247515a03 Confirmations should be minutes-based 2016-03-10 22:54:59 +01:00
JustArchi
f084a3f219 Mark inventory on login if needed 2016-03-10 21:44:45 +01:00
JustArchi
4d3673c305 Forgot to add 2016-03-10 21:19:21 +01:00
JustArchi
0253c3bf7b Add AcceptConfirmationsPeriod 2016-03-10 21:17:48 +01:00
JustArchi
0eae895676 SteamAuth update 2016-03-10 21:11:54 +01:00
JustArchi
045acb362d Code review 2016-03-10 02:21:28 +01:00
JustArchi
ebd65da3ff Misc 2016-03-10 01:23:32 +01:00
JustArchi
1de3c5bec0 Code review 2016-03-10 01:20:17 +01:00
JustArchi
b334c939df Add !2faok 2016-03-10 00:40:30 +01:00
JustArchi
8e6100e236 Add MaxFarmingTime, FarmingDelay 2016-03-09 19:25:45 +01:00
JustArchi
8cb512b6e5 Add InventoryLimiterDelay, rename RequestLimiterDelay to LoginLimiterDelay 2016-03-09 18:58:14 +01:00
JustArchi
6a28946205 Refresh farming queue when farming is finished 2016-03-09 18:50:54 +01:00
JustArchi
c632c025cb Move Statistics to global config 2016-03-09 05:02:07 +01:00
JustArchi
1403ffe190 This actually DOES work on Mono, hooray! 2016-03-09 03:52:43 +01:00
JustArchi
71ae9a84da Fix misc bug, #48 2016-03-09 03:52:04 +01:00
JustArchi
a823471771 Bump version to test updates 2016-03-09 03:40:57 +01:00
JustArchi
dac057d242 World Domination is close 2016-03-09 03:28:05 +01:00
JustArchi
3d9fe36245 The end is near... #48 2016-03-09 03:10:33 +01:00
JustArchi
5c1da24def Misc 2016-03-08 03:29:48 +01:00
JustArchi
1e961a5945 Add CustomGamePlayedWhileIdle 2016-03-08 03:18:50 +01:00
JustArchi
84898146d2 Add more debugging tools 2016-03-08 02:22:00 +01:00
JustArchi
552613e977 Add AccountPlayingDelay, closes #122 2016-03-07 23:10:07 +01:00
JustArchi
07687df91f Add debugging and NetHookAnalyzer 2016-03-07 18:12:05 +01:00
JustArchi
1a4d941a2c Fix derp, #141 2016-03-07 16:40:34 +01:00
JustArchi
cfbd880995 Add more support for SDA 2016-03-07 15:53:46 +01:00
JustArchi
09abe77495 Support SDA files 2016-03-07 15:42:38 +01:00
JustArchi
ac9943ff94 Misc 2016-03-07 14:48:59 +01:00
JustArchi
741dd2adb7 Add Holiday Sale 2013 to blacklist 2016-03-07 14:47:28 +01:00
JustArchi
1ad5d3676f Add GlobalDatabase 2016-03-07 02:39:55 +01:00
JustArchi
27254aa31e Copy ASF.json to out 2016-03-06 23:34:46 +01:00
JustArchi
bb90dc1c01 Bugfixes 2016-03-06 23:32:17 +01:00
JustArchi
292ec97b1c Work on GlobalConfig, #131 2016-03-06 23:28:56 +01:00
JustArchi
238cc2ad46 Code review 2016-03-06 22:14:02 +01:00
JustArchi
b9064bbfda Fix awaiting null tasks, closes #128 2016-03-06 21:49:34 +01:00
Łukasz Domeradzki
eddcc2816a Update README.md 2016-03-06 21:17:21 +01:00
JustArchi
52360a682a Add DismissInventoryNotifications, closes #132 2016-03-06 14:15:59 +01:00
JustArchi
709ce6489b Add only enabled bots to collection, closes #139 2016-03-06 14:09:08 +01:00
33 changed files with 915 additions and 355 deletions

4
.gitignore vendored
View File

@@ -4,11 +4,13 @@
# Ignore all config files, apart from ones we want to include
ArchiSteamFarm/config/*
!ArchiSteamFarm/config/ASF.json
!ArchiSteamFarm/config/example.json
!ArchiSteamFarm/config/minimal.json
# Ignore local debugging log file
# Ignore local debugging
ArchiSteamFarm/log.txt
ArchiSteamFarm/debug/*
#################
## Eclipse

View File

@@ -24,9 +24,11 @@
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler {
@@ -48,7 +50,11 @@ namespace ArchiSteamFarm {
Items = 514
}
internal ENotificationType NotificationType { get; set; }
internal readonly ENotificationType NotificationType;
internal Notification(ENotificationType notificationType) {
NotificationType = notificationType;
}
}
internal readonly List<Notification> Notifications;
@@ -62,9 +68,7 @@ namespace ArchiSteamFarm {
Notifications = new List<Notification>(msg.notifications.Count);
foreach (var notification in msg.notifications) {
Notifications.Add(new Notification {
NotificationType = (Notification.ENotificationType) notification.user_notification_type
});
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
}
}
@@ -77,7 +81,7 @@ namespace ArchiSteamFarm {
if (msg.count_new_items > 0) {
Notifications = new List<Notification>(1) {
new Notification { NotificationType = Notification.ENotificationType.Items }
new Notification(Notification.ENotificationType.Items)
};
}
}
@@ -100,7 +104,7 @@ namespace ArchiSteamFarm {
}
internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult {
internal enum EPurchaseResult : int {
Unknown = -1,
OK = 0,
AlreadyOwned = 9,
@@ -126,6 +130,10 @@ namespace ArchiSteamFarm {
Result = (EResult) msg.eresult;
PurchaseResult = (EPurchaseResult) msg.purchase_result_details;
if (msg.purchase_receipt_info == null) {
return;
}
ReceiptInfo = new KeyValue();
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
if (!ReceiptInfo.TryReadAsBinary(ms)) {
@@ -178,6 +186,27 @@ namespace ArchiSteamFarm {
Client.Send(request);
}
internal void PlayGame(string gameName) {
if (!Client.IsConnected) {
return;
}
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
var gamePlayed = new CMsgClientGamesPlayed.GamePlayed();
if (!string.IsNullOrEmpty(gameName)) {
gamePlayed.game_id = new GameID() {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
};
gamePlayed.game_extra_info = gameName;
}
request.Body.games_played.Add(gamePlayed);
Client.Send(request);
}
internal void PlayGames(params uint[] gameIDs) {
if (!Client.IsConnected) {
return;
@@ -198,7 +227,7 @@ namespace ArchiSteamFarm {
}
internal void PlayGames(ICollection<uint> gameIDs) {
if (gameIDs == null || gameIDs.Count == 0 || !Client.IsConnected) {
if (gameIDs == null || !Client.IsConnected) {
return;
}
@@ -216,7 +245,7 @@ namespace ArchiSteamFarm {
Client.Send(request);
}
internal AsyncJob<PurchaseResponseCallback> RedeemKey(string key) {
internal async Task<PurchaseResponseCallback> RedeemKey(string key) {
if (string.IsNullOrEmpty(key) || !Client.IsConnected) {
return null;
}
@@ -228,7 +257,12 @@ namespace ArchiSteamFarm {
request.Body.key = key;
Client.Send(request);
return new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
try {
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
/*

View File

@@ -12,6 +12,7 @@
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper>
<TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
@@ -26,7 +27,6 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -102,17 +102,17 @@
<Compile Include="ArchiWebHandler.cs" />
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
<Compile Include="CMsgClientClanInviteAction.cs" />
<Compile Include="CMsgs\CMsgClientClanInviteAction.cs" />
<Compile Include="Debugging.cs" />
<Compile Include="GlobalConfig.cs" />
<Compile Include="JSON\GitHub.cs" />
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" />
<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" />
@@ -120,6 +120,9 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="config\example.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -158,6 +161,7 @@
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
copy "$(TargetDir)config\ASF.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\example.json" "$(TargetDir)out\config"
copy "$(TargetDir)config\minimal.json" "$(TargetDir)out\config"
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
@@ -165,6 +169,7 @@
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(TargetDir)out" "$(TargetDir)out/config"
cp "$(TargetDir)config/ASF.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/example.json" "$(TargetDir)out/config"
cp "$(TargetDir)config/minimal.json" "$(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"

View File

@@ -34,14 +34,22 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler {
private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds
private static int Timeout = 30 * 1000;
private readonly Bot Bot;
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
private ulong SteamID;
internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
}
internal ArchiWebHandler(Bot bot) {
if (bot == null) {
return;
}
Bot = bot;
}
@@ -58,7 +66,7 @@ namespace ArchiSteamFarm {
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on
byte[] cryptedSessionKey = null;
byte[] cryptedSessionKey;
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
cryptedSessionKey = rsa.Encrypt(sessionKey);
}
@@ -134,14 +142,14 @@ namespace ArchiSteamFarm {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
var restart = Task.Run(async () => await Bot.Restart().ConfigureAwait(false));
Task.Run(async () => await Bot.Restart().ConfigureAwait(false)).Forget();
return true;
}
return false;
}
internal List<SteamTradeOffer> GetTradeOffers() {
internal List<Steam.TradeOffer> GetTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return null;
}
@@ -168,15 +176,15 @@ namespace ArchiSteamFarm {
return null;
}
List<SteamTradeOffer> result = new List<SteamTradeOffer>();
List<Steam.TradeOffer> result = new List<Steam.TradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) {
SteamTradeOffer tradeOffer = new SteamTradeOffer {
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
tradeofferid = trade["tradeofferid"].AsString(),
accountid_other = trade["accountid_other"].AsInteger(),
trade_offer_state = trade["trade_offer_state"].AsEnum<SteamTradeOffer.ETradeOfferState>()
trade_offer_state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
};
foreach (KeyValue item in trade["items_to_give"].Children) {
tradeOffer.items_to_give.Add(new SteamItem {
tradeOffer.items_to_give.Add(new Steam.Item {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
@@ -186,7 +194,7 @@ namespace ArchiSteamFarm {
});
}
foreach (KeyValue item in trade["items_to_receive"].Children) {
tradeOffer.items_to_receive.Add(new SteamItem {
tradeOffer.items_to_receive.Add(new Steam.Item {
appid = item["appid"].AsString(),
contextid = item["contextid"].AsString(),
assetid = item["assetid"].AsString(),
@@ -293,7 +301,7 @@ namespace ArchiSteamFarm {
return true;
}
internal async Task<List<SteamItem>> GetMyTradableInventory() {
internal async Task<List<Steam.Item>> GetMyTradableInventory() {
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?trading=1", Cookie).ConfigureAwait(false);
@@ -310,10 +318,10 @@ namespace ArchiSteamFarm {
return null;
}
List<SteamItem> result = new List<SteamItem>();
List<Steam.Item> result = new List<Steam.Item>();
foreach (JToken jToken in jTokens) {
try {
result.Add(JsonConvert.DeserializeObject<SteamItem>(jToken.ToString()));
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
}
@@ -322,7 +330,7 @@ namespace ArchiSteamFarm {
return result;
}
internal async Task<bool> SendTradeOffer(List<SteamItem> inventory, ulong partnerID, string token = null) {
internal async Task<bool> SendTradeOffer(List<Steam.Item> inventory, ulong partnerID, string token = null) {
if (inventory == null || inventory.Count == 0 || partnerID == 0) {
return false;
}
@@ -332,21 +340,21 @@ namespace ArchiSteamFarm {
return false;
}
List<SteamTradeOfferRequest> trades = new List<SteamTradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
List<Steam.TradeOfferRequest> trades = new List<Steam.TradeOfferRequest>(1 + inventory.Count / Trading.MaxItemsPerTrade);
SteamTradeOfferRequest singleTrade = null;
Steam.TradeOfferRequest singleTrade = null;
for (ushort i = 0; i < inventory.Count; i++) {
if (i % Trading.MaxItemsPerTrade == 0) {
if (trades.Count >= Trading.MaxTradesPerAccount) {
break;
}
singleTrade = new SteamTradeOfferRequest();
singleTrade = new Steam.TradeOfferRequest();
trades.Add(singleTrade);
}
SteamItem item = inventory[i];
singleTrade.me.assets.Add(new SteamItem() {
Steam.Item item = inventory[i];
singleTrade.me.assets.Add(new Steam.Item() {
appid = "753",
contextid = "6",
amount = item.amount,
@@ -357,14 +365,14 @@ namespace ArchiSteamFarm {
string referer = "https://steamcommunity.com/tradeoffer/new";
string request = referer + "/send";
foreach (SteamTradeOfferRequest trade in trades) {
foreach (Steam.TradeOfferRequest trade in trades) {
Dictionary<string, string> data = new Dictionary<string, string>(6) {
{"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
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
};
HttpResponseMessage response = null;
@@ -462,13 +470,20 @@ namespace ArchiSteamFarm {
}
foreach (string setCookieValue in setCookieValues) {
if (setCookieValue.Contains("steamparental=")) {
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14);
setCookie = setCookie.Substring(0, setCookie.IndexOf(';'));
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
if (!setCookieValue.Contains("steamparental=")) {
continue;
}
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=", StringComparison.Ordinal) + 14);
int index = setCookie.IndexOf(';');
if (index > 0) {
setCookie = setCookie.Substring(0, index);
}
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
}
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);

View File

@@ -27,7 +27,6 @@ using SteamAuth;
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
@@ -40,11 +39,12 @@ namespace ArchiSteamFarm {
private const ulong ArchiSCFarmGroup = 103582791440160998;
private const ushort CallbackSleep = 500; // In miliseconds
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private readonly string SentryFile;
private readonly Timer AcceptConfirmationsTimer;
private readonly Timer SendItemsTimer;
internal readonly string BotName;
@@ -67,20 +67,12 @@ namespace ArchiSteamFarm {
private bool LoggedInElsewhere = false;
private string AuthCode, TwoFactorAuth;
internal static string GetAnyBotName() {
foreach (string botName in Bots.Keys) {
return botName;
}
return null;
}
internal static async Task RefreshCMs() {
internal static async Task RefreshCMs(uint cellID) {
bool initialized = false;
for (byte i = 0; i < 3 && !initialized; i++) {
try {
Logging.LogGenericInfo("Refreshing list of CMs...");
await SteamDirectory.Initialize().ConfigureAwait(false);
await SteamDirectory.Initialize(cellID).ConfigureAwait(false);
initialized = true;
} catch (Exception e) {
Logging.LogGenericException(e);
@@ -111,18 +103,6 @@ namespace ArchiSteamFarm {
return;
}
bool alreadyExists;
lock (Bots) {
alreadyExists = Bots.ContainsKey(botName);
if (!alreadyExists) {
Bots[botName] = this;
}
}
if (alreadyExists) {
return;
}
BotName = botName;
string botPath = Path.Combine(Program.ConfigDirectory, botName);
@@ -176,12 +156,49 @@ namespace ArchiSteamFarm {
return;
}
bool alreadyExists;
lock (Bots) {
alreadyExists = Bots.ContainsKey(botName);
if (!alreadyExists) {
Bots[botName] = this;
}
}
if (alreadyExists) {
return;
}
BotDatabase = BotDatabase.Load(botPath + ".db");
SentryFile = botPath + ".bin";
// Support and convert SDA files
if (BotDatabase.SteamGuardAccount == null && File.Exists(botPath + ".maFile")) {
Logging.LogGenericInfo("Converting SDA .maFile into ASF format...", botName);
try {
BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(botPath + ".maFile"));
File.Delete(botPath + ".maFile");
Logging.LogGenericInfo("Success!", botName);
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
}
// Initialize
SteamClient = new SteamClient();
if (Program.GlobalConfig.Debug && !Debugging.NetHookAlreadyInitialized) {
try {
if (Directory.Exists(Program.DebugDirectory)) {
Directory.Delete(Program.DebugDirectory, true);
}
Directory.CreateDirectory(Program.DebugDirectory);
SteamClient.DebugNetworkListener = new NetHookNetworkListener(Program.DebugDirectory);
Debugging.NetHookAlreadyInitialized = true;
} catch (Exception e) {
Logging.LogGenericException(e, botName);
}
}
ArchiHandler = new ArchiHandler();
SteamClient.AddHandler(ArchiHandler);
@@ -214,6 +231,15 @@ namespace ArchiSteamFarm {
CardsFarmer = new CardsFarmer(this);
Trading = new Trading(this);
if (BotConfig.AcceptConfirmationsPeriod > 0 && AcceptConfirmationsTimer == null) {
AcceptConfirmationsTimer = new Timer(
async e => await AcceptAllConfirmations().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod), // Delay
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
);
}
if (BotConfig.SendTradePeriod > 0 && SendItemsTimer == null) {
SendItemsTimer = new Timer(
async e => await ResponseSendTrade().ConfigureAwait(false),
@@ -236,10 +262,17 @@ namespace ArchiSteamFarm {
return;
}
await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false);
if (!await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false)) {
return;
}
Confirmation[] confirmations = await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false);
if (confirmations == null) {
return;
}
try {
foreach (Confirmation confirmation in await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) {
foreach (Confirmation confirmation in confirmations) {
if (BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) {
Logging.LogGenericInfo("Accepting confirmation: Success!", BotName);
} else {
@@ -254,8 +287,8 @@ namespace ArchiSteamFarm {
}
internal void ResetGamesPlayed() {
if (BotConfig.GamesPlayedWhileIdle.Contains(0)) {
ArchiHandler.PlayGames(0);
if (!string.IsNullOrEmpty(BotConfig.CustomGamePlayedWhileIdle)) {
ArchiHandler.PlayGame(BotConfig.CustomGamePlayedWhileIdle);
} else {
ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle);
}
@@ -291,6 +324,8 @@ namespace ArchiSteamFarm {
return Response2FA();
case "!2faoff":
return Response2FAOff();
case "!2faok":
return await Response2FAOK().ConfigureAwait(false);
case "!exit":
Program.Exit();
return null;
@@ -298,7 +333,7 @@ namespace ArchiSteamFarm {
return ResponseRejoinChat();
case "!restart":
Program.Restart();
return "Done";
return null;
case "!status":
return ResponseStatus();
case "!statusall":
@@ -317,6 +352,8 @@ namespace ArchiSteamFarm {
return Response2FA(args[1]);
case "!2faoff":
return Response2FAOff(args[1]);
case "!2faok":
return await Response2FAOK(args[1]).ConfigureAwait(false);
case "!addlicense":
if (args.Length > 2) {
return await ResponseAddLicense(args[1], args[2]).ConfigureAwait(false);
@@ -356,7 +393,7 @@ namespace ArchiSteamFarm {
if (!KeepRunning) {
KeepRunning = true;
var handleCallbacks = Task.Run(() => HandleCallbacks());
Task.Run(() => HandleCallbacks()).Forget();
}
Logging.LogGenericInfo("Starting...", BotName);
@@ -429,7 +466,7 @@ namespace ArchiSteamFarm {
}
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
List<SteamItem> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
List<Steam.Item> inventory = await ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
if (inventory == null || inventory.Count == 0) {
return "Nothing to send, inventory seems empty!";
@@ -503,6 +540,28 @@ namespace ArchiSteamFarm {
return bot.Response2FAOff();
}
private async Task<string> Response2FAOK() {
if (BotDatabase.SteamGuardAccount == null) {
return "That bot doesn't have ASF 2FA enabled!";
}
await AcceptAllConfirmations().ConfigureAwait(false);
return "Done!";
}
private static async Task<string> Response2FAOK(string botName) {
if (string.IsNullOrEmpty(botName)) {
return null;
}
Bot bot;
if (!Bots.TryGetValue(botName, out bot)) {
return "Couldn't find any bot named " + botName + "!";
}
return await bot.Response2FAOK().ConfigureAwait(false);
}
private async Task<string> ResponseRedeem(string message, bool validate) {
if (string.IsNullOrEmpty(message)) {
return null;
@@ -525,7 +584,7 @@ namespace ArchiSteamFarm {
ArchiHandler.PurchaseResponseCallback result;
try {
result = await currentBot.ArchiHandler.RedeemKey(key);
result = await currentBot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
} catch (Exception e) {
Logging.LogGenericException(e, currentBot.BotName);
break;
@@ -576,7 +635,7 @@ namespace ArchiSteamFarm {
ArchiHandler.PurchaseResponseCallback otherResult;
try {
otherResult = await bot.ArchiHandler.RedeemKey(key);
otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
} catch (Exception e) {
Logging.LogGenericException(e, bot.BotName);
break; // We're done with this key
@@ -823,17 +882,16 @@ namespace ArchiSteamFarm {
return;
}
// TODO: I really need something better
if (steamID < 110300000000000000) {
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message);
} else {
if (new SteamID(steamID).IsChatAccount) {
SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message);
} else {
SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message);
}
}
private bool LinkMobileAuthenticator() {
private void LinkMobileAuthenticator() {
if (BotDatabase.SteamGuardAccount != null) {
return false;
return;
}
Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName);
@@ -846,7 +904,7 @@ namespace ArchiSteamFarm {
break;
default:
Logging.LogGenericError("Unhandled situation: " + loginResult, BotName);
return false;
return;
}
}
@@ -860,7 +918,7 @@ namespace ArchiSteamFarm {
break;
default:
Logging.LogGenericError("Unhandled situation: " + linkResult, BotName);
return false;
return;
}
}
@@ -870,12 +928,11 @@ namespace ArchiSteamFarm {
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName);
DelinkMobileAuthenticator();
return false;
return;
}
Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName);
Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, BotDatabase.SteamGuardAccount.RevocationCode);
return true;
}
private bool DelinkMobileAuthenticator() {
@@ -976,8 +1033,8 @@ namespace ArchiSteamFarm {
}
} else if (LoggedInElsewhere) {
LoggedInElsewhere = false;
Logging.LogGenericWarning("Account is being used elsewhere, will try reconnecting in 30 minutes...", BotName);
await Utilities.SleepAsync(30 * 60 * 1000).ConfigureAwait(false);
Logging.LogGenericInfo("Account is being used elsewhere, ASF will try to resume farming in " + Program.GlobalConfig.AccountPlayingDelay + " minutes...", BotName);
await Utilities.SleepAsync(Program.GlobalConfig.AccountPlayingDelay * 60 * 1000).ConfigureAwait(false);
}
Logging.LogGenericInfo("Reconnecting...", BotName);
@@ -1153,6 +1210,23 @@ namespace ArchiSteamFarm {
case EResult.OK:
Logging.LogGenericInfo("Successfully logged on!", BotName);
if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID;
}
// Support and convert SDA files
string maFilePath = Path.Combine(Program.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (BotDatabase.SteamGuardAccount == null && File.Exists(maFilePath)) {
Logging.LogGenericInfo("Converting SDA .maFile into ASF format...", BotName);
try {
BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(maFilePath));
File.Delete(maFilePath);
Logging.LogGenericInfo("Success!", BotName);
} catch (Exception e) {
Logging.LogGenericException(e, BotName);
}
}
if (BotConfig.UseAsfAsMobileAuthenticator && TwoFactorAuth == null && BotDatabase.SteamGuardAccount == null) {
LinkMobileAuthenticator();
}
@@ -1172,19 +1246,23 @@ namespace ArchiSteamFarm {
return;
}
if (BotConfig.DismissInventoryNotifications) {
await ArchiWebHandler.MarkInventory().ConfigureAwait(false);
}
if (BotConfig.SteamMasterClanID != 0) {
await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false);
JoinMasterChat();
}
if (BotConfig.Statistics) {
if (Program.GlobalConfig.Statistics) {
await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false);
SteamFriends.JoinChat(ArchiSCFarmGroup);
}
Trading.CheckTrades();
var start = Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false));
Task.Run(async () => await CardsFarmer.StartFarming().ConfigureAwait(false)).Forget();
break;
case EResult.NoConnection:
case EResult.ServiceUnavailable:
@@ -1267,7 +1345,7 @@ namespace ArchiSteamFarm {
Trading.CheckTrades();
}
if (markInventory) {
if (markInventory && BotConfig.DismissInventoryNotifications) {
await ArchiWebHandler.MarkInventory().ConfigureAwait(false);
}
}
@@ -1284,14 +1362,14 @@ namespace ArchiSteamFarm {
SteamFriends.RequestOfflineMessages();
}
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
if (callback == null) {
return;
}
if (callback.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);
Task.Run(async () => await CardsFarmer.RestartFarming().ConfigureAwait(false)).Forget();
}
}
}

View File

@@ -30,8 +30,6 @@ using System.Xml;
namespace ArchiSteamFarm {
internal sealed class BotConfig {
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false;
@@ -59,6 +57,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool CardDropsRestricted { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool DismissInventoryNotifications { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool FarmOffline { get; private set; } = false;
@@ -87,10 +88,13 @@ namespace ArchiSteamFarm {
internal byte SendTradePeriod { get; private set; } = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
internal byte AcceptConfirmationsPeriod { get; private set; } = 0;
[JsonProperty]
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
internal static BotConfig Load(string path) {
@@ -196,8 +200,6 @@ namespace ArchiSteamFarm {
}
break;
case "Statistics":
botConfig.Statistics = bool.Parse(value);
break;
case "Blacklist":
case "SteamNickname":
break;

View File

@@ -34,6 +34,10 @@ namespace ArchiSteamFarm {
return _LoginKey;
}
set {
if (_LoginKey == value) {
return;
}
_LoginKey = value;
Save();
}
@@ -44,6 +48,10 @@ namespace ArchiSteamFarm {
return _SteamGuardAccount;
}
set {
if (_SteamGuardAccount == value) {
return;
}
_SteamGuardAccount = value;
Save();
}

View File

@@ -27,17 +27,15 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer {
private const byte StatusCheckSleep = 5; // In minutes, how long to wait before checking the appID again
private const ushort MaxFarmingTime = 600; // In minutes, how long ASF is allowed to farm one game in solo mode
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly List<uint> CurrentGamesFarming = new List<uint>();
internal readonly HashSet<uint> CurrentGamesFarming = new HashSet<uint>();
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
@@ -49,22 +47,28 @@ namespace ArchiSteamFarm {
private bool NowFarming = false;
internal CardsFarmer(Bot bot) {
if (bot == null) {
return;
}
Bot = bot;
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
TimeSpan.FromMinutes(15), // Delay
TimeSpan.FromMinutes(60) // Period
);
if (Program.GlobalConfig.IdleFarmingPeriod > 0 && Timer == null) {
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
}
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
internal static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
if (gamesToFarm == null) {
return null;
}
List<uint> result = new List<uint>();
HashSet<uint> result = new HashSet<uint>();
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm) {
if (keyValue.Value >= 2) {
result.Add(keyValue.Key);
@@ -79,11 +83,7 @@ namespace ArchiSteamFarm {
return 0;
}
foreach (uint appID in gamesToFarm.Keys) {
return appID;
}
return 0;
return gamesToFarm.Keys.FirstOrDefault();
}
internal async Task<bool> SwitchToManualMode(bool manualMode) {
@@ -98,7 +98,7 @@ namespace ArchiSteamFarm {
await StopFarming().ConfigureAwait(false);
} else {
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
var start = Task.Run(async () => await StartFarming().ConfigureAwait(false));
Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
}
return true;
@@ -110,7 +110,6 @@ namespace ArchiSteamFarm {
}
float maxHour = 0;
foreach (float hour in appIDs.Values) {
if (hour > maxHour) {
maxHour = hour;
@@ -168,6 +167,7 @@ namespace ArchiSteamFarm {
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
Semaphore.Release(); // We have nothing to do, don't forget to release semaphore
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
return;
}
@@ -181,10 +181,10 @@ namespace ArchiSteamFarm {
if (Bot.BotConfig.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);
HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo[0];
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
@@ -197,7 +197,6 @@ namespace ArchiSteamFarm {
}
} else {
if (FarmMultiple(GamesToFarm)) {
farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
} else {
NowFarming = false;
@@ -222,6 +221,14 @@ namespace ArchiSteamFarm {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
NowFarming = false;
// We finished our queue for now, make sure that everything is indeed farmed before proceeding further
// Some games could be added in the meantime
if (await IsAnythingToFarm().ConfigureAwait(false)) {
Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
return;
}
Logging.LogGenericInfo("Farming finished!", Bot.BotName);
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false);
}
@@ -247,7 +254,7 @@ namespace ArchiSteamFarm {
private async Task<bool> IsAnythingToFarm() {
if (NowFarming) {
return false;
return true;
}
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
@@ -290,12 +297,12 @@ namespace ArchiSteamFarm {
await Task.WhenAll(tasks).ConfigureAwait(false);
if (GamesToFarm.Count == 0) {
return true;
return false;
}
// If we have restricted card drops, actually do check hours of all games that are left to farm
if (Bot.BotConfig.CardDropsRestricted) {
tasks = new List<Task>(GamesToFarm.Keys.Count);
tasks = new List<Task>(GamesToFarm.Count);
Logging.LogGenericInfo("Checking hours...", Bot.BotName);
foreach (uint appID in GamesToFarm.Keys) {
tasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false)));
@@ -318,7 +325,7 @@ namespace ArchiSteamFarm {
foreach (HtmlNode htmlNode in htmlNodeCollection) {
string steamLink = htmlNode.GetAttributeValue("href", null);
if (steamLink == null) {
if (string.IsNullOrEmpty(steamLink)) {
continue;
}
@@ -327,7 +334,7 @@ namespace ArchiSteamFarm {
continue;
}
if (BotConfig.GlobalBlacklist.Contains(appID)) {
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
continue;
}
@@ -385,7 +392,7 @@ namespace ArchiSteamFarm {
}
private async Task CheckGamesForFarming() {
if (NowFarming || ManualMode || GamesToFarm.Count > 0 || !Bot.SteamClient.IsConnected) {
if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
return;
}
@@ -417,9 +424,9 @@ namespace ArchiSteamFarm {
bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
for (ushort farmingTime = 0; farmingTime <= MaxFarmingTime && (!keepFarming.HasValue || keepFarming.Value); farmingTime += StatusCheckSleep) {
for (ushort farmingTime = 0; farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
}
@@ -441,13 +448,13 @@ namespace ArchiSteamFarm {
bool success = true;
while (maxHour < 2) {
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
}
// Don't forget to update our GamesToFarm hours
float timePlayed = StatusCheckSleep / 60.0F;
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
foreach (KeyValuePair<uint, float> gameToFarm in GamesToFarm) {
if (!appIDs.Contains(gameToFarm.Key)) {
continue;

View File

@@ -31,5 +31,7 @@ namespace ArchiSteamFarm {
#endif
internal static bool IsReleaseBuild => !IsDebugBuild;
internal static bool NetHookAlreadyInitialized { get; set; } = false;
}
}

View File

@@ -0,0 +1,103 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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;
using System.Collections.Generic;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class GlobalConfig {
internal enum EUpdateChannel : byte {
Unknown,
Stable,
Experimental
}
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxFarmingTime { get; private set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte IdleFarmingPeriod { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte FarmingDelay { get; private set; } = 5;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AccountPlayingDelay { get; private set; } = 5;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 7;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = 30;
[JsonProperty(Required = Required.DisallowNull)]
internal string WCFHostname { get; private set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = 1242;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);
internal static GlobalConfig Load() {
string filePath = Path.Combine(Program.ConfigDirectory, Program.GlobalConfigFile);
if (!File.Exists(filePath)) {
return null;
}
GlobalConfig globalConfig;
try {
globalConfig = JsonConvert.DeserializeObject<GlobalConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalConfig;
}
// This constructor is used only by deserializer
private GlobalConfig() { }
}
}

View File

@@ -23,33 +23,57 @@
*/
using Newtonsoft.Json;
using System;
using System.IO;
namespace ArchiSteamFarm {
internal sealed class SteamItem {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
internal sealed class GlobalDatabase {
private static readonly string FilePath = Path.Combine(Program.ConfigDirectory, Program.GlobalDatabaseFile);
[JsonProperty(Required = Required.DisallowNull)]
internal string appid { get; set; }
internal uint CellID {
get {
return _CellID;
}
set {
if (_CellID == value) {
return;
}
[JsonProperty(Required = Required.DisallowNull)]
internal string contextid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string assetid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string id {
get { return assetid; }
set { assetid = value; }
_CellID = value;
Save();
}
}
[JsonProperty(Required = Required.AllowNull)]
internal string classid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID = 0;
[JsonProperty(Required = Required.AllowNull)]
internal string instanceid { get; set; }
internal static GlobalDatabase Load() {
if (!File.Exists(FilePath)) {
return new GlobalDatabase();
}
[JsonProperty(Required = Required.Always)]
internal string amount { get; set; }
GlobalDatabase globalDatabase;
try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(FilePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
return globalDatabase;
}
// This constructor is used only by deserializer
private GlobalDatabase() { }
private void Save() {
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
}
}

View File

@@ -26,8 +26,21 @@ using Newtonsoft.Json;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal sealed class SteamItemList {
[JsonProperty(Required = Required.Always)]
internal List<SteamItem> assets { get; } = new List<SteamItem>();
internal static class GitHub {
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
}
internal sealed class ReleaseResponse {
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; }
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal List<Asset> Assets { get; private set; }
}
}
}

View File

@@ -0,0 +1,122 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal static class Steam {
internal sealed class Item {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
[JsonProperty(Required = Required.DisallowNull)]
internal string appid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string contextid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string assetid { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal string id {
get { return assetid; }
set { assetid = value; }
}
[JsonProperty(Required = Required.AllowNull)]
internal string classid { get; set; }
[JsonProperty(Required = Required.AllowNull)]
internal string instanceid { get; set; }
[JsonProperty(Required = Required.Always)]
internal string amount { get; set; }
}
internal sealed class ItemList {
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> assets { get; } = new List<Steam.Item>();
}
internal sealed class TradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
[JsonProperty(Required = Required.Always)]
internal string tradeofferid { get; set; }
[JsonProperty(Required = Required.Always)]
internal int accountid_other { get; set; }
[JsonProperty(Required = Required.Always)]
internal ETradeOfferState trade_offer_state { get; set; }
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_give { get; } = new List<Steam.Item>();
[JsonProperty(Required = Required.Always)]
internal List<Steam.Item> items_to_receive { get; } = new List<Steam.Item>();
// Extra
private ulong _OtherSteamID64 = 0;
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 == 0 && accountid_other != 0) {
_OtherSteamID64 = new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
}
return _OtherSteamID64;
}
}
}
internal sealed class TradeOfferRequest {
[JsonProperty(Required = Required.Always)]
internal bool newversion { get; } = true;
[JsonProperty(Required = Required.Always)]
internal int version { get; } = 2;
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList me { get; } = new Steam.ItemList();
[JsonProperty(Required = Required.Always)]
internal Steam.ItemList them { get; } = new Steam.ItemList();
}
}
}

View File

@@ -31,17 +31,18 @@ namespace ArchiSteamFarm {
internal static class Logging {
private static readonly object FileLock = new object();
internal static bool LogToFile { get; set; } = false;
internal static bool? LogToFile { get; set; } = null;
internal static void Init() {
if (!LogToFile.HasValue) {
LogToFile = true;
}
lock (FileLock) {
try {
File.Delete(Program.LogFile);
} catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false;
LogGenericException(e);
LogToFile = logToFile;
}
}
}
@@ -121,15 +122,14 @@ namespace ArchiSteamFarm {
Console.Write(loggedMessage);
}
if (LogToFile) {
if (LogToFile.GetValueOrDefault()) {
lock (FileLock) {
try {
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) {
bool logToFile = LogToFile;
LogToFile = false;
LogGenericException(e);
LogToFile = logToFile;
LogToFile = true;
}
}
}

View File

@@ -22,9 +22,12 @@
*/
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -48,48 +51,202 @@ namespace ArchiSteamFarm {
Server // Normal + WCF server
}
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest";
private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases";
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const string GlobalConfigFile = ASF + ".json";
internal const string GlobalDatabaseFile = ASF + ".db";
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 ExecutableFile = Assembly.Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF();
internal static readonly string Version = Assembly.GetName().Version.ToString();
private static EMode Mode;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool ConsoleIsBusy { get; private set; } = false;
private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal;
private static async Task CheckForUpdate() {
JObject response = await WebBrowser.UrlGetToJObject(LatestGithubReleaseURL).ConfigureAwait(false);
if (response == null) {
string oldExeFile = ExecutableFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
}
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
return;
}
string remoteVersion = response["tag_name"].ToString();
if (string.IsNullOrEmpty(remoteVersion)) {
string releaseURL = GithubReleaseURL;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest";
}
string response = null;
Logging.LogGenericInfo("Checking new version...");
for (byte i = 0; i < WebBrowser.MaxRetries && string.IsNullOrEmpty(response); i++) {
response = await WebBrowser.UrlGetToContent(releaseURL).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(response)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
string localVersion = Version;
GitHub.ReleaseResponse releaseResponse;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
try {
releaseResponse = JsonConvert.DeserializeObject<GitHub.ReleaseResponse>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
} else {
List<GitHub.ReleaseResponse> releases;
try {
releases = JsonConvert.DeserializeObject<List<GitHub.ReleaseResponse>>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
Logging.LogGenericInfo("Local version: " + localVersion);
Logging.LogGenericInfo("Remote version: " + remoteVersion);
if (releases == null || releases.Count == 0) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
int comparisonResult = localVersion.CompareTo(remoteVersion);
if (comparisonResult < 0) {
releaseResponse = releases[0];
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + releaseResponse.Tag);
if (string.Compare(Version, releaseResponse.Tag, StringComparison.Ordinal) >= 0) { // If local version is the same or newer than remote version
if (GlobalConfig.AutoUpdates && AutoUpdatesTimer == null) {
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
}
return;
}
if (!GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
} else if (comparisonResult > 0) {
Logging.LogGenericInfo("You're currently using pre-release version!");
Logging.LogGenericInfo("Be careful!");
return;
}
// Auto update logic starts here
if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
return;
}
GitHub.Asset binaryAsset = null;
foreach (var asset in releaseResponse.Assets) {
if (string.IsNullOrEmpty(asset.Name) || !asset.Name.Equals(ExecutableName)) {
continue;
}
binaryAsset = asset;
break;
}
if (binaryAsset == null) {
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
return;
}
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
return;
}
Logging.LogGenericInfo("Downloading new version...");
Stream newExe = await WebBrowser.UrlGetToStream(binaryAsset.DownloadURL).ConfigureAwait(false);
if (newExe == null) {
Logging.LogGenericWarning("Could not download new version!");
return;
}
// We start deep update logic here
string newExeFile = ExecutableFile + ".new";
// Firstly we create new exec
try {
using (FileStream fileStream = File.Open(newExeFile, FileMode.Create)) {
await newExe.CopyToAsync(fileStream).ConfigureAwait(false);
}
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
// Now we move current -> old
try {
File.Move(ExecutableFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Delete(newExeFile);
} catch { }
return;
}
// Now we move new -> current
try {
File.Move(newExeFile, ExecutableFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, ExecutableFile);
File.Delete(newExeFile);
} catch { }
return;
}
Logging.LogGenericInfo("Update process is finished! ASF will now restart itself...");
await Utilities.SleepAsync(5000);
if (!Restart()) {
// Make sure that we won't try updating again in this case
if (AutoUpdatesTimer != null) {
AutoUpdatesTimer.Dispose();
AutoUpdatesTimer = null;
}
// Inform user about failure
Logging.LogGenericWarning("ASF could not restart itself, you may need to restart it manually!");
await Utilities.SleepAsync(5000);
}
}
@@ -97,17 +254,26 @@ namespace ArchiSteamFarm {
Environment.Exit(exitCode);
}
internal static void Restart() {
System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs()));
Exit();
internal static bool Restart() {
try {
if (Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1))) != null) {
Exit();
return true;
} else {
return false;
}
} catch (Exception e) {
Logging.LogGenericException(e);
return false;
}
}
internal static async Task LimitSteamRequestsAsync() {
await SteamSemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => {
await Utilities.SleepAsync(7000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter
Task.Run(async () => {
await Utilities.SleepAsync(GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false);
SteamSemaphore.Release();
});
}).Forget();
}
internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) {
@@ -147,7 +313,7 @@ namespace ArchiSteamFarm {
ConsoleIsBusy = false;
}
return result.Trim(); // Get rid of all whitespace characters
return string.IsNullOrEmpty(result) ? null : result.Trim();
}
internal static void OnBotShutdown() {
@@ -166,8 +332,23 @@ namespace ArchiSteamFarm {
}
private static void InitServices() {
Logging.Init();
GlobalConfig = GlobalConfig.Load();
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.db exists and is valid!");
Thread.Sleep(5000);
Exit(1);
}
GlobalDatabase = GlobalDatabase.Load();
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!");
Thread.Sleep(5000);
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WCF.Init();
}
private static void ParseArgs(string[] args) {
@@ -244,11 +425,7 @@ namespace ArchiSteamFarm {
}
}
// By default we're operating on normal mode
Mode = EMode.Normal;
Logging.LogToFile = true;
// But that can be overriden by arguments
// Parse args
ParseArgs(args);
// If we ran ASF as a client, we're done by now
@@ -256,7 +433,8 @@ namespace ArchiSteamFarm {
return;
}
Task.Run(async () => await CheckForUpdate().ConfigureAwait(false)).Wait();
// From now on it's server mode
Logging.Init();
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!");
@@ -264,11 +442,17 @@ namespace ArchiSteamFarm {
Exit(1);
}
CheckForUpdate().Wait();
// Before attempting to connect, initialize our list of CMs
Bot.RefreshCMs().Wait();
Bot.RefreshCMs(GlobalDatabase.CellID).Wait();
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
if (botName.Equals(ASF)) {
continue;
}
Bot bot = new Bot(botName);
if (!bot.BotConfig.Enabled) {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015")]
[assembly: AssemblyCopyright("Copyright © Łukasz Domeradzki 2015-2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -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("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: AssemblyVersion("2.0.0.4")]
[assembly: AssemblyFileVersion("2.0.0.4")]

View File

@@ -1,74 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 SteamKit2;
using System.Collections.Generic;
namespace ArchiSteamFarm {
internal sealed class SteamTradeOffer {
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
[JsonProperty(Required = Required.Always)]
internal string tradeofferid { get; set; }
[JsonProperty(Required = Required.Always)]
internal int accountid_other { get; set; }
[JsonProperty(Required = Required.Always)]
internal ETradeOfferState trade_offer_state { get; set; }
[JsonProperty(Required = Required.Always)]
internal List<SteamItem> items_to_give { get; } = new List<SteamItem>();
[JsonProperty(Required = Required.Always)]
internal List<SteamItem> items_to_receive { get; } = new List<SteamItem>();
// Extra
private ulong _OtherSteamID64 = 0;
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 == 0 && accountid_other != 0) {
_OtherSteamID64 = new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
}
return _OtherSteamID64;
}
}
}
}

View File

@@ -1,41 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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 sealed class SteamTradeOfferRequest {
[JsonProperty(Required = Required.Always)]
internal bool newversion { get; } = true;
[JsonProperty(Required = Required.Always)]
internal int version { get; } = 2;
[JsonProperty(Required = Required.Always)]
internal SteamItemList me { get; } = new SteamItemList();
[JsonProperty(Required = Required.Always)]
internal SteamItemList them { get; } = new SteamItemList();
}
}

View File

@@ -39,37 +39,43 @@ namespace ArchiSteamFarm {
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
var releaseLater = Task.Run(async () => {
await Utilities.SleepAsync(3000).ConfigureAwait(false); // We must add some delay to not get caught by Steam rate limiter
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release();
});
}).Forget();
}
internal Trading(Bot bot) {
if (bot == null) {
return;
}
Bot = bot;
}
internal async void CheckTrades() {
if (ParsingTasks < 2) {
ParsingTasks++;
await Semaphore.WaitAsync().ConfigureAwait(false);
await ParseActiveTrades().ConfigureAwait(false);
Semaphore.Release();
ParsingTasks--;
if (ParsingTasks >= 2) {
return;
}
ParsingTasks++;
await Semaphore.WaitAsync().ConfigureAwait(false);
await ParseActiveTrades().ConfigureAwait(false);
Semaphore.Release();
ParsingTasks--;
}
private async Task ParseActiveTrades() {
List<SteamTradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
List<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
if (tradeOffers == null) {
return;
}
List<Task> tasks = new List<Task>();
foreach (SteamTradeOffer tradeOffer in tradeOffers) {
if (tradeOffer.trade_offer_state == SteamTradeOffer.ETradeOfferState.Active) {
foreach (Steam.TradeOffer tradeOffer in tradeOffers) {
if (tradeOffer.trade_offer_state == Steam.TradeOffer.ETradeOfferState.Active) {
tasks.Add(Task.Run(async () => await ParseTrade(tradeOffer).ConfigureAwait(false)));
}
}
@@ -78,7 +84,7 @@ namespace ArchiSteamFarm {
await Bot.AcceptAllConfirmations().ConfigureAwait(false);
}
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
return;
}

View File

@@ -27,6 +27,8 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
internal static void Forget(this Task task) { }
internal static async Task SleepAsync(int miliseconds) {
await Task.Delay(miliseconds).ConfigureAwait(false);
}

View File

@@ -23,6 +23,7 @@
*/
using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
@@ -35,11 +36,15 @@ namespace ArchiSteamFarm {
internal sealed class WCF : IWCF {
private const string URL = "http://localhost:1242/ASF"; // 1242 = 1024 + A(65) + S(83) + F(70)
private static string URL = "http://localhost:1242/ASF";
private ServiceHost ServiceHost;
private Client Client;
internal static void Init() {
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}
internal bool IsServerRunning() {
return ServiceHost != null;
}
@@ -96,7 +101,7 @@ namespace ArchiSteamFarm {
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();
botName = Bot.Bots.Keys.FirstOrDefault();
}
if (string.IsNullOrEmpty(botName)) {

View File

@@ -26,6 +26,7 @@ using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
@@ -33,7 +34,6 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class WebBrowser {
internal const byte HttpTimeout = 180; // In seconds, how long we can wait for server's response
internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. 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)
@@ -42,10 +42,12 @@ namespace ArchiSteamFarm {
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
UseCookies = false
}) {
Timeout = TimeSpan.FromSeconds(HttpTimeout)
Timeout = TimeSpan.FromSeconds(30)
};
internal static void Init() {
HttpClient.Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout);
// 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);
@@ -80,7 +82,7 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false);
}
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies, string referer = null) {
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
@@ -90,12 +92,28 @@ namespace ArchiSteamFarm {
return null;
}
HttpContent httpContent = httpResponse.Content;
if (httpContent == null) {
if (httpResponse.Content == null) {
return null;
}
return await httpContent.ReadAsStringAsync().ConfigureAwait(false);
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
internal static async Task<Stream> UrlGetToStream(string request, Dictionary<string, string> cookies = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
if (httpResponse == null) {
return null;
}
if (httpResponse.Content == null) {
return null;
}
return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
@@ -144,7 +162,7 @@ namespace ArchiSteamFarm {
HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null) {
if (data != null && data.Count > 0) {
try {
requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) {
@@ -161,7 +179,7 @@ namespace ArchiSteamFarm {
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
}
if (referer != null) {
if (!string.IsNullOrEmpty(referer)) {
requestMessage.Headers.Referrer = new Uri(referer);
}

View File

@@ -0,0 +1,22 @@
{
"Debug": false,
"AutoUpdates": true,
"UpdateChannel": 1,
"MaxFarmingTime": 10,
"IdleFarmingPeriod": 3,
"FarmingDelay": 5,
"AccountPlayingDelay": 5,
"LoginLimiterDelay": 7,
"InventoryLimiterDelay": 3,
"HttpTimeout": 30,
"WCFHostname": "localhost",
"WCFPort": 1242,
"Statistics": true,
"Blacklist": [
267420,
303700,
335590,
368020,
425280
]
}

View File

@@ -8,6 +8,7 @@
"SteamMasterID": 0,
"SteamMasterClanID": 0,
"CardDropsRestricted": false,
"DismissInventoryNotifications": true,
"FarmOffline": false,
"HandleOfflineMessages": false,
"ForwardKeysToOtherBots": false,
@@ -17,8 +18,9 @@
"SendOnFarmingFinished": false,
"SteamTradeToken": null,
"SendTradePeriod": 0,
"AcceptConfirmationsPeriod": 0,
"CustomGamePlayedWhileIdle": null,
"GamesPlayedWhileIdle": [
0
],
"Statistics": true
]
}

View File

@@ -1,7 +1,7 @@
ArchiSteamFarm
===================
[![Build status](https://ci.appveyor.com/api/projects/status/yi0y25nipcb1j1yj?svg=true)](https://ci.appveyor.com/project/JustArchi/archisteamfarm) [![GitHub release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/total.svg)](https://github.com/JustArchi/ArchiSteamFarm/releases) [![Paypal donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
[![Build status](https://ci.appveyor.com/api/projects/status/yi0y25nipcb1j1yj?svg=true)](https://ci.appveyor.com/project/JustArchi/archisteamfarm) [![GitHub release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/total.svg)](https://github.com/JustArchi/ArchiSteamFarm/releases) [![Paypal donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Steam donate](https://img.shields.io/badge/steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
---

View File

@@ -8,8 +8,29 @@ namespace SteamAuth
{
public class Confirmation
{
public string ConfirmationID;
public string ConfirmationKey;
public string ConfirmationDescription;
public string ID;
public string Key;
public string Description;
public ConfirmationType ConfType
{
get
{
if (String.IsNullOrEmpty(Description)) return ConfirmationType.Unknown;
if (Description.StartsWith("Confirm ")) return ConfirmationType.GenericConfirmation;
if (Description.StartsWith("Trade with ")) return ConfirmationType.Trade;
if (Description.StartsWith("Sell -")) return ConfirmationType.MarketSellTransaction;
return ConfirmationType.Unknown;
}
}
public enum ConfirmationType
{
GenericConfirmation,
Trade,
MarketSellTransaction,
Unknown
}
}
}

View File

@@ -71,7 +71,7 @@ namespace SteamAuth
if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false;
return true;
}
catch (Exception e)
catch (Exception)
{
return false;
}
@@ -115,7 +115,7 @@ namespace SteamAuth
codePoint /= steamGuardCodeTranslations.Length;
}
}
catch (Exception e)
catch (Exception)
{
return null; //Change later, catch-alls are bad!
}
@@ -162,9 +162,9 @@ namespace SteamAuth
string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation()
{
ConfirmationDescription = confDesc,
ConfirmationID = confID,
ConfirmationKey = confKey
Description = confDesc,
ID = confID,
Key = confKey
};
ret.Add(conf);
}
@@ -212,9 +212,9 @@ namespace SteamAuth
string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation()
{
ConfirmationDescription = confDesc,
ConfirmationID = confID,
ConfirmationKey = confKey
Description = confDesc,
ID = confID,
Key = confKey
};
ret.Add(conf);
}
@@ -268,7 +268,7 @@ namespace SteamAuth
this.Session.SteamLoginSecure = tokenSecure;
return true;
}
catch (Exception e)
catch (Exception)
{
return false;
}
@@ -300,7 +300,7 @@ namespace SteamAuth
this.Session.SteamLoginSecure = tokenSecure;
return true;
}
catch (Exception e)
catch (Exception)
{
return false;
}
@@ -308,7 +308,7 @@ namespace SteamAuth
private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf)
{
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ConfirmationID + "?";
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ID + "?";
string queryString = GenerateConfirmationQueryParams("details");
url += queryString;
@@ -329,7 +329,7 @@ namespace SteamAuth
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
string queryString = "?op=" + op + "&";
queryString += GenerateConfirmationQueryParams(op);
queryString += "&cid=" + conf.ConfirmationID + "&ck=" + conf.ConfirmationKey;
queryString += "&cid=" + conf.ID + "&ck=" + conf.Key;
url += queryString;
CookieContainer cookies = new CookieContainer();
@@ -401,7 +401,7 @@ namespace SteamAuth
string hash = WebUtility.UrlEncode(encodedData);
return hash;
}
catch (Exception e)
catch (Exception)
{
return null; //Fix soon: catch-all is BAD!
}

View File

@@ -161,8 +161,6 @@ namespace SteamAuth
this.LoggedIn = true;
return LoginResult.LoginOkay;
}
return LoginResult.GeneralFailure;
}
private class LoginResponse

BIN
tools/NetHook2.dll Normal file

Binary file not shown.

BIN
tools/NetHookAnalyzer2.exe Normal file

Binary file not shown.

1
tools/hook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Inject

1
tools/unhook.bat Normal file
View File

@@ -0,0 +1 @@
rundll32 NetHook2.dll,Eject