tgalal / yowsup

The WhatsApp lib
GNU General Public License v3.0
7.06k stars 2.23k forks source link

Multiple sessions #666

Open rdiasfreitas opened 9 years ago

rdiasfreitas commented 9 years ago

Hi there Thanks for the beautiful work!

I'm working on a prototype that will need to have multiple sessions (different numbers) connected at the same time to whatsapp servers.

What I'm trying to do is:

1) I have a class ThisIsMyLayer(YowInterfaceLayer), that connects to my backend services 2) Stack declaration is implemented in a class that extends Threading, and I pass ThisIsMyLayer in the stack 3) There's a main Thread, that starts N other threads, one to each number I wish to get connected

The problem is that it looks like Yowsup was not built to be used with multiple sessions, and when the second session is started, the solution stops working.

Could you please offer me some guidance in how to perform such a thing?

Thanks

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

emamirazavi commented 9 years ago

You should find asyncore out correctly and use it to get the good result! Just call asyncore.loop in new thread and don't use yowstack.loop directly and make new instance(yowstack) with new credentials again and again... no problem! several SIMs at the same time can be connected without any problem! But i think this asyncore is obsolete and i'm in doubt about using it by Tgalal!!!

rdiasfreitas commented 9 years ago

Hi @emamirazavi

That's amazing, it's really working the way you suggested! Thanks a lot!

I come from a Java world, and I'm very familiar with multi-thread applications... But I'm just trying to figure it out how the Python world works. And I can tell you, it's not so easy to understand and start coding low level functions.

I've got this exception every time I restarted the application, I think it's related to some kind of synchronization, but I'm not sure how to start digging about that. Could you please help me?

asyncore.loop()

File "/usr/lib/python2.7/asyncore.py", line 216, in loop poll_fun(timeout, map) File "/usr/lib/python2.7/asyncore.py", line 156, in poll read(obj) File "/usr/lib/python2.7/asyncore.py", line 87, in read obj.handle_error() File "/usr/lib/python2.7/asyncore.py", line 83, in read obj.handle_read_event() File "/usr/lib/python2.7/asyncore.py", line 449, in handle_read_event self.handle_read() File "/storage/projetos/my_project/venv/lib/python2.7/site-packages/yowsup/layers/network/layer.py", line 47, in handle_read data = self.recv(readSize) File "/usr/lib/python2.7/asyncore.py", line 387, in recv data = self.socket.recv(buffer_size) error: [Errno 11] Resource temporarily unavailable

emamirazavi commented 9 years ago

Probably this exception reads when you stop(terminate) your code. Does it?

On Tue, Mar 3, 2015 at 6:34 AM, Rodrigo Freitas notifications@github.com wrote:

Hi @emamirazavi https://github.com/emamirazavi

That's amazing, it's really working the way you suggested! Thanks a lot!

I come from a Java world, and I'm very familiar with multi-thread applications... But I'm just trying to figure it out how the Python world works. And I can tell you, it's not so easy to understand and start coding low level functions.

I've got this exception every time I restarted the application, I think it's related to some kind of synchronization, but I'm not sure how to start digging about that. Could you please help me?

asyncore.loop()

File "/usr/lib/python2.7/asyncore.py", line 216, in loop poll_fun(timeout, map) File "/usr/lib/python2.7/asyncore.py", line 156, in poll read(obj) File "/usr/lib/python2.7/asyncore.py", line 87, in read obj.handle_error() File "/usr/lib/python2.7/asyncore.py", line 83, in read obj.handle_read_event() File "/usr/lib/python2.7/asyncore.py", line 449, in handle_read_event self.handle_read() File "/storage/projetos/my_project/venv/lib/python2.7/site-packages/yowsup/layers/network/layer.py", line 47, in handle_read data = self.recv(readSize) File "/usr/lib/python2.7/asyncore.py", line 387, in recv data = self.socket.recv(buffer_size) error: [Errno 11] Resource temporarily unavailable

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-76878619.

rdiasfreitas commented 9 years ago

No, it's just after the start...

emamirazavi commented 9 years ago

Where do you use asyncore.loop?! You should use in a thread

import threading

loop_thread = threading.Thread(target=asyncore.loop, name="Asyncore Loop")# If you want to make the thread a daemon# loop_thread.daemon = True loop_thread.start()

