SteamRE / SteamKit

SteamKit2 is a .NET library designed to interoperate with Valve's Steam network. It aims to provide a simple, yet extensible, interface to perform various actions on the network.
GNU Lesser General Public License v2.1
2.63k stars 497 forks source link

[Question] Possible to get a list of online groupchat members #62

Closed TheAnthonyNL closed 10 years ago

TheAnthonyNL commented 10 years ago

Hello everyone,

Is it possible to get a list of all online chatroom members from a groupchat?

I've been struggling with this for a day now, i saw that steam for node.js ( https://github.com/seishun/node-steam ) has this functionality but i can't re-create it in the C# steambot... so maybe you guys can help me out?

Netshroud commented 10 years ago

IIRC when you send a join-chat message, you should receive persona state callbacks with the Source ID set to the chat room's SteamID. This will tell you what users are in the chat room.

I took a quick look at node-steam, but I couldn't find this functionality in any of the handler .js files.

6d656368 commented 10 years ago

It's all in ChatEnterCallback data with chat name online members count and few messageObjects (looks like those are JSON data ) with members info (rank, clan permissions and steamid)

https://gist.github.com/6d656368/4b2d7c9ba7b13d40c5a7 Excuse me my bad C# ( some people say i should never ever write code :) )

voided commented 10 years ago

@6d656368 It might make more sense if you knew the data you were working with.

MessageObjects are derivatives of Valve's KeyValue class. They're normally written and read as binary. Here's the code that Valve uses for reading these.

6d656368 commented 10 years ago

Maybe, but that's a pretty old code, wasn't really interested in details, just wanted to get it working asap.

TheAnthonyNL commented 10 years ago

Thanks for the answers guys, looking into it now.

TheAnthonyNL commented 10 years ago

Here i am again, i edited the steamkit2 repo from here but i get this error now:

"The type name 'JobCallback' does not exist in the type 'SteamKit2.SteamClient'"

so either i did something wrong or the repo is slightly outdated?

voided commented 10 years ago

JobCallback<T> was removed as part of a recent refactoring of the callback system. You can use Callback<T> with job based callbacks now.

TheAnthonyNL commented 10 years ago

Allright thanks, so i assume the following error is caused by new refactoring too

Cannot implicitly convert type 'SteamKit2.ICallbackMsg' to 'SteamKit2.CallbackMsg'. An explicit conversion exists (are you missing a cast?)

Netshroud commented 10 years ago

That would be caused by the same refactoring. Check the updated Samples solution on how to handle callbacks now if you need a reference, but you should be able to recompile with just a handful of small changes.

TheAnthonyNL commented 10 years ago

@Netshroud i know how to handle callbacks but and i also was able to recompile the dll but just my project failed.

// handle a special JobCallback differently than the others if (msg.IsType< SteamClient.JobCallback < SteamUser.UpdateMachineAuthCallback >>()) { msg.Handle< SteamClient.JobCallback < SteamUser.UpdateMachineAuthCallback >>( jobCallback => OnUpdateMachineAuthCallback(jobCallback.Callback, jobCallback.JobID) ); }

endregion

code which gives error. If i check what options there are for SteamClient. i see only 4 options which aren't related at all to what i need.

TheAnthonyNL commented 10 years ago

Got a workaround for it, makes my bot disconnect sometimes when loading but it works.

koelen3 commented 9 years ago

@TheAnthonyNL could you please post your workaround here? I have the exact same issue.

TheAnthonyNL commented 9 years ago

Will do later today ... but note it was build on older version of steamkit.

koelen3 commented 9 years ago

It's no problem, my fix was with IGenericCallback instead of Callback, this fixed a part of my code, but then I got another error with the JobCallback again

TheAnthonyNL commented 9 years ago

Snap ... lost the code to steamkit project but i have the dll if you want it?

anyway the way i used it in my steambot program:


TestUserHandler.cs
 public override void OnChatEnter(SteamKit2.SteamFriends.ChatEnterCallback callback)
        {
            List<string> chatters = new List<string>();
            foreach (SteamFriends.MsgObject chat in callback.Chatters)
            {
                if (!chat.Rank.Equals(EClanPermission.Nobody))
                {
                    if (chat.ChatterID != Bot.SteamClient.SteamID)
                    {
                        chatters.Add(chat.ChatterID.ConvertToUInt64().ToString());
                    }
                }
            }
            tools.FillChatMembersData(chatters);
            Console.Write("Chatroom {0} joined owned by {1}." + Environment.NewLine, callback.ChatID, callback.ClanID);

        }

