Giter Club home page Giter Club logo

python-wechaty's Introduction

python-wechaty

Python Wechaty

PyPI Version Python Wechaty Getting Started Python 3.7 Downloads Wechaty in Python codecov PyPI PyPI - Downloads

📄 Chinese Document python-wechaty-template

What's Python Wechaty

Python Wechaty is an Open Source software application for building chatbots. It is a modern Conversational RPA SDK which Chatbot makers can use to create a bot in a few lines of code.

You can use Wechaty to build a chatbot which automates conversations and interact with people through instant messaging platforms such as WhatsApp, WeChat, WeCom, Gitter and Lark among others.

Features

  • Message Processing: You can use the simple code, similar to natural language, to process the message receving & sending.
  • Plugin System: You can use the community-contributed plugins to handle your scenario.
  • Write onece, run multi IM platform: python wechaty support many IM platforms with one code, all of you need to do is switch the token token type.
  • Wechaty UI: you can use the powerful wechaty-ui to create interactive chatbot
  • ...

Getting Started

There are few steps to start your bot, and we give a bot-template for you to getting started quickly.

Join Us

Wechaty is used in many ChatBot projects by thousands of developers. If you want to talk with other developers, just scan the following QR Code in WeChat with secret code python wechaty, join our Wechaty Python Developers' Home.

Wechaty Friday.BOT QR Code

Scan now, because other Wechaty Python developers want to talk with you too! (secret code: python wechaty)

Requirements

  1. Python 3.7+

Install

pip3 install wechaty

See Also

Static & Instance of Class

Typings

History

v0.6 (Jun 19, 2020)

Python Wechaty Scala Wechaty BETA Released!

Read more from our Multi-language Wechaty Beta Release event from our blog:

v0.4 (Mar 15, 2020) master

Welcome @huangaszaq for joining the project! #42

  1. Add a friendly exception message for PyPI users. #24

v0.1 (Mar 8, 2020)

Welcome @wj-Mcat for joining the project! #4

  1. Starting translate TypeScript of Wechaty to Python
  2. DevOps Setup
    1. Type Checking: mypy & pytype
    2. Unit Testing: pytest
    3. Linting: pylint, pycodestyle, and flake8
    4. CI/CD: GitHub Actions
  3. Publish to PyPI automatically after the tests passed.

v0.0.1 (Aug 25, 2018)

Project created, publish a empty module wechaty on PyPI.

Related Projects

  • Wechaty - Conversatioanl AI Chatot SDK for Wechaty Individual Accounts (TypeScript)
  • Python Wechaty - Python WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Python)
  • Go Wechaty - Go WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Go)
  • Java Wechaty - Java WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Java)
  • Scala Wechaty - Scala WeChaty Conversational AI Chatbot SDK for WechatyIndividual Accounts (Scala)

Badge

Wechaty in Python

