Menu

Perl API to find buddies

Hari Dara
2007-10-15
2013-01-14
  • Hari Dara

    Hari Dara - 2007-10-15

    I am trying to get a privacy mode that is more like "Allow only the users on my buddy list, and autoreply others with a message". What it should do is to block messages from users that are not in my buddy list and send them an auto response with a configurable message.

    When receiving-im-msg handler is called, the first argument is a Purple::Account object which I presume identifies the sender. I was hoping, a call to Purple::Accounts::find() with the information extracted from the first argument will get me to it, something like this:

    Purple::Accounts::find($acct->get_username(), $acct->get_protocol_id());

    but what I noticed is that it will find an account even if there is no such buddy. What account is it finding?

    Next I found the Purple::Find::buddy API, but I am confused on how to use it, as it requires a buddy name to be passed in. All that I want here is to know if there a buddy for that account, don't care what the name is.

    Next I found the Purple::Find:buddies with an undef argument (which I found in one of the samples). However, I don't get any buddies returned, though there is one. I am using it as:

    $buddy_array = Purple::Find::buddies($acct, undef);

    where $acct is the first argument passed to receiving-im-msg handler.

    Any help on what API I should use here? I haven't started looking for API to autosend a reply, but any references will be great.

    Also, I found the API dump at http://developer.pidgin.im/wiki/Perl_API useful, but it is more helpful if some information on parameters is also dumped along.

     
    • Etan Reisner

      Etan Reisner - 2007-10-15

      Purple::Accounts are local objects, they represent the accounts you have set up in pidgin, so the first argument is the account on which you got the message.

      Purple::Accounts::find($acct->get_username(), $acct->get_protocol_id()); is a useless thing to do as it will find you only and exactly $acct, since you told it to find an account with exactly that name on exactly that protocol.

      The perl api is exactly the C api (with a number of notable exceptions but for the most part you can ignore that until you run into it). So for documentation on what the arguments to random functions are you should look at the normal C api docs.

      Using NULL as the buddy name in a Purple::Find::buddies() call should get you every buddy on that accounts buddy list that pidgin knows about.

      It seems to me that you very much want to use Purple::Find::buddy (singular) with the name of the person who sent you the message so you can see if they are on the buddy list, and don't care how many occurances of that buddy or buddies with that 'name' are on the buddy list for that account.

      I think you have a slight misunderstanding of how this stuff all works together in pidgin, so if the above doesn't clear things up for you please explain how you think this stuff all works together and I'll try to untangle you.

       
      • Hari Dara

        Hari Dara - 2007-10-16

        Thanks for the reference to the C API. I looked up the doc for purple_find_buddy() here:

        http://developer.pidgin.im/doxygen/2.0.0/html/blist_8h.html#e6237e2f2fa866deba06532796f007cb

        So the second argument is the screenname, I was under the impression that it is the alias/buddy name. I will give this a try and get back to you if I need more help. Any references to how to send automatic replies will be great.

        I also looked up the doc for purple_accounts_find() at:

        http://developer.pidgin.im/doxygen/2.0.0/html/account_8h.html#0805dc5d7da79f465d3af83f0427c9d9

        To clarify, it is searching the network for the requested account, not looking at the local (known) accounts, right?

         
        • Etan Reisner

          Etan Reisner - 2007-10-16

          The second argument is the 'screenname of the buddy' and the account on which to search for the buddy.

          purple_accounts_find is looking at the local accounts and *not* for buddies on the account.

          I think this is where your problem is coming from. An "account" is an account you set up in pidgin which lets you connect to a protocol. An account has many "buddies" added to it (either from pidgin or which were added before with some other client). When you get a message you are given a pointer to which one of *your* accounts it came in on and the name of the person it came from, if you want to see whether that person is on the buddy list for that account you need to search for a buddy with that name on that account.

           
    • Hari Dara

      Hari Dara - 2007-10-16

      Ok, I do need more help here. I at first thought the first arg $acct is sender's account, but with a bit of info, realized that it is my a/c on which I received the message. So this means, I will have to first find the PidginAccount object for the sender before I can lookup the buddy. I went back to the Pidgin::Accounts:find() to do this, using the second argument ($sender) but can't find any. When I print out $sender, it looks more like "xxx@gmail.com/gmail.8072721A". Am I supposed to extract the part before "/" for the below lookup to work?

      $protoId = $acct->get_protocol_id();
      $senderAcct = Purple::Accounts::find($sender, protoId);

      In fact, even after removing that part, it still can't find it. If I compare it with the one that worked (which was for my own a/c), it had /Home at the end, so I even tried adding that, so something like "xxx@gmail.com/Home" with the protocol ID extracted from the first argument, but still no luck. I would appreciate more help on this.

       
      • Etan Reisner

        Etan Reisner - 2007-10-16

        There is *no* PurpleAccount object for people who are sending you messages. An account is an inherently local object, it represents one of the accounts you have added to and configured for use *in pidgin*. A PurpleBuddy represents people who you interact with on a specific protocol. You don't want to lookup a buddy on an account representing the person who sent you the message, you want to look up the *buddy* representing the person who sent you the message, on the *account* they sent the message to. Are you starting to see how this stuff works?

         
        • Hari Dara

          Hari Dara - 2007-10-16

          Yes, I am now beginning to get what they are and their relationships. Given your explanations, I tried this:

              $buddy = Purple::Find::buddy($acct, $sender);

          and got it working. I will now lookup for information on how to send messages. Thanks a lot for your help.

           
    • Hari Dara

      Hari Dara - 2007-10-16

      Hello Etan, could you tell me if I should start a new conversation to be able to start an automatic reply? At the time the receiving-im-msg handler is executed, the message is still not accepted, so I presume it hasn't started a conversion yet. May be I should clarify what the terms conversion, IM and chat refer to as in the APIs Purple::Conversation::new, Purple::Conversation::get_im_data and Purple::Conversation::get_chat_data respectively. Again, my aim is to simply reply a message indicating why the message is being blocked, not really start a chat session. So far one way to do this is to call these in the order:

      Purple::Conversation::new
      Purple::Conversation::get_im_data
      Purple::Conversation::IM::send
      Purple::Conversation::destroy

      But I am thinking the ::new would actually create a new tab and the ::destroy would remove it, which is unnecessary and there should be a way to do this without the GUI elements being involved. Any references are appreciated.

       
      • Etan Reisner

        Etan Reisner - 2007-10-16

        Yes, when receiving the first message from someone the receiving-im-msg signal fires before the conversation is created. You can create the conversation yourself like you thought if that is what you want to do. I'm not sure how best you could create the conversation in a hidden fashion though. You could store the incoming message in your script, use Serv::send_im to manually send the buddy a message and then replay the stored message when they do whatever you want them to do to get unblocked. The only danger with that is that if pidgin crashes you will lose any stored messages (which wouldn't have happened if they had gone through to pidgin and you had logging on).

        Also note that a number of protocols have privacy option that let you simply ignore messages from people who aren't on your buddy list (which won't inform then that you blocked them or let them talk to you to get unblocked but might be of use to you).

        Is your goal here to implement a sort of CAPTCHA plugin? So that people who aren't on your list need to indicate that they are real people before they can talk to you? Or do you have some other goal in mind?

         
        • Hari Dara

          Hari Dara - 2007-10-16

          What you stated is exactly my goal. I know about the existing privacy option, but that is not good enough for me. We use IM a lot to communicate in our office and not all the folks are added as buddies, so I don't want to turn on the option to allow only the users on my buddy list, as this will make messages from colleagues that have never chatted with me to be completely ignored. This flexibility opens up my a/c for all spam messages so I am devising this solution as an intermediate privacy option in which the sender knows that they have to first add me as a buddy before they can send me IMs. The spam messages should see the same responses, but I guess they are generated by bots so there would be nobody on the other side to care.

          The idea is not preserve the IM until they get unblocked, but rather to just let them know that the IM has been just ignored (blocked is probably a wrong word, as it could give the sender a wrong impression that the IM is still in the queue to be read) and why. The sender would then know that they have to first add me as a buddy and resend the message (which should usually be a copy paste from his history).

          I looked at the Purple::Serv::send_im() which takes a PurpleConnection, and I see a     Purple::Account::get_connection given an account. Will this be the right sequence then?

          $conn = Purple::Account::get_connection($acct)
          Purple::Serv::send_im($conn, $reply, $flags)

          Not sure what the flags should be, PURPLE_MESSAGE_AUTO_RESP, PURPLE_MESSAGE_ERROR, PURPLE_MESSAGE_NOTIFY seem like good candidates, or I could probably not pass any flags.

          Is there an API to close the connection? I couldn't find one.

           
          • Hari Dara

            Hari Dara - 2007-10-17

            Just wanted to update that I did find Purple::Connection::destroy to close connections. I will give this a try along with the previous API that you suggested.

             
            • Etan Reisner

              Etan Reisner - 2007-10-17

              Purple::Connection::destroy is really a private function and you shouldn't be calling it. If you want to sign an account off you should call Purple::Account::disconnect() instead.

               
          • Etan Reisner

            Etan Reisner - 2007-10-17

            First off "the sender knows that they have to first add me as a
            buddy before they can send me IMs" is backwards, they don't need to add you, you need to add *them*.

            Having the other person add you is unrelated to whether they exist on your buddy list. So sending a message to them doesn't help anything if you don't get notified about the fact that they sent it to you.

            Making people do anything extra to send you a message is annoying, making them have to *resend* the first message is over-the-top as far as I'm concerned. I, personally, just wouldn't bother talking to you.

            Yes, that is the right sequence though you can shorten the first line to "$conv = $account->get_connection()" if you want to.

            And you don't want to close the connection after that, the connection is what keeps your account connected to the server. Closing that will sign your account off.

             
            • Hari Dara

              Hari Dara - 2007-10-17

              What I am referring to "adding as buddy" is that when the sender adds me as a buddy, it kind of starts a workflow prompting me to accept or deny, at which point I can add the sender as a buddy. At least, this is what I have always noticed for YM, which is what I am most interested in. I don't know what would be a more generic solution to this. One possibility is to include my email address such the sender can email me to request a manual add on my side, but this will be even more annoying.

              As per my original process, yes, it could be annoying to some people, but not worse than some email spam filters require email senders to do. They have to do this only the first time they ever contact me, as they are not my buddy list yet, but the workflow around adding a new buddy in YM protocol makes it simple enough for them to do this. To confirm how simple this is going to be, I just logged into an alternate yahoo id from the same pidgin instance and added my main yahoo id as a buddy. Immediately, pidgin prompted me to authorize the add, and when I accepted, each got added as a buddy to other. Even if the process is a bit annoying, I think it is going to fine, at least I am not in servicing any external customers :)

              Thanks for clarifying the connection usage, I will try this out soon.

              PS: There could be one usability issue here. If I am not online, the process will prevent them from sending offline messages (again for the first time), as I am not there to authorize. I need to think about this more.

               
              • Etan Reisner

                Etan Reisner - 2007-10-17

                Yes, that works for some protocols some of the time, but is not at all guaranteed to work.

                The best solution to this is to not bother with it at all, but assuming you aren't going to do that you should keep the original message they sent you, send them a challenge (like you see on websites for account creation) and if they respond correctly whitelist them for the future (or add them to the buddy list, either way) and forward the original message through.

                I hate the email spam filters also, and I essentially refuse to use them. This hasn't mattered as I haven't yet needed to talk to anyone that does that but I wouldn't bother going through with it to talk to them.

                Yes, dropping messages silently is basically never a good idea. You really want to keep the original around and forward it up if they do the right thing and periodically drop any un-acknowledged messages from your storage area (so as not to let spam messages fill it up).

                But really the best solution to this is to stop using a protocol as crappy as yahoo for work related stuff. If your company needs a work IM solution setting up an internal XMPP server is an incredibly better solution than using yahoo is. Creating a work-only yahoo account would help cut down spam also, especially if you only used it to talk to work people and didn't join chat rooms with it.

                 
                • Hari Dara

                  Hari Dara - 2007-10-17

                  We tried setting up an XMPP server, but due to the nature of our distributed development it didn't work out (e.g., you need to sign in to VPN to be able to connect). Also, there was significant resistance to install a new client as a lot of them don't use a multiprotocol client. I was surprised myself to see folks using YM and Skype to communicate, but I guess it just evolved.

                  Sending a challenge response seems like a better idea, but it will complicate the implementation of the plugin as I would then need to do quite a bit of state management, and need to spend more time as well. Also, I don't know if I have to resort to base perl API to keep messages etc. or if purple API has something built into the plugin support. I will definitely look into this at some point.

                  Using a separate yahoo id for work would have been good, but I didn't have the spam problem at that time and didn't have to think of privacy options. Most of them use their personal ids so I was set a bad example when I joined.

                   
        • Hari Dara

          Hari Dara - 2007-10-21

          I am giving the conversation API another look because of the automatic archiving I will get. The idea as I mentioned previously is to open a conversation window, send a response and close it. The response need not have any challenge, but just a warning that the message might not be noticed by the receiver because the sender is not a buddy. The advantage here is that once the sender is added as a buddy, the pending messages can be looked up by simply opening a conversation window with the sender (or the next message from the sender will automatically do this).

          When I tried the API, I got into an issue, wonder if I found another API that is not exported correctly. I tried to use Purple::Conversation::new with the arguments described in the C API. The C API is like this:

          PurpleConversation* purple_conversation_new      (      PurpleConversationType        type,
                  PurpleAccount *      account,
                  const char *      name    
              )            

          There are exactly 3 arguments here and when I try something like this in the perl plugin:

              $conv = Purple::Conversation::new(Purple::Conversation::Type::IM, $acct, $sender);

          the plugin doesn't load with the below error in the debug window:

          (12:36:30) perl: Perl function Purple::PerlLoader::load_n_eval exited abnormally: Errors loading file C:\Documents and Settings\hari\Application Data\.purple\plugins\blockmessages.pl: Not enough arguments for Purple::Conversation::new at (eval 4) line 148, near "$sender)"

          I searched in the .xs files for the equivalent and found this:

          Purple::Conversation
          purple_conversation_new(class, type, account, name)
              Purple::ConversationType type
              Purple::Account account
              const char *name
              C_ARGS:
              type, account, name

          Seems like it expects an aditional argument at the start, so I tried passing an undef at the start and used the resultant conversation like in the below:

              $convData = Purple::Conversation::get_im_data($conv);
              Purple::Conversation::IM::send($convData, "Auto Reply to: "+$message);
              Purple::Conversation::destroy($conv);

          The message is getting sent, but closing the conversation tab works only with alternative messages. When closing tab works, there is this error:

          purple_conv_im_write: assertion `im != NULL' failed

          and for the immediate next message closing the conversation doesn't work, and I see a different message (repeated a few times):

          pidgin_conv_get_window: assertion `gtkconv != NULL' failed

          Could you help me use the API correctly here? Thank you.

           
          • Etan Reisner

            Etan Reisner - 2007-10-22

            Counting on a subsequent conversation window to re-present a message to you relies on the second message being shortly after the first (and the new deferred closing of conversations that is now in pidgin) or the history plugin to restore the last log. Depending on those is fine you just need to actually be aware of what you are depending on and make notes of it in the documentation of your plugin if you ever intend on distributing it.

            purple_conversation_new is exported to perl correctly, perl class new() functions are generally run as Class->new(arguments), the -> syntax passes the object to the left of the -> as the first argument to the function so the correct way to call it is as Purple::Conversation->new(type, account, name).  Which will do the correct thing for you.

            Where are you calling these functions such that they cause your plugin to fail to probe/load? Are you calling them in the main body? The load function? What are you calling them with at that point?

            I can't be sure if the way you are calling purple_conversation_new is related to any of your error messages (but I would doubt it), but other than that I'm not sure what your problem there might be.

             
            • Hari Dara

              Hari Dara - 2007-10-22

              Your suggested usage is working fine as for the problem with the syntax. I am using this call in the receiving-im-msg handler which is not executed during the plugin load, but none the less causes a parsing failure. I felt it was strange that perl would validate the number of arguments at the time of parsing, but it was consistent with other mistakes I made wrt. pidgin API.

              I now have this code:

                      $conv = Purple::Conversation->new(Purple::Conversation::Type::IM, $acct, $sender);
                      $convData = Purple::Conversation::get_im_data($conv);
                      Purple::Conversation::IM::send($convData, "Auto Reply to: ".$message);
                      Purple::Conversation::destroy($conv);

              and it consistently produces the behavior that I described. The destory only works with every alternative calls to it. How do I debug this issue?

               
              • Etan Reisner

                Etan Reisner - 2007-10-22

                pidgin does not validate the call to every function at probe/load time, in fact that isn't even possible until runtime (and execution of the function time to be specific) as far as I am aware. I think something else is going on or you are misinterpreting how your code is written or some-such. As to your second issue I really don't know what to tell you other than to say that if you put your code up online that might be helpful in working this all out.

                 
                • Hari Dara

                  Hari Dara - 2007-10-22

                  I know Pidgin could not do the validation, and the message seems to be coming from perl directly. Thanks a lot for offering to take a closer look, I have the file uploaded to:

                  http://haridara.googlepages.com/blockmessages.pl

                  My changes are on top of the existing "Message Blocker 2" plugin that blocks AOL system messages, but you may look directly under receiving_im_msg_cb function for the code that I am referring to. To specifically reproduce the parsing error, you may replace the line:

                          $conv = Purple::Conversation->new(Purple::Conversation::Type::IM, $acct, $sender);

                  with:

                          Purple::Conversation::new(Purple::Conversation::Type::IM, $acct, $sender);

                  To reproduce the errors and faulty behavior with handling messages, try logging in from a couple of your MSN or yahoo ids and send a message from one to the other. You will see that the conversation tab opens and closes with every alternative message and in the debug window you would notice the error messages.

                   
                  • Ka-Hing Cheung

                    Ka-Hing Cheung - 2007-10-22

                    I haven't looked at the code or followed the discussion, but Purple::Conversation->new and Purple::Conversation::new have different behaviors that you cannot just replace one with another. The -> operator implicitly passes the invocant as the first parameter to the function, so if the function expects that and you use :: it will get one less parameter than it expects, and futhermore all the parameters' index will be off by one.

                     
                    • Etan Reisner

                      Etan Reisner - 2007-10-22

                      Thanks, I covered that bit in one of my many messages. But thanks for trying to help, feel free to look over the code before I get a chance to. =)