Tools.cs
/// <summary>
        /// Method to fill a list of all current chat members of a group chat.
        /// </summary>
        /// <param name="database"></param>
        public void FillChatMembersData(List<string> database)
        {
            Globals.data_online_chatters.Clear();
            foreach (string member in database)
            {
                //  Console.Write(member + "\n\r");
                Globals.data_online_chatters.Add(member);
            }

        }

download to dll if this is allowed ... https://dl.dropboxusercontent.com/u/13120823/ShareX/2015/05/steamkit-dll-koelen3-from-anthony.rar

Thats how i got it working ...

TheAnthonyNL commented 9 years ago

@koelen3 any progress?

koelen3 commented 9 years ago

Yeah, I fixed the error ;) I'm now running into another error where I try to add all Dota2 or TF2 items in a single trade where I get a nullexceptionerror. I'll post about that when I'm on my pc in like half an hour probably

TheAnthonyNL commented 9 years ago

@koelen3 allright, glad i was able to help you. Mind posting the new steamkit code somehow?

koelen3 commented 9 years ago

Use

void HandleSteamMessage(ICallbackMsg msg)

So ICallbackMsg here

then we use

msg.Handle<SteamUser.UpdateMachineAuthCallback>(
            authCallback => OnUpdateMachineAuthCallback(authCallback)
);

and finally

private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        //Was CallbackMsg msg;
        //Fix is ICallbackMsg
        ICallbackMsg msg;

        while (!backgroundWorker.CancellationPending)
        {
            try
            {
                //msg = SteamClient.WaitForCallback(true);
                msg = SteamClient.WaitForCallback(true);
                HandleSteamMessage(msg);
            }
            catch (WebException e)
            {
                log.Error("URI: " + (e.Response != null && e.Response.ResponseUri != null ? e.Response.ResponseUri.ToString() : "unknown") + " >> " + e.ToString());
                System.Threading.Thread.Sleep(5000);//Steam is down, retry in 5 seconds.
            }
            catch (Exception e)
            {
                log.Error(e.ToString());
                log.Warn("Restarting bot...");
            }
        }
    }

This should work on the newest steamkit version

TheAnthonyNL commented 9 years ago

I more meant the part of the only groupchat members of a chatroom inside the steamkit as i edited too much which got changed in newest version (though you fixed it, so thats why i asked it :D)

koelen3 commented 9 years ago

Oh, I ain't even using groupchat for my bot :D It's just a storage bot with limited admins, sorry I can't help you with it I guess.

TheAnthonyNL commented 9 years ago

@koelen3 ah my bad then :) i supposed you were looking for that but thanks on the other fix :).

TheAnthonyNL commented 9 years ago

@VoiDeD any chance you could look into the dll and tell me what is difference between the current version cause i got lost somehow ...

voided commented 9 years ago

@TheAnthonyNL I'm not sure what you're asking.

TheAnthonyNL commented 9 years ago

@VoiDeD

http://puu.sh/iaBDv.txt is my old steamkit.steamfriends class and when i compare it to the current version ( https://github.com/SteamRE/SteamKit/blob/master/SteamKit2/SteamKit2/Steam/Handlers/SteamFriends/SteamFriends.cs ) it's changed a lot.

Netshroud commented 9 years ago

It hasn't changed significantly beyond the callback restructure. https://github.com/SteamRE/SteamKit/commits/master/SteamKit2/SteamKit2/Steam/Handlers/SteamFriends/SteamFriends.cs (451f66c and 6b86fdb)

TheAnthonyNL commented 9 years ago

@Netshroud thank you, i will look into this today and report back later on.

JustArchi commented 9 years ago

Is there any chance that those permissions can be implemented anytime soon? https://github.com/SteamRE/SteamKit/blob/master/SteamKit2/SteamKit2/Steam/Handlers/SteamFriends/Callbacks.cs#L613

@6d656368 posted solution AFAIK.

I've been trying to find ANY possible way to get channel permission of given user and couldn't find any possible way right now. It's quite easy to get list of chat users, but getting their permissions seems like impossible for me now.

Any help?

Cheers.

TheAnthonyNL commented 9 years ago

@JustArchi want to make that public of getting list of chat users you use atm (assuming you used the latest steamkit version)? I will happily help you out on the permissions problem.

JustArchi commented 9 years ago

This is not yet perfect, but it does the thing.

http://pastebin.com/cX8bmA3c

I think I included all relevant parts.

Basically it works like this: steamNicknameToID() creates new object of my custom callbackEvent class, it sets unique key which consists of literal + requestedNickname, adds it to the global List of ongoing callbacks, then fires joinChat() and waits for event to complete. I receive usual data in OnPersonaState(), and if one of the response matches my previously defined literal + requestedNickname, then I put all the data in that callback, and notifies waiter (steamNicknameToID()) that it's ready. Of course it's not perfect because we may have a situation when requested nickname is not on the channel, so I made auto-wakeup after 3 seconds in case nickname cannot be found.

