2017-11-18 17:27:06 +01:00
|
|
|
|
// _ _ _ ____ _ _____
|
|
|
|
|
|
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
|
|
|
|
|
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
|
|
|
|
|
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
|
|
|
|
|
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
|
|
|
|
|
//
|
2018-07-27 04:52:14 +02:00
|
|
|
|
// Copyright 2015-2018 Łukasz "JustArchi" Domeradzki
|
|
|
|
|
|
// Contact: JustArchi@JustArchi.net
|
2017-11-18 17:27:06 +01:00
|
|
|
|
//
|
2018-07-27 04:52:14 +02:00
|
|
|
|
// 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
|
2017-11-18 17:27:06 +01:00
|
|
|
|
//
|
2018-07-27 04:52:14 +02:00
|
|
|
|
// 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;
|
2018-09-08 01:03:55 +02:00
|
|
|
|
using ArchiSteamFarm.NLog;
|
2016-11-24 07:32:16 +01:00
|
|
|
|
using SteamKit2;
|
|
|
|
|
|
using SteamKit2.Internal;
|
2018-07-24 23:43:25 +02:00
|
|
|
|
using SteamKit2.Unified.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;
|
2018-07-24 23:43:25 +02:00
|
|
|
|
private readonly SteamUnifiedMessages.UnifiedService<IChatRoom> UnifiedChatRoomService;
|
2018-07-25 17:11:19 +02:00
|
|
|
|
private readonly SteamUnifiedMessages.UnifiedService<IClanChatRooms> UnifiedClanChatRoomsService;
|
2018-07-24 23:43:25 +02:00
|
|
|
|
private readonly SteamUnifiedMessages.UnifiedService<IFriendMessages> UnifiedFriendMessagesService;
|
|
|
|
|
|
private readonly SteamUnifiedMessages.UnifiedService<IPlayer> UnifiedPlayerService;
|
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
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal ArchiHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnifiedMessages) {
|
|
|
|
|
|
if ((archiLogger == null) || (steamUnifiedMessages == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(archiLogger) + " || " + nameof(steamUnifiedMessages));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ArchiLogger = archiLogger;
|
|
|
|
|
|
UnifiedChatRoomService = steamUnifiedMessages.CreateService<IChatRoom>();
|
2018-07-25 17:11:19 +02:00
|
|
|
|
UnifiedClanChatRoomsService = steamUnifiedMessages.CreateService<IClanChatRooms>();
|
2018-07-24 23:43:25 +02:00
|
|
|
|
UnifiedFriendMessagesService = steamUnifiedMessages.CreateService<IFriendMessages>();
|
|
|
|
|
|
UnifiedPlayerService = steamUnifiedMessages.CreateService<IPlayer>();
|
|
|
|
|
|
}
|
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.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;
|
2018-06-11 21:40:52 +02:00
|
|
|
|
case EMsg.ClientVanityURLChangedNotification:
|
|
|
|
|
|
HandleVanityURLChangedNotification(packetMsg);
|
|
|
|
|
|
break;
|
2016-09-30 01:15:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal void AckChatMessage(ulong chatGroupID, ulong chatID, uint timestamp) {
|
|
|
|
|
|
if ((chatGroupID == 0) || (chatID == 0) || (timestamp == 0)) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(timestamp));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CChatRoom_AckChatMessage_Notification request = new CChatRoom_AckChatMessage_Notification {
|
|
|
|
|
|
chat_group_id = chatGroupID,
|
|
|
|
|
|
chat_id = chatID,
|
|
|
|
|
|
timestamp = timestamp
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
UnifiedChatRoomService.SendMessage(x => x.AckChatMessage(request), true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal void AckMessage(ulong steamID, uint timestamp) {
|
|
|
|
|
|
if ((steamID == 0) || (timestamp == 0)) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(timestamp));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CFriendMessages_AckMessage_Notification request = new CFriendMessages_AckMessage_Notification {
|
|
|
|
|
|
steamid_partner = steamID,
|
|
|
|
|
|
timestamp = timestamp
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
UnifiedFriendMessagesService.SendMessage(x => x.AckMessage(request), true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal async Task<bool> AddFriend(ulong steamID) {
|
|
|
|
|
|
if (steamID == 0) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(steamID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPlayer_AddFriend_Request request = new CPlayer_AddFriend_Request { steamid = steamID };
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedPlayerService.SendMessage(x => x.AddFriend(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response.Result == EResult.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-25 17:11:19 +02:00
|
|
|
|
internal async Task<ulong> GetClanChatGroupID(ulong steamID) {
|
|
|
|
|
|
if ((steamID == 0) || !new SteamID(steamID).IsClanAccount) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(steamID));
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CClanChatRooms_GetClanChatRoomInfo_Request request = new CClanChatRooms_GetClanChatRoomInfo_Request {
|
|
|
|
|
|
autocreate = true,
|
|
|
|
|
|
steamid = steamID
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedClanChatRoomsService.SendMessage(x => x.GetClanChatRoomInfo(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response.Result != EResult.OK) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CClanChatRooms_GetClanChatRoomInfo_Response body = response.GetDeserializedResponse<CClanChatRooms_GetClanChatRoomInfo_Response>();
|
|
|
|
|
|
return body.chat_group_summary.chat_group_id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal async Task<HashSet<ulong>> GetMyChatGroupIDs() {
|
|
|
|
|
|
CChatRoom_GetMyChatRoomGroups_Request request = new CChatRoom_GetMyChatRoomGroups_Request();
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedChatRoomService.SendMessage(x => x.GetMyChatRoomGroups(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response.Result != EResult.OK) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CChatRoom_GetMyChatRoomGroups_Response body = response.GetDeserializedResponse<CChatRoom_GetMyChatRoomGroups_Response>();
|
|
|
|
|
|
return body.chat_room_groups.Select(chatRoom => chatRoom.group_summary.chat_group_id).ToHashSet();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<bool> JoinChatRoomGroup(ulong chatGroupID) {
|
|
|
|
|
|
if (chatGroupID == 0) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(chatGroupID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CChatRoom_JoinChatRoomGroup_Request request = new CChatRoom_JoinChatRoomGroup_Request { chat_group_id = chatGroupID };
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedChatRoomService.SendMessage(x => x.JoinChatRoomGroup(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response.Result == EResult.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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)) {
|
2018-09-08 00:46:40 +02:00
|
|
|
|
// If we have custom name to display, we must workaround the Steam network broken behaviour and send request on clean non-playing session
|
2017-08-23 17:04:12 +02:00
|
|
|
|
// This ensures that custom name will in fact display properly
|
|
|
|
|
|
Client.Send(request);
|
|
|
|
|
|
await Task.Delay(Bot.CallbackSleep).ConfigureAwait(false);
|
|
|
|
|
|
|
2018-06-16 07:47:07 +02:00
|
|
|
|
request.Body.games_played.Add(
|
|
|
|
|
|
new CMsgClientGamesPlayed.GamePlayed {
|
|
|
|
|
|
game_extra_info = gameName,
|
|
|
|
|
|
game_id = new GameID {
|
|
|
|
|
|
AppType = GameID.GameType.Shortcut,
|
|
|
|
|
|
ModID = uint.MaxValue
|
|
|
|
|
|
}
|
2016-12-18 16:03:32 +01:00
|
|
|
|
}
|
2018-06-16 07:47:07 +02:00
|
|
|
|
);
|
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 {
|
|
|
|
|
|
return await new AsyncJob<RedeemGuestPassResponseCallback>(Client, request.SourceJobID);
|
|
|
|
|
|
} 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 {
|
|
|
|
|
|
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
|
|
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal async Task<bool> RemoveFriend(ulong steamID) {
|
|
|
|
|
|
if (steamID == 0) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(steamID));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CPlayer_RemoveFriend_Request request = new CPlayer_RemoveFriend_Request { steamid = steamID };
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedPlayerService.SendMessage(x => x.RemoveFriend(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response.Result == EResult.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
internal async Task<bool> SendMessage(ulong steamID, string message) {
|
|
|
|
|
|
if ((steamID == 0) || string.IsNullOrEmpty(message)) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CFriendMessages_SendMessage_Request request = new CFriendMessages_SendMessage_Request {
|
|
|
|
|
|
chat_entry_type = (int) EChatEntryType.ChatMsg,
|
2018-07-25 01:01:15 +02:00
|
|
|
|
contains_bbcode = true,
|
2018-07-24 23:43:25 +02:00
|
|
|
|
message = message,
|
|
|
|
|
|
steamid = steamID
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedFriendMessagesService.SendMessage(x => x.SendMessage(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response.Result == EResult.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal async Task<bool> SendMessage(ulong chatGroupID, ulong chatID, string message) {
|
|
|
|
|
|
if ((chatGroupID == 0) || (chatID == 0) || string.IsNullOrEmpty(message)) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(message));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CChatRoom_SendChatMessage_Request request = new CChatRoom_SendChatMessage_Request {
|
|
|
|
|
|
chat_group_id = chatGroupID,
|
|
|
|
|
|
chat_id = chatID,
|
|
|
|
|
|
message = message
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
SteamUnifiedMessages.ServiceMethodResponse response;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
response = await UnifiedChatRoomService.SendMessage(x => x.SendChatMessage(request));
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
ArchiLogger.LogGenericWarningException(e);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (response == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(response));
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response.Result == EResult.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal void SetCurrentMode(uint chatMode) {
|
|
|
|
|
|
if (chatMode == 0) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(chatMode));
|
2015-12-20 19:01:05 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 23:43:25 +02:00
|
|
|
|
ClientMsgProtobuf<CMsgClientUIMode> request = new ClientMsgProtobuf<CMsgClientUIMode>(EMsg.ClientCurrentUIMode) { Body = { chat_mode = chatMode } };
|
|
|
|
|
|
Client.Send(request);
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-11 21:40:52 +02:00
|
|
|
|
private void HandleVanityURLChangedNotification(IPacketMsg packetMsg) {
|
|
|
|
|
|
if (packetMsg == null) {
|
|
|
|
|
|
ArchiLogger.LogNullError(nameof(packetMsg));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClientMsgProtobuf<CMsgClientVanityURLChangedNotification> response = new ClientMsgProtobuf<CMsgClientVanityURLChangedNotification>(packetMsg);
|
|
|
|
|
|
Client.PostCallback(new VanityURLChangedCallback(packetMsg.TargetJobID, response.Body));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-06-11 21:40:52 +02:00
|
|
|
|
|
|
|
|
|
|
internal sealed class VanityURLChangedCallback : CallbackMsg {
|
|
|
|
|
|
internal readonly string VanityURL;
|
|
|
|
|
|
|
|
|
|
|
|
internal VanityURLChangedCallback(JobID jobID, CMsgClientVanityURLChangedNotification msg) {
|
|
|
|
|
|
if ((jobID == null) || (msg == null)) {
|
|
|
|
|
|
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
JobID = jobID;
|
|
|
|
|
|
VanityURL = msg.vanity_url;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-10-25 06:16:50 +01:00
|
|
|
|
}
|
2018-07-11 16:31:46 +02:00
|
|
|
|
}
|