Files
ArchiSteamFarm/ArchiSteamFarm/Statistics.cs

161 lines
6.3 KiB
C#
Raw Normal View History

/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
2017-01-02 20:05:21 +01:00
Copyright 2015-2017 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Collections.Generic;
2016-12-04 20:41:01 +01:00
using System.Linq;
2016-12-04 02:49:56 +01:00
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
2017-07-10 23:04:33 +02:00
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm {
2016-12-04 02:49:56 +01:00
internal sealed class Statistics : IDisposable {
2017-03-20 17:21:56 +01:00
private const byte MinAnnouncementCheckTTL = 6; // Minimum amount of hours we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL
2017-02-08 14:55:40 +01:00
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
2017-07-10 23:04:33 +02:00
private const byte MinItemsCount = 100; // Minimum amount of items to be eligible for public listing
2017-03-20 17:21:56 +01:00
private const byte MinPersonaStateTTL = 8; // Minimum amount of hours we must wait before requesting persona state update
2016-12-04 02:08:45 +01:00
2017-06-26 03:36:51 +02:00
private const string URL = "https://" + SharedInfo.StatisticsServer;
2016-12-04 02:08:45 +01:00
private readonly Bot Bot;
2016-12-04 02:49:56 +01:00
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
private DateTime LastAnnouncementCheck = DateTime.MinValue;
2016-12-04 02:08:45 +01:00
private DateTime LastHeartBeat = DateTime.MinValue;
private DateTime LastPersonaStateRequest = DateTime.MinValue;
private bool ShouldSendHeartBeats;
2016-12-04 02:08:45 +01:00
internal Statistics(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
2016-12-04 02:08:45 +01:00
public void Dispose() => Semaphore.Dispose();
2016-12-04 02:49:56 +01:00
2016-12-04 02:08:45 +01:00
internal async Task OnHeartBeat() {
// Request persona update if needed
if ((DateTime.UtcNow > LastPersonaStateRequest.AddHours(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))) {
LastPersonaStateRequest = DateTime.UtcNow;
Bot.RequestPersonaStateUpdate();
}
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
return;
}
2016-12-04 02:49:56 +01:00
await Semaphore.WaitAsync().ConfigureAwait(false);
try {
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
2016-12-04 02:49:56 +01:00
return;
}
2017-06-26 03:36:51 +02:00
const string request = URL + "/api/HeartBeat";
Dictionary<string, string> data = new Dictionary<string, string>(2) {
{ "SteamID", Bot.SteamID.ToString() },
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") }
2016-12-04 02:49:56 +01:00
};
2016-12-04 02:49:56 +01:00
// We don't need retry logic here
if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false)) {
LastHeartBeat = DateTime.UtcNow;
2016-12-04 02:49:56 +01:00
}
} finally {
Semaphore.Release();
}
2016-12-04 02:08:45 +01:00
}
internal async Task OnLoggedOn() => await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false);
2016-12-04 02:08:45 +01:00
internal async Task OnPersonaState(SteamFriends.PersonaStateCallback callback) {
if (callback == null) {
ASF.ArchiLogger.LogNullError(nameof(callback));
return;
}
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
return;
}
// Don't announce if we don't meet conditions
2017-06-18 01:27:45 +02:00
string tradeToken;
2017-07-10 23:04:33 +02:00
if (!Bot.HasMobileAuthenticator || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) || (Bot.BotConfig.MatchableTypes.Count == 0) || !await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) || !await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false) || string.IsNullOrEmpty(tradeToken = await Bot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false))) {
LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false;
return;
}
2016-12-04 20:41:01 +01:00
string nickname = callback.Name ?? "";
string avatarHash = "";
2016-12-04 20:41:01 +01:00
if ((callback.AvatarHash != null) && (callback.AvatarHash.Length > 0) && callback.AvatarHash.Any(singleByte => singleByte != 0)) {
avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
if (avatarHash.All(singleChar => singleChar == '0')) {
2016-12-04 20:41:01 +01:00
avatarHash = "";
}
2016-12-04 06:01:18 +01:00
}
2016-12-04 02:49:56 +01:00
await Semaphore.WaitAsync().ConfigureAwait(false);
try {
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
return;
}
2017-07-10 23:04:33 +02:00
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, Bot.BotConfig.MatchableTypes).ConfigureAwait(false);
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
if (inventory == null) {
ShouldSendHeartBeats = false;
2016-12-04 02:49:56 +01:00
return;
}
// This is actual inventory
2017-07-10 23:04:33 +02:00
if (inventory.Count < MinItemsCount) {
LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false;
return;
}
2017-06-26 03:36:51 +02:00
const string request = URL + "/api/Announce";
2017-07-10 23:04:33 +02:00
Dictionary<string, string> data = new Dictionary<string, string>(8) {
2016-12-04 02:49:56 +01:00
{ "SteamID", Bot.SteamID.ToString() },
2016-12-07 14:05:19 +01:00
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") },
2016-12-04 20:41:01 +01:00
{ "Nickname", nickname },
{ "AvatarHash", avatarHash },
2017-07-10 23:04:33 +02:00
{ "MatchableTypes", JsonConvert.SerializeObject(Bot.BotConfig.MatchableTypes) },
{ "MatchEverything", Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) ? "1" : "0" },
2017-06-18 01:27:45 +02:00
{ "TradeToken", tradeToken },
2017-07-10 23:04:33 +02:00
{ "ItemsCount", inventory.Count.ToString() }
2016-12-04 02:49:56 +01:00
};
2016-12-04 15:30:00 +01:00
// We don't need retry logic here
2016-12-04 20:41:01 +01:00
if (await Program.WebBrowser.UrlPost(request, data).ConfigureAwait(false)) {
LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = true;
2016-12-04 20:41:01 +01:00
}
2016-12-04 02:49:56 +01:00
} finally {
Semaphore.Release();
}
}
}
}