wslarkbot


Namewslarkbot JSON
Version 0.2.5 PyPI version JSON
download
home_page
Summarylark(feishu) websocket client
upload_time2023-12-07 01:56:13
maintainer
docs_urlNone
authorlloydzhou@gmail..com
requires_python
licenseMIT
keywords feishu lark webhook websocket bot
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Feishu-Webhook-Proxy

1. 将飞书webhook代理成websocket
2. 企业自建应用不用创建公网的回调地址,直接本地使用websocket客户端连上这个转发地址


# 设计
1. 使用nchan维护websocket的连接
2. 将飞书的回调消息,抽取飞书相关的头信息,外面包一层json,使用X-Request-Id作为唯一ID,推送给对应的channel,如果连接对应websocket的客户端回复了X-Request-Id对应的消息,就回复给飞书(这里主要用于第一次配置回调)
3. 客户端自己保存飞书的密钥信息,从转发服务走的消息都是加密的。
4. 客户端调用飞书其他接口,直接走自己的网络

## 安全性
1. 飞书回调消息都是加密的,只能由websocket客户端自己解密,转发服务是透明的。
2. 如何确保自己的channel不会被别人恶意使用?
> 使用nginx basic auth,nchan支持auth_request,在对应的request里面使用basic auth就能做校验  


## 实现
- [x] 部署一个nchan(openresty版本)
- [x] 配置一个internal的location,给内部转发飞书消息使用
- [x] 配置一个location,作为飞书webhook转发(处理消息转发逻辑,如果是配置连接,就重定向到request_id对应的channel等待客户端返回challenge给飞书)


# organization
> 使用一个organization对当前组织下面的所有bot进行管理
> 这样所有的消息可以通过`org_<name>`一个channel推送,这种模式下启动服务的时候可以不用提前注册所有的bot,可以动态的加入新的bot进去
- [x] 对org_<name>的channel增加basic auth
- [x] hook链接转发消息兼容organization
- [x] 新增一个支持organization的client

```
client = Client(bot1, bot2, org_name='org_lloyd', org_passwd='passwd')
```


# 使用

## python sdk
```
pip install wslarkbot

from wslarkbot import *

class MyBot(Bot):
    def on_message(self, data, raw_message, **kwargs):
        # 定义每一个机器人拿到消息后的处理逻辑
        print('on_message', self.app_id, data, raw_message)
        if 'header' in data:
            if data['header']['event_type'] == 'im.message.receive_v1' and data['event']['message']['message_type'] == 'text':
                message_id = data['event']['message']['message_id']
                content = json.loads(data['event']['message']['content'])
                text = content['text']
                # 测试回复消息,初始化bot的时候,需要配置app_secret才能发出去消息
                self.reply_text(message_id, 'reply: ' + text)
                # 回复卡片消息
                self.reply_card(message_id, FeishuMessageCard(
                    FeishuMessageDiv('reply'),
                    FeishuMessageHr(),
                    FeishuMessageDiv(text),
                    FeishuMessageNote(FeishuMessagePlainText('🤖'))
                ))

bot1 = MyBot('cli_xxx', app_secret='xxx', encrypt_key='xxx')
bot2 = MyBot('cli_xxx', app_secret='xxx', encrypt_key='xxx')

# 一个websocket连接,支持同时监听多个机器人回调消息
client = Client(bot1, bot2)
client.start()
```

## 集成openai
> test_openai.py文件中
1. 继承Bot增加自己处理消息的回调
```
class TextMessageBot(Bot):
    def on_message(self, data, *args, **kwargs):
        if 'header' in data:
            if data['header']['event_type'] == 'im.message.receive_v1' and data['event']['message']['message_type'] == 'text':
                content = json.loads(data['event']['message']['content'])
                if self.app:
                    return self.app.process_text_message(text=content['text'], **data['event']['message'])


```
2. 写一个应用:处理文本消息
```
class Application(object):
    def process_text_message(self, text, message_id, **kwargs):
        if text == '/help' or text == '帮助':
            self.bot.reply_card(
                message_id,
                FeishuMessageCard(
                    FeishuMessageDiv('👋 你好呀,我是一款基于OpenAI技术的智能聊天机器人'),
                    FeishuMessageHr(),
                    FeishuMessageDiv('🎒 **需要更多帮助**\n文本回复 *帮助* 或 */help*', tag='lark_md'),
                    header=FeishuMessageCardHeader('🎒需要帮助吗?'),
                )
            )
        elif text:
            chat = ChatOpenAI(
                callbacks=[OpenAICallbackHandler(self.bot, message_id)],
                **self.openai_options
            )
            system_message = [SystemMessage(content=self.system_role)] if self.system_role else []
            chat_history = []  # TODO
            messages = system_message + chat_history + [HumanMessage(content=text)]
            message = chat(messages)
            logging.debug("reply message %r", message)
        else:
            logging.warn("empty text", text)
```
3. 初始化应用,启动机器人
```
if __name__ == "__main__":
    app = Application(
        openai_api_base='',
        openai_api_key='',
        app_id='',
        app_secret='',
        encrypt_key='',
        verification_token='',
    )
    client = Client(app.bot)
    client.start(True)  # debug mode

```

