mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-16 14:30:31 +00:00
Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
765f9d29bb | ||
|
|
99af5c86a5 | ||
|
|
0bb2b3ed5f | ||
|
|
4181f3ba21 | ||
|
|
c189b398c0 | ||
|
|
de4fbf8b04 | ||
|
|
f91a558314 | ||
|
|
7587ff024c | ||
|
|
06778c9bb0 | ||
|
|
7a97045412 | ||
|
|
5d9bedfd28 | ||
|
|
575992c25d | ||
|
|
72db5bc9f3 | ||
|
|
b015720a3e | ||
|
|
84a6d4501b | ||
|
|
07049e71c0 | ||
|
|
4710d9c1eb | ||
|
|
d08462745a | ||
|
|
eb4e9ee077 | ||
|
|
601a486b13 | ||
|
|
14867f470d | ||
|
|
187f0800b2 | ||
|
|
3afa202d0b | ||
|
|
d33e76c8b0 | ||
|
|
3061c55eaf | ||
|
|
621a1dc2cb | ||
|
|
1a832780a2 | ||
|
|
ab531c80df | ||
|
|
f20ea0a87f | ||
|
|
3e7f726afb | ||
|
|
d247515a03 | ||
|
|
f084a3f219 | ||
|
|
4d3673c305 | ||
|
|
0253c3bf7b | ||
|
|
0eae895676 | ||
|
|
045acb362d | ||
|
|
ebd65da3ff | ||
|
|
1de3c5bec0 | ||
|
|
b334c939df | ||
|
|
8e6100e236 | ||
|
|
8cb512b6e5 | ||
|
|
6a28946205 | ||
|
|
c632c025cb | ||
|
|
1403ffe190 | ||
|
|
71ae9a84da | ||
|
|
a823471771 | ||
|
|
dac057d242 | ||
|
|
3d9fe36245 | ||
|
|
5c1da24def | ||
|
|
1e961a5945 | ||
|
|
84898146d2 | ||
|
|
552613e977 | ||
|
|
07687df91f | ||
|
|
1a4d941a2c | ||
|
|
cfbd880995 | ||
|
|
09abe77495 | ||
|
|
ac9943ff94 | ||
|
|
741dd2adb7 | ||
|
|
1ad5d3676f | ||
|
|
27254aa31e | ||
|
|
bb90dc1c01 | ||
|
|
292ec97b1c | ||
|
|
238cc2ad46 | ||
|
|
b9064bbfda | ||
|
|
eddcc2816a | ||
|
|
52360a682a | ||
|
|
709ce6489b | ||
|
|
220a9baa7d | ||
|
|
a7f2dd99c3 | ||
|
|
3481adb3c5 | ||
|
|
9f5197a426 | ||
|
|
f6a631a33a | ||
|
|
9dc04ff5a0 | ||
|
|
248d200764 | ||
|
|
e3d08eca3f | ||
|
|
dc5d2483c1 | ||
|
|
93aacad350 | ||
|
|
1b822bee91 | ||
|
|
c65d6fee5a | ||
|
|
6bc715575f | ||
|
|
335044e38a | ||
|
|
6a6232b655 | ||
|
|
75ff6b4f11 | ||
|
|
9cea6e35e4 | ||
|
|
52d8dc88d2 | ||
|
|
1d5a50bcca | ||
|
|
44a595c1df | ||
|
|
a528dee670 | ||
|
|
b6e96d8711 | ||
|
|
c6cc4e2c9d | ||
|
|
6974e51148 | ||
|
|
25da5fb650 | ||
|
|
a1e8af0660 | ||
|
|
af61ad66c6 | ||
|
|
a06af3402a | ||
|
|
08b7f18796 | ||
|
|
6feb1a46cc | ||
|
|
3f8e6125d1 | ||
|
|
ff887196c3 | ||
|
|
3509b1be8c | ||
|
|
b27aa50083 | ||
|
|
45230a57ee | ||
|
|
b3e420d423 | ||
|
|
65d9f4fbee | ||
|
|
935e11ab2a | ||
|
|
c2cd7ca52d | ||
|
|
120691c6bf | ||
|
|
334d2df4ef | ||
|
|
1a2616c648 | ||
|
|
9858c0e0c1 | ||
|
|
7f19d04e93 | ||
|
|
8ba497855b | ||
|
|
7ef8b224eb | ||
|
|
a841b9494a | ||
|
|
856351aea0 | ||
|
|
6a172ce269 | ||
|
|
f86bc32f0d | ||
|
|
18800ff8d5 | ||
|
|
3b610432ec | ||
|
|
b20423e415 | ||
|
|
e4189b2bc4 | ||
|
|
c5ac0196de | ||
|
|
8934e004fb | ||
|
|
ed0fd3df78 | ||
|
|
bd19cc794a | ||
|
|
c52a4be7e7 | ||
|
|
7ae29aa122 | ||
|
|
49d213290f | ||
|
|
fc618cb24f | ||
|
|
2826fdb475 | ||
|
|
b0e990f9f3 | ||
|
|
6a7a568e59 | ||
|
|
547be25b57 | ||
|
|
9559bcb3b8 | ||
|
|
049b78eb02 | ||
|
|
d55c734718 | ||
|
|
79b6fc8b17 | ||
|
|
8603e7a579 | ||
|
|
ea1b401228 | ||
|
|
ee73a12440 | ||
|
|
cad47368ac | ||
|
|
50c7d32ed7 | ||
|
|
eab136f635 | ||
|
|
ae829d7942 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -4,11 +4,13 @@
|
||||
|
||||
# Ignore all config files, apart from ones we want to include
|
||||
ArchiSteamFarm/config/*
|
||||
!ArchiSteamFarm/config/example.xml
|
||||
!ArchiSteamFarm/config/minimal.xml
|
||||
!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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
|
||||
@@ -24,8 +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 {
|
||||
@@ -39,36 +42,54 @@ namespace ArchiSteamFarm {
|
||||
*/
|
||||
|
||||
internal sealed class NotificationsCallback : CallbackMsg {
|
||||
internal class Notification {
|
||||
internal enum ENotificationType {
|
||||
internal sealed class Notification {
|
||||
internal enum ENotificationType : uint {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
// Only custom below, different than ones available as user_notification_type
|
||||
Items = 514
|
||||
}
|
||||
|
||||
internal ENotificationType NotificationType { get; set; }
|
||||
internal readonly ENotificationType NotificationType;
|
||||
|
||||
internal Notification(ENotificationType notificationType) {
|
||||
NotificationType = notificationType;
|
||||
}
|
||||
}
|
||||
|
||||
internal List<Notification> Notifications { get; private set; }
|
||||
internal readonly List<Notification> Notifications;
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
|
||||
JobID = jobID;
|
||||
|
||||
if (msg == null || msg.notifications == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Notifications = new List<Notification>(msg.notifications.Count);
|
||||
foreach (var notification in msg.notifications) {
|
||||
Notifications.Add(new Notification((Notification.ENotificationType) notification.user_notification_type));
|
||||
}
|
||||
}
|
||||
|
||||
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements 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
|
||||
});
|
||||
if (msg.count_new_items > 0) {
|
||||
Notifications = new List<Notification>(1) {
|
||||
new Notification(Notification.ENotificationType.Items)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class OfflineMessageCallback : CallbackMsg {
|
||||
internal uint OfflineMessages { get; private set; }
|
||||
internal List<uint> Users { get; private set; }
|
||||
internal readonly uint OfflineMessages;
|
||||
internal readonly List<uint> Users;
|
||||
|
||||
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
|
||||
JobID = jobID;
|
||||
@@ -83,7 +104,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal sealed class PurchaseResponseCallback : CallbackMsg {
|
||||
internal enum EPurchaseResult {
|
||||
internal enum EPurchaseResult : int {
|
||||
Unknown = -1,
|
||||
OK = 0,
|
||||
AlreadyOwned = 9,
|
||||
@@ -94,10 +115,10 @@ namespace ArchiSteamFarm {
|
||||
OnCooldown = 53
|
||||
}
|
||||
|
||||
internal EResult Result { get; private set; }
|
||||
internal EPurchaseResult PurchaseResult { get; private set; }
|
||||
internal KeyValue ReceiptInfo { get; private set; }
|
||||
internal Dictionary<uint, string> Items { get; private set; }
|
||||
internal readonly EResult Result;
|
||||
internal readonly EPurchaseResult PurchaseResult;
|
||||
internal readonly KeyValue ReceiptInfo;
|
||||
internal readonly Dictionary<uint, string> Items;
|
||||
|
||||
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
|
||||
JobID = jobID;
|
||||
@@ -106,19 +127,27 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
ReceiptInfo = new KeyValue();
|
||||
Items = new Dictionary<uint, string>();
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValue lineItem in ReceiptInfo["lineitems"].Children) {
|
||||
Items.Add((uint) lineItem["PackageID"].AsUnsignedLong(), lineItem["ItemDescription"].AsString());
|
||||
List<KeyValue> lineItems = ReceiptInfo["lineitems"].Children;
|
||||
Items = new Dictionary<uint, string>(lineItems.Count);
|
||||
|
||||
foreach (KeyValue lineItem in lineItems) {
|
||||
uint appID = (uint) lineItem["PackageID"].AsUnsignedLong();
|
||||
string gameName = lineItem["ItemDescription"].AsString();
|
||||
gameName = WebUtility.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML
|
||||
Items.Add(appID, gameName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,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;
|
||||
@@ -177,7 +227,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal void PlayGames(ICollection<uint> gameIDs) {
|
||||
if (!Client.IsConnected) {
|
||||
if (gameIDs == null || !Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,17 +245,54 @@ 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;
|
||||
}
|
||||
|
||||
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey);
|
||||
request.SourceJobID = Client.GetNextJobID();
|
||||
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) {
|
||||
SourceJobID = Client.GetNextJobID()
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
|
||||
internal void HackedLogOn(SteamUser.LogOnDetails details) {
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamID steamID = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual);
|
||||
|
||||
var logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon);
|
||||
logon.Body.obfustucated_private_ip = details.LoginID.Value;
|
||||
logon.ProtoHeader.client_sessionid = 0;
|
||||
logon.ProtoHeader.steamid = steamID.ConvertToUInt64();
|
||||
logon.Body.account_name = details.Username;
|
||||
logon.Body.password = details.Password;
|
||||
logon.Body.should_remember_password = details.ShouldRememberPassword;
|
||||
logon.Body.protocol_version = MsgClientLogon.CurrentProtocol;
|
||||
logon.Body.client_os_type = (uint) details.ClientOSType;
|
||||
logon.Body.client_language = details.ClientLanguage;
|
||||
logon.Body.cell_id = details.CellID;
|
||||
logon.Body.steam2_ticket_request = details.RequestSteam2Ticket;
|
||||
logon.Body.client_package_version = 1771;
|
||||
logon.Body.auth_code = details.AuthCode;
|
||||
logon.Body.two_factor_code = details.TwoFactorCode;
|
||||
logon.Body.login_key = details.LoginKey;
|
||||
logon.Body.sha_sentryfile = details.SentryFileHash;
|
||||
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
|
||||
|
||||
Client.Send(logon);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -217,7 +304,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
*/
|
||||
|
||||
public sealed override void HandleMsg(IPacketMsg packetMsg) {
|
||||
public override void HandleMsg(IPacketMsg packetMsg) {
|
||||
if (packetMsg == null) {
|
||||
return;
|
||||
}
|
||||
@@ -226,6 +313,9 @@ namespace ArchiSteamFarm {
|
||||
case EMsg.ClientFSOfflineMessageNotification:
|
||||
HandleFSOfflineMessageNotification(packetMsg);
|
||||
break;
|
||||
case EMsg.ClientItemAnnouncements:
|
||||
HandleItemAnnouncements(packetMsg);
|
||||
break;
|
||||
case EMsg.ClientPurchaseResponse:
|
||||
HandlePurchaseResponse(packetMsg);
|
||||
break;
|
||||
@@ -241,11 +331,16 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientOfflineMessageNotification>(packetMsg);
|
||||
if (response == null) {
|
||||
Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
|
||||
private void HandleItemAnnouncements(IPacketMsg packetMsg) {
|
||||
if (packetMsg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body));
|
||||
var response = new ClientMsgProtobuf<CMsgClientItemAnnouncements>(packetMsg);
|
||||
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
|
||||
private void HandlePurchaseResponse(IPacketMsg packetMsg) {
|
||||
@@ -254,10 +349,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new PurchaseResponseCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
|
||||
@@ -267,10 +358,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
var response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ArchiSteamFarm</RootNamespace>
|
||||
<AssemblyName>ArchiSteamFarm</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<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>
|
||||
@@ -36,7 +36,7 @@
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -60,6 +60,16 @@
|
||||
<PropertyGroup>
|
||||
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyOriginatorKeyFile>
|
||||
</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DelaySign>false</DelaySign>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
||||
@@ -91,16 +101,18 @@
|
||||
<Compile Include="ArchiHandler.cs" />
|
||||
<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" />
|
||||
@@ -108,6 +120,15 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="config\ASF.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config\example.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="config\minimal.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -124,12 +145,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cirno.ico" />
|
||||
<Content Include="config\example.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="config\minimal.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SteamAuth\SteamAuth.csproj">
|
||||
@@ -137,6 +152,7 @@
|
||||
<Name>SteamAuth</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
@@ -145,15 +161,17 @@
|
||||
<PropertyGroup>
|
||||
<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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
rm "$(TargetDir)out/ASF.exe.config"
|
||||
</PostBuildEvent>
|
||||
|
||||
@@ -27,7 +27,6 @@ using HtmlAgilityPack;
|
||||
using SteamKit2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -35,102 +34,59 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ArchiWebHandler {
|
||||
private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds
|
||||
private const string SteamCommunity = "steamcommunity.com";
|
||||
|
||||
private static string SteamCommunityURL = "https://" + SteamCommunity;
|
||||
|
||||
private static int Timeout = 30 * 1000;
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly string ApiKey;
|
||||
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
|
||||
|
||||
private ulong SteamID;
|
||||
private string VanityURL;
|
||||
|
||||
// This is required because home_process request must be done on final URL
|
||||
private string GetHomeProcess() {
|
||||
if (!string.IsNullOrEmpty(VanityURL)) {
|
||||
return "https://steamcommunity.com/id/" + VanityURL + "/home_process";
|
||||
} else if (SteamID != 0) {
|
||||
return "https://steamcommunity.com/profiles/" + SteamID + "/home_process";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
internal static void Init() {
|
||||
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
|
||||
SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunity;
|
||||
}
|
||||
|
||||
private async Task UnlockParentalAccount(string parentalPin) {
|
||||
if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) {
|
||||
internal ArchiWebHandler(Bot bot) {
|
||||
if (bot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Unlocking parental account...");
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{ "pin", parentalPin }
|
||||
};
|
||||
|
||||
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.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<string> setCookieValues;
|
||||
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
|
||||
Logging.LogNullError(Bot.BotName, "setCookieValues");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string setCookieValue in setCookieValues) {
|
||||
if (setCookieValue.Contains("steamparental=")) {
|
||||
string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14);
|
||||
setCookie = setCookie.Substring(0, setCookie.IndexOf(';'));
|
||||
Cookie.Add("steamparental", setCookie);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Success!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning(Bot.BotName, "Failed to unlock parental account!");
|
||||
}
|
||||
|
||||
internal ArchiWebHandler(Bot bot, string apiKey) {
|
||||
Bot = bot;
|
||||
|
||||
if (!string.IsNullOrEmpty(apiKey) && !apiKey.Equals("null")) {
|
||||
ApiKey = apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string vanityURL, string parentalPin) {
|
||||
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
|
||||
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SteamID = steamClient.SteamID;
|
||||
VanityURL = vanityURL;
|
||||
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString(CultureInfo.InvariantCulture)));
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString()));
|
||||
|
||||
// Generate an AES session key
|
||||
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);
|
||||
}
|
||||
|
||||
// Copy our login key
|
||||
byte[] loginKey = new byte[20];
|
||||
byte[] loginKey = new byte[webAPIUserNonce.Length];
|
||||
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
|
||||
|
||||
// AES encrypt the loginkey with our session key
|
||||
byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
|
||||
|
||||
// Send the magic
|
||||
// Do the magic
|
||||
Logging.LogGenericInfo("Logging in to ISteamUserAuth...", Bot.BotName);
|
||||
|
||||
KeyValue authResult;
|
||||
Logging.LogGenericInfo(Bot.BotName, "Logging in to ISteamUserAuth...");
|
||||
using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
|
||||
iSteamUserAuth.Timeout = Timeout;
|
||||
|
||||
@@ -140,10 +96,10 @@ namespace ArchiSteamFarm {
|
||||
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
||||
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -152,7 +108,7 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Success!");
|
||||
Logging.LogGenericInfo("Success!", Bot.BotName);
|
||||
|
||||
string steamLogin = authResult["token"].AsString();
|
||||
string steamLoginSecure = authResult["tokensecure"].AsString();
|
||||
@@ -160,7 +116,9 @@ namespace ArchiSteamFarm {
|
||||
Cookie["sessionid"] = sessionID;
|
||||
Cookie["steamLogin"] = steamLogin;
|
||||
Cookie["steamLoginSecure"] = steamLoginSecure;
|
||||
Cookie["birthtime"] = "-473356799";
|
||||
|
||||
// The below is used for display purposes only
|
||||
Cookie["webTradeEligibility"] = "{\"allowed\":0,\"reason\":0,\"allowed_at_time\":0,\"steamguard_required_days\":0,\"sales_this_year\":0,\"max_sales_per_year\":0,\"forms_requested\":0}";
|
||||
|
||||
await UnlockParentalAccount(parentalPin).ConfigureAwait(false);
|
||||
return true;
|
||||
@@ -173,11 +131,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
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);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -188,21 +146,21 @@ 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!");
|
||||
var restart = Task.Run(async () => await Bot.Restart().ConfigureAwait(false));
|
||||
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
|
||||
Task.Run(async () => await Bot.Restart().ConfigureAwait(false)).Forget();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal List<SteamTradeOffer> GetTradeOffers() {
|
||||
if (ApiKey == null) {
|
||||
internal List<Steam.TradeOffer> GetTradeOffers() {
|
||||
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
@@ -210,58 +168,44 @@ namespace ArchiSteamFarm {
|
||||
response = iEconService.GetTradeOffers(
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
secure: true
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
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(),
|
||||
message = trade["message"].AsString(),
|
||||
expiration_time = trade["expiration_time"].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(),
|
||||
time_created = trade["time_created"].AsInteger(),
|
||||
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 = trade["confirmation_method"].AsEnum<SteamTradeOffer.ETradeOfferConfirmationMethod>()
|
||||
accountid_other = (uint) trade["accountid_other"].AsUnsignedLong(), // TODO: Correct this when SK2 with https://github.com/SteamRE/SteamKit/pull/255 gets released
|
||||
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(),
|
||||
currencyid = item["currencyid"].AsString(),
|
||||
classid = item["classid"].AsString(),
|
||||
instanceid = item["instanceid"].AsString(),
|
||||
amount = item["amount"].AsString(),
|
||||
missing = item["missing"].AsBoolean()
|
||||
});
|
||||
}
|
||||
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(),
|
||||
currencyid = item["currencyid"].AsString(),
|
||||
classid = item["classid"].AsString(),
|
||||
instanceid = item["instanceid"].AsString(),
|
||||
amount = item["amount"].AsString(),
|
||||
missing = item["missing"].AsBoolean()
|
||||
});
|
||||
}
|
||||
result.Add(tradeOffer);
|
||||
@@ -280,9 +224,9 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = "https://steamcommunity.com/gid/" + clanID;
|
||||
string request = SteamCommunityURL + "/gid/" + clanID;
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(2) {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "join"}
|
||||
};
|
||||
@@ -293,37 +237,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<bool> LeaveClan(ulong clanID) {
|
||||
if (clanID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string request = GetHomeProcess();
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "leaveGroup"},
|
||||
{"groupId", clanID.ToString()}
|
||||
};
|
||||
|
||||
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(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -340,10 +254,10 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID;
|
||||
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
|
||||
string request = referer + "/accept";
|
||||
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(3) {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"tradeofferid", tradeID.ToString()}
|
||||
@@ -355,7 +269,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -363,12 +277,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0 || ApiKey == null) {
|
||||
if (tradeID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValue response = null;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
@@ -376,44 +290,52 @@ namespace ArchiSteamFarm {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
secure: !Program.GlobalConfig.ForceHttp
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<List<SteamItem>> GetInventory() {
|
||||
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", Cookie).ConfigureAwait(false);
|
||||
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (jObject == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
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.*");
|
||||
if (jTokens == null) {
|
||||
Logging.LogNullError("jTokens", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Steam.Item> result = new List<Steam.Item>();
|
||||
foreach (JToken jToken in jTokens) {
|
||||
result.Add(JsonConvert.DeserializeObject<SteamItem>(jToken.ToString()));
|
||||
try {
|
||||
result.Add(JsonConvert.DeserializeObject<Steam.Item>(jToken.ToString()));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e, Bot.BotName);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -423,17 +345,21 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<SteamTradeOfferRequest> trades = new List<SteamTradeOfferRequest>();
|
||||
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) {
|
||||
singleTrade = new SteamTradeOfferRequest();
|
||||
if (trades.Count >= Trading.MaxTradesPerAccount) {
|
||||
break;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -441,17 +367,17 @@ namespace ArchiSteamFarm {
|
||||
});
|
||||
}
|
||||
|
||||
string referer = "https://steamcommunity.com/tradeoffer/new";
|
||||
string referer = SteamCommunityURL + "/tradeoffer/new";
|
||||
string request = referer + "/send";
|
||||
|
||||
foreach (SteamTradeOfferRequest trade in trades) {
|
||||
Dictionary<string, string> data = new Dictionary<string, string>() {
|
||||
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;
|
||||
@@ -460,7 +386,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -475,11 +401,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
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);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -493,15 +419,82 @@ namespace ArchiSteamFarm {
|
||||
|
||||
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);
|
||||
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogGenericWTF(Bot.BotName, "Request failed even after " + WebBrowser.MaxRetries + " tries");
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return null;
|
||||
}
|
||||
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
internal async Task<bool> MarkInventory() {
|
||||
if (SteamID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpResponseMessage response = null;
|
||||
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
|
||||
response = await WebBrowser.UrlGet(SteamCommunityURL + "/profiles/" + SteamID + "/inventory", Cookie).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task UnlockParentalAccount(string parentalPin) {
|
||||
if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
|
||||
Dictionary<string, string> data = new Dictionary<string, string>(1) {
|
||||
{ "pin", parentalPin }
|
||||
};
|
||||
|
||||
string referer = SteamCommunityURL;
|
||||
string request = referer + "/parental/ajaxunlock";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
IEnumerable<string> setCookieValues;
|
||||
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
|
||||
Logging.LogNullError("setCookieValues", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string setCookieValue in setCookieValues) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1366
ArchiSteamFarm/Bot.cs
Normal file → Executable file
1366
ArchiSteamFarm/Bot.cs
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
258
ArchiSteamFarm/BotConfig.cs
Normal file
258
ArchiSteamFarm/BotConfig.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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;
|
||||
using System.Xml;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class BotConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool Enabled { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool StartOnLaunch { get; private set; } = true;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamLogin { get; set; } = null;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamPassword { get; set; } = null;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamParentalPIN { get; set; } = "0";
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamApiKey { get; private set; } = null;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal ulong SteamMasterID { get; private set; } = 0;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal ulong SteamMasterClanID { get; private set; } = 0;
|
||||
|
||||
[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;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool HandleOfflineMessages { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ForwardKeysToOtherBots { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool DistributeKeys { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool SendOnFarmingFinished { get; private set; } = false;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamTradeToken { get; private set; } = null;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte SendTradePeriod { get; private set; } = 0;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte AcceptConfirmationsPeriod { get; private set; } = 0;
|
||||
|
||||
[JsonProperty]
|
||||
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
|
||||
|
||||
|
||||
internal static BotConfig Load(string path) {
|
||||
if (!File.Exists(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BotConfig botConfig;
|
||||
try {
|
||||
botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(path));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return botConfig;
|
||||
}
|
||||
|
||||
// TODO: This should be removed soon
|
||||
internal static BotConfig LoadOldFormat(string path) {
|
||||
if (!File.Exists(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BotConfig botConfig = new BotConfig();
|
||||
|
||||
try {
|
||||
using (XmlReader reader = XmlReader.Create(path)) {
|
||||
while (reader.Read()) {
|
||||
if (reader.NodeType != XmlNodeType.Element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string key = reader.Name;
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string value = reader.GetAttribute("value");
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "Enabled":
|
||||
botConfig.Enabled = bool.Parse(value);
|
||||
break;
|
||||
case "SteamLogin":
|
||||
botConfig.SteamLogin = value;
|
||||
break;
|
||||
case "SteamPassword":
|
||||
botConfig.SteamPassword = value;
|
||||
break;
|
||||
case "SteamApiKey":
|
||||
botConfig.SteamApiKey = value;
|
||||
break;
|
||||
case "SteamTradeToken":
|
||||
botConfig.SteamTradeToken = value;
|
||||
break;
|
||||
case "SteamParentalPIN":
|
||||
botConfig.SteamParentalPIN = value;
|
||||
break;
|
||||
case "SteamMasterID":
|
||||
botConfig.SteamMasterID = ulong.Parse(value);
|
||||
break;
|
||||
case "SteamMasterClanID":
|
||||
botConfig.SteamMasterClanID = ulong.Parse(value);
|
||||
break;
|
||||
case "StartOnLaunch":
|
||||
botConfig.StartOnLaunch = bool.Parse(value);
|
||||
break;
|
||||
case "UseAsfAsMobileAuthenticator":
|
||||
botConfig.UseAsfAsMobileAuthenticator = bool.Parse(value);
|
||||
break;
|
||||
case "CardDropsRestricted":
|
||||
botConfig.CardDropsRestricted = bool.Parse(value);
|
||||
break;
|
||||
case "FarmOffline":
|
||||
botConfig.FarmOffline = bool.Parse(value);
|
||||
break;
|
||||
case "HandleOfflineMessages":
|
||||
botConfig.HandleOfflineMessages = bool.Parse(value);
|
||||
break;
|
||||
case "ForwardKeysToOtherBots":
|
||||
botConfig.ForwardKeysToOtherBots = bool.Parse(value);
|
||||
break;
|
||||
case "DistributeKeys":
|
||||
botConfig.DistributeKeys = bool.Parse(value);
|
||||
break;
|
||||
case "ShutdownOnFarmingFinished":
|
||||
botConfig.ShutdownOnFarmingFinished = bool.Parse(value);
|
||||
break;
|
||||
case "SendOnFarmingFinished":
|
||||
botConfig.SendOnFarmingFinished = bool.Parse(value);
|
||||
break;
|
||||
case "SendTradePeriod":
|
||||
botConfig.SendTradePeriod = byte.Parse(value);
|
||||
break;
|
||||
case "GamesPlayedWhileIdle":
|
||||
botConfig.GamesPlayedWhileIdle.Clear();
|
||||
foreach (string appID in value.Split(',')) {
|
||||
botConfig.GamesPlayedWhileIdle.Add(uint.Parse(appID));
|
||||
}
|
||||
break;
|
||||
case "Statistics":
|
||||
case "Blacklist":
|
||||
case "SteamNickname":
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fixups for new format
|
||||
if (botConfig.SteamLogin != null && botConfig.SteamLogin.Equals("null")) {
|
||||
botConfig.SteamLogin = null;
|
||||
}
|
||||
|
||||
if (botConfig.SteamPassword != null && botConfig.SteamPassword.Equals("null")) {
|
||||
botConfig.SteamPassword = null;
|
||||
}
|
||||
|
||||
if (botConfig.SteamApiKey != null && botConfig.SteamApiKey.Equals("null")) {
|
||||
botConfig.SteamApiKey = null;
|
||||
}
|
||||
|
||||
if (botConfig.SteamParentalPIN != null && botConfig.SteamParentalPIN.Equals("null")) {
|
||||
botConfig.SteamParentalPIN = null;
|
||||
}
|
||||
|
||||
if (botConfig.SteamTradeToken != null && botConfig.SteamTradeToken.Equals("null")) {
|
||||
botConfig.SteamTradeToken = null;
|
||||
}
|
||||
|
||||
return botConfig;
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
private BotConfig() { }
|
||||
|
||||
// TODO: This should be removed soon
|
||||
internal bool Convert(string path) {
|
||||
try {
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning("Your config was converted to new ASF V2.0 format");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
ArchiSteamFarm/BotDatabase.cs
Normal file
104
ArchiSteamFarm/BotDatabase.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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 SteamAuth;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class BotDatabase {
|
||||
internal string LoginKey {
|
||||
get {
|
||||
return _LoginKey;
|
||||
}
|
||||
set {
|
||||
if (_LoginKey == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_LoginKey = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
internal SteamGuardAccount SteamGuardAccount {
|
||||
get {
|
||||
return _SteamGuardAccount;
|
||||
}
|
||||
set {
|
||||
if (_SteamGuardAccount == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_SteamGuardAccount = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
private string _LoginKey;
|
||||
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
private SteamGuardAccount _SteamGuardAccount;
|
||||
|
||||
private string FilePath;
|
||||
|
||||
internal static BotDatabase Load(string filePath) {
|
||||
if (!File.Exists(filePath)) {
|
||||
return new BotDatabase(filePath);
|
||||
}
|
||||
|
||||
BotDatabase botDatabase;
|
||||
try {
|
||||
botDatabase = JsonConvert.DeserializeObject<BotDatabase>(File.ReadAllText(filePath));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
botDatabase.FilePath = filePath;
|
||||
return botDatabase;
|
||||
}
|
||||
|
||||
// This constructor is used when creating new database
|
||||
private BotDatabase(string filePath) {
|
||||
FilePath = filePath;
|
||||
Save();
|
||||
}
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
private BotDatabase() { }
|
||||
|
||||
internal void Save() {
|
||||
lock (FilePath) {
|
||||
try {
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,13 @@
|
||||
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage, ISteamSerializable {
|
||||
internal ulong GroupID = 0;
|
||||
internal bool AcceptInvite = true;
|
||||
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage {
|
||||
internal ulong GroupID { get; set; } = 0;
|
||||
internal bool AcceptInvite { get; set; } = true;
|
||||
|
||||
EMsg ISteamSerializableMessage.GetEMsg() {
|
||||
return EMsg.ClientAcknowledgeClanInvite;
|
||||
@@ -44,8 +45,8 @@ namespace ArchiSteamFarm {
|
||||
BinaryWriter binaryWriter = new BinaryWriter(stream);
|
||||
binaryWriter.Write(GroupID);
|
||||
binaryWriter.Write(AcceptInvite);
|
||||
} catch {
|
||||
throw new IOException();
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +59,8 @@ namespace ArchiSteamFarm {
|
||||
BinaryReader binaryReader = new BinaryReader(stream);
|
||||
GroupID = binaryReader.ReadUInt64();
|
||||
AcceptInvite = binaryReader.ReadBoolean();
|
||||
} catch {
|
||||
throw new IOException();
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,14 +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 HashSet<uint> CurrentGamesFarming = new HashSet<uint>();
|
||||
|
||||
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
@@ -42,29 +43,32 @@ namespace ArchiSteamFarm {
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer Timer;
|
||||
|
||||
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) {
|
||||
if (bot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bot = bot;
|
||||
|
||||
Timer = new Timer(
|
||||
async e => await CheckGamesForFarming().ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromMinutes(15), // Delay
|
||||
TimeSpan.FromMinutes(15) // 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,27 +83,25 @@ namespace ArchiSteamFarm {
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach (uint appID in gamesToFarm.Keys) {
|
||||
return appID;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return gamesToFarm.Keys.FirstOrDefault();
|
||||
}
|
||||
|
||||
internal async Task SwitchToManualMode(bool manualMode) {
|
||||
internal async Task<bool> SwitchToManualMode(bool manualMode) {
|
||||
if (ManualMode == manualMode) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ManualMode = manualMode;
|
||||
|
||||
if (ManualMode) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Now running in Manual Farming mode");
|
||||
Logging.LogGenericInfo("Now running in Manual Farming mode", Bot.BotName);
|
||||
await StopFarming().ConfigureAwait(false);
|
||||
} else {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Now running in Automatic Farming mode");
|
||||
var start = Task.Run(async () => await StartFarming().ConfigureAwait(false));
|
||||
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
|
||||
Task.Run(async () => await StartFarming().ConfigureAwait(false)).Forget();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool FarmMultiple(ConcurrentDictionary<uint, float> appIDs) {
|
||||
@@ -108,7 +110,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
float maxHour = 0;
|
||||
|
||||
foreach (float hour in appIDs.Values) {
|
||||
if (hour > maxHour) {
|
||||
maxHour = hour;
|
||||
@@ -116,11 +117,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
CurrentGamesFarming.Clear();
|
||||
CurrentGamesFarming.TrimExcess();
|
||||
foreach (uint appID in appIDs.Keys) {
|
||||
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;
|
||||
@@ -136,9 +138,10 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
CurrentGamesFarming.Clear();
|
||||
CurrentGamesFarming.TrimExcess();
|
||||
CurrentGamesFarming.Add(appID);
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + appID);
|
||||
Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName);
|
||||
if (await Farm(appID).ConfigureAwait(false)) {
|
||||
float hours;
|
||||
GamesToFarm.TryRemove(appID, out hours);
|
||||
@@ -164,24 +167,29 @@ 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;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "We have a total of " + GamesToFarm.Count + " games to farm on this account...");
|
||||
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
|
||||
|
||||
bool farmedSomething = false;
|
||||
|
||||
// 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
|
||||
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Complex");
|
||||
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)) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + appID);
|
||||
farmedSomething = true;
|
||||
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
|
||||
gamesToFarmSolo.Remove(appID);
|
||||
gamesToFarmSolo.TrimExcess();
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -189,7 +197,7 @@ 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;
|
||||
@@ -197,11 +205,12 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
} else { // If we have unrestricted card drops, we use simple algorithm
|
||||
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Simple");
|
||||
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);
|
||||
farmedSomething = true;
|
||||
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -210,9 +219,18 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
CurrentGamesFarming.Clear();
|
||||
CurrentGamesFarming.TrimExcess();
|
||||
NowFarming = false;
|
||||
Logging.LogGenericInfo(Bot.BotName, "Farming finished!");
|
||||
await Bot.OnFarmingFinished().ConfigureAwait(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);
|
||||
}
|
||||
|
||||
internal async Task StopFarming() {
|
||||
@@ -223,33 +241,33 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Sending signal to stop farming");
|
||||
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
|
||||
FarmResetEvent.Set();
|
||||
for (byte i = 0; i < 5 && NowFarming; i++) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Waiting for reaction...");
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking badges...");
|
||||
Logging.LogGenericInfo("Checking badges...", Bot.BotName);
|
||||
|
||||
// Find the number of badge pages
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking first page...");
|
||||
Logging.LogGenericInfo("Checking first page...", Bot.BotName);
|
||||
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!");
|
||||
Logging.LogGenericWarning("Could not get badges information, will try again later!", Bot.BotName);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -265,30 +283,31 @@ namespace ArchiSteamFarm {
|
||||
GamesToFarm.Clear();
|
||||
|
||||
// Find APPIDs we need to farm
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking other pages...");
|
||||
Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
|
||||
|
||||
List<Task> checkPagesTasks = new List<Task>();
|
||||
List<Task> tasks = new List<Task>(maxPages - 1);
|
||||
for (byte page = 1; page <= maxPages; page++) {
|
||||
if (page == 1) {
|
||||
CheckPage(htmlDocument); // Because we fetched page number 1 already
|
||||
} else {
|
||||
checkPagesTasks.Add(Task.Run(async () => await CheckPage(page).ConfigureAwait(false)));
|
||||
byte currentPage = page; // We need a copy of variable being passed when in for loops
|
||||
tasks.Add(Task.Run(async () => await CheckPage(currentPage).ConfigureAwait(false)));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(checkPagesTasks).ConfigureAwait(false);
|
||||
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.CardDropsRestricted) {
|
||||
List<Task> checkHoursTasks = new List<Task>();
|
||||
Logging.LogGenericInfo(Bot.BotName, "Checking hours...");
|
||||
if (Bot.BotConfig.CardDropsRestricted) {
|
||||
tasks = new List<Task>(GamesToFarm.Count);
|
||||
Logging.LogGenericInfo("Checking hours...", Bot.BotName);
|
||||
foreach (uint appID in GamesToFarm.Keys) {
|
||||
checkHoursTasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false)));
|
||||
tasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false)));
|
||||
}
|
||||
await Task.WhenAll(checkHoursTasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -306,7 +325,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
foreach (HtmlNode htmlNode in htmlNodeCollection) {
|
||||
string steamLink = htmlNode.GetAttributeValue("href", null);
|
||||
if (steamLink == null) {
|
||||
if (string.IsNullOrEmpty(steamLink)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -315,7 +334,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Bot.GlobalBlacklist.Contains(appID) || Bot.Blacklist.Contains(appID)) {
|
||||
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -344,19 +363,19 @@ namespace ArchiSteamFarm {
|
||||
|
||||
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
|
||||
if (htmlDocument == null) {
|
||||
Logging.LogNullError(Bot.BotName, "htmlDocument");
|
||||
Logging.LogNullError("htmlDocument", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='badge_title_stats_playtime']");
|
||||
if (htmlNode == null) {
|
||||
Logging.LogNullError(Bot.BotName, "htmlNode");
|
||||
Logging.LogNullError("htmlNode", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
string hoursString = htmlNode.InnerText;
|
||||
if (string.IsNullOrEmpty(hoursString)) {
|
||||
Logging.LogNullError(Bot.BotName, "hoursString");
|
||||
Logging.LogNullError("hoursString", Bot.BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -373,14 +392,14 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private async Task CheckGamesForFarming() {
|
||||
if (NowFarming || GamesToFarm.Count > 0 || !Bot.SteamClient.IsConnected) {
|
||||
if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await StartFarming().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<bool?> ShouldFarm(ulong appID) {
|
||||
private async Task<bool?> ShouldFarm(uint appID) {
|
||||
if (appID == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -405,17 +424,17 @@ namespace ArchiSteamFarm {
|
||||
bool success = true;
|
||||
|
||||
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);
|
||||
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
|
||||
for (ushort farmingTime = 0; farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
|
||||
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(0);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + appID);
|
||||
Bot.ResetGamesPlayed();
|
||||
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -428,14 +447,14 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool success = true;
|
||||
while (maxHour < 2) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Still farming: " + string.Join(", ", appIDs));
|
||||
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
|
||||
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
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;
|
||||
@@ -447,8 +466,8 @@ namespace ArchiSteamFarm {
|
||||
maxHour += timePlayed;
|
||||
}
|
||||
|
||||
Bot.ArchiHandler.PlayGames(0);
|
||||
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + string.Join(", ", appIDs));
|
||||
Bot.ResetGamesPlayed();
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
*/
|
||||
|
||||
using SteamKit2;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Debugging {
|
||||
#if DEBUG
|
||||
@@ -30,6 +34,30 @@ namespace ArchiSteamFarm {
|
||||
internal static readonly bool IsDebugBuild = false;
|
||||
#endif
|
||||
|
||||
internal static bool IsReleaseBuild { get { return !IsDebugBuild; } }
|
||||
internal static bool IsReleaseBuild => !IsDebugBuild;
|
||||
|
||||
internal static bool NetHookAlreadyInitialized { get; set; } = false;
|
||||
|
||||
internal sealed class DebugListener : IDebugListener {
|
||||
private readonly string FilePath;
|
||||
|
||||
internal DebugListener(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FilePath = filePath;
|
||||
}
|
||||
|
||||
public void WriteLine(string category, string msg) {
|
||||
lock (FilePath) {
|
||||
try {
|
||||
File.AppendAllText(FilePath, category + " | " + msg + Environment.NewLine);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
110
ArchiSteamFarm/GlobalConfig.cs
Normal file
110
ArchiSteamFarm/GlobalConfig.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
|
||||
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 bool ForceHttp { get; private set; } = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal byte HttpTimeout { get; private set; } = 60;
|
||||
|
||||
[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;
|
||||
|
||||
// TODO: Please remove me immediately after https://github.com/SteamRE/SteamKit/issues/254 gets fixed
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool HackIgnoreMachineID { get; private set; } = false;
|
||||
|
||||
[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() { }
|
||||
}
|
||||
}
|
||||
@@ -23,42 +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]
|
||||
internal string appid { get; set; }
|
||||
internal uint CellID {
|
||||
get {
|
||||
return _CellID;
|
||||
}
|
||||
set {
|
||||
if (_CellID == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string contextid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string assetid { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal string id {
|
||||
get { return assetid; }
|
||||
set { assetid = value; }
|
||||
_CellID = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string currencyid { get; set; }
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private uint _CellID = 0;
|
||||
|
||||
[JsonProperty]
|
||||
internal string classid { get; set; }
|
||||
internal static GlobalDatabase Load() {
|
||||
if (!File.Exists(FilePath)) {
|
||||
return new GlobalDatabase();
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string instanceid { get; set; }
|
||||
GlobalDatabase globalDatabase;
|
||||
try {
|
||||
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(FilePath));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal string amount { get; set; }
|
||||
return globalDatabase;
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal bool missing { get; set; }
|
||||
// This constructor is used only by deserializer
|
||||
private GlobalDatabase() { }
|
||||
|
||||
[JsonProperty]
|
||||
internal int pos { get; set; }
|
||||
private void Save() {
|
||||
lock (FilePath) {
|
||||
try {
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,21 @@ using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class SteamItemList {
|
||||
[JsonProperty]
|
||||
internal List<SteamItem> assets { get; set; } = new List<SteamItem>();
|
||||
internal static class GitHub {
|
||||
internal sealed class Asset {
|
||||
[JsonProperty(PropertyName = "name", Required = Required.Always)]
|
||||
internal string Name { get; private set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal List<string> currency { get; set; } = new List<string>();
|
||||
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
|
||||
internal string DownloadURL { get; private set; }
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
internal bool ready { get; set; } = false;
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
122
ArchiSteamFarm/JSON/Steam.cs
Normal file
122
ArchiSteamFarm/JSON/Steam.cs
Normal 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 uint 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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,82 @@ 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() {
|
||||
File.Delete(Program.LogFile);
|
||||
if (!LogToFile.HasValue) {
|
||||
LogToFile = true;
|
||||
}
|
||||
|
||||
lock (FileLock) {
|
||||
try {
|
||||
File.Delete(Program.LogFile);
|
||||
} catch (Exception e) {
|
||||
LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (exception == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
|
||||
Log("[!] StackTrace:" + Environment.NewLine + exception.StackTrace);
|
||||
|
||||
if (exception.InnerException != null) {
|
||||
LogGenericException(exception.InnerException, botName, previousMethodName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(nullObjectName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") {
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
private static void Log(string message) {
|
||||
@@ -46,57 +118,21 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Write on console only when not awaiting response from user
|
||||
if (!Program.ConsoleIsBusy) {
|
||||
Console.Write(loggedMessage);
|
||||
try {
|
||||
Console.Write(loggedMessage);
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (LogToFile) {
|
||||
if (LogToFile.GetValueOrDefault()) {
|
||||
lock (FileLock) {
|
||||
File.AppendAllText(Program.LogFile, loggedMessage);
|
||||
try {
|
||||
File.AppendAllText(Program.LogFile, loggedMessage);
|
||||
} catch (Exception e) {
|
||||
LogToFile = false;
|
||||
LogGenericException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericWTF(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
|
||||
}
|
||||
|
||||
internal static void LogGenericError(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericException(string botName, Exception exception, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
|
||||
Log("[!] StackTrace: " + exception.StackTrace);
|
||||
|
||||
Exception innerException = exception.InnerException;
|
||||
if (innerException != null) {
|
||||
LogGenericException(botName, innerException, previousMethodName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = "") {
|
||||
LogGenericException("ASF", exception, previousMethodName);
|
||||
}
|
||||
|
||||
internal static void LogGenericWarning(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericInfo(string botName, string message, [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 botName, string nullObjectName, [CallerMemberName] string previousMethodName = "") {
|
||||
LogGenericError(botName, nullObjectName + " is null!", previousMethodName);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogGenericDebug(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -32,6 +35,8 @@ using System.Threading.Tasks;
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Program {
|
||||
internal enum EUserInputType : byte {
|
||||
Unknown,
|
||||
DeviceID,
|
||||
Login,
|
||||
Password,
|
||||
PhoneNumber,
|
||||
@@ -43,78 +48,251 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
internal enum EMode : byte {
|
||||
Unknown,
|
||||
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";
|
||||
private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases"; // GitHub API is HTTPS only
|
||||
|
||||
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 readonly Version Version = Assembly.GetName().Version;
|
||||
|
||||
internal static GlobalConfig GlobalConfig { get; private set; }
|
||||
internal static GlobalDatabase GlobalDatabase { get; private set; }
|
||||
internal static bool ConsoleIsBusy { get; private set; } = false;
|
||||
|
||||
private static async Task CheckForUpdate() {
|
||||
JObject response = await WebBrowser.UrlGetToJObject(LatestGithubReleaseURL).ConfigureAwait(false);
|
||||
if (response == null) {
|
||||
private static Timer AutoUpdatesTimer;
|
||||
private static EMode Mode = EMode.Normal;
|
||||
|
||||
internal static async Task CheckForUpdate() {
|
||||
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.LogGenericNotice("", "Local version: " + localVersion);
|
||||
Logging.LogGenericNotice("", "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) {
|
||||
Logging.LogGenericNotice("", "New version is available!");
|
||||
Logging.LogGenericNotice("", "Consider updating yourself!");
|
||||
releaseResponse = releases[0];
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
|
||||
Logging.LogGenericWarning("Could not check latest version!");
|
||||
return;
|
||||
}
|
||||
|
||||
Version newVersion = new Version(releaseResponse.Tag);
|
||||
|
||||
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion);
|
||||
|
||||
if (Version.CompareTo(newVersion) >= 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.LogGenericNotice("", "You're currently using pre-release version!");
|
||||
Logging.LogGenericNotice("", "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);
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task Exit(int exitCode = 0) {
|
||||
await Bot.ShutdownAllBots().ConfigureAwait(false);
|
||||
internal static void Exit(int exitCode = 0) {
|
||||
Environment.Exit(exitCode);
|
||||
}
|
||||
|
||||
internal static async Task Restart() {
|
||||
await Bot.ShutdownAllBots().ConfigureAwait(false);
|
||||
System.Diagnostics.Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs()));
|
||||
Environment.Exit(0);
|
||||
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);
|
||||
await Utilities.SleepAsync(5 * 1000).ConfigureAwait(false); // We must add some delay to not get caught by Steam anty-DoS
|
||||
SteamSemaphore.Release();
|
||||
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) {
|
||||
if (userInputType == EUserInputType.Unknown) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string result;
|
||||
lock (ConsoleLock) {
|
||||
ConsoleIsBusy = true;
|
||||
switch (userInputType) {
|
||||
case EUserInputType.DeviceID:
|
||||
Console.Write("<" + botLogin + "> Please enter your Device ID (including \"android:\"): ");
|
||||
break;
|
||||
case EUserInputType.Login:
|
||||
Console.Write("<" + botLogin + "> Please enter your login: ");
|
||||
break;
|
||||
@@ -141,25 +319,51 @@ namespace ArchiSteamFarm {
|
||||
case EUserInputType.TwoFactorAuthentication:
|
||||
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
|
||||
break;
|
||||
default:
|
||||
Console.Write("<" + botLogin + "> Please enter not documented yet value of \"" + userInputType + "\": ");
|
||||
break;
|
||||
}
|
||||
result = Console.ReadLine();
|
||||
Console.Clear(); // For security purposes
|
||||
ConsoleIsBusy = false;
|
||||
}
|
||||
|
||||
return result.Trim(); // Get rid of all whitespace characters
|
||||
return string.IsNullOrEmpty(result) ? null : result.Trim();
|
||||
}
|
||||
|
||||
internal static void OnBotShutdown() {
|
||||
if (Bot.GetRunningBotsCount() == 0) {
|
||||
Logging.LogGenericInfo("Main", "No bots are running, exiting");
|
||||
ShutdownResetEvent.Set();
|
||||
foreach (Bot bot in Bot.Bots.Values) {
|
||||
if (bot.KeepRunning) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WCF.IsServerRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("No bots are running, exiting");
|
||||
ShutdownResetEvent.Set();
|
||||
}
|
||||
|
||||
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.json 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) {
|
||||
@@ -181,19 +385,19 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
default:
|
||||
if (arg.StartsWith("--")) {
|
||||
Logging.LogGenericWarning("Main", "Unrecognized parameter: " + arg);
|
||||
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Mode != EMode.Client) {
|
||||
Logging.LogGenericWarning("Main", "Ignoring command because --client wasn't specified: " + arg);
|
||||
Logging.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logging.LogGenericNotice("WCF", "Command sent: \"" + arg + "\"");
|
||||
Logging.LogGenericInfo("Command sent: \"" + arg + "\"");
|
||||
|
||||
// We intentionally execute this async block synchronously
|
||||
Logging.LogGenericNotice("WCF", "Response received: \"" + WCF.SendCommand(arg) + "\"");
|
||||
Logging.LogGenericInfo("Response received: \"" + WCF.SendCommand(arg) + "\"");
|
||||
/*
|
||||
Task.Run(async () => {
|
||||
Logging.LogGenericNotice("WCF", "Response received: " + await WCF.SendCommand(arg).ConfigureAwait(false));
|
||||
@@ -209,13 +413,22 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericException("Main", (Exception) args.ExceptionObject);
|
||||
Logging.LogGenericException((Exception) args.ExceptionObject);
|
||||
}
|
||||
|
||||
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
|
||||
if (sender == null || args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericException(args.Exception);
|
||||
}
|
||||
|
||||
private static void Main(string[] args) {
|
||||
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
|
||||
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
|
||||
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
|
||||
|
||||
Logging.LogGenericInfo("Main", "Archi's Steam Farm, version " + Version);
|
||||
Logging.LogGenericInfo("Archi's Steam Farm, version " + Version);
|
||||
Directory.SetCurrentDirectory(ExecutableDirectory);
|
||||
InitServices();
|
||||
|
||||
@@ -236,11 +449,19 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
// By default we're operating on normal mode
|
||||
Mode = EMode.Normal;
|
||||
Logging.LogToFile = true;
|
||||
// If debugging is on, we prepare debug directory prior to running
|
||||
if (GlobalConfig.Debug) {
|
||||
if (Directory.Exists(Program.DebugDirectory)) {
|
||||
Directory.Delete(Program.DebugDirectory, true);
|
||||
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
|
||||
}
|
||||
Directory.CreateDirectory(Program.DebugDirectory);
|
||||
|
||||
// But that can be overriden by arguments
|
||||
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(Program.DebugDirectory, "debug.txt")));
|
||||
SteamKit2.DebugLog.Enabled = true;
|
||||
}
|
||||
|
||||
// Parse args
|
||||
ParseArgs(args);
|
||||
|
||||
// If we ran ASF as a client, we're done by now
|
||||
@@ -248,22 +469,43 @@ 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("Main", "Config directory doesn't exist!");
|
||||
Logging.LogGenericError("Config directory doesn't exist!");
|
||||
Thread.Sleep(5000);
|
||||
Task.Run(async () => await Exit(1).ConfigureAwait(false)).Wait();
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
|
||||
CheckForUpdate().Wait();
|
||||
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
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.Enabled) {
|
||||
Logging.LogGenericInfo(botName, "Not starting this instance because it's disabled in config file");
|
||||
if (bot.BotConfig == null || !bot.BotConfig.Enabled) {
|
||||
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
|
||||
}
|
||||
}
|
||||
|
||||
// CONVERSION START
|
||||
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
|
||||
string botName = Path.GetFileNameWithoutExtension(configFile);
|
||||
Logging.LogGenericWarning("Found legacy " + botName + ".xml config file, it will now be converted to new ASF V2.0 format!");
|
||||
Bot bot = new Bot(botName);
|
||||
if (bot.BotConfig == null || !bot.BotConfig.Enabled) {
|
||||
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
|
||||
}
|
||||
}
|
||||
// CONVERSION END
|
||||
|
||||
// Check if we got any bots running
|
||||
OnBotShutdown();
|
||||
|
||||
|
||||
@@ -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("1.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.4.0.0")]
|
||||
[assembly: AssemblyVersion("2.0.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.1.0")]
|
||||
|
||||
@@ -1,104 +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 {
|
||||
Unknown,
|
||||
Invalid,
|
||||
Active,
|
||||
Accepted,
|
||||
Countered,
|
||||
Expired,
|
||||
Canceled,
|
||||
Declined,
|
||||
InvalidItems,
|
||||
EmailPending,
|
||||
EmailCanceled,
|
||||
OnHold
|
||||
}
|
||||
|
||||
internal enum ETradeOfferConfirmationMethod {
|
||||
Invalid,
|
||||
Email,
|
||||
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; }
|
||||
|
||||
[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
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 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();
|
||||
}
|
||||
}
|
||||
@@ -22,52 +22,70 @@
|
||||
|
||||
*/
|
||||
|
||||
using SteamAuth;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Trading {
|
||||
internal const ushort MaxItemsPerTrade = 150;
|
||||
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
|
||||
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
|
||||
|
||||
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
|
||||
private volatile byte ParsingTasks = 0;
|
||||
|
||||
internal static async Task LimitInventoryRequestsAsync() {
|
||||
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Bot.AcceptAllConfirmations().ConfigureAwait(false);
|
||||
await Bot.AcceptConfirmations(Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
|
||||
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
return;
|
||||
}
|
||||
@@ -77,11 +95,11 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.SteamMasterID) {
|
||||
Logging.LogGenericInfo(Bot.BotName, "Accepting trade: " + tradeID);
|
||||
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
|
||||
@@ -33,34 +34,42 @@ namespace ArchiSteamFarm {
|
||||
string HandleCommand(string input);
|
||||
}
|
||||
|
||||
internal class WCF : IWCF {
|
||||
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;
|
||||
}
|
||||
|
||||
internal void StartServer() {
|
||||
if (ServiceHost != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericNotice("WCF", "Starting WCF server...");
|
||||
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", "WCF service could not be started because of AddressAccessDeniedException");
|
||||
Logging.LogGenericWarning("WCF", "If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions");
|
||||
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("WCF", e);
|
||||
Logging.LogGenericException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericNotice("WCF", "WCF server ready!");
|
||||
Logging.LogGenericInfo("WCF server ready!");
|
||||
}
|
||||
|
||||
internal void StopServer() {
|
||||
@@ -92,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)) {
|
||||
@@ -104,24 +113,24 @@ namespace ArchiSteamFarm {
|
||||
return "ERROR: Couldn't find any bot named: " + botName;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("WCF", "Received command: \"" + input + "\"");
|
||||
Logging.LogGenericInfo("Received command: \"" + input + "\"");
|
||||
|
||||
string command = '!' + input;
|
||||
string output = bot.HandleMessage(command).Result; // TODO: This should be asynchronous
|
||||
|
||||
Logging.LogGenericInfo("WCF", "Answered to command: \"" + input + "\" with: \"" + output + "\"");
|
||||
Logging.LogGenericInfo("Answered to command: \"" + input + "\" with: \"" + output + "\"");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Client : ClientBase<IWCF>, IWCF {
|
||||
internal sealed 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("WCF", e);
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,33 +26,28 @@ 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;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class WebBrowser {
|
||||
[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 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)
|
||||
|
||||
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) };
|
||||
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
|
||||
UseCookies = false
|
||||
}) {
|
||||
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);
|
||||
@@ -65,16 +60,109 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
|
||||
ServicePointManager.Expect100Continue = false;
|
||||
|
||||
// Reuse ports if possible
|
||||
// TODO: Mono doesn't support that feature yet
|
||||
//ServicePointManager.ReusePort = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
internal static async Task<HttpResponseMessage> UrlGet(string request, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Get, null, cookies, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HttpResponseMessage> UrlPost(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<string> UrlGetToContent(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.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) {
|
||||
if (string.IsNullOrEmpty(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
HtmlDocument htmlDocument = new HtmlDocument();
|
||||
htmlDocument.LoadHtml(content);
|
||||
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
internal static async Task<JObject> UrlGetToJObject(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);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JObject jObject;
|
||||
|
||||
try {
|
||||
jObject = JObject.Parse(content);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return jObject;
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) {
|
||||
if (string.IsNullOrEmpty(request) || httpMethod == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -91,18 +179,10 @@ namespace ArchiSteamFarm {
|
||||
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
|
||||
}
|
||||
|
||||
if (referer != null) {
|
||||
if (!string.IsNullOrEmpty(referer)) {
|
||||
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
|
||||
@@ -116,211 +196,5 @@ namespace ArchiSteamFarm {
|
||||
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
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, requestOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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, data, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> HttpResponseToHtmlDocument(HttpResponseMessage httpResponse) {
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpContent httpContent = httpResponse.Content;
|
||||
if (httpContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string content = await httpContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
HtmlDocument htmlDocument = new HtmlDocument();
|
||||
htmlDocument.LoadHtml(content);
|
||||
|
||||
return htmlDocument;
|
||||
}
|
||||
|
||||
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 httpResponse = await UrlGet(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (httpResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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> 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;
|
||||
}
|
||||
|
||||
HttpContent httpContent = httpResponse.Content;
|
||||
if (httpContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await httpContent.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//head/title");
|
||||
if (htmlNode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return htmlNode.InnerText;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JArray jArray;
|
||||
|
||||
try {
|
||||
jArray = JArray.Parse(content);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return jArray;
|
||||
}
|
||||
|
||||
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, 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<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, requestOptions).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
XmlDocument xmlDocument = new XmlDocument();
|
||||
|
||||
try {
|
||||
xmlDocument.LoadXml(content);
|
||||
} catch (XmlException e) {
|
||||
Logging.LogGenericException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return xmlDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
ArchiSteamFarm/config/ASF.json
Normal file
24
ArchiSteamFarm/config/ASF.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"Debug": false,
|
||||
"AutoUpdates": true,
|
||||
"UpdateChannel": 1,
|
||||
"MaxFarmingTime": 10,
|
||||
"IdleFarmingPeriod": 3,
|
||||
"FarmingDelay": 5,
|
||||
"AccountPlayingDelay": 5,
|
||||
"LoginLimiterDelay": 7,
|
||||
"InventoryLimiterDelay": 3,
|
||||
"ForceHttp": false,
|
||||
"HttpTimeout": 60,
|
||||
"WCFHostname": "localhost",
|
||||
"WCFPort": 1242,
|
||||
"Statistics": true,
|
||||
"HackIgnoreMachineID": false,
|
||||
"Blacklist": [
|
||||
267420,
|
||||
303700,
|
||||
335590,
|
||||
368020,
|
||||
425280
|
||||
]
|
||||
}
|
||||
26
ArchiSteamFarm/config/example.json
Normal file
26
ArchiSteamFarm/config/example.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"Enabled": false,
|
||||
"StartOnLaunch": true,
|
||||
"SteamLogin": null,
|
||||
"SteamPassword": null,
|
||||
"SteamParentalPIN": "0",
|
||||
"SteamApiKey": null,
|
||||
"SteamMasterID": 0,
|
||||
"SteamMasterClanID": 0,
|
||||
"CardDropsRestricted": false,
|
||||
"DismissInventoryNotifications": true,
|
||||
"FarmOffline": false,
|
||||
"HandleOfflineMessages": false,
|
||||
"ForwardKeysToOtherBots": false,
|
||||
"DistributeKeys": false,
|
||||
"UseAsfAsMobileAuthenticator": false,
|
||||
"ShutdownOnFarmingFinished": false,
|
||||
"SendOnFarmingFinished": false,
|
||||
"SteamTradeToken": null,
|
||||
"SendTradePeriod": 0,
|
||||
"AcceptConfirmationsPeriod": 0,
|
||||
"CustomGamePlayedWhileIdle": null,
|
||||
"GamesPlayedWhileIdle": [
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
<?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: [& - &] | [" - "] | [' - '] | [< - <] | [> - >] -->
|
||||
<!-- So e.g. if your SteamPassword is "foo&" you should write value="foo&" -->
|
||||
|
||||
<!-- 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 -->
|
||||
<!-- 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 -->
|
||||
<!-- 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 -->
|
||||
<!-- 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 -->
|
||||
<!-- 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 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 -->
|
||||
<!-- 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/ -->
|
||||
<!-- 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" -->
|
||||
<!-- 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 -->
|
||||
<!-- 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. 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 if bot should disconnect once farming is finished -->
|
||||
<!-- 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"/>
|
||||
|
||||
<!-- 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 on steam -->
|
||||
<!-- Consider leaving it at "true", this way I can check how many running bots are active -->
|
||||
<!-- 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>
|
||||
5
ArchiSteamFarm/config/minimal.json
Normal file
5
ArchiSteamFarm/config/minimal.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Enabled": false,
|
||||
"SteamLogin": null,
|
||||
"SteamPassword": null
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<!-- 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"/>
|
||||
</configuration>
|
||||
15
README.md
15
README.md
@@ -1,6 +1,10 @@
|
||||
ArchiSteamFarm
|
||||
===================
|
||||
|
||||
[](https://ci.appveyor.com/project/JustArchi/archisteamfarm) [](https://github.com/JustArchi/ArchiSteamFarm/releases/latest) [](https://github.com/JustArchi/ArchiSteamFarm/releases) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
|
||||
|
||||
---
|
||||
|
||||
ASF is a C# application that allows you to farm steam cards using multiple steam accounts simultaneously. Unlike idle master which works only on one account at given time, requires steam client running in background, and launches additional processes imitiating "game playing" status, ASF doesn't require any steam client running in the background, doesn't launch any additional processes and is made to handle unlimited steam accounts at once. In addition to that, it's meant to be run on servers or other desktop-less machines, and features full Mono support, which makes it possible to launch on any Mono-supported operating system, such as Windows, Linux or OS X. ASF is based on, and possible, thanks to [SteamKit2](https://github.com/SteamRE/SteamKit).
|
||||
|
||||
ASF doesn't require and doesn't interfere in any way with Steam client. In addition to that, it no longer requires exclusive access to given account, which means that you can use your main account in Steam client, and use ASF for farming the same account at the same time. If you decide to launch a game, ASF will get disconnected, and resume farming once you finish playing your game, being as transparent as possible.
|
||||
@@ -23,16 +27,7 @@ Detailed setting up instructions are available on **[our wiki](https://github.co
|
||||
|
||||
**Current Commands:**
|
||||
|
||||
- `!2fa` Generates temporary 2FA token for current bot instance
|
||||
- `!2fa <BOT>` Generates temporary 2FA token for given bot instance
|
||||
- `!2faoff` Deactivates 2FA for current bot instance
|
||||
- `!2faoff <BOT>` Deactivates 2FA for given bot instance
|
||||
- `!exit` Stops whole ASF
|
||||
- `!redeem <KEY>` Redeems cd-key on current bot instance. You can also paste cd-key directly to the chat
|
||||
- `!start <BOT>` Starts given bot instance
|
||||
- `!status` Prints current status of ASF
|
||||
- `!stop` Stops current bot instance
|
||||
- `!stop <BOT>` Stops given bot instance
|
||||
Detailed documentation of all available commands is available on **[our wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki/Commands)**.
|
||||
|
||||
> Commands can be executed via a private chat with your bot.
|
||||
> Remember that bot accepts commands only from ```SteamMasterID```. That property can be configured in the config.
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace SteamAuth
|
||||
/// </summary>
|
||||
public class AuthenticatorLinker
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set to register a new phone number when linking. If a phone number is not set on the account, this must be set. If a phone number is set on the account, this must be null.
|
||||
@@ -100,21 +100,23 @@ namespace SteamAuth
|
||||
|
||||
public FinalizeResult FinalizeAddAuthenticator(string smsCode)
|
||||
{
|
||||
bool smsCodeGood = false;
|
||||
//The act of checking the SMS code is necessary for Steam to finalize adding the phone number to the account.
|
||||
//Of course, we only want to check it if we're adding a phone number in the first place...
|
||||
|
||||
if (!String.IsNullOrEmpty(this.PhoneNumber) && !this._checkSMSCode(smsCode))
|
||||
{
|
||||
return FinalizeResult.BadSMSCode;
|
||||
}
|
||||
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("steamid", _session.SteamID.ToString());
|
||||
postData.Add("access_token", _session.OAuthToken);
|
||||
postData.Add("activation_code", smsCode);
|
||||
postData.Add("authenticator_code", "");
|
||||
int tries = 0;
|
||||
while (tries <= 30)
|
||||
{
|
||||
postData.Set("authenticator_code", tries == 0 ? "" : LinkedAccount.GenerateSteamGuardCode());
|
||||
postData.Add("authenticator_time", TimeAligner.GetSteamTime().ToString());
|
||||
|
||||
if(smsCodeGood)
|
||||
postData.Set("activation_code", "");
|
||||
postData.Set("authenticator_code", LinkedAccount.GenerateSteamGuardCode());
|
||||
postData.Set("authenticator_time", TimeAligner.GetSteamTime().ToString());
|
||||
|
||||
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", postData);
|
||||
if (response == null) return FinalizeResult.GeneralFailure;
|
||||
@@ -126,14 +128,14 @@ namespace SteamAuth
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if(finalizeResponse.Response.Status == 89)
|
||||
if (finalizeResponse.Response.Status == 89)
|
||||
{
|
||||
return FinalizeResult.BadSMSCode;
|
||||
}
|
||||
|
||||
if(finalizeResponse.Response.Status == 88)
|
||||
if (finalizeResponse.Response.Status == 88)
|
||||
{
|
||||
if(tries >= 30)
|
||||
if (tries >= 30)
|
||||
{
|
||||
return FinalizeResult.UnableToGenerateCorrectCodes;
|
||||
}
|
||||
@@ -144,9 +146,8 @@ namespace SteamAuth
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
if (finalizeResponse.Response.WantMore)
|
||||
if (finalizeResponse.Response.WantMore)
|
||||
{
|
||||
smsCodeGood = true;
|
||||
tries++;
|
||||
continue;
|
||||
}
|
||||
@@ -158,6 +159,20 @@ namespace SteamAuth
|
||||
return FinalizeResult.GeneralFailure;
|
||||
}
|
||||
|
||||
private bool _checkSMSCode(string smsCode)
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
postData.Add("op", "check_sms_code");
|
||||
postData.Add("arg", smsCode);
|
||||
postData.Add("sessionid", _session.SessionID);
|
||||
|
||||
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
|
||||
if (response == null) return false;
|
||||
|
||||
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
|
||||
return addPhoneNumberResponse.Success;
|
||||
}
|
||||
|
||||
private bool _addPhoneNumber()
|
||||
{
|
||||
var postData = new NameValueCollection();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SteamAuth</RootNamespace>
|
||||
<AssemblyName>SteamAuth</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -222,6 +222,16 @@ namespace SteamAuth
|
||||
return ret.ToArray();
|
||||
}
|
||||
|
||||
public long GetConfirmationTradeOfferID(Confirmation conf)
|
||||
{
|
||||
var confDetails = _getConfirmationDetails(conf);
|
||||
if (confDetails == null || !confDetails.Success) return -1;
|
||||
|
||||
Regex tradeOfferIDRegex = new Regex("<div class=\"tradeoffer\" id=\"tradeofferid_(\\d+)\" >");
|
||||
if(!tradeOfferIDRegex.IsMatch(confDetails.HTML)) return -1;
|
||||
return long.Parse(tradeOfferIDRegex.Match(confDetails.HTML).Groups[1].Value);
|
||||
}
|
||||
|
||||
public bool AcceptConfirmation(Confirmation conf)
|
||||
{
|
||||
return _sendConfirmationAjax(conf, "allow");
|
||||
@@ -258,7 +268,7 @@ namespace SteamAuth
|
||||
this.Session.SteamLoginSecure = tokenSecure;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -290,18 +300,36 @@ namespace SteamAuth
|
||||
this.Session.SteamLoginSecure = tokenSecure;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf)
|
||||
{
|
||||
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ID + "?";
|
||||
string queryString = GenerateConfirmationQueryParams("details");
|
||||
url += queryString;
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
this.Session.AddCookies(cookies);
|
||||
string referer = GenerateConfirmationURL();
|
||||
|
||||
string response = SteamWeb.Request(url, "GET", null, cookies, null);
|
||||
if (String.IsNullOrEmpty(response)) return null;
|
||||
|
||||
var confResponse = JsonConvert.DeserializeObject<ConfirmationDetailsResponse>(response);
|
||||
if (confResponse == null) return null;
|
||||
return confResponse;
|
||||
}
|
||||
|
||||
private bool _sendConfirmationAjax(Confirmation conf, string op)
|
||||
{
|
||||
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
|
||||
string queryString = "?op=" + op + "&";
|
||||
queryString += GenerateConfirmationQueryParams(op);
|
||||
queryString += "&cid=" + conf.ConfirmationID + "&ck=" + conf.ConfirmationKey;
|
||||
queryString += "&cid=" + conf.ID + "&ck=" + conf.Key;
|
||||
url += queryString;
|
||||
|
||||
CookieContainer cookies = new CookieContainer();
|
||||
@@ -373,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!
|
||||
}
|
||||
@@ -415,5 +443,14 @@ namespace SteamAuth
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
private class ConfirmationDetailsResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("html")]
|
||||
public string HTML { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,6 @@ namespace SteamAuth
|
||||
this.LoggedIn = true;
|
||||
return LoginResult.LoginOkay;
|
||||
}
|
||||
|
||||
return LoginResult.GeneralFailure;
|
||||
}
|
||||
|
||||
private class LoginResponse
|
||||
|
||||
BIN
tools/NetHook2.dll
Normal file
BIN
tools/NetHook2.dll
Normal file
Binary file not shown.
BIN
tools/NetHookAnalyzer2.exe
Normal file
BIN
tools/NetHookAnalyzer2.exe
Normal file
Binary file not shown.
1
tools/hook.bat
Normal file
1
tools/hook.bat
Normal file
@@ -0,0 +1 @@
|
||||
rundll32 NetHook2.dll,Inject
|
||||
1
tools/unhook.bat
Normal file
1
tools/unhook.bat
Normal file
@@ -0,0 +1 @@
|
||||
rundll32 NetHook2.dll,Eject
|
||||
Reference in New Issue
Block a user