mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-16 08:25:28 +00:00
Initial source drop
This commit is contained in:
6
ArchiSteamFarm/App.config
Normal file
6
ArchiSteamFarm/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
</startup>
|
||||
</configuration>
|
||||
114
ArchiSteamFarm/ArchiHandler.cs
Normal file
114
ArchiSteamFarm/ArchiHandler.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ArchiHandler : ClientMsgHandler {
|
||||
|
||||
internal sealed class PurchaseResponseCallback : CallbackMsg {
|
||||
internal enum EPurchaseResult {
|
||||
Unknown,
|
||||
OK,
|
||||
AlreadyOwned = 9,
|
||||
InvalidKey = 14,
|
||||
DuplicatedKey = 15,
|
||||
OnCooldown = 53
|
||||
}
|
||||
|
||||
internal EResult Result { get; private set; }
|
||||
internal EPurchaseResult PurchaseResult { get; private set; }
|
||||
internal int ErrorCode { get; private set; }
|
||||
internal byte[] ReceiptInfo { get; private set; }
|
||||
|
||||
internal PurchaseResponseCallback(CMsgClientPurchaseResponse body) {
|
||||
Result = (EResult) body.eresult;
|
||||
ErrorCode = body.purchase_result_details;
|
||||
ReceiptInfo = body.purchase_receipt_info;
|
||||
|
||||
if (Result == EResult.OK) {
|
||||
PurchaseResult = EPurchaseResult.OK;
|
||||
} else {
|
||||
PurchaseResult = (EPurchaseResult) ErrorCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotificationCallback : CallbackMsg {
|
||||
internal enum ENotificationType {
|
||||
Unknown = 0,
|
||||
Trading = 1,
|
||||
}
|
||||
|
||||
internal ENotificationType NotificationType { get; private set; }
|
||||
|
||||
internal NotificationCallback(CMsgClientUserNotifications.Notification body) {
|
||||
uint notificationType = body.user_notification_type;
|
||||
switch (notificationType) {
|
||||
case 1:
|
||||
NotificationType = (ENotificationType) notificationType;
|
||||
break;
|
||||
default:
|
||||
NotificationType = ENotificationType.Unknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AcceptClanInvite(ulong clanID) {
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = true;
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void DeclineClanInvite(ulong clanID) {
|
||||
var request = new ClientMsg<CMsgClientClanInviteAction>((int) EMsg.ClientAcknowledgeClanInvite);
|
||||
request.Body.GroupID = clanID;
|
||||
request.Body.AcceptInvite = false;
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void PlayGames(params ulong[] gameIDs) {
|
||||
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
||||
foreach (ulong gameID in gameIDs) {
|
||||
if (gameID != 0) {
|
||||
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
|
||||
game_id = new GameID(gameID),
|
||||
});
|
||||
}
|
||||
}
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
// Will provide result in ClientPurchaseResponse, regardless if success or not
|
||||
internal void RedeemKey(string key) {
|
||||
var request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey);
|
||||
request.Body.key = key;
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
public sealed override void HandleMsg(IPacketMsg packetMsg) {
|
||||
if (packetMsg != null) {
|
||||
switch (packetMsg.MsgType) {
|
||||
case EMsg.ClientPurchaseResponse:
|
||||
HandlePurchaseResponse(packetMsg);
|
||||
break;
|
||||
case EMsg.ClientUserNotifications:
|
||||
HandleUserNotifications(packetMsg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePurchaseResponse(IPacketMsg packetMsg) {
|
||||
var response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
|
||||
Client.PostCallback(new PurchaseResponseCallback(response.Body));
|
||||
}
|
||||
|
||||
private void HandleUserNotifications(IPacketMsg packetMsg) {
|
||||
var response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
|
||||
foreach (var notification in response.Body.notifications) {
|
||||
Client.PostCallback(new NotificationCallback(notification));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
ArchiSteamFarm/ArchiSteamFarm.csproj
Normal file
82
ArchiSteamFarm/ArchiSteamFarm.csproj
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{35AF7887-08B9-40E8-A5EA-797D8B60B30C}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>ArchiSteamFarm</RootNamespace>
|
||||
<AssemblyName>ArchiSteamFarm</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</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>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SteamKit2, Version=1.6.5.29095, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SteamKit2.1.6.5\lib\net40\SteamKit2.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ArchiHandler.cs" />
|
||||
<Compile Include="ArchiWebHandler.cs" />
|
||||
<Compile Include="Bot.cs" />
|
||||
<Compile Include="CardsFarmer.cs" />
|
||||
<Compile Include="CMsgClientClanInviteAction.cs" />
|
||||
<Compile Include="Logging.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SteamItem.cs" />
|
||||
<Compile Include="SteamTradeOffer.cs" />
|
||||
<Compile Include="Trading.cs" />
|
||||
<Compile Include="Utilities.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
251
ArchiSteamFarm/ArchiWebHandler.cs
Normal file
251
ArchiSteamFarm/ArchiWebHandler.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using HtmlAgilityPack;
|
||||
using SteamKit2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class ArchiWebHandler {
|
||||
private const int Timeout = 1000 * 15; // In miliseconds
|
||||
private readonly Bot Bot;
|
||||
private readonly string ApiKey;
|
||||
|
||||
private ulong SteamID;
|
||||
private string VanityURL;
|
||||
private readonly Dictionary<string, string> SteamCookieDictionary = new Dictionary<string, string>();
|
||||
|
||||
// This is required because home_process request must be done on final URL
|
||||
private string GetHomeProcess() {
|
||||
if (!string.IsNullOrEmpty(VanityURL)) {
|
||||
return "http://steamcommunity.com/id/" + VanityURL + "/home_process";
|
||||
} else if (SteamID != 0) {
|
||||
return "http://steamcommunity.com/profiles/" + SteamID + "/home_process";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal ArchiWebHandler(Bot bot, string apiKey) {
|
||||
Bot = bot;
|
||||
ApiKey = apiKey;
|
||||
}
|
||||
|
||||
internal void Init(SteamClient steamClient, string webAPIUserNonce, string vanityURL) {
|
||||
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(vanityURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamID = steamClient.SteamID;
|
||||
VanityURL = vanityURL;
|
||||
|
||||
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
// 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;
|
||||
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
|
||||
cryptedSessionKey = rsa.Encrypt(sessionKey);
|
||||
}
|
||||
|
||||
// Copy our login key
|
||||
byte[] loginKey = new byte[20];
|
||||
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
|
||||
KeyValue authResult;
|
||||
Logging.LogGenericInfo(Bot.BotName, "Logging in to ISteamUserAuth...");
|
||||
using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) {
|
||||
iSteamUserAuth.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
authResult = iSteamUserAuth.AuthenticateUser(
|
||||
steamid: SteamID,
|
||||
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
|
||||
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
steamClient.Disconnect(); // We may get 403 if we use the same webAPIUserNonce twice
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (authResult == null) {
|
||||
steamClient.Disconnect(); // Try again
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(Bot.BotName, "Success!");
|
||||
|
||||
string steamLogin = authResult["token"].AsString();
|
||||
string steamLoginSecure = authResult["tokensecure"].AsString();
|
||||
|
||||
SteamCookieDictionary.Clear();
|
||||
SteamCookieDictionary.Add("sessionid", sessionID);
|
||||
SteamCookieDictionary.Add("steamLogin", steamLogin);
|
||||
SteamCookieDictionary.Add("steamLoginSecure", steamLoginSecure);
|
||||
SteamCookieDictionary.Add("birthtime", "-473356799"); // ( ͡° ͜ʖ ͡°)
|
||||
|
||||
Bot.Trading.CheckTrades();
|
||||
}
|
||||
|
||||
internal List<SteamTradeOffer> GetTradeOffers() {
|
||||
KeyValue response;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
|
||||
// Timeout
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.GetTradeOffers(
|
||||
key: ApiKey,
|
||||
get_received_offers: 1,
|
||||
active_only: 1,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<SteamTradeOffer> result = new List<SteamTradeOffer>();
|
||||
foreach (KeyValue trade in response["trade_offers_received"].Children) {
|
||||
SteamTradeOffer tradeOffer = new SteamTradeOffer {
|
||||
tradeofferid = trade["tradeofferid"].AsString(),
|
||||
accountid_other = trade["accountid_other"].AsInteger(),
|
||||
message = trade["message"].AsString(),
|
||||
expiration_time = trade["expiration_time"].AsInteger(),
|
||||
trade_offer_state = (SteamTradeOffer.ETradeOfferState) trade["trade_offer_state"].AsInteger(),
|
||||
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()
|
||||
};
|
||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
||||
tradeOffer.items_to_give.Add(new SteamItem {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string referer = "https://steamcommunity.com/tradeoffer/" + tradeID + "/";
|
||||
string request = referer + "accept";
|
||||
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
{"sessionid", sessionID},
|
||||
{"serverid", "1"},
|
||||
{"tradeofferid", tradeID.ToString()}
|
||||
};
|
||||
|
||||
return await Utilities.UrlPostRequest(request, postData, SteamCookieDictionary, referer).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal bool DeclineTradeOffer(ulong tradeID) {
|
||||
if (tradeID == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValue response;
|
||||
using (dynamic iEconService = WebAPI.GetInterface("IEconService")) {
|
||||
// Timeout
|
||||
iEconService.Timeout = Timeout;
|
||||
|
||||
try {
|
||||
response = iEconService.DeclineTradeOffer(
|
||||
key: ApiKey,
|
||||
tradeofferid: tradeID.ToString(),
|
||||
method: WebRequestMethods.Http.Post,
|
||||
secure: true
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Logging.LogGenericException(Bot.BotName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return response != null; // Steam API doesn't respond with any error code, assume any response is a success
|
||||
}
|
||||
|
||||
internal async Task LeaveClan(ulong clanID) {
|
||||
if (clanID == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionID;
|
||||
if (!SteamCookieDictionary.TryGetValue("sessionid", out sessionID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string request = GetHomeProcess();
|
||||
Dictionary<string, string> postData = new Dictionary<string, string>() {
|
||||
{"sessionID", sessionID},
|
||||
{"action", "leaveGroup"},
|
||||
{"groupId", clanID.ToString()}
|
||||
};
|
||||
await Utilities.UrlPostRequest(request, postData, SteamCookieDictionary).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> GetBadgePage(int page) {
|
||||
HtmlDocument result = null;
|
||||
if (SteamID != 0 && page != 0) {
|
||||
result = await Utilities.UrlToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/badges?p=" + page, SteamCookieDictionary).ConfigureAwait(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
|
||||
HtmlDocument result = null;
|
||||
if (SteamID != 0 && appID != 0) {
|
||||
result = await Utilities.UrlToHtmlDocument("http://steamcommunity.com/profiles/" + SteamID + "/gamecards/" + appID, SteamCookieDictionary).ConfigureAwait(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
336
ArchiSteamFarm/Bot.cs
Normal file
336
ArchiSteamFarm/Bot.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using SteamKit2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class Bot {
|
||||
private const byte CallbackSleep = 100; // In miliseconds
|
||||
|
||||
private readonly Dictionary<string, string> Config = new Dictionary<string, string>();
|
||||
|
||||
internal readonly string BotName;
|
||||
private readonly string ConfigFile;
|
||||
private readonly string SentryFile;
|
||||
|
||||
private readonly CardsFarmer CardsFarmer;
|
||||
|
||||
internal ulong BotID { get; private set; }
|
||||
private string AuthCode, TwoFactorAuth;
|
||||
|
||||
internal ArchiHandler ArchiHandler { get; private set; }
|
||||
internal ArchiWebHandler ArchiWebHandler { get; private set; }
|
||||
internal CallbackManager CallbackManager { get; private set; }
|
||||
internal SteamClient SteamClient { get; private set; }
|
||||
internal SteamFriends SteamFriends { get; private set; }
|
||||
internal SteamUser SteamUser { get; private set; }
|
||||
internal Trading Trading { get; private set; }
|
||||
|
||||
// Config variables
|
||||
private bool Enabled { get { return bool.Parse(Config["Enabled"]); } }
|
||||
private string SteamLogin { get { return Config["SteamLogin"]; } }
|
||||
private string SteamPassword { get { return Config["SteamPassword"]; } }
|
||||
private string SteamNickname { get { return Config["SteamNickname"]; } }
|
||||
private string SteamApiKey { get { return Config["SteamApiKey"]; } }
|
||||
internal ulong SteamMasterID { get { return ulong.Parse(Config["SteamMasterID"]); } }
|
||||
internal ulong SteamMasterClanID { get { return ulong.Parse(Config["SteamMasterClanID"]); } }
|
||||
|
||||
internal Bot (string botName) {
|
||||
BotName = botName;
|
||||
CardsFarmer = new CardsFarmer(this);
|
||||
|
||||
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
|
||||
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
|
||||
|
||||
ReadConfig();
|
||||
|
||||
if (!Enabled) {
|
||||
return;
|
||||
}
|
||||
Start();
|
||||
}
|
||||
|
||||
private void ReadConfig() {
|
||||
using (XmlReader reader = XmlReader.Create(ConfigFile)) {
|
||||
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;
|
||||
}
|
||||
|
||||
Config.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start() {
|
||||
if (SteamClient != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamClient = new SteamClient();
|
||||
|
||||
ArchiHandler = new ArchiHandler();
|
||||
SteamClient.AddHandler(ArchiHandler);
|
||||
|
||||
CallbackManager = new CallbackManager(SteamClient);
|
||||
CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
|
||||
CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);
|
||||
|
||||
SteamFriends = SteamClient.GetHandler<SteamFriends>();
|
||||
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
|
||||
CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
|
||||
|
||||
SteamUser = SteamClient.GetHandler<SteamUser>();
|
||||
CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
|
||||
CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
|
||||
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
|
||||
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
|
||||
|
||||
CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
|
||||
CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
|
||||
|
||||
ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
|
||||
Trading = new Trading(this);
|
||||
|
||||
SteamClient.Connect();
|
||||
Task.Run(() => HandleCallbacks());
|
||||
}
|
||||
|
||||
internal void Stop() {
|
||||
if (SteamClient == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamClient.Disconnect();
|
||||
SteamClient = null;
|
||||
CallbackManager = null;
|
||||
}
|
||||
|
||||
internal void PlayGame(params ulong[] gameIDs) {
|
||||
ArchiHandler.PlayGames(gameIDs);
|
||||
}
|
||||
|
||||
private void HandleCallbacks() {
|
||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
|
||||
while (CallbackManager != null) {
|
||||
CallbackManager.RunWaitCallbacks(timeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnected(SteamClient.ConnectedCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.Result != EResult.OK) {
|
||||
Logging.LogGenericError(BotName, "Unable to connect to Steam: " + callback.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Connected to Steam!");
|
||||
|
||||
byte[] sentryHash = null;
|
||||
if (File.Exists(SentryFile)) {
|
||||
byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
|
||||
sentryHash = CryptoHelper.SHAHash(sentryFileContent);
|
||||
}
|
||||
|
||||
SteamUser.LogOn(new SteamUser.LogOnDetails {
|
||||
Username = SteamLogin,
|
||||
Password = SteamPassword,
|
||||
AuthCode = AuthCode,
|
||||
TwoFactorCode = TwoFactorAuth,
|
||||
SentryFileHash = sentryHash
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDisconnected(SteamClient.DisconnectedCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericWarning(BotName, "Disconnected from Steam, reconnecting...");
|
||||
Thread.Sleep(TimeSpan.FromMilliseconds(CallbackSleep));
|
||||
SteamClient.Connect();
|
||||
}
|
||||
|
||||
private void OnFriendsList(SteamFriends.FriendsListCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var friend in callback.FriendList) {
|
||||
if (friend.Relationship != EFriendRelationship.RequestRecipient) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SteamID steamID = friend.SteamID;
|
||||
switch (steamID.AccountType) {
|
||||
case EAccountType.Clan:
|
||||
//ArchiHandler.AcceptClanInvite(steamID);
|
||||
break;
|
||||
default:
|
||||
if (steamID == SteamMasterID) {
|
||||
SteamFriends.AddFriend(steamID);
|
||||
} else {
|
||||
SteamFriends.RemoveFriend(steamID);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFriendMsg(SteamFriends.FriendMsgCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.EntryType != EChatEntryType.ChatMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong steamID = callback.Sender;
|
||||
if (steamID != SteamMasterID) {
|
||||
return;
|
||||
}
|
||||
|
||||
string message = callback.Message;
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Length == 17 && message[5] == '-' && message[11] == '-') {
|
||||
ArchiHandler.RedeemKey(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAccountInfo(SteamUser.AccountInfoCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamFriends.SetPersonaState(EPersonaState.Online);
|
||||
}
|
||||
|
||||
private void OnLoggedOff(SteamUser.LoggedOffCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Logged off of Steam: " + callback.Result);
|
||||
}
|
||||
|
||||
private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback.ClientSteamID != 0) {
|
||||
BotID = callback.ClientSteamID;
|
||||
}
|
||||
|
||||
EResult result = callback.Result;
|
||||
switch (result) {
|
||||
case EResult.AccountLogonDenied:
|
||||
AuthCode = Program.GetSteamGuardCode(SteamLogin, false);
|
||||
break;
|
||||
case EResult.AccountLoginDeniedNeedTwoFactor:
|
||||
TwoFactorAuth = Program.GetSteamGuardCode(SteamLogin, true);
|
||||
break;
|
||||
case EResult.OK:
|
||||
Logging.LogGenericInfo(BotName, "Successfully logged on!");
|
||||
SteamFriends.SetPersonaName(SteamNickname);
|
||||
ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, callback.VanityURL);
|
||||
|
||||
ulong clanID = SteamMasterClanID;
|
||||
if (clanID != 0) {
|
||||
SteamFriends.JoinChat(clanID);
|
||||
}
|
||||
|
||||
await CardsFarmer.StartFarming().ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult + ", retrying...");
|
||||
Stop();
|
||||
Thread.Sleep(5000);
|
||||
Start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo(BotName, "Updating sentryfile...");
|
||||
|
||||
int fileSize;
|
||||
byte[] sentryHash;
|
||||
|
||||
using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) {
|
||||
fileStream.Seek(callback.Offset, SeekOrigin.Begin);
|
||||
fileStream.Write(callback.Data, 0, callback.BytesToWrite);
|
||||
fileSize = (int) fileStream.Length;
|
||||
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) {
|
||||
sentryHash = sha.ComputeHash(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Inform the steam servers that we're accepting this sentry file
|
||||
SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails {
|
||||
JobID = callback.JobID,
|
||||
FileName = callback.FileName,
|
||||
BytesWritten = callback.BytesToWrite,
|
||||
FileSize = fileSize,
|
||||
Offset = callback.Offset,
|
||||
Result = EResult.OK,
|
||||
LastError = 0,
|
||||
OneTimePassword = callback.OneTimePassword,
|
||||
SentryFileHash = sentryHash,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnNotification(ArchiHandler.NotificationCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (callback.NotificationType) {
|
||||
case ArchiHandler.NotificationCallback.ENotificationType.Trading:
|
||||
Trading.CheckTrades();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var purchaseResult = callback.PurchaseResult;
|
||||
SteamFriends.SendChatMessage(SteamMasterID, EChatEntryType.ChatMsg, "Status: " + purchaseResult);
|
||||
|
||||
if (purchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
|
||||
await CardsFarmer.StartFarming().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
ArchiSteamFarm/CMsgClientClanInviteAction.cs
Normal file
47
ArchiSteamFarm/CMsgClientClanInviteAction.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
using System.IO;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
/// <summary>
|
||||
/// Message used to Accept or Decline a group(clan) invite.
|
||||
/// </summary>
|
||||
internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage, ISteamSerializable {
|
||||
EMsg ISteamSerializableMessage.GetEMsg() {
|
||||
return EMsg.ClientAcknowledgeClanInvite;
|
||||
}
|
||||
|
||||
public CMsgClientClanInviteAction() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group invited to.
|
||||
/// </summary>
|
||||
internal ulong GroupID = 0;
|
||||
|
||||
/// <summary>
|
||||
/// To accept or decline the invite.
|
||||
/// </summary>
|
||||
internal bool AcceptInvite = true;
|
||||
|
||||
void ISteamSerializable.Serialize(Stream stream) {
|
||||
try {
|
||||
BinaryWriter binaryWriter = new BinaryWriter(stream);
|
||||
binaryWriter.Write(GroupID);
|
||||
binaryWriter.Write(AcceptInvite);
|
||||
} catch {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
void ISteamSerializable.Deserialize(Stream stream) {
|
||||
try {
|
||||
BinaryReader binaryReader = new BinaryReader(stream);
|
||||
GroupID = binaryReader.ReadUInt64();
|
||||
AcceptInvite = binaryReader.ReadBoolean();
|
||||
} catch {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
ArchiSteamFarm/CardsFarmer.cs
Normal file
109
ArchiSteamFarm/CardsFarmer.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using HtmlAgilityPack;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal class CardsFarmer {
|
||||
private readonly Bot Bot;
|
||||
private bool NowFarming;
|
||||
private readonly AutoResetEvent AutoResetEvent = new AutoResetEvent(false);
|
||||
|
||||
internal CardsFarmer(Bot bot) {
|
||||
Bot = bot;
|
||||
}
|
||||
|
||||
internal async Task StartFarming() {
|
||||
// Find the number of badge pages
|
||||
HtmlDocument badgesDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
|
||||
if (badgesDocument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var maxPages = 1;
|
||||
HtmlNodeCollection badgesPagesNodeCollection = badgesDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
|
||||
if (badgesPagesNodeCollection != null) {
|
||||
maxPages = (byte) (badgesPagesNodeCollection.Count / 2 + 1); // Don't do this at home
|
||||
}
|
||||
|
||||
// Find APPIDs we need to farm
|
||||
List<uint> appIDs = new List<uint>();
|
||||
for (var page = 1; page <= maxPages; page++) {
|
||||
if (page > 1) { // Because we fetched page number 1 already
|
||||
badgesDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
|
||||
if (badgesDocument == null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HtmlNodeCollection badgesPageNodes = badgesDocument.DocumentNode.SelectNodes("//a[@class='btn_green_white_innerfade btn_small_thin']");
|
||||
if (badgesPageNodes == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (HtmlNode badgesPageNode in badgesPageNodes) {
|
||||
string steamLink = badgesPageNode.GetAttributeValue("href", null);
|
||||
if (steamLink == null) {
|
||||
page = maxPages; // Break from outer loop
|
||||
break;
|
||||
}
|
||||
|
||||
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
|
||||
if (appID == 0) {
|
||||
page = maxPages; // Break from outer loop
|
||||
break;
|
||||
}
|
||||
|
||||
appIDs.Add(appID);
|
||||
}
|
||||
}
|
||||
|
||||
// Start farming
|
||||
while (appIDs.Count > 0) {
|
||||
uint appID = appIDs[0];
|
||||
if (await Farm(appID).ConfigureAwait(false)) {
|
||||
appIDs.Remove(appID);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool?> ShouldFarm(ulong appID) {
|
||||
bool? result = null;
|
||||
HtmlDocument gamePageDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
|
||||
if (gamePageDocument != null) {
|
||||
HtmlNode gamePageNode = gamePageDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
|
||||
if (gamePageNode != null) {
|
||||
result = !gamePageNode.InnerText.Contains("No card drops");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> Farm(ulong appID) {
|
||||
if (NowFarming) {
|
||||
AutoResetEvent.Set();
|
||||
Thread.Sleep(1000);
|
||||
AutoResetEvent.Reset();
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
while (keepFarming == null || keepFarming.Value) {
|
||||
if (!NowFarming) {
|
||||
NowFarming = true;
|
||||
Bot.PlayGame(appID);
|
||||
}
|
||||
if (AutoResetEvent.WaitOne(1000 * 60 * 5)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
|
||||
}
|
||||
Bot.PlayGame(0);
|
||||
NowFarming = false;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
ArchiSteamFarm/Logging.cs
Normal file
38
ArchiSteamFarm/Logging.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Logging {
|
||||
private static void Log(string message) {
|
||||
Console.WriteLine(DateTime.Now + " " + message);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 LogGenericDebug(string botName, string message, [CallerMemberName] string previousMethodName = "") {
|
||||
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
|
||||
}
|
||||
|
||||
internal static void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = "") {
|
||||
LogGenericDebug("DEBUG", message, previousMethodName);
|
||||
}
|
||||
|
||||
internal static void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = "") {
|
||||
LogGenericError(nullObjectName + " is null!", previousMethodName);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
ArchiSteamFarm/Program.cs
Normal file
58
ArchiSteamFarm/Program.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Program {
|
||||
internal const string ConfigDirectoryPath = "config";
|
||||
private static readonly HashSet<Bot> Bots = new HashSet<Bot>();
|
||||
internal static readonly object ConsoleLock = new object();
|
||||
|
||||
internal static void Exit(int exitCode = 0) {
|
||||
ShutdownAllBots();
|
||||
Environment.Exit(exitCode);
|
||||
}
|
||||
|
||||
internal static string GetSteamGuardCode(string botLogin, bool twoFactorAuthentication) {
|
||||
lock (ConsoleLock) {
|
||||
if (twoFactorAuthentication) {
|
||||
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
|
||||
} else {
|
||||
Console.Write("<" + botLogin + "> Please enter the auth code sent to your email : ");
|
||||
}
|
||||
return Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShutdownAllBots() {
|
||||
lock (Bots) {
|
||||
foreach (Bot bot in Bots) {
|
||||
bot.Stop();
|
||||
}
|
||||
Bots.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Main(string[] args) {
|
||||
for (var i = 0; i < 4 && !Directory.Exists(ConfigDirectoryPath); i++) {
|
||||
Directory.SetCurrentDirectory("..");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(ConfigDirectoryPath)) {
|
||||
Logging.LogGenericError("Main", "Config directory doesn't exist!");
|
||||
Console.ReadLine();
|
||||
Exit(1);
|
||||
}
|
||||
|
||||
lock (Bots) {
|
||||
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) {
|
||||
string botName = Path.GetFileNameWithoutExtension(configFile);
|
||||
Bots.Add(new Bot(botName));
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ArchiSteamFarm/Properties/AssemblyInfo.cs
Normal file
36
ArchiSteamFarm/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ArchiSteamFarm")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ArchiSteamFarm")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("35af7887-08b9-40e8-a5ea-797d8b60b30c")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
13
ArchiSteamFarm/SteamItem.cs
Normal file
13
ArchiSteamFarm/SteamItem.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class SteamItem {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
|
||||
internal string appid { get; set; }
|
||||
internal string contextid { get; set; }
|
||||
internal string assetid { get; set; }
|
||||
internal string currencyid { get; set; }
|
||||
internal string classid { get; set; }
|
||||
internal string instanceid { get; set; }
|
||||
internal string amount { get; set; }
|
||||
internal bool missing { get; set; }
|
||||
}
|
||||
}
|
||||
41
ArchiSteamFarm/SteamTradeOffer.cs
Normal file
41
ArchiSteamFarm/SteamTradeOffer.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using SteamKit2;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class SteamTradeOffer {
|
||||
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
|
||||
internal enum ETradeOfferState {
|
||||
Unknown,
|
||||
Invalid,
|
||||
Active,
|
||||
Accepted,
|
||||
Countered,
|
||||
Expired,
|
||||
Canceled,
|
||||
Declined,
|
||||
InvalidItems,
|
||||
EmailPending,
|
||||
EmailCanceled
|
||||
}
|
||||
|
||||
internal string tradeofferid { get; set; }
|
||||
internal int accountid_other { get; set; }
|
||||
internal string message { get; set; }
|
||||
internal int expiration_time { get; set; }
|
||||
internal ETradeOfferState trade_offer_state { get; set; }
|
||||
internal List<SteamItem> items_to_give { get; set; }
|
||||
internal List<SteamItem> items_to_receive { get; set; }
|
||||
internal bool is_our_offer { get; set; }
|
||||
internal int time_created { get; set; }
|
||||
internal int time_updated { get; set; }
|
||||
internal bool from_real_time_trade { get; set; }
|
||||
|
||||
// Extra
|
||||
internal ulong OtherSteamID64 {
|
||||
get {
|
||||
return new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
|
||||
}
|
||||
private set { }
|
||||
}
|
||||
}
|
||||
}
|
||||
74
ArchiSteamFarm/Trading.cs
Normal file
74
ArchiSteamFarm/Trading.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class Trading {
|
||||
private Bot Bot;
|
||||
private volatile byte ParsingTasks = 0;
|
||||
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal Trading(Bot bot) {
|
||||
Bot = bot;
|
||||
}
|
||||
|
||||
internal void CheckTrades() {
|
||||
if (ParsingTasks < 2) {
|
||||
ParsingTasks++;
|
||||
Task.Run(() => ParseActiveTrades());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ParseActiveTrades() {
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
List<SteamTradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
|
||||
if (tradeOffers != null) {
|
||||
List<Task> tasks = new List<Task>();
|
||||
foreach (SteamTradeOffer tradeOffer in tradeOffers) {
|
||||
if (tradeOffer.trade_offer_state == SteamTradeOffer.ETradeOfferState.Active) {
|
||||
Task task = Task.Run(async () => {
|
||||
await ParseTrade(tradeOffer).ConfigureAwait(false);
|
||||
});
|
||||
tasks.Add(task);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ParsingTasks--;
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
|
||||
if (tradeOffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong tradeID;
|
||||
if (!ulong.TryParse(tradeOffer.tradeofferid, out tradeID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ulong steamID = tradeOffer.OtherSteamID64;
|
||||
bool success = false;
|
||||
bool tradeAccepted = false;
|
||||
|
||||
if (tradeOffer.items_to_give.Count == 0 || steamID == Bot.SteamMasterID) {
|
||||
tradeAccepted = true;
|
||||
success = await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
|
||||
} else {
|
||||
success = Bot.ArchiWebHandler.DeclineTradeOffer(tradeID);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
Logging.LogGenericWarning(Bot.BotName, "Response to trade " + tradeID + " failed!");
|
||||
}
|
||||
|
||||
if (tradeAccepted && success) {
|
||||
// Do whatever we want with success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
ArchiSteamFarm/Utilities.cs
Normal file
117
ArchiSteamFarm/Utilities.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using HtmlAgilityPack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Utilities {
|
||||
internal static ulong OnlyNumbers(string inputString) {
|
||||
if (string.IsNullOrEmpty(inputString)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
string resultString;
|
||||
try {
|
||||
Regex regexObj = new Regex(@"[^\d]");
|
||||
resultString = regexObj.Replace(inputString, "");
|
||||
} catch (ArgumentException e) {
|
||||
Logging.LogGenericException("Utilities", e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ulong result = ulong.Parse(resultString, CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static async Task<HttpResponseMessage> UrlToHttpResponse(string websiteAddress, Dictionary<string, string> cookieVariables) {
|
||||
HttpResponseMessage result = null;
|
||||
if (!string.IsNullOrEmpty(websiteAddress)) {
|
||||
try {
|
||||
using (HttpClientHandler clientHandler = new HttpClientHandler { UseCookies = false }) {
|
||||
using (HttpClient client = new HttpClient(clientHandler)) {
|
||||
client.Timeout = TimeSpan.FromSeconds(10);
|
||||
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, websiteAddress);
|
||||
if (cookieVariables != null) {
|
||||
StringBuilder cookie = new StringBuilder();
|
||||
foreach (KeyValuePair<string, string> cookieVariable in cookieVariables) {
|
||||
cookie.Append(cookieVariable.Key + "=" + cookieVariable.Value + ";");
|
||||
}
|
||||
requestMessage.Headers.Add("Cookie", cookie.ToString());
|
||||
}
|
||||
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
if (responseMessage != null) {
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
result = responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static async Task<HttpResponseMessage> UrlToHttpResponse(string websiteAddress) {
|
||||
return await UrlToHttpResponse(websiteAddress, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress, Dictionary<string, string> cookieVariables) {
|
||||
HtmlDocument result = null;
|
||||
if (!string.IsNullOrEmpty(websiteAddress)) {
|
||||
try {
|
||||
HttpResponseMessage responseMessage = await UrlToHttpResponse(websiteAddress, cookieVariables).ConfigureAwait(false);
|
||||
if (responseMessage != null) {
|
||||
string source = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(source)) {
|
||||
source = WebUtility.HtmlDecode(source);
|
||||
result = new HtmlDocument();
|
||||
result.LoadHtml(source);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress) {
|
||||
return await UrlToHtmlDocument(websiteAddress, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<bool> UrlPostRequest(string request, Dictionary<string, string> postData, Dictionary<string, string> cookieVariables, string referer = null) {
|
||||
bool result = false;
|
||||
if (!string.IsNullOrEmpty(request)) {
|
||||
try {
|
||||
using (HttpClientHandler clientHandler = new HttpClientHandler { UseCookies = false }) {
|
||||
using (HttpClient client = new HttpClient(clientHandler)) {
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, request);
|
||||
requestMessage.Content = new FormUrlEncodedContent(postData);
|
||||
if (cookieVariables != null && cookieVariables.Count > 0) {
|
||||
StringBuilder cookie = new StringBuilder();
|
||||
foreach (KeyValuePair<string, string> cookieVariable in cookieVariables) {
|
||||
cookie.Append(cookieVariable.Key + "=" + cookieVariable.Value + ";");
|
||||
}
|
||||
requestMessage.Headers.Add("Cookie", cookie.ToString());
|
||||
}
|
||||
if (referer != null) {
|
||||
requestMessage.Headers.Referrer = new Uri(referer);
|
||||
}
|
||||
HttpResponseMessage responseMessage = await client.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
if (responseMessage != null) {
|
||||
result = responseMessage.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
ArchiSteamFarm/config/example.xml
Normal file
16
ArchiSteamFarm/config/example.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- Every bot should have it's own unique .xml configuration file, this is example on which you can base on -->
|
||||
|
||||
<Enabled value="true"/> <!-- Master switch to turn account on and off -->
|
||||
|
||||
<!-- Steam -->
|
||||
<SteamLogin value="Foo"/> <!-- This is your steam login, the one you use for logging in to steam -->
|
||||
<SteamPassword value="Bar"/> <!-- This is your steam password, the one you use for logging in to steam -->
|
||||
<SteamNickname value="ArchiSteamFarmer"/> <!-- This is your steam nickname, the one you want to use for bot. Can be anything up to 32 characters -->
|
||||
<SteamApiKey value="FFFFFFFF"/> <!-- Get one at https://steamcommunity.com/dev/apikey -->
|
||||
<SteamMasterID value="76561198006963719"/> <!-- This is steamID of the master, aka the "root" user being able to execute any command -->
|
||||
<SteamMasterClanID value="0"/> <!-- If you want from the bot to join particular chat of given clan, set it here, otherwise leave at 0 -->
|
||||
|
||||
</configuration>
|
||||
6
ArchiSteamFarm/packages.config
Normal file
6
ArchiSteamFarm/packages.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
|
||||
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
|
||||
<package id="SteamKit2" version="1.6.5" targetFramework="net45" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user