# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: PaddlePaddle 2.0.0b0 (Python 3.5)
#     language: python
#     name: py35-paddle1.2.0
# ---

# # 项目背景-BOTBAY机器人港湾
#
# BotBay致力于创建专属的拟人机器人，我们理想中它可以接入不同的平台【微信、5G】，作为每一人完成日常工作生活专属助理，你可以给它起一个名字，这样就可以伴随终身，我们希望无论你今后的工作生活如何变化，它都可以普适的服务能力，目前版本我们赋能BotBay工作消息整理和待办提醒功能，例如：
# 1.把机器人拉进群，帮助我记录群里面的文字、图片、文件，并自动将文件存储到云盘，文字经过过滤后形成纪要；
# 2.在群里面@我或者私聊我，要求查看当日信息“日报”、“纪要”、并支持将“纪要发送邮箱”；
# 3.模拟一个工作任务，看看机器人如何提醒我的。
#
# # 作品演示
# ## 视频
# [B站链接](https://www.bilibili.com/video/BV1q64y127Vd/)
#
# ## 部分截图
# ### 账号绑定和给机器人起名字
# > 新用户启动chatbot交互时，由于它还不认识你，所以需要向你确认账户【基于本团队之前开发过的一套用户体系】和机器人它自己的姓名
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/26294f201ccc4a26a27e954653a920190d991798392b47218bc8a4412b363c31"/>
#
# ### 停止与启动机器人应答
# > 由于我们使用的是本人微信号，考虑到不影响日常收发消息，所以实现了开关
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/99c5993e00314b27a657cce8811a48852d549d16a003444da8a9fd0b4b5ee7b3"/>
#
# ### 自动纪要生成
# > 根据关键词提取算法，判断群聊消息中那些内容更加有可能属于重要信息，支持纪要发送邮箱【模拟会议纪要的过程】
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/88c174b364254c76992ac6395d87a7ba2d72fcffa02845169936ec2df15d89da"/>
#
# ### 群文件、图片、音频、视频自动归档-移动端
# > 一个工程向的小机制，帮助归档群聊文件，防止文件过期、手机电脑更换等问题
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/118241d57082479fb446e4559db2add8e46b2d8179d043cf8ebcc5bbbda5cf61"/>
#
# > 当然这个其实是一个正儿八经的网盘系统【基于本团队之前开发过的一套网盘】
# <br/>
# <img src="https://ai-studio-static-online.cdn.bcebos.com/3bab69230c9e4772ab55362d737f2f628128ef5b8b8c448ba80286c7511ecce1"/>
#
# ### 待办提醒与代操作
# > 如果BOTBAY接入了业务办公系统的话，那它就可以采用询问的方式协助你处理待办工作，如下图我们模拟了一个申请单提交审批流程
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/07d16b6d0aeb4fb6ba610993f422b10413050d41881d4d3c9dd6153baf3a6b9e"/>
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/7aeae982dc3245cab77befdca611e095c292f869043d4ed087c6d7ff70ef9edf"/>
#
# ### 信息归档日报
# > 根据收集到的Text/Audio/Video/Attachement/Image，以及Room/Contact/mentionList等信息，进行归类、统计、分析
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/b0400068e3184cb58cd06c00857dffaad874fbc7168542bab1076f076432e94c"/>
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/7b257d289fb34168ad4caa21d0a2aca83fdbd6f37c4944d5994f18cf4b6ed632"/>
# <br/>
# <img width="300" src="https://ai-studio-static-online.cdn.bcebos.com/a6434c93e2ca4d64be28aef5a48899e746d84c4534714e5ea3e66464bba7ba04"/>
#
# > 当然也有PC端的展现
# <br/>
# <img src="https://ai-studio-static-online.cdn.bcebos.com/b2c9693c682047aea0d000a70cf97b7426d1995c642745a2b4b5b18a6a9870fd"/>
#
# # 平台架构
# 本项目采用一入口，一平台，多支撑的模式进行设计与开发，其中：
#
# * 一入口 - 微信入口，采用chatbot模式实现用户与系统的交互与应答。
#
# * 一平台 - botPlatform：托管chatbot，启动wechaty实例，接收消息，按状态机模式处理基础消息响应与逻辑分发。
#
# * 多支撑 - paddleWorkers：使用paddleHub提供的支撑服务，本项目中使用paddle提供的图片OCR解析微信消息中的图片文字，今后可拓展不同的paddle服务，支撑chatbot实现更多功能。
#
# # 核心逻辑
# ## botPlatform-托管chatbot
# > 技术路线为NodeJs+Express+MongoDB，主要关键技术为：状态机、分词与关键词提取
# 由于整体代码量巨大，因此本次只上传了关键部分代码
#
# | 序号 | 模块名称 | 功能 | 代码 |
# | -------- | -------- | -------- | -------- |
# | 1 | CMX-CoreHandler | 实现用户认证、用户管理、角色权限等功能 | 无 |
# | 1.1 | user.js | 用户相关功能 | 无 |
# | 1.2 | bot.js | chatbot相关功能 | **有** |
# | 1.3 | application.js | 应用相关功能 | 无 |
# | 2 | CMX-FileHandler | 实现文件处理、自动归档，网盘功能 | 无 |
# | 3 | CMX-ResourceHandler | 实现流程处理、表单数据处理功能 | 无 |
#
# ---
# ### 配置信息
# > 这里主要是botWechatMap变量在后面的login过程限制了可以扫码的微信用户白名单
# ```
# var _LANG = 'ch';//默认中文
# const BOTCONFIG = {
#   autoregistHello: 'hello bot',
#   botWechatMap: {
#     porbello: 'https://u.wechat.com/MG3oDlaSML_iJ3AN6me3Uv4'//不是随意的微信扫码都有效，这里配置了白名单
#   },
#   language: {
#     ch: {
#       hello: '您好，我是您的专属助手',
#       //...等等其它语言
#     }
#   }
# };
# ```
# config/index.js
# ```
# const config = {
# 	bot: {
#       enable: true,//开启机器人服务
#       tokens: ['<PASSWORD>']//如果有多个token，可以启动多个实例
#   	},
# }
# ```
#
# ### 启动wechaty实例
# ```
# var bots = [];
# if (config.bot && config.bot.enable) {
#   for (let i = 0; i < config.bot.tokens.length; i++) {//如果有多个token，则循环运行实例
#     const bot = new Wechaty({
#       puppet: new PuppetPadlocal({
#         token: config.bot.tokens[i]
#       }),
#       name: 'BotBay'
#     });
#     bot.cmx = bot.cmx || {};
#     bot.cmx.use = false;
#     bots.push(bot);
#     bot
#       .on('scan', (qrcode, status) => {
#         bot.cmx.qrcode = qrcode;//有二维码时赋值
#       })
#       .on('login', async (user) => {
#         console.log(`User ${user} logged in`);
#         const contact = bot.userSelf();
#         if (BOTCONFIG.botWechatMap[contact.id]) {
#           console.log(`check pass`);
#           bot.cmx.use = true;//本bot状态是否为待机
#           bot.cmx.qrcode = '';//把二维码输出到前端管理页面用
#           bot.cmx.wechatqr = BOTCONFIG.botWechatMap[contact.id];//给前端管理页面显示本bot对应的微信二维码
#         } else {
#           console.log(`check fail`);//如果不是白名单微信扫码，则强制登出，这里复现貌似登出后不能立刻回调到scan事件
#           await bot.logout();
#           //TOOD 不知道是否需要主动重启wechaty
#         }
#
#       })
#       .on('logout', user => {
#         console.log(`User ${user} log out`);
#         bot.cmx.use = false;//bot状态置为待机
#       })
#       .on('error', e => console.info('Bot', 'error: %s', e))
#       .on('message', message => onMessage(message, bot))
#       .on('friendship', friendship => onFriendship(friendship, bot))
#       .on('room-join', (room, inviteeList, inviter) => onRoomJoin(room, inviteeList, inviter, bot))
#       .start();
#   }
# }
# ```
# ### 获取我自己的bot
# ```
# async function getMyBot(wechatid) {
#   return new Promise((resolve, reject) => {
#     Models.Botlists.findOne({//里面存储了每个人bot的信息
#       wechatid: wechatid
#     }).lean().exec((err, data) => {
#       if (err || !data) resolve(false);
#       else {
#         if (data.owner) {
#           Models.Userworkspacelinks.findOne({
#             user: data.owner
#           }).lean().exec((dmErr, dmData) => {
#             if (dmErr || !dmData) resolve(false);
#             else resolve(Object.assign(data, {
#               workspace: dmData.workspace
#             }));
#           });
#         } else {
#           resolve(false);
#         }
#       }
#     });
#   });
# }
# ```
# 其中bot的字段大致如下
# ```
# const botlistsScheMa = new Schema({
#     nickname: String,//昵称
#     owner: String,//所属人
#     expires: { type: Date },//过期时间
#     state: { type: Number, default: 1 },//状态
#     birthday: { type: Date, default: Date.now },//生日
#     desc: String,//个人简介
#     worldranking: Number,//排名
#     level: Number,//等级
#     wechatid: String,//关联微信号
#     hello: String//自定义触发语
# });
#
# ```
# ### 状态机状态枚举
# 1. HELLO - 初始化状态，新添加机器人为好友或使用“变身机器人”触发
# 2. WAITUSERNAME - 检查发现不明确用户账户，等待账户信息
# 3. WAITNICKNAME - 检查用户尚未给本机器人起名，等待昵称信息
# 4. FREE - 目前基础信息完整，响应交互
#
# 在下文中状态机在不同时机发生状态变化
#
# ### 添加好友
# ```
# async function onFriendship(friendship, bot) {
#   const contact = friendship.contact();
#   if (friendship.type() === bot.Friendship.Type.Receive) { // 1. receive new friendship request from new contact
#     let hasbotinfo = await ((key, wechatid) => {
#       return new Promise((resolve, reject) => {
#         Models.Botlists.findOne({
#           $or: [{ hello: key }, { wechatid: wechatid }]//根据触发语或微信号检查是否已有机器人
#         }).lean().exec((err, data) => {
#           if (err) resolve(false);
#           else resolve(data || false);
#         });
#       });
#     })(friendship.hello(), contact.id);
#     if (hasbotinfo === false && friendship.hello() == BOTCONFIG.autoregistHello) {//autoregistHello是默认通用的触发语
#       hasbotinfo = 'new wechat user';
#     }
#     if (hasbotinfo !== false) {
#       await friendship.accept();//接收好友申请
#       console.log(`Request from ${contact.name()} is accept succesfully!`);
#       if (hasbotinfo == 'new wechat user') {
#         RedisClient.set('BOT-' + contact.id, 'WAITUSERNAME');//状态机置为等待账户名
#         await fsmJob(bot, contact);
#       } else {
#         if (!isEmpty(hasbotinfo.nickname)) {
#           RedisClient.set('BOT-' + contact.id, 'HELLO');//状态机置为打招呼
#           await fsmJob(bot, contact, hasbotinfo.nickname);
#         } else {
#           RedisClient.set('BOT-' + contact.id, 'WAITNICKNAME');//状态机置为等待昵称
#           await fsmJob(bot, contact);
#         }
#         await ((_query, _updatedata) => {
#           return new Promise((resolve, reject) => {
#             Models.Botlists.updateOne(_query, _updatedata, (err, data) => {
#               if (err) {
#                 console.error(err);
#                 resolve(false);
#               } else resolve(data);
#             });
#           });
#         })({
#           _id: hasbotinfo._id
#         }, {
#           wechatid: contact.id//更新一下微信号
#         });
#       }
#     } else {
#       RedisClient.del('BOT-' + contact.id);
#       console.log(`no exist botinfo from ${friendship.hello()}`);
#     }
#   } else if (friendship.type() === bot.Friendship.Type.Confirm) { // 2. confirm friendship
#     console.log(`New friendship confirmed with ${contact.name()}`);
#   }
# }
# ```
#
# ### 接收消息
# ```
# async function onMessage(msg, bot) {
#   const contact = msg.talker();
#   if (contact.id == 'wexin' || msg.self()) {
#     return;
#   }
#   await fsmJob(bot, contact, msg, true);//直接调用状态机动作
# }
# ```
#
# ### 状态机动作
# ```
# async function fsmJob(bot, contact, msg, reply) {
#   let FSM = await (() => {//查询当前用户状态
#     return new Promise((resolve, reject) => {
#       RedisClient.get('BOT-' + contact.id, function (err, result) {
#         if (err) {
#           resolve(false);
#         } else {
#           resolve(result || '');
#         }
#       });
#     });
#   })();
#   if (FSM)
#     await botDoProcess[FSM](bot, contact, msg, reply);//直接执行对应动作
#   else {//说明是新用户
#     if (reply && msg) {//说明用户主动发消息给bot
#       //...有若干代码，主要思想就是根据用户发的消息，进行相应处理
#     }
#   }
# }
# ```
# 主要逻辑在botDoProcess变量中实现，
# ```
# const botDoProcess = {
#    WAITUSERNAME: async (bot, contact, msg, reply)=>{//接收到的是用户账户名，检查数据库是否存在，存在则与bot绑定},
#    WAITNICKNAME: async (bot, contact, msg, reply)=>{//接收到的是bot昵称，更新数据库},
#    FREE: async (bot, contact, msg, reply)=>{//处理指令【纪要、纪要发送邮箱、日报、帮助等】，对文本、音频、视频、附件、图片进行处理、归档、统计、分析},
#    HELLO: async (bot, contact, nickname)=>{
#      await contact.say(BOTCONFIG.language[_LANG].hello + nickname);
#      RedisClient.set('BOT-' + contact.id, 'FREE');//空闲
#      await fsmJob(bot, contact);
#    },
# }
# ```
#
# ### 分词与关键词提取
# 使用CppJieba提供底层分词算法实现
#
# ## paddleWorkers-提供chatbot支撑服务
# > 本次只使用了图片OCR这一个功能，并且封装为http接口【因为pyton实现的paddleWorker，nodejs实现的botPlatform】，暴露给botPlatform使用，得力于paddlehub的组件成熟度，所以代码量很少，这里给paddlehub点个赞
#
# ```
# from flask import request, Flask
# import json
# import paddlehub as hub
# import cv2
# import requests
# import os
#
#
# app = Flask(__name__)
# ocr = None
#
#
# @app.route('/imageOcr', methods=['GET'])
# def image_ocr():
#     path = request.args.get('imagePath')
#     print(path)
#     file_name = os.path.basename(path)
#     file_down = requests.get(path)
#     with open('/mnt/'+file_name,'wb') as f:
#         f.write(file_down.content)
#     ocr_res = ocr.recognize_text(images=[cv2.imread('/mnt/'+file_name)])
#     data = ocr_res[0]['data']
#     res_data = {}
#     text = []
#     for item in data:
#         text.append(item['text'])
#     res_data['msg'] = '请求成功'
#     res_data['code'] = 200
#     res_data['data'] = text
#     os.remove('/mnt/'+file_name)
#     return json.dumps(res_data,ensure_ascii=False)
#
#
#
# def load_model():
#     global ocr
#     ocr = hub.Module(name="chinese_ocr_db_crnn_server")
#
#
# if __name__ == "__main__":
#     load_model()
#     app.run(host="0.0.0.0", port=9000)
#
# ```
# ## webpages-实现前端页面
# > 虽然写前端页面的工作相比于高大上的机器学习、深度学习、人工智能、自然语言处理这些门类，显得不上档次，但是有一个"友好一点点"的界面总还算是件好事。
#
# 基本上就是这个样子按组件化编写的页面
# ```
# <a-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
#     <div class="duplicate-file-item border-size p-size dark-bg9">
#     <p class="item-title">
#         <img src="~assets/img/information-archiving/icon@2x.png" alt />群聊文件
#     </p>
#     <duplicate-file v-if="fileBot.length" :data="fileBot"></duplicate-file>
#     <a-empty
#         v-else
#         :image="require('~/static/images/error/no-data@2x.png')"
#         :image-style="{
#         height: '95px',
#         }"
#     >
#         <span slot="description">暂无数据~</span>
#     </a-empty>
#     </div>
# </a-col>
# ```
# 开发语言为VUE，使用Echarts的图表，这部分就不赘述如何开发的了，按设计稿实现就好了。
#
# # 尚未解决问题
# 1. 目前版本基于状态机的消息处理逻辑是不能应答非标准化的指令的，可以通过引入自然语言处理和多轮对话技术辅助触发状态变化；
# 2. 由于wechaty原理基于微信号的消息收发，所以存在添加好友人数上线，目前本方案可支持不添加好友情况下，在微信群中@机器人的方式进行交互，但复杂场景下还是需要添加好友的。考虑到这点，botPlatform在最开始的配置信息中，预留了多个wechaty实例使用的token数组，并且通过循环创建的方式，可以在服务器端启动多个wechaty实例待机，并且根据策略派发实例响应用户交互（ps.不成熟）；
# 3. 基于wechaty作为消息收发中枢的模式无法满足生产环境下高可用的要求，如果一个实例宕机，其它实例目前没有平滑无缝接管服务的方式，所以只能多拜拜大神了。
#
#

StarcoderdataJupyterScriptsDedupFiltered