@see http://stackoverflow.com/questions/14483195/how-to-handle-asyncore-within-a-class-in-python-without-blocking-anything

On Tue, Mar 3, 2015 at 7:06 AM, Rodrigo Freitas notifications@github.com wrote:

No, it's just after the start...

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-76881257.

emamirazavi commented 9 years ago

If in your view, everything is okay, what just stays suspicious is your stack starter! See yowsup demos more accurate. To start see EchoClient. Everything you want to start is there.

On Tue, Mar 3, 2015 at 7:10 AM, S.Mohammad Emami Razavi < emamirazavi@gmail.com> wrote:

Where do you use asyncore.loop?! You should use in a thread

import threading

loop_thread = threading.Thread(target=asyncore.loop, name="Asyncore Loop")# If you want to make the thread a daemon# loop_thread.daemon = True loop_thread.start()

@see http://stackoverflow.com/questions/14483195/how-to-handle-asyncore-within-a-class-in-python-without-blocking-anything

On Tue, Mar 3, 2015 at 7:06 AM, Rodrigo Freitas notifications@github.com wrote:

No, it's just after the start...

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-76881257.

rdiasfreitas commented 9 years ago

Hmm, I did the changes you suggested, but the error is still happening. I was spawning a new thread, and calling asyncore.loop inside the run(self), I think this would have the same behavior. I will dedicate more time studying this issue, and I will keep you posted if I find something.

Thanks for the help

rami-dabain commented 9 years ago

@rdiasfreitas did it work out with you?

yniv commented 8 years ago

@rdiasfreitas @emamirazavi does this work? I've implemented the stuff mentioned here but its unstable. each thread disconnects the other... any solution?

yniv commented 8 years ago

it works! i moved all my multi-threaded code to asyncore and it is working now.

eduardosan commented 8 years ago

@yniv Can you give any specifics about your solution? I've tried the following solution:

def login(self):
        self.logger.debug("CLIENT: Iniciando login para %s", self.credentials)
        self.layer.init()
        self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
        try:
            self.stack.loop()
        except (KeyboardInterrupt, SystemExit):
            self.logger.error("CLIENT: Interrupcao forcada")
            return False
        except AuthError as e:
            self.logger.error("CLIENT: Erro de autenticacao!\n%s", e.message)
            # Desabilita o telefone
            self.disable_sender()
            return False
def call(self, method, params):
        return self.layer.call(method, params)

The I call login method in a thread:

t[sender.id] = threading.Thread(target=stack_list[sender.id].login)
t[sender.id].daemon = True
t[sender.id].start()

Calling the call method still producess the error:

stack_list[recipient.sender.id].call(
      'message_send',
       [recipient.get_full_phone(), campaign.message]
 )

This code still producess the error:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/vagrant/web/src/djyowsup/client/stack.py", line 61, in login
    self.stack.loop()
  File "/home/vagrant/.virtualenvs/plataforma_marketing_web/src/yowsup2/yowsup/stacks/yowstack.py", line 195, in loop
    asyncore.loop(*args, **kwargs)
  File "/usr/lib/python2.7/asyncore.py", line 216, in loop
    poll_fun(timeout, map)
  File "/usr/lib/python2.7/asyncore.py", line 156, in poll
    read(obj)
  File "/usr/lib/python2.7/asyncore.py", line 87, in read
    obj.handle_error()
  File "/usr/lib/python2.7/asyncore.py", line 83, in read
    obj.handle_read_event()
  File "/usr/lib/python2.7/asyncore.py", line 449, in handle_read_event
    self.handle_read()
  File "/home/vagrant/.virtualenvs/plataforma_marketing_web/src/yowsup2/yowsup/layers/network/layer.py", line 85, in handle_read
    data = self.recv(readSize)
  File "/usr/lib/python2.7/asyncore.py", line 387, in recv
    data = self.socket.recv(buffer_size)
error: [Errno 11] Resource temporarily unavailable
fnbns commented 8 years ago

Can you give more details regarding the loop in asyncore ? Thank you!

yniv commented 8 years ago

@funnybones the asyncore.loop() should be called only once in your server main(). you don't need to call stack.loop(). you don't need threads if you work with asyncore (only if you want an input thread or something like that)

hope that helps. ask me specific questions and i'll try to help more.

fnbns commented 8 years ago