### 运行示例
```
pip install wslarkbot langchain openai click
python test_openai.py
```
![image](https://github.com/ConnectAI-E/Feishu-Webhook-Proxy/assets/1826685/531c8ff5-3b46-4c15-9600-e02dae55cee2)






            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "wslarkbot",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "Feishu,Lark,Webhook,Websocket,Bot",
    "author": "lloydzhou@gmail..com",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/3d/78/7624e29485cb269663493ea45ce79ac99f5215facf8e7e75570aa29f15ba/wslarkbot-0.2.5.tar.gz",
    "platform": null,
    "description": "# Feishu-Webhook-Proxy\n\n1. \u5c06\u98de\u4e66webhook\u4ee3\u7406\u6210websocket\n2. \u4f01\u4e1a\u81ea\u5efa\u5e94\u7528\u4e0d\u7528\u521b\u5efa\u516c\u7f51\u7684\u56de\u8c03\u5730\u5740\uff0c\u76f4\u63a5\u672c\u5730\u4f7f\u7528websocket\u5ba2\u6237\u7aef\u8fde\u4e0a\u8fd9\u4e2a\u8f6c\u53d1\u5730\u5740\n\n\n# \u8bbe\u8ba1\n1. \u4f7f\u7528nchan\u7ef4\u62a4websocket\u7684\u8fde\u63a5\n2. \u5c06\u98de\u4e66\u7684\u56de\u8c03\u6d88\u606f\uff0c\u62bd\u53d6\u98de\u4e66\u76f8\u5173\u7684\u5934\u4fe1\u606f\uff0c\u5916\u9762\u5305\u4e00\u5c42json\uff0c\u4f7f\u7528X-Request-Id\u4f5c\u4e3a\u552f\u4e00ID\uff0c\u63a8\u9001\u7ed9\u5bf9\u5e94\u7684channel\uff0c\u5982\u679c\u8fde\u63a5\u5bf9\u5e94websocket\u7684\u5ba2\u6237\u7aef\u56de\u590d\u4e86X-Request-Id\u5bf9\u5e94\u7684\u6d88\u606f\uff0c\u5c31\u56de\u590d\u7ed9\u98de\u4e66\uff08\u8fd9\u91cc\u4e3b\u8981\u7528\u4e8e\u7b2c\u4e00\u6b21\u914d\u7f6e\u56de\u8c03\uff09\n3. \u5ba2\u6237\u7aef\u81ea\u5df1\u4fdd\u5b58\u98de\u4e66\u7684\u5bc6\u94a5\u4fe1\u606f\uff0c\u4ece\u8f6c\u53d1\u670d\u52a1\u8d70\u7684\u6d88\u606f\u90fd\u662f\u52a0\u5bc6\u7684\u3002\n4. \u5ba2\u6237\u7aef\u8c03\u7528\u98de\u4e66\u5176\u4ed6\u63a5\u53e3\uff0c\u76f4\u63a5\u8d70\u81ea\u5df1\u7684\u7f51\u7edc\n\n## \u5b89\u5168\u6027\n1. \u98de\u4e66\u56de\u8c03\u6d88\u606f\u90fd\u662f\u52a0\u5bc6\u7684\uff0c\u53ea\u80fd\u7531websocket\u5ba2\u6237\u7aef\u81ea\u5df1\u89e3\u5bc6\uff0c\u8f6c\u53d1\u670d\u52a1\u662f\u900f\u660e\u7684\u3002\n2. \u5982\u4f55\u786e\u4fdd\u81ea\u5df1\u7684channel\u4e0d\u4f1a\u88ab\u522b\u4eba\u6076\u610f\u4f7f\u7528\uff1f\n> \u4f7f\u7528nginx basic auth\uff0cnchan\u652f\u6301auth_request\uff0c\u5728\u5bf9\u5e94\u7684request\u91cc\u9762\u4f7f\u7528basic auth\u5c31\u80fd\u505a\u6821\u9a8c  \n\n\n## \u5b9e\u73b0\n- [x] \u90e8\u7f72\u4e00\u4e2anchan\uff08openresty\u7248\u672c\uff09\n- [x] \u914d\u7f6e\u4e00\u4e2ainternal\u7684location\uff0c\u7ed9\u5185\u90e8\u8f6c\u53d1\u98de\u4e66\u6d88\u606f\u4f7f\u7528\n- [x] \u914d\u7f6e\u4e00\u4e2alocation\uff0c\u4f5c\u4e3a\u98de\u4e66webhook\u8f6c\u53d1\uff08\u5904\u7406\u6d88\u606f\u8f6c\u53d1\u903b\u8f91\uff0c\u5982\u679c\u662f\u914d\u7f6e\u8fde\u63a5\uff0c\u5c31\u91cd\u5b9a\u5411\u5230request_id\u5bf9\u5e94\u7684channel\u7b49\u5f85\u5ba2\u6237\u7aef\u8fd4\u56dechallenge\u7ed9\u98de\u4e66\uff09\n\n\n# organization\n> \u4f7f\u7528\u4e00\u4e2aorganization\u5bf9\u5f53\u524d\u7ec4\u7ec7\u4e0b\u9762\u7684\u6240\u6709bot\u8fdb\u884c\u7ba1\u7406\n> \u8fd9\u6837\u6240\u6709\u7684\u6d88\u606f\u53ef\u4ee5\u901a\u8fc7`org_<name>`\u4e00\u4e2achannel\u63a8\u9001\uff0c\u8fd9\u79cd\u6a21\u5f0f\u4e0b\u542f\u52a8\u670d\u52a1\u7684\u65f6\u5019\u53ef\u4ee5\u4e0d\u7528\u63d0\u524d\u6ce8\u518c\u6240\u6709\u7684bot\uff0c\u53ef\u4ee5\u52a8\u6001\u7684\u52a0\u5165\u65b0\u7684bot\u8fdb\u53bb\n- [x] \u5bf9org_<name>\u7684channel\u589e\u52a0basic auth\n- [x] hook\u94fe\u63a5\u8f6c\u53d1\u6d88\u606f\u517c\u5bb9organization\n- [x] \u65b0\u589e\u4e00\u4e2a\u652f\u6301organization\u7684client\n\n```\nclient = Client(bot1, bot2, org_name='org_lloyd', org_passwd='passwd')\n```\n\n\n# \u4f7f\u7528\n\n## python sdk\n```\npip install wslarkbot\n\nfrom wslarkbot import *\n\nclass MyBot(Bot):\n    def on_message(self, data, raw_message, **kwargs):\n        # \u5b9a\u4e49\u6bcf\u4e00\u4e2a\u673a\u5668\u4eba\u62ff\u5230\u6d88\u606f\u540e\u7684\u5904\u7406\u903b\u8f91\n        print('on_message', self.app_id, data, raw_message)\n        if 'header' in data:\n            if data['header']['event_type'] == 'im.message.receive_v1' and data['event']['message']['message_type'] == 'text':\n                message_id = data['event']['message']['message_id']\n                content = json.loads(data['event']['message']['content'])\n                text = content['text']\n                # \u6d4b\u8bd5\u56de\u590d\u6d88\u606f\uff0c\u521d\u59cb\u5316bot\u7684\u65f6\u5019\uff0c\u9700\u8981\u914d\u7f6eapp_secret\u624d\u80fd\u53d1\u51fa\u53bb\u6d88\u606f\n                self.reply_text(message_id, 'reply: ' + text)\n                # \u56de\u590d\u5361\u7247\u6d88\u606f\n                self.reply_card(message_id, FeishuMessageCard(\n                    FeishuMessageDiv('reply'),\n                    FeishuMessageHr(),\n                    FeishuMessageDiv(text),\n                    FeishuMessageNote(FeishuMessagePlainText('\ud83e\udd16'))\n                ))\n\nbot1 = MyBot('cli_xxx', app_secret='xxx', encrypt_key='xxx')\nbot2 = MyBot('cli_xxx', app_secret='xxx', encrypt_key='xxx')\n\n# \u4e00\u4e2awebsocket\u8fde\u63a5\uff0c\u652f\u6301\u540c\u65f6\u76d1\u542c\u591a\u4e2a\u673a\u5668\u4eba\u56de\u8c03\u6d88\u606f\nclient = Client(bot1, bot2)\nclient.start()\n```\n\n## \u96c6\u6210openai\n> test_openai.py\u6587\u4ef6\u4e2d\n1. \u7ee7\u627fBot\u589e\u52a0\u81ea\u5df1\u5904\u7406\u6d88\u606f\u7684\u56de\u8c03\n```\nclass TextMessageBot(Bot):\n    def on_message(self, data, *args, **kwargs):\n        if 'header' in data:\n            if data['header']['event_type'] == 'im.message.receive_v1' and data['event']['message']['message_type'] == 'text':\n                content = json.loads(data['event']['message']['content'])\n                if self.app:\n                    return self.app.process_text_message(text=content['text'], **data['event']['message'])\n\n\n```\n2. \u5199\u4e00\u4e2a\u5e94\u7528\uff1a\u5904\u7406\u6587\u672c\u6d88\u606f\n```\nclass Application(object):\n    def process_text_message(self, text, message_id, **kwargs):\n        if text == '/help' or text == '\u5e2e\u52a9':\n            self.bot.reply_card(\n                message_id,\n                FeishuMessageCard(\n                    FeishuMessageDiv('\ud83d\udc4b \u4f60\u597d\u5440\uff0c\u6211\u662f\u4e00\u6b3e\u57fa\u4e8eOpenAI\u6280\u672f\u7684\u667a\u80fd\u804a\u5929\u673a\u5668\u4eba'),\n                    FeishuMessageHr(),\n                    FeishuMessageDiv('\ud83c\udf92 **\u9700\u8981\u66f4\u591a\u5e2e\u52a9**\\n\u6587\u672c\u56de\u590d *\u5e2e\u52a9* \u6216 */help*', tag='lark_md'),\n                    header=FeishuMessageCardHeader('\ud83c\udf92\u9700\u8981\u5e2e\u52a9\u5417\uff1f'),\n                )\n            )\n        elif text:\n            chat = ChatOpenAI(\n                callbacks=[OpenAICallbackHandler(self.bot, message_id)],\n                **self.openai_options\n            )\n            system_message = [SystemMessage(content=self.system_role)] if self.system_role else []\n            chat_history = []  # TODO\n            messages = system_message + chat_history + [HumanMessage(content=text)]\n            message = chat(messages)\n            logging.debug(\"reply message %r\", message)\n        else:\n            logging.warn(\"empty text\", text)\n```\n3. \u521d\u59cb\u5316\u5e94\u7528\uff0c\u542f\u52a8\u673a\u5668\u4eba\n```\nif __name__ == \"__main__\":\n    app = Application(\n        openai_api_base='',\n        openai_api_key='',\n        app_id='',\n        app_secret='',\n        encrypt_key='',\n        verification_token='',\n    )\n    client = Client(app.bot)\n    client.start(True)  # debug mode\n\n```\n\n### \u8fd0\u884c\u793a\u4f8b\n```\npip install wslarkbot langchain openai click\npython test_openai.py\n```\n![image](https://github.com/ConnectAI-E/Feishu-Webhook-Proxy/assets/1826685/531c8ff5-3b46-4c15-9600-e02dae55cee2)\n\n\n\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "lark(feishu) websocket client",
    "version": "0.2.5",
    "project_urls": {
        "Code": "http://github.com/connectAI-E/Feishu-Webhook-Proxy",
        "Documentation": "https://www.connectai-e.com",
        "Issue tracker": "http://github.com/connectAI-E/Feishu-Webhook-Proxy/issues"
    },
    "split_keywords": [
        "feishu",
        "lark",
        "webhook",
        "websocket",
        "bot"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3d787624e29485cb269663493ea45ce79ac99f5215facf8e7e75570aa29f15ba",
                "md5": "07202a9a82a039fd50a2d5989e37000e",
                "sha256": "c073d480081524b367ab2a262738fdaa1a46c3e95d7ac77b07bb7877f2abac27"
            },
            "downloads": -1,
            "filename": "wslarkbot-0.2.5.tar.gz",
            "has_sig": false,
            "md5_digest": "07202a9a82a039fd50a2d5989e37000e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 10250,
            "upload_time": "2023-12-07T01:56:13",
            "upload_time_iso_8601": "2023-12-07T01:56:13.256864Z",
            "url": "https://files.pythonhosted.org/packages/3d/78/7624e29485cb269663493ea45ce79ac99f5215facf8e7e75570aa29f15ba/wslarkbot-0.2.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-12-07 01:56:13",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "connectAI-E",
    "github_project": "Feishu-Webhook-Proxy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "wslarkbot"
}
        
Elapsed time: 0.15266s