"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.registerWeChatPublicEventCallback = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var url_1 = require("url"); var sha1_1 = tslib_1.__importDefault(require("sha1")); var x2js_1 = tslib_1.__importDefault(require("x2js")); var oak_external_sdk_1 = require("oak-external-sdk"); var uuid_1 = require("oak-domain/lib/utils/uuid"); var domain_1 = require("../utils/domain"); var url_2 = require("oak-domain/lib/utils/url"); var X2Js = new x2js_1.default(); function assertFromWeChat(query, config) { var _a; var signature = query.signature, nonce = query.nonce, timestamp = query.timestamp; var token = (_a = config.server) === null || _a === void 0 ? void 0 : _a.token; var stringArray = [nonce, timestamp, token]; var sign = stringArray.sort().reduce(function (acc, val) { acc += val; return acc; }); var sha1Sign = (0, sha1_1.default)(sign); return signature === sha1Sign; } var CALLBACK = {}; function registerWeChatPublicEventCallback(appId, callback) { (0, assert_1.default)(!CALLBACK.hasOwnProperty(appId)); CALLBACK[appId] = callback; } exports.registerWeChatPublicEventCallback = registerWeChatPublicEventCallback; /** * 用户取关事件,注意要容wechatUser不存在的情况 * @param openId * @param context * @returns */ function setUserUnsubscribed(openId, context) { return tslib_1.__awaiter(this, void 0, void 0, function () { var list, weChatUser, _a, _b, _c, _d, _e, _f; var _g, _h, _j; return tslib_1.__generator(this, function (_k) { switch (_k.label) { case 0: return [4 /*yield*/, context.select('wechatUser', { data: { id: 1, subscribed: 1, subscribedAt: 1, }, filter: { applicationId: context.getApplicationId(), openId: openId, }, indexFrom: 0, count: 10, }, { dontCollect: true })]; case 1: list = _k.sent(); if (!(list && list.length > 0)) return [3 /*break*/, 5]; (0, assert_1.default)(list.length === 1); weChatUser = list[0]; if (!weChatUser.subscribed) return [3 /*break*/, 4]; _b = (_a = context).operate; _c = ['wechatUser']; _g = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 2: return [4 /*yield*/, _b.apply(_a, _c.concat([(_g.id = _k.sent(), _g.action = 'update', _g.data = { subscribed: false, unsubscribeAt: Date.now(), }, _g.filter = { id: weChatUser.id, }, _g), { dontCollect: true, dontCreateOper: true }]))]; case 3: _k.sent(); _k.label = 4; case 4: return [3 /*break*/, 9]; case 5: _e = (_d = context).operate; _f = ['wechatUser']; _h = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 6: _h.id = _k.sent(), _h.action = 'create'; _j = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 7: return [4 /*yield*/, _e.apply(_d, _f.concat([(_h.data = (_j.id = _k.sent(), _j.subscribed = false, _j.applicationId = context.getApplicationId(), _j.openId = openId, _j), _h), { dontCollect: true, dontCreateOper: true }]))]; case 8: _k.sent(); _k.label = 9; case 9: return [2 /*return*/]; } }); }); } function setUserSubscribed(openId, eventKey, context) { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a, applicationId, applicationType, list, now, data, doUpdate, sceneStr, wcqId, _b, wechatQrCode, application, _c, type, config, systemId, _d, appId, appSecret, wechatInstance, expired, entity, entityId, _e, _f, userEntityGrant, _g, id, granter, expired_1, entity2, qrCodeType, name_1, _h, appMp, config_1, appId_1, url, content, _j, domain, url, _k, wechatLogin, qrCodeType, expired_2, userId, type_1, successed, _l, domain, url, title, description; var _this = this; return tslib_1.__generator(this, function (_m) { switch (_m.label) { case 0: _a = context.getApplication(), applicationId = _a.id, applicationType = _a.type; return [4 /*yield*/, context.select('wechatUser', { data: { id: 1, subscribed: 1, subscribedAt: 1, }, filter: { applicationId: applicationId, openId: openId, }, indexFrom: 0, count: 1, }, { dontCollect: true })]; case 1: list = _m.sent(); now = Date.now(); data = { // activeAt: now, }; doUpdate = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var wechatUser, _a, _b, _c, _d, _e, _f, _g, _h, _j; var _k, _l, _m; return tslib_1.__generator(this, function (_o) { switch (_o.label) { case 0: if (!(list && list.length > 0)) return [3 /*break*/, 3]; (0, assert_1.default)(list.length === 1); wechatUser = list[0]; if (!wechatUser.subscribed) { Object.assign(data, { subscribed: true, subscribedAt: now, }); } _b = (_a = context).operate; _c = ['wechatUser']; _k = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 1: return [4 /*yield*/, _b.apply(_a, _c.concat([(_k.id = _o.sent(), _k.action = 'update', _k.data = data, _k.filter = { id: wechatUser.id, }, _k), { dontCollect: true, dontCreateOper: true }]))]; case 2: return [2 /*return*/, _o.sent()]; case 3: _e = (_d = Object).assign; _f = [data]; _l = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 4: _e.apply(_d, _f.concat([(_l.id = _o.sent(), _l.subscribed = true, _l.subscribedAt = now, _l.origin = applicationType === 'wechatPublic' ? 'public' : 'web', _l.applicationId = applicationId, _l.openId = openId, _l)])); _h = (_g = context).operate; _j = ['wechatUser']; _m = {}; return [4 /*yield*/, (0, uuid_1.generateNewIdAsync)()]; case 5: return [4 /*yield*/, _h.apply(_g, _j.concat([(_m.id = _o.sent(), _m.action = 'create', _m.data = data, _m), { dontCollect: true }]))]; case 6: // 这里试着直接把user也创建出来,by Xc 20190720 /** * 这里不能创建user,否则会出现一个weChatUser有openId和userId,却没有unionId * 当同一个user先从小程序登录,再从公众号登录时就会生成两个user */ /* return warden.insertEntity(tables.user, { state: UserState.normal, activeAt: Date.now(), }, txn).then( (user) => { assign(data, { userId: user.id }); return warden.insertEntity(tables.weChatUser, data, txn); } );*/ return [2 /*return*/, _o.sent()]; } }); }); }; if (!eventKey) return [3 /*break*/, 18]; sceneStr = void 0; if (eventKey.startsWith('qrscene_')) { sceneStr = eventKey.slice(eventKey.indexOf('qrscene_') + 8); } else { sceneStr = eventKey; } wcqId = (0, uuid_1.expandUuidTo36Bytes)(sceneStr); return [4 /*yield*/, context.select('wechatQrCode', { data: { id: 1, entity: 1, entityId: 1, expired: 1, }, filter: { id: wcqId, }, indexFrom: 0, count: 10, }, { dontCollect: true })]; case 2: _b = tslib_1.__read.apply(void 0, [_m.sent(), 1]), wechatQrCode = _b[0]; if (!wechatQrCode) return [3 /*break*/, 17]; application = context.getApplication(); _c = application, type = _c.type, config = _c.config, systemId = _c.systemId; (0, assert_1.default)(type === 'wechatPublic'); _d = config, appId = _d.appId, appSecret = _d.appSecret; wechatInstance = oak_external_sdk_1.WechatSDK.getInstance(appId, 'wechatPublic', appSecret); expired = wechatQrCode.expired; if (expired) { // 若二维码已经过期,则直接告知用户已经过期 wechatInstance.sendServeMessage({ openId: openId, type: 'text', content: '此二维码已经过期,请重新获取', }); return [2 /*return*/]; } entity = wechatQrCode.entity, entityId = wechatQrCode.entityId; _e = entity; switch (_e) { case 'user': return [3 /*break*/, 3]; case 'userEntityGrant': return [3 /*break*/, 4]; case 'wechatLogin': return [3 /*break*/, 10]; } return [3 /*break*/, 15]; case 3: { // 裂变获得的用户 if (list[0] && !list[0].userId) { Object.assign(data, { userId: entityId }); } return [3 /*break*/, 16]; } _m.label = 4; case 4: return [4 /*yield*/, context.select('userEntityGrant', { data: { id: 1, qrCodeType: 1, granter: { id: 1, name: 1, nickname: 1, }, expired: 1, entity: 1, }, filter: { id: entityId, }, }, { dontCollect: true })]; case 5: _f = tslib_1.__read.apply(void 0, [_m.sent(), 1]), userEntityGrant = _f[0]; _g = userEntityGrant, id = _g.id, granter = _g.granter, expired_1 = _g.expired, entity2 = _g.entity, qrCodeType = _g.qrCodeType; name_1 = (granter === null || granter === void 0 ? void 0 : granter.name) || (granter === null || granter === void 0 ? void 0 : granter.nickname) || '某用户'; if (!(qrCodeType === 'wechatPublicForMp')) return [3 /*break*/, 7]; return [4 /*yield*/, context.select('application', { data: { id: 1, config: 1, }, filter: { systemId: systemId, type: 'wechatMp', }, }, { dontCollect: true })]; case 6: _h = tslib_1.__read.apply(void 0, [_m.sent(), 1]), appMp = _h[0]; (0, assert_1.default)(appMp, '公众号推送小程序码时找不到关联的小程序'); config_1 = appMp.config; appId_1 = config_1.appId; url = (0, url_2.composeUrl)('pages/wechatQrCode/scan/index', { scene: sceneStr, time: "".concat(Date.now()), }); content = "".concat(name_1, "\u4E3A\u60A8\u521B\u5EFA\u4E86\u4E00\u4E2A\u6388\u6743\uFF0C\u8BF7\u70B9\u51FB\u9886\u53D6"); if (!expired_1) { wechatInstance.sendServeMessage({ openId: openId, type: 'text', content: content, }); } else { wechatInstance.sendServeMessage({ openId: openId, type: 'text', content: '您好,您扫描的二维码已经过期,请联系管理员重新获取', }); } return [3 /*break*/, 9]; case 7: return [4 /*yield*/, context.select('domain', { data: { id: 1, url: 1, apiPath: 1, protocol: 1, port: 1, }, filter: { system: { application$system: { id: applicationId, }, }, }, }, { dontCollect: true })]; case 8: _j = tslib_1.__read.apply(void 0, [_m.sent(), 1]), domain = _j[0]; (0, assert_1.default)(domain, "\u5904\u7406userEntityGrant\u65F6\uFF0C\u627E\u4E0D\u5230\u5BF9\u5E94\u7684domain\uFF0CapplicationId\u662F\u300C".concat(applicationId, "\u300D")); url = (0, domain_1.composeDomainUrl)(domain, 'wechatQrCode/scan', { scene: sceneStr, time: "".concat(Date.now()), }); if (!expired_1) { wechatInstance.sendServeMessage({ openId: openId, type: 'news', url: url, title: "".concat(name_1, "\u4E3A\u60A8\u521B\u5EFA\u4E86\u4E00\u4E2A\u6388\u6743"), description: '请接受', picurl: 'http://img95.699pic.com/element/40018/2473.png_860.png', }); } else { wechatInstance.sendServeMessage({ openId: openId, type: 'text', content: '您好,您扫描的二维码已经过期,请联系管理员重新获取', }); } _m.label = 9; case 9: return [3 /*break*/, 16]; case 10: return [4 /*yield*/, context.select('wechatLogin', { data: { id: 1, qrCodeType: 1, expired: 1, userId: 1, type: 1, successed: 1, }, filter: { id: entityId, }, }, { dontCollect: true })]; case 11: _k = tslib_1.__read.apply(void 0, [_m.sent(), 1]), wechatLogin = _k[0]; qrCodeType = wechatLogin.qrCodeType, expired_2 = wechatLogin.expired, userId = wechatLogin.userId, type_1 = wechatLogin.type, successed = wechatLogin.successed; if (!(qrCodeType === 'wechatPublicForMp')) return [3 /*break*/, 12]; return [3 /*break*/, 14]; case 12: return [4 /*yield*/, context.select('domain', { data: { id: 1, url: 1, apiPath: 1, protocol: 1, port: 1, }, filter: { system: { application$system: { id: applicationId, } } }, }, { dontCollect: true })]; case 13: _l = tslib_1.__read.apply(void 0, [_m.sent(), 1]), domain = _l[0]; (0, assert_1.default)(domain, "\u5904\u7406wechatLogin\u65F6\uFF0C\u627E\u4E0D\u5230\u5BF9\u5E94\u7684domain\uFF0CapplicationId\u662F\u300C".concat(applicationId, "\u300D")); url = (0, domain_1.composeDomainUrl)(domain, 'wechatQrCode/scan', { scene: sceneStr, time: "".concat(Date.now()), }); title = type_1 === 'bind' ? '扫码绑定' : '扫码登录'; description = type_1 === 'bind' ? '去绑定' : '去登录'; if (!expired_2) { wechatInstance.sendServeMessage({ openId: openId, type: 'news', url: url, title: title, description: description, picurl: 'http://img95.699pic.com/element/40018/2473.png_860.png', }); } else { wechatInstance.sendServeMessage({ openId: openId, type: 'text', content: '您好,您扫描的二维码已经过期,请重新生成', }); } _m.label = 14; case 14: return [3 /*break*/, 16]; case 15: { return [3 /*break*/, 16]; } _m.label = 16; case 16: return [3 /*break*/, 18]; case 17: console.warn("\u7EBF\u4E0A\u6709\u626B\u63CF\u4E8C\u7EF4\u7801\u573A\u666F\u503C\uFF0C\u4F46\u627E\u4E0D\u5230\u5BF9\u5E94\u7684qrCode\uFF0CeventKey\u662F".concat(eventKey)); _m.label = 18; case 18: return [4 /*yield*/, doUpdate()]; case 19: _m.sent(); return [2 /*return*/]; } }); }); } function onWeChatPublicEvent(data, context) { var ToUserName = data.ToUserName, FromUserName = data.FromUserName, CreateTime = data.CreateTime, MsgType = data.MsgType, Event = data.Event, Content = data.Content, EventKey = data.EventKey; var appId = context.getApplicationId(); var evt; // 如果有应用注入的事件回调则处理之,不依赖其返回 if (CALLBACK[appId]) { CALLBACK[appId](data, context); } if (Event) { var event_1 = Event.toLowerCase(); switch (event_1) { case 'subscribe': setUserSubscribed(FromUserName, EventKey, context); evt = "\u7528\u6237".concat(FromUserName, "\u5173\u6CE8\u516C\u4F17\u53F7"); break; case 'scan': setUserSubscribed(FromUserName, EventKey, context); evt = "\u7528\u6237".concat(FromUserName, "\u518D\u6B21\u626B\u63CF\u5E26").concat(EventKey, "\u952E\u503C\u7684\u4E8C\u7EF4\u7801"); break; case 'unsubscribe': { setUserUnsubscribed(FromUserName, context); evt = "\u7528\u6237".concat(FromUserName, "\u53D6\u5173"); break; } case 'location': { evt = "\u7528\u6237".concat(FromUserName, "\u4E0A\u4F20\u4E86\u5730\u7406\u4F4D\u7F6E\u4FE1\u606F"); break; } case 'click': { evt = "\u7528\u6237".concat(FromUserName, "\u70B9\u51FB\u83DC\u5355\u3010").concat(EventKey, "\u3011"); break; } case 'view': { evt = "\u7528\u6237".concat(FromUserName, "\u70B9\u51FB\u83DC\u5355\u8DF3\u8F6C\u94FE\u63A5\u3010").concat(EventKey, "\u3011"); break; } case 'templatesendjobfinish': { // 模板消息发送完成,去更新对应的messageSent对象 // 这个在线上测试没法通过,返回的msgId不符合,不知道为什么 var msgId = data.MsgID, status_1 = data.Status, openId = data.FromUserName; evt = "\u5E94\u7528".concat(appId, "\u7684\u7528\u6237").concat(FromUserName, "\u53D1\u6765\u4E86").concat(Event, "\u4E8B\u4EF6\uFF0C\u5185\u5BB9\u662F").concat(JSON.stringify(data)); break; } default: { evt = "\u5E94\u7528".concat(appId, "\u7684\u7528\u6237").concat(FromUserName, "\u53D1\u6765\u4E86").concat(Event, "\u4E8B\u4EF6\uFF0C\u5185\u5BB9\u662F").concat(JSON.stringify(data)); break; } } if (process.env.NODE_ENV === 'development') { console.log(evt); } return { content: '', contentType: 'application/text', }; } (0, assert_1.default)(MsgType); var content = '' + "".concat(FromUserName, "") + "".concat(ToUserName, "") + "".concat(CreateTime, "") + 'transfer_customer_service' + ''; switch (MsgType) { case 'text': case 'link': { evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684\u6587\u5B57\u6D88\u606F\uFF1A".concat(Content); break; } case 'image': { evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684\u56FE\u7247\u6D88\u606F\uFF1A".concat(Content); break; } default: { evt = "\u63A5\u6536\u5230\u6765\u81EA\u7528\u6237\u7684".concat(MsgType, "\u578B\u6D88\u606F"); break; } } if (process.env.NODE_ENV === 'development') { console.log(evt); } return { content: content, contentType: 'application/xml', }; } var endpoints = { wechatPublicEvent: [{ name: '微信公众号回调接口', method: 'post', params: ['appId'], fn: function (context, params, headers, req, body) { return tslib_1.__awaiter(void 0, void 0, void 0, function () { var appId, data, _a, content, contentType; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: appId = params.appId; if (!appId || appId === '20230210') { console.error('applicationId参数不存在'); console.log(JSON.stringify(body)); return [2 /*return*/, '']; } return [4 /*yield*/, context.setApplication(appId)]; case 1: _b.sent(); data = X2Js.xml2js(body).xml; _a = onWeChatPublicEvent(data, context), content = _a.content, contentType = _a.contentType; return [2 /*return*/, content]; } }); }); }, }, { name: '微信公众号验证接口', method: 'get', params: ['appId'], fn: function (context, params, body, req, headers) { return tslib_1.__awaiter(void 0, void 0, void 0, function () { var searchParams, appId, echostr, _a, application, signature, timestamp, nonce, isWeChat, echostr; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: searchParams = new url_1.URL("http://".concat(req.headers.host).concat(req.url)).searchParams; appId = params.appId; if (!appId || appId === '20230210') { console.error('applicationId参数不存在'); echostr = searchParams.get('echostr'); return [2 /*return*/, echostr]; } return [4 /*yield*/, context.select('application', { data: { id: 1, config: 1, }, filter: { id: appId, }, }, {})]; case 1: _a = tslib_1.__read.apply(void 0, [_b.sent(), 1]), application = _a[0]; if (!application) { throw new Error("\u672A\u627E\u5230".concat(appId, "\u5BF9\u5E94\u7684app")); } signature = searchParams.get('signature'); timestamp = searchParams.get('timestamp'); nonce = searchParams.get('nonce'); isWeChat = assertFromWeChat({ signature: signature, timestamp: timestamp, nonce: nonce }, application.config); if (isWeChat) { echostr = searchParams.get('echostr'); return [2 /*return*/, echostr]; } else { throw new Error('Verify Failed'); } return [2 /*return*/]; } }); }); }, }], }; exports.default = endpoints;