2017-11-18 17:27:06 +01:00
|
|
|
|
// _ _ _ ____ _ _____
|
|
|
|
|
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
|
|
|
|
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
|
|
|
|
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
|
|
|
|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
|
|
|
|
|
//
|
2018-01-01 02:56:53 +01:00
|
|
|
|
// Copyright 2015-2018 Łukasz "JustArchi" Domeradzki
|
2017-11-18 17:27:06 +01:00
|
|
|
|
// 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.
|
2015-10-28 19:21:27 +01:00
|
|
|
|
|
2016-03-06 21:49:34 +01:00
|
|
|
|
using System;
|
2015-11-01 16:46:02 +01:00
|
|
|
|
using System.Collections.Generic;
|
2017-12-01 00:49:19 +01:00
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2015-11-01 16:46:02 +01:00
|
|
|
|
using System.IO;
|
2016-05-13 06:32:42 +02:00
|
|
|
|
using System.Linq;
|
2016-02-22 18:34:45 +01:00
|
|
|
|
using System.Net;
|
2016-03-06 21:49:34 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2016-12-11 04:23:44 +01:00
|
|
|
|
using ArchiSteamFarm.CMsgs;
|
2017-12-01 00:49:19 +01:00
|
|
|
|
using ArchiSteamFarm.Localization;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
using SteamKit2;
|
|
|
|
|
|
using SteamKit2.Internal;
|
2015-10-25 06:16:50 +01:00
|
|
|
|
|
|
|
|
|
|
namespace ArchiSteamFarm {
|
|
|
|
|
|
internal sealed class ArchiHandler : ClientMsgHandler {
|
2016-12-26 22:07:49 +01:00
|
|
|
|
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
|
|
|
|
|
|
|
2016-11-06 12:06:02 +01:00
|
|
|
|
private readonly ArchiLogger ArchiLogger;
|
2016-04-08 04:30:51 +02:00
|
|
|
|
|
2017-12-17 11:31:42 +01:00
|
|
|
|
internal DateTime LastPacketReceived { get; private set; }
|
2017-01-23 00:34:48 +01:00
|
|
|
|
|
2017-04-05 17:01:18 +02:00
|
|
|
|
internal ArchiHandler(ArchiLogger archiLogger) => ArchiLogger = archiLogger ?? throw new ArgumentNullException(nameof(archiLogger));
|
2016-04-08 04:30:51 +02:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
public override void HandleMsg(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
|
|
|
|
|
return;
|
2016-09-25 01:23:31 +02:00
|
|
|
|
}
|
2016-09-30 02:41:27 +02:00
|
|
|
|
|
2017-01-23 00:46:44 +01:00
|
|
|
|
LastPacketReceived = DateTime.UtcNow;
|
2017-01-23 00:33:08 +01:00
|
|
|
|
|
2016-11-24 07:32:16 +01:00
|
|
|
|
switch (packetMsg.MsgType) {
|
|
|
|
|
|
case EMsg.ClientFSOfflineMessageNotification:
|
|
|
|
|
|
HandleFSOfflineMessageNotification(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientItemAnnouncements:
|
|
|
|
|
|
HandleItemAnnouncements(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientPlayingSessionState:
|
|
|
|
|
|
HandlePlayingSessionState(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientPurchaseResponse:
|
|
|
|
|
|
HandlePurchaseResponse(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientRedeemGuestPassResponse:
|
|
|
|
|
|
HandleRedeemGuestPassResponse(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientSharedLibraryLockStatus:
|
|
|
|
|
|
HandleSharedLibraryLockStatus(packetMsg);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EMsg.ClientUserNotifications:
|
|
|
|
|
|
HandleUserNotifications(packetMsg);
|
|
|
|
|
|
break;
|
2016-09-30 01:15:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-23 22:24:14 +02:00
|
|
|
|
internal void AcknowledgeClanInvite(ulong clanID, bool acceptInvite) {
|
2016-12-15 01:56:13 +01:00
|
|
|
|
if (clanID == 0) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(clanID));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Client.IsConnected) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-23 22:24:14 +02:00
|
|
|
|
ClientMsg<CMsgClientAcknowledgeClanInvite> request = new ClientMsg<CMsgClientAcknowledgeClanInvite> {
|
2017-07-03 12:14:23 +02:00
|
|
|
|
Body = {
|
|
|
|
|
|
ClanID = clanID,
|
2018-04-23 22:24:14 +02:00
|
|
|
|
AcceptInvite = acceptInvite
|
2017-07-03 12:14:23 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2016-12-15 01:56:13 +01:00
|
|
|
|
|
|
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-23 17:04:12 +02:00
|
|
|
|
internal async Task PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
if (gameIDs == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(gameIDs));
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Client.IsConnected) {
|
2016-01-10 20:55:00 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
|
2016-07-12 04:40:56 +02:00
|
|
|
|
|
2018-03-04 07:29:27 +01:00
|
|
|
|
byte maxGamesCount = MaxGamesPlayedConcurrently;
|
|
|
|
|
|
|
2016-07-12 04:40:56 +02:00
|
|
|
|
if (!string.IsNullOrEmpty(gameName)) {
|
2017-08-23 17:04:12 +02:00
|
|
|
|
// If we have custom name to display, we must workaround the Steam network fuckup and send request on clean non-playing session
|
|
|
|
|
|
// This ensures that custom name will in fact display properly
|
|
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
|
|
|
|
|
|
2016-12-18 16:03:32 +01:00
|
|
|
|
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
|
|
|
|
|
|
game_extra_info = gameName,
|
|
|
|
|
|
game_id = new GameID {
|
|
|
|
|
|
AppType = GameID.GameType.Shortcut,
|
|
|
|
|
|
ModID = uint.MaxValue
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2018-03-04 07:29:27 +01:00
|
|
|
|
|
2018-03-06 10:14:48 +01:00
|
|
|
|
// Max games count is affected by valid AppIDs only, therefore gameName alone doesn't need exclusive slot
|
2018-03-04 07:29:27 +01:00
|
|
|
|
maxGamesCount++;
|
2016-07-12 04:40:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
|
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
|
|
|
|
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) });
|
2018-01-21 23:19:30 +01:00
|
|
|
|
|
2018-03-04 07:29:27 +01:00
|
|
|
|
if (request.Body.games_played.Count >= maxGamesCount) {
|
2018-01-21 23:19:30 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2016-01-02 17:22:35 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-25 01:23:31 +02:00
|
|
|
|
internal async Task<RedeemGuestPassResponseCallback> RedeemGuestPass(ulong guestPassID) {
|
|
|
|
|
|
if (guestPassID == 0) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(guestPassID));
|
2016-09-25 01:23:31 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Client.IsConnected) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:46:37 +01:00
|
|
|
|
ClientMsgProtobuf<CMsgClientRedeemGuestPass> request = new ClientMsgProtobuf<CMsgClientRedeemGuestPass>(EMsg.ClientRedeemGuestPass) {
|
2017-07-03 12:14:23 +02:00
|
|
|
|
SourceJobID = Client.GetNextJobID(),
|
|
|
|
|
|
Body = { guest_pass_id = guestPassID }
|
2016-11-24 07:46:37 +01:00
|
|
|
|
};
|
2016-09-25 01:23:31 +02:00
|
|
|
|
|
|
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2018-04-13 09:17:27 +02:00
|
|
|
|
#pragma warning disable ConfigureAwaitChecker // CAC001
|
2016-09-25 01:23:31 +02:00
|
|
|
|
return await new AsyncJob<RedeemGuestPassResponseCallback>(Client, request.SourceJobID);
|
2018-04-13 09:17:27 +02:00
|
|
|
|
#pragma warning restore ConfigureAwaitChecker // CAC001
|
2016-09-25 01:23:31 +02:00
|
|
|
|
} catch (Exception e) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogGenericException(e);
|
2016-09-25 01:23:31 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-06 21:49:34 +01:00
|
|
|
|
internal async Task<PurchaseResponseCallback> RedeemKey(string key) {
|
2016-05-30 01:57:06 +02:00
|
|
|
|
if (string.IsNullOrEmpty(key)) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(key));
|
2016-05-30 01:57:06 +02:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Client.IsConnected) {
|
2016-01-02 17:22:35 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-24 07:46:37 +01:00
|
|
|
|
ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) {
|
2017-07-03 12:14:23 +02:00
|
|
|
|
SourceJobID = Client.GetNextJobID(),
|
|
|
|
|
|
Body = { key = key }
|
2016-11-24 07:46:37 +01:00
|
|
|
|
};
|
2016-02-24 06:20:35 +01:00
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
Client.Send(request);
|
2016-04-02 19:23:09 +02:00
|
|
|
|
|
2016-03-06 21:49:34 +01:00
|
|
|
|
try {
|
2018-04-13 09:17:27 +02:00
|
|
|
|
#pragma warning disable ConfigureAwaitChecker // CAC001
|
2016-03-06 21:49:34 +01:00
|
|
|
|
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
|
2018-04-13 09:17:27 +02:00
|
|
|
|
#pragma warning restore ConfigureAwaitChecker // CAC001
|
2016-03-06 21:49:34 +01:00
|
|
|
|
} catch (Exception e) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogGenericException(e);
|
2016-03-06 21:49:34 +01:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
Fix latest Steam notifications fuckup
Initially the issue was observed in #697, but that itself wasn't exactly what was fixed here, as multiple evaluation of the same trade is still wanted scenario.
The real issue was reported in http://steamcommunity.com/groups/ascfarm/discussions/1/2425614539578192287/
In a huge TL;DR, Steam is now sending trades notification each time something fetches current trade offers, be it ASF, the user, or some other script.
This will lead to possible ASF trade loop, as we'll get wanted notification about new trades, fetch them, leave some trades untouched, get new notification about trades and so on.
Initially I wanted to fix this in dirty way by just ignoring any extra notifications that happened since API call until 5 extra seconds after we were done with entire parsing, but I found much better solution - Steam actually includes extra info about amount of trades/items in notification (makes sense, since Steam client displays that info too). We can make use of that info and simply ignore any extra notification that results in same or smaller count.
Thanks to that we didn't only add a decent workaround for this recent Steam fuckup, but we also improved internal ASF code that will no longer schedule extra parsing if we accepted/rejected only some of the trades, making me happy with the actual solution.
2017-11-18 17:20:24 +01:00
|
|
|
|
internal void RequestItemAnnouncements() {
|
|
|
|
|
|
ClientMsgProtobuf<CMsgClientRequestItemAnnouncements> request = new ClientMsgProtobuf<CMsgClientRequestItemAnnouncements>(EMsg.ClientRequestItemAnnouncements);
|
|
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-20 19:01:05 +01:00
|
|
|
|
private void HandleFSOfflineMessageNotification(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2015-12-20 19:01:05 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientOfflineMessageNotification> response = new ClientMsgProtobuf<CMsgClientOfflineMessageNotification>(packetMsg);
|
2016-01-02 17:22:35 +01:00
|
|
|
|
Client.PostCallback(new OfflineMessageCallback(packetMsg.TargetJobID, response.Body));
|
2015-12-20 19:01:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-28 03:40:59 +01:00
|
|
|
|
private void HandleItemAnnouncements(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2016-02-28 03:40:59 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientItemAnnouncements> response = new ClientMsgProtobuf<CMsgClientItemAnnouncements>(packetMsg);
|
2017-12-01 01:36:58 +01:00
|
|
|
|
Client.PostCallback(new UserNotificationsCallback(packetMsg.TargetJobID, response.Body));
|
2016-02-28 03:40:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-30 00:09:42 +02:00
|
|
|
|
private void HandlePlayingSessionState(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2016-05-30 00:09:42 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClientMsgProtobuf<CMsgClientPlayingSessionState> response = new ClientMsgProtobuf<CMsgClientPlayingSessionState>(packetMsg);
|
|
|
|
|
|
Client.PostCallback(new PlayingSessionStateCallback(packetMsg.TargetJobID, response.Body));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
private void HandlePurchaseResponse(IPacketMsg packetMsg) {
|
2015-12-20 19:01:05 +01:00
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2015-12-20 19:01:05 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientPurchaseResponse> response = new ClientMsgProtobuf<CMsgClientPurchaseResponse>(packetMsg);
|
2016-01-02 17:22:35 +01:00
|
|
|
|
Client.PostCallback(new PurchaseResponseCallback(packetMsg.TargetJobID, response.Body));
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-25 01:23:31 +02:00
|
|
|
|
private void HandleRedeemGuestPassResponse(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2016-09-25 01:23:31 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClientMsgProtobuf<CMsgClientRedeemGuestPassResponse> response = new ClientMsgProtobuf<CMsgClientRedeemGuestPassResponse>(packetMsg);
|
|
|
|
|
|
Client.PostCallback(new RedeemGuestPassResponseCallback(packetMsg.TargetJobID, response.Body));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-30 01:15:26 +02:00
|
|
|
|
private void HandleSharedLibraryLockStatus(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2016-09-30 01:15:26 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClientMsgProtobuf<CMsgClientSharedLibraryLockStatus> response = new ClientMsgProtobuf<CMsgClientSharedLibraryLockStatus>(packetMsg);
|
|
|
|
|
|
Client.PostCallback(new SharedLibraryLockStatusCallback(packetMsg.TargetJobID, response.Body));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-10-25 06:16:50 +01:00
|
|
|
|
private void HandleUserNotifications(IPacketMsg packetMsg) {
|
2015-12-20 19:01:05 +01:00
|
|
|
|
if (packetMsg == null) {
|
2016-11-06 12:06:02 +01:00
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
2015-12-20 19:01:05 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-05-13 06:32:42 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientUserNotifications> response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
|
2017-12-01 01:36:58 +01:00
|
|
|
|
Client.PostCallback(new UserNotificationsCallback(packetMsg.TargetJobID, response.Body));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class OfflineMessageCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly uint OfflineMessagesCount;
|
2018-04-23 23:17:56 +02:00
|
|
|
|
internal readonly HashSet<ulong> SteamIDs;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
|
|
|
|
|
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
OfflineMessagesCount = msg.offline_messages;
|
2017-05-31 21:49:42 +02:00
|
|
|
|
|
|
|
|
|
|
if (msg.friends_with_offline_messages == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-09 00:45:15 +02:00
|
|
|
|
SteamIDs = msg.friends_with_offline_messages.Select(steam3ID => new SteamID(steam3ID, EUniverse.Public, EAccountType.Individual).ConvertToUInt64()).ToHashSet();
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class PlayingSessionStateCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly bool PlayingBlocked;
|
|
|
|
|
|
|
|
|
|
|
|
internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
PlayingBlocked = msg.playing_blocked;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class PurchaseResponseCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly Dictionary<uint, string> Items;
|
|
|
|
|
|
|
2017-02-11 22:24:49 +01:00
|
|
|
|
internal EPurchaseResultDetail PurchaseResultDetail { get; set; }
|
2017-04-05 15:03:27 +02:00
|
|
|
|
internal EResult Result { get; set; }
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
|
|
|
|
|
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
2017-02-11 22:24:49 +01:00
|
|
|
|
PurchaseResultDetail = (EPurchaseResultDetail) msg.purchase_result_details;
|
2017-04-05 15:03:27 +02:00
|
|
|
|
Result = (EResult) msg.eresult;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
|
|
|
|
|
|
if (msg.purchase_receipt_info == null) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(msg.purchase_receipt_info));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyValue receiptInfo = new KeyValue();
|
|
|
|
|
|
using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) {
|
|
|
|
|
|
if (!receiptInfo.TryReadAsBinary(ms)) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(ms));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
List<KeyValue> lineItems = receiptInfo["lineitems"].Children;
|
|
|
|
|
|
if (lineItems.Count == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Items = new Dictionary<uint, string>(lineItems.Count);
|
|
|
|
|
|
foreach (KeyValue lineItem in lineItems) {
|
|
|
|
|
|
uint packageID = lineItem["PackageID"].AsUnsignedInteger();
|
|
|
|
|
|
if (packageID == 0) {
|
2017-01-27 21:57:23 +01:00
|
|
|
|
// Coupons have PackageID of -1 (don't ask me why)
|
|
|
|
|
|
// We'll use ItemAppID in this case
|
2016-11-24 07:32:16 +01:00
|
|
|
|
packageID = lineItem["ItemAppID"].AsUnsignedInteger();
|
|
|
|
|
|
if (packageID == 0) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(packageID));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string gameName = lineItem["ItemDescription"].Value;
|
|
|
|
|
|
if (string.IsNullOrEmpty(gameName)) {
|
2017-01-31 01:10:01 +01:00
|
|
|
|
ASF.ArchiLogger.LogNullError(nameof(gameName));
|
2016-11-24 07:32:16 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-01-27 21:57:23 +01:00
|
|
|
|
// Apparently steam expects client to decode sent HTML
|
|
|
|
|
|
gameName = WebUtility.HtmlDecode(gameName);
|
|
|
|
|
|
Items[packageID] = gameName;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class RedeemGuestPassResponseCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly EResult Result;
|
|
|
|
|
|
|
|
|
|
|
|
internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
Result = (EResult) msg.eresult;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class SharedLibraryLockStatusCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly ulong LibraryLockedBySteamID;
|
|
|
|
|
|
|
|
|
|
|
|
internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
|
|
|
|
|
|
if (msg.own_library_locked_by == 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LibraryLockedBySteamID = new SteamID(msg.own_library_locked_by, EUniverse.Public, EAccountType.Individual);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-12-01 01:36:58 +01:00
|
|
|
|
|
|
|
|
|
|
internal sealed class UserNotificationsCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly Dictionary<EUserNotification, uint> Notifications;
|
|
|
|
|
|
|
|
|
|
|
|
internal UserNotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
|
|
|
|
|
|
// We might get null body here, and that means there are no notifications related to trading
|
|
|
|
|
|
Notifications = new Dictionary<EUserNotification, uint>(1) { { EUserNotification.Trading, 0 } };
|
|
|
|
|
|
|
|
|
|
|
|
if (msg.notifications == null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
|
|
|
|
|
|
EUserNotification type = (EUserNotification) notification.user_notification_type;
|
|
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case EUserNotification.AccountAlerts:
|
|
|
|
|
|
case EUserNotification.Chat:
|
|
|
|
|
|
case EUserNotification.Comments:
|
|
|
|
|
|
case EUserNotification.GameTurns:
|
|
|
|
|
|
case EUserNotification.Gifts:
|
|
|
|
|
|
case EUserNotification.HelpRequestReplies:
|
|
|
|
|
|
case EUserNotification.Invites:
|
|
|
|
|
|
case EUserNotification.Items:
|
|
|
|
|
|
case EUserNotification.ModeratorMessages:
|
|
|
|
|
|
case EUserNotification.Trading:
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
2017-12-15 10:37:51 +01:00
|
|
|
|
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(type), type));
|
2017-12-01 01:36:58 +01:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Notifications[type] = notification.count;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal UserNotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
Notifications = new Dictionary<EUserNotification, uint>(1) { { EUserNotification.Items, msg.count_new_items } };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
|
|
|
|
|
internal enum EUserNotification : byte {
|
|
|
|
|
|
Unknown,
|
|
|
|
|
|
Trading,
|
|
|
|
|
|
GameTurns,
|
|
|
|
|
|
ModeratorMessages,
|
|
|
|
|
|
Comments,
|
|
|
|
|
|
Items,
|
|
|
|
|
|
Invites,
|
|
|
|
|
|
Unknown7, // No clue what 7 stands for, and I doubt we can find out
|
|
|
|
|
|
Gifts,
|
|
|
|
|
|
Chat,
|
|
|
|
|
|
HelpRequestReplies,
|
|
|
|
|
|
AccountAlerts
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|