It's not 100% reliable, but works pretty nicely until SteamKit implements some good solution.

Now my problem is that I can't receive clan ranks in OnPersonaState(), I already tried specifying custom ERequests in requestFriendInfo(), and nothing works - ClanRank is always 0.

TheAnthonyNL commented 9 years ago

This paste has been removed! :(. could you reupload it please. So i could look for an solution for you.

JustArchi commented 9 years ago

Uh, I don't have it anymore as I rewrote my whole logic into a Dictionary of SteamID and HashSet. Now I just put and take away user SteamIDs into proper HashSet linked to Chatroom on joined/left events.

This is much simpler and faster.

TheAnthonyNL commented 9 years ago

Mind sharing that solution ?

JustArchi commented 9 years ago

It's really easy.

        // ClanID to Members mapping
        private static readonly Dictionary<ulong, HashSet<ulong>> steamChatMembersDictionary = new Dictionary<ulong, HashSet<ulong>>();
        private static readonly object steamChatMembersDictionaryLock = new object();

Two functions then:

        private static void steamMemberEnteredChat(ulong chatID, ulong steamID) {
            HashSet<ulong> chatters;
            lock (steamChatMembersDictionaryLock) {
                if (!steamChatMembersDictionary.TryGetValue(chatID, out chatters)) {
                    chatters = new HashSet<ulong>();
                    steamChatMembersDictionary.Add(chatID, chatters);
                }
            }
            chatters.Add(steamID);
        }

        private static void steamMemberLeftChat(ulong chatID, ulong steamID) {
            HashSet<ulong> chatters;
            if (steamChatMembersDictionary.TryGetValue(chatID, out chatters)) {
                chatters.Remove(steamID);
            }
        }

And callbacks:

        private static void OnMemberInfo(SteamFriends.ChatMemberInfoCallback callback) {
            if (callback != null) {
                SteamID chatID = callback.ChatRoomID;
                SteamFriends.ChatMemberInfoCallback.StateChangeDetails stateChangeInfo = callback.StateChangeInfo;
                if (chatID != null && stateChangeInfo != null) {
                    SteamID chatterID = stateChangeInfo.ChatterActedOn;
                    if (chatterID != null) {
                        switch (stateChangeInfo.StateChange) {
                            case EChatMemberStateChange.Entered:
                                steamMemberEnteredChat(chatID, chatterID);
                                break;
                            case EChatMemberStateChange.Banned:
                            case EChatMemberStateChange.Disconnected:
                            case EChatMemberStateChange.Kicked:
                            case EChatMemberStateChange.Left:
                                steamMemberLeftChat(chatID, chatterID);
                                break;
                        }
                    }
                }
            }
        }

If you want to do it properly, you also need to handle initial onPersonaState when joining chat with members already being in it:

        private static void OnPersonaState(SteamFriends.PersonaStateCallback callback) {
            if (callback != null) {

                SteamID steamID = callback.FriendID;
                SteamID sourceSteamID = callback.SourceSteamID;

               if (sourceSteamID.IsChatAccount) {
                    steamMemberEnteredChat(sourceSteamID, steamID);
                }
            }
        }

Good luck.

Possible TODOs:

Of course it's only one possible solution, quite memory-intensive as you hold a HashSet of SteamIDs (ulongs), but it works and is good in terms of quality.

TheAnthonyNL commented 9 years ago

@justarchi managed to get it more completed as you wrote some todo.

JustArchi commented 9 years ago

Yeah. but my new solution is now more complex, but also much better in terms of quality and error-prone.

I created it like this.

I have an abstract class Chat.cs which is a core for handling all of that: http://pastebin.com/nmewRagt

Then you need to forward all expected actions marked as virtual void where you steam client receives them: http://pastebin.com/sLcsGkwV

Notice that two above examples contain my own code that is not needed to make it work, basically you only need forwarding.

JustArchi commented 9 years ago

So basically it works like this:

Main entrypoint (steam client) receives callbacks from all chats and all actions.

OnChatEnter() initiates the action, and creates new Chat if the chat object with received ID doesn't exist yet (it's GroupChat in my case, it inherits it's core from Chat)

All other important chat-related callbacks are then forwarded to proper chat object, which can be then handled in proper way, such as adding/removing guy from our active users on chat collection.

Finally, it's nice to note that in case of logged off situation, all users on all chats are cleared (in OnLoggedOn()), and bot rejoins all of them automatically, so we receive new list of users shortly after.

I think this solution is excellent, can be a bit complex to understand at first, but you have excellent structure of Chat object with Users in it, and it's very nicely handled through callback forwarding mechanism.