3 / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
8 Copyright 2015 Łukasz "JustArchi" Domeradzki
9 Contact: JustArchi@JustArchi.net
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 http://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
27 using System
.Collections
.Generic
;
29 using System
.Security
.Cryptography
;
30 using System
.Threading
;
31 using System
.Threading
.Tasks
;
34 namespace ArchiSteamFarm
{
36 private const ushort CallbackSleep
= 500; // In miliseconds
38 private readonly Dictionary
<string, string> Config
= new Dictionary
<string, string>();
40 internal readonly string BotName
;
41 private readonly string ConfigFile
;
42 private readonly string SentryFile
;
44 private readonly CardsFarmer CardsFarmer
;
46 internal ulong BotID { get; private set; }
47 private string AuthCode
, TwoFactorAuth
;
49 internal ArchiHandler ArchiHandler { get; private set; }
50 internal ArchiWebHandler ArchiWebHandler { get; private set; }
51 internal CallbackManager CallbackManager { get; private set; }
52 internal SteamClient SteamClient { get; private set; }
53 internal SteamFriends SteamFriends { get; private set; }
54 internal SteamUser SteamUser { get; private set; }
55 internal Trading Trading { get; private set; }
58 internal bool Enabled { get { return bool.Parse(Config["Enabled"]); }
}
59 private string SteamLogin { get { return Config["SteamLogin"]; }
}
60 private string SteamPassword { get { return Config["SteamPassword"]; }
}
61 private string SteamNickname { get { return Config["SteamNickname"]; }
}
62 private string SteamApiKey { get { return Config["SteamApiKey"]; }
}
63 private string SteamParentalPIN { get { return Config["SteamParentalPIN"]; }
}
64 internal ulong SteamMasterID { get { return ulong.Parse(Config["SteamMasterID"]); }
}
65 private ulong SteamMasterClanID { get { return ulong.Parse(Config["SteamMasterClanID"]); }
}
66 internal HashSet
<uint> Blacklist { get; }
= new HashSet
<uint>();
67 internal bool Statistics { get { return bool.Parse(Config["Statistics"]); }
}
69 internal Bot(string botName
) {
71 CardsFarmer
= new CardsFarmer(this);
73 ConfigFile
= Path
.Combine(Program
.ConfigDirectoryPath
, BotName
+ ".xml");
74 SentryFile
= Path
.Combine(Program
.ConfigDirectoryPath
, BotName
+ ".bin");
85 private void ReadConfig() {
86 using (XmlReader reader
= XmlReader
.Create(ConfigFile
)) {
87 while (reader
.Read()) {
88 if (reader
.NodeType
!= XmlNodeType
.Element
) {
92 string key
= reader
.Name
;
93 if (string.IsNullOrEmpty(key
)) {
97 string value = reader
.GetAttribute("value");
98 if (string.IsNullOrEmpty(value)) {
102 Config
.Add(key
, value);
106 foreach (string appID
in value.Split(',')) {
107 Blacklist
.Add(uint.Parse(appID
));
115 internal void Start() {
116 if (SteamClient
!= null) {
120 SteamClient
= new SteamClient();
122 ArchiHandler
= new ArchiHandler();
123 SteamClient
.AddHandler(ArchiHandler
);
125 CallbackManager
= new CallbackManager(SteamClient
);
126 CallbackManager
.Subscribe
<SteamClient
.ConnectedCallback
>(OnConnected
);
127 CallbackManager
.Subscribe
<SteamClient
.DisconnectedCallback
>(OnDisconnected
);
129 SteamFriends
= SteamClient
.GetHandler
<SteamFriends
>();
130 CallbackManager
.Subscribe
<SteamFriends
.FriendsListCallback
>(OnFriendsList
);
131 CallbackManager
.Subscribe
<SteamFriends
.FriendMsgCallback
>(OnFriendMsg
);
132 CallbackManager
.Subscribe
<SteamFriends
.PersonaStateCallback
>(OnPersonaState
);
134 SteamUser
= SteamClient
.GetHandler
<SteamUser
>();
135 CallbackManager
.Subscribe
<SteamUser
.AccountInfoCallback
>(OnAccountInfo
);
136 CallbackManager
.Subscribe
<SteamUser
.LoggedOffCallback
>(OnLoggedOff
);
137 CallbackManager
.Subscribe
<SteamUser
.LoggedOnCallback
>(OnLoggedOn
);
138 CallbackManager
.Subscribe
<SteamUser
.UpdateMachineAuthCallback
>(OnMachineAuth
);
140 CallbackManager
.Subscribe
<ArchiHandler
.NotificationCallback
>(OnNotification
);
141 CallbackManager
.Subscribe
<ArchiHandler
.PurchaseResponseCallback
>(OnPurchaseResponse
);
143 ArchiWebHandler
= new ArchiWebHandler(this, SteamApiKey
);
144 Trading
= new Trading(this);
146 SteamClient
.Connect();
147 Task
.Run(() => HandleCallbacks());
150 internal void Stop() {
151 if (SteamClient
== null) {
155 SteamClient
.Disconnect();
157 CallbackManager
= null;
160 internal void PlayGame(params ulong[] gameIDs
) {
161 ArchiHandler
.PlayGames(gameIDs
);
164 private void HandleCallbacks() {
165 TimeSpan timeSpan
= TimeSpan
.FromMilliseconds(CallbackSleep
);
166 while (CallbackManager
!= null) {
167 CallbackManager
.RunWaitCallbacks(timeSpan
);
171 private void OnConnected(SteamClient
.ConnectedCallback callback
) {
172 if (callback
== null) {
176 if (callback
.Result
!= EResult
.OK
) {
177 Logging
.LogGenericError(BotName
, "Unable to connect to Steam: " + callback
.Result
);
181 Logging
.LogGenericInfo(BotName
, "Connected to Steam!");
183 byte[] sentryHash
= null;
184 if (File
.Exists(SentryFile
)) {
185 byte[] sentryFileContent
= File
.ReadAllBytes(SentryFile
);
186 sentryHash
= CryptoHelper
.SHAHash(sentryFileContent
);
189 string steamLogin
= SteamLogin
;
190 if (string.IsNullOrEmpty(steamLogin
) || steamLogin
.Equals("null")) {
191 steamLogin
= Program
.GetUserInput(BotName
, Program
.EUserInputType
.Login
);
192 Config
["SteamLogin"] = steamLogin
;
195 string steamPassword
= SteamPassword
;
196 if (string.IsNullOrEmpty(steamPassword
) || steamPassword
.Equals("null")) {
197 steamPassword
= Program
.GetUserInput(BotName
, Program
.EUserInputType
.Password
);
198 Config
["SteamPassword"] = steamPassword
;
201 SteamUser
.LogOn(new SteamUser
.LogOnDetails
{
202 Username
= steamLogin
,
203 Password
= steamPassword
,
205 TwoFactorCode
= TwoFactorAuth
,
206 SentryFileHash
= sentryHash
210 private void OnDisconnected(SteamClient
.DisconnectedCallback callback
) {
211 if (callback
== null) {
215 if (SteamClient
== null) {
219 Logging
.LogGenericWarning(BotName
, "Disconnected from Steam, reconnecting...");
220 Thread
.Sleep(TimeSpan
.FromMilliseconds(CallbackSleep
));
221 SteamClient
.Connect();
224 private void OnFriendsList(SteamFriends
.FriendsListCallback callback
) {
225 if (callback
== null) {
229 foreach (var friend
in callback
.FriendList
) {
230 if (friend
.Relationship
!= EFriendRelationship
.RequestRecipient
) {
234 SteamID steamID
= friend
.SteamID
;
235 switch (steamID
.AccountType
) {
236 case EAccountType
.Clan
:
237 //ArchiHandler.AcceptClanInvite(steamID);
240 if (steamID
== SteamMasterID
) {
241 SteamFriends
.AddFriend(steamID
);
243 SteamFriends
.RemoveFriend(steamID
);
250 private void OnFriendMsg(SteamFriends
.FriendMsgCallback callback
) {
251 if (callback
== null) {
255 if (callback
.EntryType
!= EChatEntryType
.ChatMsg
) {
259 ulong steamID
= callback
.Sender
;
260 if (steamID
!= SteamMasterID
) {
264 string message
= callback
.Message
;
265 if (string.IsNullOrEmpty(message
)) {
269 if (message
.Length
== 17 && message
[5] == '-' && message
[11] == '-') {
270 ArchiHandler
.RedeemKey(message
);
274 private void OnPersonaState(SteamFriends
.PersonaStateCallback callback
) {
275 if (callback
== null) {
279 SteamID steamID
= callback
.FriendID
;
280 SteamID sourceSteamID
= callback
.SourceSteamID
;
281 string steamNickname
= callback
.Name
;
282 EPersonaState personaState
= callback
.State
;
283 EClanRank clanRank
= (EClanRank
) callback
.ClanRank
;
286 private void OnAccountInfo(SteamUser
.AccountInfoCallback callback
) {
287 if (callback
== null) {
291 SteamFriends
.SetPersonaState(EPersonaState
.Online
);
294 private void OnLoggedOff(SteamUser
.LoggedOffCallback callback
) {
295 if (callback
== null) {
299 Logging
.LogGenericInfo(BotName
, "Logged off of Steam: " + callback
.Result
);
302 private async void OnLoggedOn(SteamUser
.LoggedOnCallback callback
) {
303 if (callback
== null) {
307 if (callback
.ClientSteamID
!= 0) {
308 BotID
= callback
.ClientSteamID
;
311 EResult result
= callback
.Result
;
313 case EResult
.AccountLogonDenied
:
314 AuthCode
= Program
.GetUserInput(SteamLogin
, Program
.EUserInputType
.SteamGuard
);
316 case EResult
.AccountLoginDeniedNeedTwoFactor
:
317 TwoFactorAuth
= Program
.GetUserInput(SteamLogin
, Program
.EUserInputType
.TwoFactorAuthentication
);
320 Logging
.LogGenericInfo(BotName
, "Successfully logged on!");
322 string steamNickname
= SteamNickname
;
323 if (!string.IsNullOrEmpty(steamNickname
) && !steamNickname
.Equals("null")) {
324 SteamFriends
.SetPersonaName(steamNickname
);
327 string steamParentalPIN
= SteamParentalPIN
;
328 if (string.IsNullOrEmpty(steamParentalPIN
) || steamParentalPIN
.Equals("null")) {
329 steamParentalPIN
= Program
.GetUserInput(BotName
, Program
.EUserInputType
.SteamParentalPIN
);
330 Config
["SteamParentalPIN"] = steamParentalPIN
;
333 await ArchiWebHandler
.Init(SteamClient
, callback
.WebAPIUserNonce
, callback
.VanityURL
, steamParentalPIN
).ConfigureAwait(false);
335 ulong clanID
= SteamMasterClanID
;
337 SteamFriends
.JoinChat(clanID
);
341 SteamFriends
.JoinChat(Program
.ArchiSCFarmGroup
);
342 await ArchiWebHandler
.JoinClan(Program
.ArchiSCFarmGroup
).ConfigureAwait(false);
345 await CardsFarmer
.StartFarming().ConfigureAwait(false);
347 case EResult
.Timeout
:
348 case EResult
.TryAnotherCM
:
349 Logging
.LogGenericWarning(BotName
, "Unable to login to Steam: " + callback
.Result
+ " / " + callback
.ExtendedResult
+ ", retrying...");
355 Logging
.LogGenericWarning(BotName
, "Unable to login to Steam: " + callback
.Result
+ " / " + callback
.ExtendedResult
);
361 private void OnMachineAuth(SteamUser
.UpdateMachineAuthCallback callback
) {
362 if (callback
== null) {
366 Logging
.LogGenericInfo(BotName
, "Updating sentryfile...");
371 using (FileStream fileStream
= File
.Open(SentryFile
, FileMode
.OpenOrCreate
, FileAccess
.ReadWrite
)) {
372 fileStream
.Seek(callback
.Offset
, SeekOrigin
.Begin
);
373 fileStream
.Write(callback
.Data
, 0, callback
.BytesToWrite
);
374 fileSize
= (int) fileStream
.Length
;
376 fileStream
.Seek(0, SeekOrigin
.Begin
);
377 using (SHA1CryptoServiceProvider sha
= new SHA1CryptoServiceProvider()) {
378 sentryHash
= sha
.ComputeHash(fileStream
);
383 // Inform the steam servers that we're accepting this sentry file
384 SteamUser
.SendMachineAuthResponse(new SteamUser
.MachineAuthDetails
{
385 JobID
= callback
.JobID
,
386 FileName
= callback
.FileName
,
387 BytesWritten
= callback
.BytesToWrite
,
389 Offset
= callback
.Offset
,
392 OneTimePassword
= callback
.OneTimePassword
,
393 SentryFileHash
= sentryHash
,
396 Logging
.LogGenericInfo(BotName
, "Sentryfile updated successfully!");
399 private void OnNotification(ArchiHandler
.NotificationCallback callback
) {
400 if (callback
== null) {
404 switch (callback
.NotificationType
) {
405 case ArchiHandler
.NotificationCallback
.ENotificationType
.Trading
:
406 Trading
.CheckTrades();
411 private async void OnPurchaseResponse(ArchiHandler
.PurchaseResponseCallback callback
) {
412 if (callback
== null) {
416 var purchaseResult
= callback
.PurchaseResult
;
417 SteamFriends
.SendChatMessage(SteamMasterID
, EChatEntryType
.ChatMsg
, "Status: " + purchaseResult
);
419 if (purchaseResult
== ArchiHandler
.PurchaseResponseCallback
.EPurchaseResult
.OK
) {
420 await CardsFarmer
.StartFarming().ConfigureAwait(false);