[![Wechaty in Python](https://img.shields.io/badge/Wechaty-Python-blue)](https://github.com/wechaty/python-wechaty)

Stargazers over time

Stargazers over time

Contributors

Made with contrib.rocks.

Support

Thanks the following supported Software.

test image size

Committers

  1. @huangaszaq - Chunhong HUANG (黄纯洪)

Creators

Copyright & License

  • Code & Docs © 2018 Wechaty Contributors https://github.com/wechaty
  • Code released under the Apache-2.0 License
  • Docs released under Creative Commons

python-wechaty's People

Contributors

dependabot[bot] avatar doublewinter0 avatar fish-ball avatar gary2018x avatar garydu0123 avatar helloocc avatar huan avatar huangaszaq avatar jiaqianjing avatar kis87988 avatar pig208 avatar seng1e avatar univerone avatar why2lyj avatar wj-mcat avatar zpaimon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-wechaty's Issues

GitHub Actions Enabled

With CI/CD, we do lint & unit test & build & deploy DevOps tasks, publish to PyPi package automatically.

Cheers!

无法显示群用户详细信息

from_contact = msg.talker()
text = msg.text()
room = msg.room()

if room is not None:
    member_list = await room.member_list()
    print(member_list)
    for member in member_list:
        print(member)

在群内发言后,没反应

Wechaty Gateway Server: Assertion failed

1. Versions

What is your wechaty version?
Answer: 0.4.14

Which puppet are you using for wechaty? (padchat/puppeteer/padpro/...)
Answer: padplus_token (converted to donut token)
I got one donut token from the official merchant, but it is invalid after I switched the account. No new qrcode is forwarded, and my puppet hangs. (It might be another bug, but I am now hosting my own gateway server.)

What is your wechaty-puppet-XXX(padchat/puppeteer/) version?
Answer: wechaty-puppet-hostie 0.2.2

What is your node version? (run node --version)
Answer: I am using docker image, so...

What os are you using
Answer: Centos7 for gateway server, MacOS for dev

2. Describe the bug

This bug has been reported multiple times in the issues, but I did not see any reaction from the developers. I am reporting again to draw more attention and hopefully, I could get some suggestions from the official developers.

I have tried to update all the libraries to the latest version including the gateway docker image.

3. To Reproduce

#1988 (comment)
Exactly the same steps here.

4. Expected behavior

Gateway server should forward the messages to puppet.

5. Actual behavior

The puppet side cannot get any message and it keeps reporting the same error as the gateway server.

6. Full Output Logs

VERB PuppetServiceImpl messagePayload()
00:04:58 VERB Puppet messagePayload(9144104376834225040)
Error [AssertionError]: Assertion failed
at new goog.asserts.AssertionError (/wechaty/node_modules/google-protobuf/google-protobuf.js:81:876)
at Object.goog.asserts.doAssertFailure_ (/wechaty/node_modules/google-protobuf/google-protobuf.js:82:257)
at Object.goog.asserts.assert [as assert] (/wechaty/node_modules/google-protobuf/google-protobuf.js:83:83)
at jspb.BinaryEncoder.writeUnsignedVarint64 (/wechaty/node_modules/google-protobuf/google-protobuf.js:458:77)
at jspb.BinaryWriter.writeUnsignedVarint64_ (/wechaty/node_modules/google-protobuf/google-protobuf.js:480:507)
at jspb.BinaryWriter.writeUint64 (/wechaty/node_modules/google-protobuf/google-protobuf.js:485:471)
at Function.proto.wechaty.puppet.MessagePayloadResponse.serializeBinaryToWriter (/wechaty/node_modules/@chatie/grpc/dist/generated/wechaty/puppet/message_pb.js:849:12)
at proto.wechaty.puppet.MessagePayloadResponse.serializeBinary (/wechaty/node_modules/@chatie/grpc/dist/generated/wechaty/puppet/message_pb.js:812:47)
at serialize_wechaty_puppet_MessagePayloadResponse (/wechaty/node_modules/@chatie/grpc/dist/generated/wechaty/puppet_grpc_pb.js:460:26)
at sendUnaryResponse (/wechaty/node_modules/grpc/src/server.js:98:15)
at sendUnaryData (/wechaty/node_modules/grpc/src/server.js:600:9)
at Object. (/wechaty/node_modules/wechaty-puppet-hostie/src/server/puppet-implementation.ts:529:16)
at Generator.next ()
at fulfilled (/wechaty/node_modules/wechaty-puppet-hostie/dist/src/server/puppet-implementation.js:5:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
reportErrorToServer: true,
messagePattern: 'Assertion failed',
code: 13

7. Additional context

I got this error every time when the gateway server receives one message.

Although this is a issue from ts-wechaty , but also occurs in python-wechaty.

Python Wechaty Architecture Diagram

We already have Wechaty in TypeScript, It will be not too hard to translate the TypeScript(TS) to Python(PY) because wechaty has only 3,000 lines of the TS code, they are well designed and de-coupled by the wechaty-puppet abstraction. So after we have translated those 3,000 lines of TypeScript code, we will almost be done.

As we have already a ecosystem of Wechaty in TypeScript, so we will not have to implement everything in Python, especially, in the Feb 2020, we have finished the @chatie/grpc service abstracting module with the wechaty-puppet-hostie implmentation.

The following diagram shows out that we can reuse almost everything in TypeScript, and what we need to do is only the block located at the top right of the diagram: Wechaty (Python).

  +--------------------------+ +--------------------------+
  |                          | |                          |
  |   Wechaty (TypeScript)   | |    Wechaty (Python)      |
  |                          | |                          |
  +--------------------------+ +--------------------------+

  +-------------------------------------------------------+
  |                 Wechaty Puppet Hostie                 |
  |                                                       |
  |                (wechaty-puppet-hostie)                |
  +-------------------------------------------------------+

+---------------------  @chatie/grpc  ----------------------+

  +-------------------------------------------------------+
  |                Wechaty Puppet Abstract                |
  |                                                       |
  |                   (wechaty-puppet)                    |
  +-------------------------------------------------------+

  +--------------------------+ +--------------------------+
  |      Pad Protocol        | |      Web Protocol        |
  |                          | |                          |
  | wechaty-puppet-padplus   | |(wechaty-puppet-puppeteer)|
  +--------------------------+ +--------------------------+
  +--------------------------+ +--------------------------+
  |    Windows Protocol      | |       Mac Protocol       |
  |                          | |                          |
  | (wechaty-puppet-windows) | | (wechaty-puppet-macpro)  |
  +--------------------------+ +--------------------------+

In this repository, we are at the top right of the diagram: Wechaty (Python).

From TypeScript to Python in Wechaty Way - External Modules

Here are all the modules outside the TS Wechaty project and need to be translated to our Python Wechaty.

We will continue tracking the progress in this issue with updates.

  • TS: TypeScript
  • SLOC: Source Lines Of Code

External Modules

  1. Class FileBox
  2. Class MemoryCard
  3. Class WechatyPuppet
  4. Class WechatyPuppetHostie

discuss `pyweb-wechaty` project

We have a short meeting talking about the new project: pyweb-wechaty with python-wechaty team members. pyweb-wechaty is built on python-wechaty that hooks the operations to restful api, so that it can package the operation to the button on web.

🔥 🔥 glad to attending the new project discussion. Below is the main idea:

  • hook plugin : hooks the operation to the restful api
  • pyweb-wechaty : build the operations with wechaty
  • wechaty-plugin : plugin can be used in python-wechaty and pyweb-wechaty which contains the html

Exception: Friendship class can not be instanciated directly!

2020-06-04 09:40:40,051 - Wechaty - INFO - receive event <EventFriendshipPayload(friendship_id='4358696816800085475')>
2020-06-04 09:40:40 INFO wechaty.py <friendship_listener> receive event <EventFriendshipPayload(friendship_id='4358696816800085475')>
2020-06-04 09:40:40,051 - FriendShip - INFO - Friendship constructor 4358696816800085475
2020-06-04 09:40:40 INFO friendship.py <init> Friendship constructor 4358696816800085475
2020-06-04 09:40:40 ERROR base_events.py <default_exception_handler> Exception in callback AsyncIOEventEmitter._emit_run.._callback(<Task finishe...d directly!')>) at /usr/local/lib/python3.7/site-packages/pyee/_asyncio.py:55
handle: <Handle AsyncIOEventEmitter._emit_run.._callback(<Task finishe...d directly!')>) at /usr/local/lib/python3.7/site-packages/pyee/_asyncio.py:55>
Traceback (most recent call last):
File "/usr/local/lib/python3.7/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "/usr/local/lib/python3.7/site-packages/pyee/_asyncio.py", line 62, in _callback
self.emit('error', exc)
File "/usr/local/lib/python3.7/site-packages/pyee/_base.py", line 111, in emit
self._emit_handle_potential_error(event, args[0] if args else None)
File "/usr/local/lib/python3.7/site-packages/pyee/_base.py", line 83, in _emit_handle_potential_error
raise error
File "/usr/local/lib/python3.7/site-packages/wechaty/wechaty.py", line 389, in friendship_listener
friendship = self.Friendship.load(payload.friendship_id)
File "/usr/local/lib/python3.7/site-packages/wechaty/user/friendship.py", line 81, in load
return cls(friendship_id)
File "/usr/local/lib/python3.7/site-packages/wechaty/user/friendship.py", line 69, in init
'Friendship class can not be instanciated directly!')
Exception: Friendship class can not be instanciated directly!

The Design of Accessory Class

The Accessory is a very special abstract class which will be inherited by all user class of Wechaty, includes: Contact, Message, Room, Friendship, RoomInvitation, etc.

The reason behind doing this is that all those class needs to be bound with a Puppet instance, and a Wechaty instance, stands for that all instances of this Contact (or Room, etc) belong to a specific Wechaty instance (also a Puppet instance too).

Related discussion: #15

We have some issues with the implementation in Python, need to be fixed.

Plugin Design Pattern

After check the design of the ts-wechaty/plugin module, I have my conception about plugin.

These code, can only complete simple plugin design. eg: WeatherPlugin, DailyWordsPlugin, DingDongPlugin and so on. there plugins are simple and have no interaction with each other. But I think our plugin System should be:

  • plugin's graph is a topology of network to interact with each other.
  • plugins can output some result, so other plugins can reuse it.
  • plugins can listen on different events to resolve different things.
  • plugin is hot plug module, managed by WechatyPluginManager.

I have complete three plugins, welcome to take some disscuss about it.

error_listener: get room timeout

After merged PR#56

start ding-dong-bot.py demo for a while, program begins to get bugs. Error msg as follow:

INFO wechaty.py <error_listener> receive event <(<Status.INTERNAL: 13>, 'get room timeout', None)
INFO wechaty.py <error_listener> receive event <(<Status.INTERNAL: 13>, 'get room timeout', None)
INFO wechaty.py <error_listener> receive event <(<Status.INTERNAL: 13>, 'get room timeout', None)
INFO wechaty.py <error_listener> receive event <(<Status.INTERNAL: 13>, 'get room timeout', None)
INFO wechaty.py <error_listener> receive event <(<Status.INTERNAL: 13>, 'get room timeout', None)

My wechat information as below for your reference:

10+rooms, 150+ contacters per room, every rooms is chatting all the time. Chatting msg include mini program, pictures, and so on.

Can not get message callback response when we try to send message to gongzhonghao

It looks like if we send messages to 公众号(contact_id start with gh_), the GRPC can not get message callback response. I think this problem is not only affected by Python version.

Here is the example code.

import os
import asyncio

from wechaty import (
    Contact,
    FileBox,
    Message,
    Wechaty,
)


async def on_message(msg: Message):
    """
    Message Handler for the Bot
    """
    if msg.text() == '#ding':
        gh = self.Contact(contact_id="gh_ed907e5f9849")
        await gh.ready()
        await gh.say('dong') #will run to endless loop here.
        
async def main():
    """
    Async Main Entry
    """
    #
    # Make sure we have set WECHATY_PUPPET_HOSTIE_TOKEN in the environment variables.
    #
    if 'WECHATY_PUPPET_HOSTIE_TOKEN' not in os.environ:
        print('''
            Error: WECHATY_PUPPET_HOSTIE_TOKEN is not found in the environment variables
            You need a TOKEN to run the Java Wechaty. Please goto our README for details
            https://github.com/wechaty/python-wechaty-getting-started/#wechaty_puppet_hostie_token
        ''')

    bot = Wechaty()
    bot.on('message',   on_message)
    await bot.start()
    print('[Python Wechaty] Ding Dong Bot started.')


asyncio.run(main())

is this project available now

at first, I want to say thanks for your work, I had installed this package but it can't work, if it's not, may I know how long it will be working, thank you!

continuous error event from hostie server

If our program occur a error, hostie-server will throw continuous error event. eg:

bot = Wechaty(token="your-token-here")
await bot.start()
assert 1 == 2

Bot receive error event and handle it. This bug has appeared in some issues: #60

multi-instance wechaty through metaclass

There are some designs that require us using singleton-instance pattern in wechaty. For example, puppet is a singleton instance in Room/Contact/Message.... So, If we have multi-bot, we will get into trouble that static attriubute in a specific class can't get the one of the two bot puppet.

bot1 = Wechaty(token="")
bot2 = Wechaty(token="")

At this scene, program will get into trouble. But there are solution: Metaclass.

@huan your accessory_test have complete metaclass concept, we should make detailed code come true.

Enforce `version` to be right

With the v0.4 Python Wechaty, we have the following two issues:

  1. After we have installed the wechaty first time, it has installed its dependency wechaty-puppet. The wechaty-puppet will not be upgraded when we have installed a new version of wechaty because there's no version information in the setup.py from wechaty, so the old wechaty-puppet will be always used.
  2. We can not use import wechaty; print(wechaty.__version__) because we have not set __version__

Solution

  1. read requirements.txt and put all dependencies into setup.py with version information
  2. add __version__ to __init__.py of wechaty directory.

See

await contact.ready() for a non-exist contact_id hangs up 300 seconds and got a GRPCError: get contact timeout

Describe the bug
When using a bot, if we load a contact with a manually given id, and then call await contact.ready() to get the contact_payload.

If the id is an actually exist wechaty user (no matter if it is a friend), the result returns as expected soon.

Otherwise (id is a non-exist user), the ready function (exactly contact.puppet.contact_payload function inside) hangs up for exactly 300 seconds, and finally throws a grpclib.exceptions.GRPCError: (<Status.,INTERNAL: 13>, 'get contact timeout', None)

In my opinion, the case when we trying to get contact_payload of a non-exists id should report an error instantiatly, rather than hanging up for such a long time.

To Reproduce

for example:

bot = Wechaty()

async def on_login(user: Contact):
        from time import time
        contact: Contact = Contact('non1exist1user')
        ttt = time()
        try:
            result = await contact.ready()
            print(result)
            print('>>> Success, Duration', time() - ttt)
        except Exception as e:
            print('>>> Fail, Duration', time() - ttt)
            raise e

bot.on('login', on_login)
await bot.start()

Expected behavior

If the given contact id not exist, It should raise an error soon, rather than hanging up for 300 seconds.

Screenshots

ERROR:asyncio:Exception in callback AsyncIOEventEmitter._emit_run.<locals>._callback(<Task finishe...meout', None)>) at /home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/pyee/_asyncio.py:55
handle: <Handle AsyncIOEventEmitter._emit_run.<locals>._callback(<Task finishe...meout', None)>) at /home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/pyee/_asyncio.py:55>
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/pyee/_asyncio.py", line 62, in _callback
    self.emit('error', exc)
  File "/home/alfred/app/python-wechaty-getting-started/python-wechaty/src/wechaty/wechaty.py", line 255, in emit
    super().emit(event, *args, **kwargs)
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/pyee/_base.py", line 111, in emit
    self._emit_handle_potential_error(event, args[0] if args else None)
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/pyee/_base.py", line 83, in _emit_handle_potential_error
    raise error
  File "/home/alfred/app/python-wechaty-getting-started/examples/puppetware/main.py", line 52, in on_login
    raise e
  File "/home/alfred/app/python-wechaty-getting-started/examples/puppetware/main.py", line 47, in on_login
    result = await contact.ready()
  File "/home/alfred/app/python-wechaty-getting-started/python-wechaty/src/wechaty/user/contact.py", line 172, in ready
    self.contact_id)
  File "/home/alfred/app/python-wechaty-getting-started/python-wechaty-puppet-hostie/src/wechaty_puppet_hostie/puppet.py", line 505, in contact_payload
    response = await self.puppet_stub.contact_payload(id=contact_id)
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/chatie_grpc/wechaty.py", line 723, in contact_payload
    "/wechaty.Puppet/ContactPayload", request, ContactPayloadResponse,
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/betterproto/__init__.py", line 1123, in _unary_unary
    response = await stream.recv_message()
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/grpclib/client.py", line 387, in recv_message
    await self.recv_initial_metadata()
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/grpclib/client.py", line 355, in recv_initial_metadata
    self._raise_for_grpc_status(headers_map)
  File "/home/alfred/.virtualenvs/wechaty/lib/python3.7/site-packages/grpclib/client.py", line 316, in _raise_for_grpc_status
    raise GRPCError(status, message, details)
grpclib.exceptions.GRPCError: (<Status.INTERNAL: 13>, 'get contact timeout', None)

Desktop (please complete the following information):

  • OS: Ubuntu 18.10 (for running bot 3.7)
  • Browser: Nope
  • Version: wechaty==0.5.dev1 wechaty-puppet==0.0.11 wechaty-puppet-hostie==0.2.6

Smartphone (please complete the following information):
Nope

Additional context
It seems that the problem is caused by the puppet-hosite server. If needed, I think puppet-server maintainer will have more information about this issue.

Publish `X.Y.devZ` (as @next version) for odd minor versions

We should publish X.Y.devZ to pypi to identify it's a developing version. (like @next in NPM)

How to Install a Developing Version

pip install --pre wechaty

The above will be able to install X.Y.devZ version of Wechaty.

--pre
Include pre-release and development versions. By default, pip only finds stable versions.

DEPRECATED test.pypi.org

It seems that it's not necessary to publish to test.pypi.org, test.pypi.org will never be used in a real environment.

  11. Upload to  Test  PyPI:  twine  upload  --repository-url  https://test.pypi.org/legacy/
       --skip-existing dist/*

See: https://packaging.python.org/guides/using-testpypi/

refactoring log format way

When I'am refactoring the base code. I got boring about the code, so I want to be lazy, and keep the code concise.

    def province(self) -> Optional[str]:
        """
        get the province of the account
        """
        log.info('province() <%s>', self)
        if self.payload is None:
            return None
        return self.payload.province

there are some problems in the code.

  • the logging format is fixed, why can't we movie it to the decorator to implement it.
  • we log the input of the function, but can't log theoutput of the function. eg : contact.city(), ccontact.province(), contact.gender(). I think the result of the funciton is important that we should to log. But we can't log the result in one line code.

In order to resolve the above problems, I create function decorator.

class wechaty_logger(object):
    def __init__(self, module_name: str = None):
        self.module_name = "Wechaty" if module_name is None else module_name

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            log = logging.getLogger(self.module_name)
            log.debug('%s: <%s> <%s>', func.__name__, *args)
            result = func(*args, **kwargs)
            self.can_do_other_things()
            log.debug('%s return value: <%s>', result)
            return result
        return wrapper

    def can_do_other_things(self):
        pass

@wechaty_logger('Contact')
def province(self) -> Optional[str]:
    """
    get the province of the account
    """
    if self.payload is None:
        return None
    return self.payload.province

So, with this code we can keep the code more concise, and more functional. we can create different decorator for different func to implement different functions.

@huan What do you think ?

Python the Wechaty Way

This issue is a summary of all related Python translation work for Wechaty.

  • Discuss the Structure of Starting python-wechaty #5
  • Python Wechaty Architecture Diagram #9
  • Example: How to Translate TypeScript to Python #10
  • From TypeScript to Python in Wechaty Way - Internal Modules #11
  • From TypeScript to Python in Wechaty Way - External Modules #12

See also: Wechaty in Other Languages like Go and Java

Suggest to refactor the `ready`, `payload` function of the related entity classes

When I fixed the bug on the Friendship class (within PR #96 ), I found the old code is not clear, and causes some error.

class Friendship(Accessory, Acceptable):

    def __init__(self, friendship_id: str):
        """
        initialization constructor for friendship
        """
        self.friendship_id = friendship_id
        self._payload: Optional[FriendshipPayload] = None

        log.info('Friendship constructor %s', friendship_id)

        if self.__class__ is Friendship:
            raise Exception(
                'Friendship class can not be instanciated directly!')
        if self.puppet is None:
            raise Exception(
                'Friendship class can not be instanciated without a puppet!')

    @classmethod
    def load(cls, friendship_id: str) -> Friendship:
        """
        load friendship without payload, which loads in a lazy way
        :param friendship_id:
        :return: initialized friendship
        """
        return cls(friendship_id)

    @property
    def payload(self) -> FriendshipPayload:
        """
        get the FriendShipPayload as a property
        :return:
        """
        if self._payload is None:
            self.ready()
        if self._payload is None:
            raise Exception('can"t load friendship payload')
        return self._payload

    def is_ready(self) -> bool:
        """
        check if friendship is ready
        """
        return self.puppet is None or self.payload is None

    async def ready(self):
        """
        load friendship payload
        """
        if not self.is_ready():
            friendship_search_response = await self.puppet.friendship_payload(
                friendship_id=self.friendship_id)
            self._payload = friendship_search_response
        if self.payload is None:
            raise Exception('can"t not load friendship payload %s'
                            % self.friendship_id)

how to send a local image

file_box = FileBox.from_file('ding-dong-icon.png', name='ding-dong.png')
await conversation.say(file_box)

I use the code above to send local image. But I got:

INFO:Wechaty:receive <error> event <(<Status.INTERNAL: 13>, 'unknown filebox json object{type}: {"_metadata":{},"name":"ding-dong.png","boxType":5,"localPath":"ding-dong-icon.png"}', None)> 2020-06-16 14:22:13,145 - Wechaty - INFO - receive <error> event <(<Status.INTERNAL: 13>, 'unknown filebox json object{type}: {"_metadata":{},"name":"ding-dong.png","boxType":5,"localPath":"ding-dong-icon.png"}', None)>

Excetions: can not get message callback response

 Traceback (most recent call last):
   File "/usr/local/lib/python3.7/site-packages/apscheduler/executors/base_py3.py", line 29, in run_coroutine_job
     retval = await job.func(*job.args, **job.kwargs)
   File "/ding-dong-bot.py", line 199, in xxxx
     await conversation.say(file_box)
   File "/usr/local/lib/python3.7/site-packages/wechaty/user/room.py", line 266, in say
     file=some_thing
   File "/usr/local/lib/python3.7/site-packages/wechaty_puppet_hostie/puppet.py", line 322, in message_send_file
     filebox=file.to_json_str()
   File "/usr/local/lib/python3.7/site-packages/chatie_grpc/wechaty.py", line 886, in message_send_file
     "/wechaty.Puppet/MessageSendFile", request, MessageSendFileResponse,
   File "/usr/local/lib/python3.7/site-packages/betterproto/__init__.py", line 1124, in _unary_unary
     response = await stream.recv_message()
   File "/usr/local/lib/python3.7/site-packages/grpclib/client.py", line 387, in recv_message
     await self.recv_initial_metadata()
   File "/usr/local/lib/python3.7/site-packages/grpclib/client.py", line 355, in recv_initial_metadata
     self._raise_for_grpc_status(headers_map)
   File "/usr/local/lib/python3.7/site-packages/grpclib/client.py", line 316, in _raise_for_grpc_status
     raise GRPCError(status, message, details)
 grpclib.exceptions.GRPCError: (<Status.INTERNAL: 13>, 'can not get message callback response.', None)

I'm using scheduler to send pictures to a ChatRoom and got issue above
code like followed:

 room = bot.Room.load("xxxxxxxxx@chatroom")
 conversation: Union[Room, Contact] = room
 await conversation.ready()
 for x in range(1,4):
        file_box = FileBox.from_url(
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/'
        'u=1116676390,2305043183&fm=26&gp=0.jpg',
        name='ding-dong.jpg')
        time.sleep(5)
        await conversation.say(file_box)

and a scheduler job to run it (3 times per hour)

the picture didn't send into chat room when I hit this issue.

From TypeScript to Python in Wechaty Way - Internal Modules

Here are all the modules inside the TS Wechaty project and need to be translated to our Python Wechaty.

We will continue tracking the progress in this issue with updates.

  • TS: TypeScript
  • SLOC: Source Lines Of Code

Wechaty Modules

  1. Class Wechaty @wj-Mcat
  2. Class Contact
  3. Class ContactSelf
  4. Class Message
  5. Class Room
  6. Class Image @wj-Mcat
  7. Class Accessory @huan
  8. Class Config @wj-Mcat
  9. Class Favorite
  10. Class Friendship
  11. Class MiniProgram
  12. Class RoomInvitation
  13. Class Tag
  14. Class UrlLink

another way to implement wechaty different event

In wechaty, all event from puppet should be emit by event bus. But, when I implement EventEmitter in python, I found there is a big unfriendly problem for developer: code annotation.

I check the code in ts-wechaty, It's able prompt dynamically by event name, but this is not work in python. If there is a another solution, please comment it.

So, I think using derived methods of the class solves this problem. Below is the sample code.

class MyWechaty(Wechaty):
    async def dong(self, message: str):
        # do something
        pass

    async def message(self, message: Message):
        # do something
        pass

MyWechaty.instance().start()

In this way, as long as you inherit a function, you can handle the event, eg: dong event,message event. Every event function has specifc param annotation.

The above is my thinking, if I'am wrong, please give me more advice.

Example: How to Translate TS Wechaty to Python

There's a 100 lines class named Image in charge of downloading the WeChat image to different sizes.

It is a great example for demonstrating how do we translate the TypeScript to Python in Wechaty Way:

Image Class Source Code

If you are interested in the translation and want to look at how it works, it will be a good start from reading and comparing those two Image class files in TypeScript and Python at the same time.

Link to Go: wechaty/go-wechaty#26

Add a friendly message for `pip install wechaty` users

Now we are on our way to the Python Wechaty world, the code base is growing very fast under the excellent work from @wj-Mcat

And we have enabled GitHub Actions for the CI/CD: we automatically doing unit tests, and deploy our code to PyPI if the tests passed.

Now you can already do this:

$ pip install wechaty
$ python
>>> import wechaty
>>> bot = Wechaty()

However, the current wechaty module on PyPI is not functional yet, so if there's a user who wants to try Python Wechaty, they will get a Wechaty instance just not works, without any useful message.

We believe the better behavior should be: throw a message to the PyPI user when they install Wechaty and instantiated it, let them know the current progress of the Python Wechaty project, and show them the GitHub Repo of Python Wechaty so that they can come to GitHub and see what happens at here, then make a reasonable expectation.

invalid annotations

FAILED: /root/temp/python-wechaty/.pytype/pyi/src/wechaty/user/contact.pyi
/usr/bin/python3 -m pytype.single --disable pyi-error --imports_info /root/temp/python-wechaty/.pytype/imports/src.wechaty.user.contact.imports --module-name src.wechaty.user.contact -V 3.7 -o /root/temp/python-wechaty/.pytype/pyi/src/wechaty/user/contact.pyi --analyze-annotated --nofail --quick /root/temp/python-wechaty/src/wechaty/user/contact.py
File "/root/temp/python-wechaty/src/wechaty/user/contact.py", line 63, in Contact: Invalid type annotation 'Dict[str, Contact]' [invalid-annotation]
Name 'Contact' is not defined

For more details, see https://google.github.io/pytype/errors.html#invalid-annotation.
ninja: build stopped: subcommand failed.
Makefile:55: recipe for target 'pytype' failed
make: *** [pytype] Error 1

I have tried Ubuntu18.04 with Python3.7.7 and Python3.7.5, and Ubuntu-server16.04 with Python3.7.7, it still occurses. How can I solve it?

watchdog is not so smart

At present, watchdog can watch the bot connection status : sending ding info to the hostie-server, and waiting for dong info, which is the food for the dog. If there is no food for a long time, dog will starve to death. This is the right solution. But, if the connection is missiing which can't ping the server because of the server local network problems. At this scene, watchdog should try to restart the wechaty bot.

This is a feature for the python-wechaty. Welcome to contribute the pr. Waiting for you !

disucss the structure of starting python-wechaty

Because the python-wechaty repo is in the very early stage and is based on wechaty-puppet, which have not started. So if we want to get start, I think the best way right now is making the interfaces of the wechaty-puppet, and don't implement it.

But there is a convention that the name of the wechaty-puppet directory should be same as the name of the repo, which will be implement soon.

We will write all the needed interface in the wechaty-puppet directory. Below is the code structure.

│── src
│   └── wechaty
│   ├── init.py
│   ├── accessory.py
│   ├── config.py
│   ├── config_test.py
│   ├── images.py
│   ├── user
│   │   └── init.py
│   └── wechaty.py
│── tests
│   └── smoke_testing_test.py
└── wechaty_puppet
│── init.py
│── file_box.py
└── puppet.py

src directory is all we should implement. wechaty_puppet directory is the interface which will be implement soon.

Below is the example of the file_box.py.

"""
docstring
"""
# dummy class
class FileBox:
    """
    maintain the file content, which is sended by wechat
    """

    def to_json(self) -> dict:
        """
        dump the file content to json object
        :return:
        """
        raise NotImplementedError

    def to_file(self, file_path: str) -> None:
        """
        save the content to the file
        :return:
        """
        raise NotImplementedError

login twice discussion

There are some developers that they occured errors when they login twice. I can reproduce this error.

python ding-dong-bot.py
python ding-dong-bot.py

At twice command, program will throw exception:

grpclib.exceptions.GRPCError: (<Status.ALREADY_EXISTS: 6>, 'GrpcServerImpl.event() can not call twice.', None)

So, I think how can we resolve this problems. There are some resolutions:

  • add a interface : is_logined() or has_login to tell developer that they have logined at somewhere.
  • auto relogin by default. This is method that I use before, but removed now. Relogin logic code is :
await self.puppet_stub.logout()
await self.puppet_stub.start()

It doesn't throw any exception that the accout doesn't login but run with logout grpc method.

@huan How do you think about this issue.

improve FileBox to support UrlLink message

Now, python-wechaty can send image, file which is base64 type, but can't send url-link message. We should enable python-wechaty to support this feature. The target is to make the below code been working well.

async def on_message(self, msg: Message):
    """
    listen for message event
    """
    from_contact = msg.talker()
    text = msg.text()
    room = msg.room()
    if text == '#ding':
        conversation: Union[
            Room, Contact] = from_contact if room is None else room
        await conversation.ready()
        url_link = UrlLink.create('https://www.baidu.com')
        await conversation.say(url_link)

Welcome to contribute codes.

room improvement plan

There are some functions that should be improved, eg: find_all, find, room-join or room-leave event register.

We should improve all of the modules in python-wechaty step by step.

how to keep clean data rather than dirty data.

Scene: if user update room topic with new_topic, payload is also with old_topic value which is the dirty data.

How to keep the data in python-wechaty is the latest ? May be this is about the mechanism of hostie-server. Is there any event that tell the client that they should refresh the data. @huan

contactlist not updated

I get the contact list of my bot from api: self.Contact.find_all()

But i found the contact_list is not updated. (Some of my friend not found, Someone I deleted is still there, the alias name is not updated)

I trace the find_all() function and found:

# chatie_grpc/wechaty.py
    async def contact_list(self) -> ContactListResponse:
        request = ContactListRequest()

        return await self._unary_unary(
            "/wechaty.Puppet/ContactList", request, ContactListResponse,
        )

I guess it request the contact list from the remote server.

How can I get the updated contact list?

Invalid type annotation

When I clone the code from master branch, Invalid type annotation occures.

File "/Users/wujingwujing/Downloads/temp/python-wechaty/src/wechaty/types.py", line 43, in Sayable: Invalid type annotation '<instance of module>' for return [invalid-annotation]
  Not a type
File "/Users/wujingwujing/Downloads/temp/python-wechaty/src/wechaty/types.py", line 43, in Sayable: Invalid type annotation '<instance of module>'
  Not a type [invalid-annotation]
  Not a type
File "/Users/wujingwujing/Downloads/temp/python-wechaty/src/wechaty/types.py", line 43, in Sayable: Invalid type annotation '<instance of module>' for reply_to [invalid-annotation]
  Not a type

For more details, see https://google.github.io/pytype/errors.html#invalid-annotation.
ninja: build stopped: subcommand failed

But this error don't occure at github action.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.