/** * pm2 管理启动 * 启动: pm2 start server * 停止:pm2 stop server * **/ const wppconnect = require('@wppconnect-team/wppconnect'); const bodyParser = require("body-parser"); const express = require("express"); const {upload_files, saveToLocal} = require("./upload_files"); const utils = require("./utils"); const {push_data, create_kafka_client} = require("./kafka_utils"); const topic_message = "whatsapp_test1"; // const topic_status = "whatsapp_test2"; const user_iphone = '8615201683893@c.us'; // TODO:需要修改当前登陆账号手机 用于判断被踢的是不是自己 // 封装部分日志打印内容 新增日期显示 { const newLog = function () { process.stdout.write(`${utils.timestampToTime(new Date() / 1000)} `) arguments.callee.oLog.apply(this, arguments); }; const newError = function () { process.stdout.write(`${utils.timestampToTime(new Date() / 1000)} `) arguments.callee.oError.apply(this, arguments); }; newLog.oLog = console.log; newError.oError = console.error; console.log = newLog; console.error = newError; } // 初始化express服务 const app = express(); const PORT = 3000; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); // 反复登陆可能是网络问题 wppconnect.create({ session: 'olive', statusFind: (statusSession, session) => { console.log('Status Session: ', statusSession); //return isLogged || notLogged || browserClose || qrReadSuccess || qrReadFail || autocloseCalled || desconnectedMobile || deleteToken console.log('Session name: ', session); }, headless: "new", disableWelcome:true, browserArgs: [ '--disable-setuid-sandbox', '--no-sandbox', '--safebrowsing-disable-auto-update', '--disable-features=LeakyPeeker' ], puppeteerOptions: { args: ["--no-sandbox", '--disable-setuid-sandbox'], } }).then((client) => start(client)).catch((error) => console.log(error)); async function _download_file(client, message_id, mine, file_name){ let response = undefined; try{ var base64 = await client.downloadMedia(message_id); response = await upload_files(base64, mine, file_name); }catch(error){ console.error("[onMessage] 下载异常: ", error); response = "protocolTimeout"; } return response; } /** * * 下载文件流程 **/ async function download_file(client, message_id, message_type, mine){ console.log("下载文件流程: ", message_type, mine); let response = {media_data: undefined, local_data: undefined}; switch(message_type){ case "image": var file_name = "image.jpg"; response = await _download_file(client, message_id, mine, file_name); // 新增下载方法 break; case "video": var file_name = "video.mp4"; response = await _download_file(client,message_id, mine, file_name); // 新增下载方法 break; case "ptt": // opus var file_name = "audio.opus"; response = await _download_file(client,message_id, mine, file_name); // 新增下载方法 break; case "sticker": // webp var file_name = "sticker.webp"; response = await _download_file(client,message_id, mine, file_name); // 新增下载方法 break; case "audio": var file_name = mine.replace("/", "."); response = await _download_file(client,message_id, mine, file_name); // 新增下载方法 break; case "document": var file_name = mine.replace("/", "."); response = await _download_file(client,message_id, mine, file_name); // 新增下载方法 break; } // if(response === "error"){ // let result = saveToLocal(base64, mine); // console.error("上传失败: 保存到本地 ", result); // return {media_data: response, local_data: result} // } return response; } /** * 服务启动入口 **/ function start(client) { // 获得kafka对象的生产者 const producer = create_kafka_client(); // 监控消息部分 // ---------------------------------------------------------- client.onMessage(async (message) => { console.log("[onMessage] 原始数据:", message); // 拦截消息类型 let chats_type = ["chat", "image", "video", "sticker", "ptt", "document", "audio"] // 拦截群组信息类型 let other_type = ["remove"] console.log("[onMessage] 原始数据类型:", message.type); if(message.isGroupMsg === true && chats_type.indexOf(message.type) > -1){ let data = { // subtype group_id: message.chatId, // 没有群组名称 sender_name: message.sender.pushname, sender_id: message.sender.id, message_id :message.id, reply_to_msg_id: message.quotedMsgId, // 这个不一定有这个字段 表示为回复内容 message_text: undefined, media: undefined, datetime: utils.timestampToTime(message.timestamp), // 修改时间戳为 yymmdd hhmmss type: message.type, mimetype: message.mimetype, subtype: message.subtype } // 下载资源 let {media_data, local_data} = await download_file(client, message.id, message.type, message.mimetype); data["media"] = media_data; data["local_data"] = local_data; // 失败的话下载到本地 // 是否有资源 影响对应的字段 if(media_data){ // 有资源文件的 data["message_text"] = message.caption; if(media_data === "protocolTimeout"){ // 如果是异常状态则修改为undefined data["media"] = undefined; console.log("下载失败 舍弃本条数据: ", data); return; } }else{ data["message_text"] = message.content; } if(message.subtype === "url"){ // chat下的url 表示为引用有外部链接 // message.thumbnail // 缩略图 base64 media_data = await upload_files(message.thumbnail, "image/jpeg", "thubm.jpeg"); console.log("[onMessage] 下载相关链接缩略图") data["media"] = media_data; data["link_title"] = message.title; data["description"] = message.description; } console.log("[onMessage] 封装数据:", data); try{ // 推送 消息 await push_data(producer, data, topic_message); }catch(error){ console.log("[onMessage] 推送异常: ", error); } }else if(message.type === "gp2" && other_type.indexOf(message.subtype) > -1 && message.recipients.indexOf(user_iphone) > -1){ // 被踢出群 且还得是自己 别人被踢也能监听到 let data = { group_id: message.chatId, sender_name: message.sender.pushname, sender_id: message.sender.id, subtype: message.subtype } let group_data = await client.getContact(message.chatId); console.log("[onMessage] 被踢群 ", group_data) data["group_title"] = group_data["name"]; try{ // 推送 消息 await push_data(producer, data, topic_message); }catch(error){ console.error("[onMessage] 推送异常: ", error.message); } console.log("[onMessage] kick out of group", data); } }); // 接口部分 // ---------------------------------------------------------- // 初始化接口 获取所有群组信息 app.get('/whatsapp/index', async (req, res) => { console.log("[index] 获取所有接口信息"); let result = {code: 200, data: undefined}; // template try{ let message = await client.listChats({onlyGroups: true}); // 获取的是聊天框里的所有聊天 即使被踢了 result["data"] = message }catch(error){ console.log(error); result = {code: 500, data: error.message} } res.send(result); }); // http://127.0.0.1:3000/whatsapp/join_group?link=https://chat.whatsapp.com/G1pxOV1USEAGmlvhzYGS8k // 加群接口 app.get("/whatsapp/join_group", async (req, res) => { // 被踢的群就没有办法再加、已经加过的群返回群id {"id": ""} // TODO: 需要区分不同失败的异常 收集 // 1. 需要认证的群,返回: // 2. 提交认证后,返回: already-exists // 3. 提交认证后且入群,返回 conflict let result = {code: 200, data: undefined}; // template let join_link = req.query.link; console.log("[join_group] 需要添加的群链接为:", join_link); try{ let data = await client.joinGroup(join_link); result["data"] = data; }catch(error){ console.log(error); // 测试error result = {code: 500, data: error.message} if(error.message === "conflict"){ // 针对加完群需要批准的 过滤异常 let data = await client.getGroupInfoFromInviteLink(join_link); console.log("[join_group] 通过邀请码反查数据:", data); result["code"] = 203 result["data"] = {id: data.id}; } } console.log("[join_group] result:", result); res.send(result); }); // 获取联系人信息 app.get("/whatsapp/get_profile", async (req, res) => { let result = {code: 200, data: undefined}; // template try{ let chat_id = req.query.userId; // 593968824284@c.us console.log("[get_profile] 需要获得用户id:", chat_id); let data = await client.getProfilePicFromServer(chat_id); result["data"] = data; }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[get_profile] result:", result); res.send(result); }); // 检查在线状态 app.get("/whatsapp/check_online", async (req, res) => { let result = {code: 200, data: undefined}; // template try{ console.log("[check_online] 检查在线状态"); let data = await client.isOnline(); result["data"] = data; }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[check_online] result:", result); res.send(result); }); // 获得某个聊天的详情介绍 包括头像图片 这个获取群组信息的最全 包含群成员****** app.get('/whatsapp/get_chat', async (req, res) => { let result = {code: 200, data: undefined}; // template try{ let chat_id = req.query.chatId; // 593968824284@c.us console.log("[get_chat] 需要获得内容的chatId:", chat_id); let data = await client.getChatById(chat_id); result["data"] = data; }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[get_chat] result:", result); res.send(result); }); // 获得某个群组 app.get('/whatsapp/get_contact', async (req, res) => { let result = {code: 200, data: undefined}; // template try{ let chat_id = req.query.contactId; // 593968824284@c.us let invoke_id = req.query.inviteCode; console.log("[get_contact] 需要获得群组内容:", chat_id, invoke_id); let data = await client.getContact(chat_id); console.log("[get_contact] data: ", data) // console.log(data.getProfilePicFromServer()) result["data"] = data; if(invoke_id){ // 如果存在邀请码则拼接其他数据 let data1 = await client.getGroupInfoFromInviteLink(invoke_id); data1.profilePicThumbObj = data.profilePicThumbObj; // 合并字段 result["data"] = data1; } }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[get_contact] result:", result); res.send(result); }); // 邀请码反查群信息 app.get('/whatsapp/invoke', async (req, res) => { let result = {code: 200, data: undefined}; // template try{ let chat_id = req.query.inviteCode; // 593968824284@c.us console.log("[invoke] 需要获得群组内容:", chat_id); let data = await client.getGroupInfoFromInviteLink(chat_id); result["data"] = data; }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[invoke] result:", result); res.send(result); }); app.post('/whatsapp/send', async (req, res) => { console.log("enter send!"); let result = {code: 200, data: undefined}; // template try{ let body = req.body; // 获取消息内容以及类型 120363335479943723@g.us let message_type = body["message_type"]; let content = body["content"] let chat_id = body["chat_id"]; var data = "" if(message_type === "text"){ // 文本 data = await client.sendText(chat_id, content); // 指定目标群组 + 内容 }else if(message_type === "file"){ // 文件 content 内容支持 本地文件/ data:text/plain;base64 data = await client.sendFile(chat_id, content); // 指定目标群组 + 内容 }else if(message_type === "image"){ // 图片 content 内容支持 本地文件/link data = await client.sendImage(chat_id, content); }else{ data = ""; } console.log("[invoke] data => ", data); console.log("[invoke] 发送内容的群组id为:", chat_id, content); result["data"] = "发送成功"; // 由于没有发送成功的回执消息 }catch(error){ console.log(error); result = {code: 500, data: error.message} } console.log("[invoke] result:", result); res.send(result); }); // 开起服务 app.listen(PORT, '0.0.0.0', () => console.log("开启服务,端口3000", new Date().toString())); }