@yniv thanks for the reply. I want to implement 2 numbers receiving messages in the same time. Unfortunately, I cannot come up with the flow for this. Can you guide me ?

emamirazavi commented 8 years ago

Yowstack constructor does this. For each client you must call yowstack separately. You must not call asyncore.loop after calling each yowstack and it must be called once(see asyncore documentation). On Dec 17, 2015 7:31 PM, "Alexandru Serban" notifications@github.com wrote:

@yniv https://github.com/yniv thanks for the reply. I want to implement 2 numbers receiving messages in the same time. Unfortunately, I cannot come up with the flow for this. Can you guide me ?

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-165494006.

fnbns commented 8 years ago

Can you post a small piece of c9de where you connect 2 clients ? Thabk you!

jodersus commented 8 years ago

How do you start more than one stack and NOT call asyncore.loop more than once? Isn't starting one stack callilng asycore.loop? Apologies if my question sounds too dumb.

emamirazavi commented 8 years ago

Alex, please search for asyncore.loop in Yowsup. Finally it will be found in the method. You must handle the method manually! I have no time to send my code and after all this feature must not be developed by yowsup as Tgalal mentioned me beforehand. It's not difficult and can make you more sophisticated in Yowsup coding.

see this file: https://github.com/tgalal/yowsup/blob/a59ac410c057e8042c528e9f73e9f3eaada6e79e/yowsup/stacks/yowstack.py

you must not call YowStack.loop and you should pay attention that asyncore exists in Network layer. For each client finally you have a certain NetworkLayer extending asyncore.dispatcher_with_send and YowLayer

see this file: https://github.com/tgalal/yowsup/blob/92dcf0a54f663848e806411117d2f8bb1c0bda0b/yowsup/layers/network/layer.py

Give me a feedback.

On Sat, Dec 19, 2015 at 6:04 PM, Alex notifications@github.com wrote:

How do you start more than one stack and NOT call asyncore.loop more than once? Isn't starting one stack callilng asycore.loop? Apologies if my question sounds too dumb.

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-165990254.

johntheknife commented 8 years ago

Hi, here is how I did it. I added the following lines on line 198 of yowstack.py

    def newLoop(self,isDaemon,onException,*args,**kwargs):
        if "discrete" in kwargs:
            discreteVal = kwargs["discrete"]
            del kwargs["discrete"]
            def discreteLoop(*args,**kwargs):
                while True:
                    asyncore.loop(*args, **kwargs)
                    time.sleep(discreteVal)
                    try:
                        callback = self.__class__.__detachedQueue.get(False) #doesn't block
                        callback()
                    except Queue.Empty:
                        pass
            t1=threading.Thread(target=discreteLoop,args=args,kwargs=kwargs)
        else:
            t1=threading.Thread(target=asyncore.loop,args=args,kwargs=kwargs)
        t1.daemon=isDaemon
        try:
            t1.start()
        except Exception as e:
            onException(e)
        except OSError as e:
            onException(e)
        return t1

It creates one thread for each loop. You can call it like this from the stack:

    def start(self):
        self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
        def onException(e):
            ...
        t1=self.stack.newLoop(isDaemon=True,onException=onException,timeout = 0.5, discrete = 0.5) #
        return t1

With this approach I have been able to create several threads, one for each line. But when I run disconnect() from the layer, asyncore fails with a 'Bad File Descriptor' error for one of the threads and all other threads disconnect.

johntheknife commented 8 years ago

And even then, I still get a [Errno 11] Resource temporarily unavailable, after a few minutes

vickz84259 commented 8 years ago

I am planning to implement something similar. (Running different numbers on different threads) From what I've understood about asyncore from different sites is that if you are planning to use asyncore on multiple threads, you should provide a global dictionary as the parameter, "map", to the dispatcher's constructor. Plus pass this dictionary to each loop you start in each thread.

I am planning to modify the dispatcher to implement this. I'll keep you guys updated if it works.

emamirazavi commented 8 years ago

Victor, asyncore itself supports multiple sessions, it can handle over almost 1k clients depends on your machine specifications, if the server supports. Therefore using threading is very vain and incorrect method! Asyncore uses eventlet to handle multiple connections. Yowsup uses threading just to handle ping of connection. On Dec 29, 2015 5:16 PM, "Victor Otieno" notifications@github.com wrote:

I am planning to implement something similar. (Running different numbers on different threads) From what I've understood about asyncore from different sites is that if you are planning to use asyncore on multiple threads, you should provide a global dictionary as the parameter, "map", to the dispatcher's constructor. Plus pass this dictionary to each loop you start in each thread.

I am planning to modify the dispatcher to implement this. I'll keep you guys updated if it works.

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-167793886.

vickz84259 commented 8 years ago

I understand you. So in essence I can have three or more different stacks created under one thread and instead of calling the stack.loop of each stack, I call the asyncore.loop and they will work perfectly??

emamirazavi commented 8 years ago

You can instantiate several stacks in single thread and don't call stack.loop. But then you must call asyncore.loop once. All things will go forward perfectly... asyncore.loop in one thread and all other instantiated stacks in one other thread. It means generally you need just two threads. After that you can trigger disconnect event to disconnect your client from WA server or instantiate new stack to connect your desire client to WA server freely without calling asyncore.loop again. Give me feedback.

On Wed, Dec 30, 2015 at 3:41 AM, Victor Otieno notifications@github.com wrote:

I understand you. So in essence I can have three or more different stacks created under one thread and instead of calling the stack.loop of each stack, I call the asyncore.loop and they will work perfectly??

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-167903286.

vickz84259 commented 8 years ago

Thank you so much for your feedback. Dude you are a life saver. Let me do just that and I will get back to you with the results.

vickz84259 commented 8 years ago

This is what I have tried:

def stackInitiator():
        stack1 = YowStack(layers)
        stack2 = YowStack(layers)
        stack1.setCredentials(CREDENTIALS1)
        stack2.setCredentials(CREDENTIALS2)

        stack1.broadcastEvent(
            YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
        stack2.broadcastEvent(
            YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))

    thread = threading.Thread(target=stackInitiator)
    thread.start()

    asyncore.loop(timeout=1.0)

and here is the output:

DEBUG:yowsup.stacks.yowstack:Initializing stack
DEBUG:yowsup.stacks.yowstack:Constructed Network Layer
DEBUG:yowsup.stacks.yowstack:Constructed Stanza Regulator Layer
DEBUG:yowsup.stacks.yowstack:Constructed Crypt Layer
DEBUG:yowsup.stacks.yowstack:Constructed Coder Layer
DEBUG:yowsup.stacks.yowstack:Constructed Logger Layer
DEBUG:yowsup.stacks.yowstack:Constructed Axolotl Layer
DEBUG:yowsup.stacks.yowstack:Constructed Authentication Layer - Messages Layer - Receipt Layer - Iq Layer - Ack Layer
DEBUG:yowsup.stacks.yowstack:Constructed Interface Layer
DEBUG:yowsup.stacks.yowstack:Initializing stack
DEBUG:yowsup.stacks.yowstack:Constructed Network Layer
DEBUG:yowsup.stacks.yowstack:Constructed Stanza Regulator Layer
DEBUG:yowsup.stacks.yowstack:Constructed Crypt Layer
DEBUG:yowsup.stacks.yowstack:Constructed Coder Layer
DEBUG:yowsup.stacks.yowstack:Constructed Logger Layer
DEBUG:yowsup.stacks.yowstack:Constructed Axolotl Layer
DEBUG:yowsup.stacks.yowstack:Constructed Authentication Layer - Messages Layer - Receipt Layer - Iq Layer - Ack Layer
DEBUG:yowsup.stacks.yowstack:Constructed Interface Layer
DEBUG:yowsup.layers.network.layer:Connecting to e9.whatsapp.net:443
DEBUG:yowsup.layers.network.layer:Connecting to e9.whatsapp.net:443

No error...It just stops..

emamirazavi commented 8 years ago

Victor, it's better you postpone some seconds to trigger connect event after calling asyncore.loop(In fact you must trigger connect event after onconnect event of asyncore being called) and to prevent thread to die create a while loop at the end of stackInitiator method. Test it and give the feedback.

On Thu, Dec 31, 2015 at 2:10 PM, Victor Otieno notifications@github.com wrote:

This is what I have tried:

def stackInitiator(): stack1 = YowStack(layers) stack2 = YowStack(layers) stack1.setCredentials(CREDENTIALS1) stack2.setCredentials(CREDENTIALS2)

    stack1.broadcastEvent(
        YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
    stack2.broadcastEvent(
        YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))

