2

I'm making an ICQ (and XMPP in prospect) bot for a service, which should send and receive messages from users.

I'm using Python and Twisted. I started from the official documentation's example (this one: http://twistedmatrix.com/documents/current/words/examples/oscardemo.py)

So far I successfully receive and process messages from ICQ contacts. Now I need to send messages, but not in a respond to their message or some event. I need to send messages with Oscar (Twisted's thing for ICQ messaging) from outside the class with event handlers. How do I do that?

I'd appreciate any help since I fail to google anything useful, there's not so much about using Oscar out there.

solarissmoke
  • 30,039
  • 14
  • 71
  • 73
Roman Vinogradov
  • 145
  • 1
  • 12

2 Answers2

1

There is no "inside" or "outside". The containment metaphor in programming is a mostly damaging one. You'd be better off forgetting about it.

Consider this class:

class Foo(object):
    def bar(self):
        return "Foo.bar says hello"

    def baz(self):
        print self.bar()

Given an instance of Foo named aFoo, consider these two ways of using it:

aFoo.baz()
print aFoo.bar()

What is the difference in behavior from these two uses? There isn't one, yet one calls bar from "inside" and one calls bar from "outside". The containment metaphor is useless and irrelevant here, nothing changes based on "where" the function is called from.

Jean-Paul Calderone
  • 47,755
  • 6
  • 94
  • 122
  • I think I understand what you want to tell me. But still, I can try saying it different words. Twisted Words examples I found cover only _reactive_ behaviour. How do I implement some _active_ behaviour? When the script sends a message not as a respond to a request, but on its own. – Roman Vinogradov Sep 03 '12 at 11:25
  • 1
    Nothing ever happens "on its own". What message are you trying to send? When do you want to send it? – Jean-Paul Calderone Sep 03 '12 at 11:26
  • Well, the message comes from a web interface. The python script must send it to user's ICQ. I'm trying to figure out how I trigger the necessary behaviour in this scheme. I guess I have to use some kind of inter-application messaging, or I might check database for new messages in a loop. But whatever I choose, I don't see a way of integrating it with Twisted. So that's what my question is about. – Roman Vinogradov Sep 03 '12 at 11:42
  • "inter-application messaging" is just a confusing way to say "function calls". In your web interface's request handler, call a function to send an ICQ message. – Jean-Paul Calderone Sep 03 '12 at 12:16
  • Yeah, but how would "a function to send an ICQ message" look like? I've got the ICQ bot script up and running, it has to keep being online and respond to messages, but _in addition_ to it, it must send that message. The whole question is about _how_ can this be done. – Roman Vinogradov Sep 03 '12 at 13:16
  • You call `sendMessage` on a `BOSConnection` instance, as the example that you linked to demonstrates. – Glyph Sep 04 '12 at 18:32
1

When you connect

protocol.ClientCreator(reactor, OA, SN, PASS, icq=icqMode).connectTCP(*hostport)

add a Deferred, which will be called when the connection is done:

oscar_prot = None

def get_B_instance(b_instance):
    global oscar_prot
    oscar_prot = b_instance

d = Deferred()
d.addCallback(get_B_instance)

protocol.ClientCreator(reactor, OA, SN, PASS, deferred=d, icq=icqMode).connectTCP(*hostport)

In the get_B_instance function, you can keep the reference to the instance of the protocol class.

Then you can just call sendMessage on that instance.

oscar_prot.sendMessage(username, message)
sloth
  • 99,095
  • 21
  • 171
  • 219
  • Wow, this looks like exactly what I needed! And it's very close to what I've been trying to do the last hour after crawling the API docs. Thank you so much! – Roman Vinogradov Sep 03 '12 at 13:22
  • Doesn't work this way though... The first problem is: BotAuth instance has no attribute 'sendMessage'. _BotAuth_ is an _OscarAuthenticator_'s child, it's passed to _ClientCreator_, it contains _BOSClass_ which is a _BOSConnection_'s child class. How do I get the _BOSConnection_ instance? Another problem is that no code is executed after the _reactor.run()_ is called until I interrupt the script execution (I guess until the reactor stops). Is it possible to run the event handling engine and send these messages at the same time? – Roman Vinogradov Sep 03 '12 at 14:04
  • 1) I edited my answer (I digged a little bit into the oscar.py sourcecode...) to address this problem. You can get the instance of `B` this way. – sloth Sep 03 '12 at 14:59
  • 2) `reactor.run()` blocks. That's how it (twisted) works. It depends a little bit on what exactly you are trying to do, but if you e.g. want to poll some kind of service regulary, you would go and *schedule* it using `reactor.callLater(delay, nameOfFunction)`. – sloth Sep 03 '12 at 15:02
  • PS: I don't have an ICQ account at hand at the moment, so sorry I could not test the code that well :-) – sloth Sep 03 '12 at 15:03
  • Adding the deferred argument as you said lead to the script getting stuck at the deferred callback function call. I've got a test print inside the _get_B_instance_(), it prints out, but after that nothing happens. Without it it would go and set up everything related to ICQ connection, set the user online, print out some info, but with the deferred thing none of that happens. Maybe the callback must return something or I don't know what... Can't get it reading oscar.py – Roman Vinogradov Sep 03 '12 at 15:33
  • Thanks for reactor.callLater! I guess it's exactly what I have to use. Just need to figure out how to send a message there. %) – Roman Vinogradov Sep 03 '12 at 15:35
  • I ended up using _callLater_ and _LoopingCall_ in _gotBuddyList_ event handler. So now the issue is solved for me. Thanks for your help! – Roman Vinogradov Sep 03 '12 at 18:13
  • It would be better to use some mechanism *other* than a global variable to communicate your `OSCAR` protocol instance. This is just a regular python program: you have objects, they have references to each other, etc. A global variable is good for a quick hack, but it makes your code a lot harder to use as a library, harder to test, et cetera. – Glyph Sep 04 '12 at 23:26
  • @Glyph You're right of course, but I hope it is clear that this simple example is just a quick hack :-) – sloth Sep 05 '12 at 06:14