thread = threading.Thread(target=stackInitiator)
thread.start()

asyncore.loop(timeout=1.0)

and here is the output:

DEBUG:yowsup.stacks.yowstack:Initializing stack DEBUG:yowsup.stacks.yowstack:Constructed Network Layer DEBUG:yowsup.stacks.yowstack:Constructed Stanza Regulator Layer DEBUG:yowsup.stacks.yowstack:Constructed Crypt Layer DEBUG:yowsup.stacks.yowstack:Constructed Coder Layer DEBUG:yowsup.stacks.yowstack:Constructed Logger Layer DEBUG:yowsup.stacks.yowstack:Constructed Axolotl Layer DEBUG:yowsup.stacks.yowstack:Constructed Authentication Layer - Messages Layer - Receipt Layer - Iq Layer - Ack Layer DEBUG:yowsup.stacks.yowstack:Constructed Interface Layer DEBUG:yowsup.stacks.yowstack:Initializing stack DEBUG:yowsup.stacks.yowstack:Constructed Network Layer DEBUG:yowsup.stacks.yowstack:Constructed Stanza Regulator Layer DEBUG:yowsup.stacks.yowstack:Constructed Crypt Layer DEBUG:yowsup.stacks.yowstack:Constructed Coder Layer DEBUG:yowsup.stacks.yowstack:Constructed Logger Layer DEBUG:yowsup.stacks.yowstack:Constructed Axolotl Layer DEBUG:yowsup.stacks.yowstack:Constructed Authentication Layer - Messages Layer - Receipt Layer - Iq Layer - Ack Layer DEBUG:yowsup.stacks.yowstack:Constructed Interface Layer DEBUG:yowsup.layers.network.layer:Connecting to e9.whatsapp.net:443 DEBUG:yowsup.layers.network.layer:Connecting to e9.whatsapp.net:443

No error...It just stops..

— Reply to this email directly or view it on GitHub https://github.com/tgalal/yowsup/issues/666#issuecomment-168167127.

vickz84259 commented 8 years ago

Ok.. Updating it with your recommendations.

rami-dabain commented 8 years ago

problem is how to add layers after the core.loop?

johntheknife commented 8 years ago

Hi, I have created an http interface project that uses telegram bot api to communicate through whatsapp using yowsup. You can check it out here.

Unfortunately, I am having big trouble in connecting with several accounts simultaneously. In fact, I have resorted to using different docker containers, one for each account. I wonder if you can check it out and let me know how to make several accounts run in the same container.

bernardogontijo commented 7 years ago

I got it working with a very simple solution:

class Phones(object):
    phs=[]
    def __init__(self,cred):
        self.cred=cred
        self.__class__.phs.append(self)
        stackBuilder = YowStackBuilder()
        self.stack = stackBuilder\
            .pushDefaultLayers(True)\
            .push(EchoLayer)\
            .build()
        print("started for "+cred[0])
        self.stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, self.cred)       
        self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))          
        self.stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])           
        self.stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
        self.stack.setProp(YowCoderLayer.PROP_RESOURCE, YowsupEnv.getCurrent().getResource())
    @classmethod
    def start(cls):
        asyncore.loop()

    @classmethod
    def send(cls,data):
        for c in cls.phs:
            if c.cred[0]+'@s.whatsapp.net'==data["phone_from"]:
                c.stack.broadcastEvent(YowLayerEvent('send_message', message=data['message'], phone_to=data['phone_to']))

It is very easy to use:


CREDENTIALS=[('phone','pass')]
for cred in CREDENTIALS:
    Phones(cred)
Phones.start()
maxi7587 commented 5 years ago

I have tried a similar solution, but the code after self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) is never executed...

maxi7587 commented 5 years ago

I could make it work for multiple lines, but had to comment out line 33 in dispatcher_asyncore.py:

asyncore.loop(timeout=1)

This line was blocking the code, preventing other lines to connect to Whatsapp.

Jean-Pier commented 4 years ago

buenas, intento hacer un bot de WASAP con varias sesiones(diferentes numeros) pero al 2 numero, le responde lo que el 1 numero tiene q ver, porfavor alguien sabe como lo puedo solucionar para diferentes numeros?