├── .eslintignore
├── tbk-api-server
├── taobao
│ ├── test.js
│ ├── lib
│ │ ├── tmc
│ │ │ ├── common.js
│ │ │ ├── tmcClient.js
│ │ │ └── tmcCodec.js
│ │ ├── topUtil.js
│ │ ├── spiUtil.js
│ │ └── api
│ │ │ ├── dingtalkClient.js
│ │ │ ├── topClient.js
│ │ │ └── network.js
│ ├── index.js
│ └── main.js
├── jd
│ ├── .gitignore
│ ├── src
│ │ ├── constants.js
│ │ ├── request.js
│ │ ├── sign.js
│ │ └── client.js
│ ├── main.js
│ ├── README.md
│ └── index.js
├── imgs
│ └── tbk-api-deno1.png
├── README.md
├── index.js
└── pdd
│ └── index.js
├── .gitignore
├── wechat
├── README.md
├── common
│ ├── reply.js
│ └── index.js
├── db_action
│ ├── test.js
│ └── index.js
├── msg.js
├── handlers
│ ├── on-friend.js
│ └── on-message.js
├── service
│ ├── msg-filter-service.js
│ └── msg-filters.js
└── index.js
├── .eslintrc.js
├── schedule
└── index.js
├── License
├── Dockerfile
├── package.json
├── config
└── index.js
├── README.md
└── utils
└── index.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | tbk-api-server/jd/src/*
2 | tbk-api-server/taobao/lib/*
--------------------------------------------------------------------------------
/tbk-api-server/taobao/test.js:
--------------------------------------------------------------------------------
1 | const { updateOrders } = require('./index');
2 |
3 | updateOrders();
4 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | yarn.lock
4 | yarn-error.log
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/tbk-api-server/imgs/tbk-api-deno1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pea-cake/tbk/HEAD/tbk-api-server/imgs/tbk-api-deno1.png
--------------------------------------------------------------------------------
/tbk-api-server/jd/src/constants.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | DEFAULT_SERVER: 'https://api.jd.com/routerjson'
3 | };
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | */.settings
2 | */target
3 | *.project
4 | *.classpath
5 | *.idea
6 | *.vscode
7 | *.iml
8 | *.DS_Store
9 | /build/
10 | /bin/
11 | *node_modules
12 | */log
13 | .env
14 | *.memory-card.json
15 | package-lock.json
--------------------------------------------------------------------------------
/wechat/README.md:
--------------------------------------------------------------------------------
1 | # 淘宝客微信bot服务
2 |
3 | * 首先完成([转链服务源码](../tbk-api-server/README.md))
4 | * 安装mongodb并创建数据库名为'taobaoke'
5 | * 执行:
6 | ```
7 | npm i ( 网不好可能需要一些时间,下载大文件: puppeteer)
8 | npm run wechat
9 | ```
10 |
11 | * 手机微信扫码登录即可
12 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/main.js:
--------------------------------------------------------------------------------
1 | const sign = require('./src/sign');
2 | const request = require('./src/request');
3 | const Client = require('./src/client');
4 |
5 | const create = (obj = {}) => new Client(obj);
6 |
7 | module.exports = {
8 | sign,
9 | request,
10 | create,
11 | };
12 |
--------------------------------------------------------------------------------
/wechat/common/reply.js:
--------------------------------------------------------------------------------
1 | const service = require('../service/msg-filter-service');
2 | /**
3 | * 获取私聊返回内容
4 | */
5 | async function getContactTextReply(that, contact, msg) {
6 | const result = await service.filterFriendMsg(that, contact, msg);
7 | console.log('result', result);
8 | return result;
9 | }
10 |
11 | module.exports = {
12 | getContactTextReply,
13 | };
14 |
--------------------------------------------------------------------------------
/wechat/db_action/test.js:
--------------------------------------------------------------------------------
1 | const { ObjectId } = require('mongodb');
2 | const db_action = require('./index');
3 |
4 | async function main() {
5 | const db_obj = await db_action.connectMongo('taobaoke');
6 | const customers = await db_action.selectCustomers(db_obj, { _id: ObjectId('61bf8e63d7ea1e8adfff90fe') });
7 | const customer = customers[0];
8 | console.log(customer);
9 | }
10 | main();
11 |
--------------------------------------------------------------------------------
/tbk-api-server/README.md:
--------------------------------------------------------------------------------
1 | # 淘宝、京东、拼多多转链服务
2 | * ../config/index.js 文件 配置你的联盟平台返利key secret
3 | * 启动服务:
4 | ```
5 | npm i
6 | npm run server
7 | ```
8 | * 使用:
9 | * 请求地址:http://127.0.0.1:3333?url='淘宝、京东、拼多多复制的商品链接(只要包含链接内容即可,无需提取出https://****.com链接)' 如:'【淘宝】https://m.tb.cn/h.U0ptZX4?tk=eCtH2FfjT0a CZ0001 「Lilbetter仿麂皮夹克男秋装新款男装美式上衣休闲工装秋季外套潮」
10 | 点击链接直接打开'
11 | * 示例:
12 |
--------------------------------------------------------------------------------
/wechat/msg.js:
--------------------------------------------------------------------------------
1 | const newTip = '我是购物小助手:现已支持 京东、淘宝、拼多多平台查询商品优惠券,下单后还可拿补贴!';
2 | const useMsg = `
3 | 【食用步骤】如下:
4 | 1.复制淘宝商品/京东/拼多多商品链接发给我
5 | 2.即可看到补贴详细信息,领取优惠券并下单
6 | 3.付款后复制订单号给我绑定补贴
7 | 4.补贴将在收货完成5-15天内,通过微信🧧方式发送给您
8 |
9 | `;
10 | // const menuBtn = '更多功能'
11 |
12 | const menuMsg = ``;
13 |
14 | const commonMsg = `输入的内容有误,你是想查商品优惠券和补贴信息吗?${useMsg}`;
15 |
16 | const subscribeMsg = `亲,终于等到了您!${menuMsg}${newTip}${useMsg}`;
17 |
18 | module.exports = {
19 | subscribeMsg,
20 | menuMsg,
21 | commonMsg,
22 | useMsg,
23 | };
24 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es2021: true,
6 | },
7 | extends: "airbnb-base",
8 | overrides: [
9 | ],
10 | parserOptions: {
11 | ecmaVersion: "latest",
12 | },
13 | rules: {
14 | camelcase: 0,
15 | "no-underscore-dangle": 0,
16 | "import/no-unresolved": 0,
17 | indent: ['error', 4],
18 | quotes: "off",
19 | "no-console": "off",
20 | "no-unused-vars": "off",
21 | "no-unreachable": "off",
22 | "no-redeclare": "warn",
23 | eqeqeq: 0,
24 | "no-await-in-loop": 0,
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/tmc/common.js:
--------------------------------------------------------------------------------
1 | var Common = function(){
2 | }
3 |
4 | Common.enum = {
5 | MessageType:{
6 | CONNECT: 0,
7 | CONNECTACK: 1,
8 | SEND: 2,
9 | SENDACK:3
10 | },
11 | HeaderType : {
12 | EndOfHeaders : 0,
13 | Custom: 1,
14 | StatusCode : 2,
15 | StatusPhrase: 3,
16 | Flag : 4,
17 | Token : 5
18 | },
19 |
20 | ValueFormat : {
21 | Void : 0,
22 | CountedString : 1,
23 | Byte : 2,
24 | Int16 : 3,
25 | Int32 : 4,
26 | Int64 : 5,
27 | Date : 6,
28 | ByteArray : 7
29 | },
30 | MessageKind :{
31 | None: 0,
32 | PullRequest : 1,
33 | Confirm : 2,
34 | Data : 3
35 | }
36 | }
37 |
38 | exports.Common = Common;
--------------------------------------------------------------------------------
/tbk-api-server/jd/src/request.js:
--------------------------------------------------------------------------------
1 | const req = require('request-promise');
2 | const format = require('dateformat');
3 | const sign = require('./sign');
4 | const {DEFAULT_SERVER} = require('./constants');
5 |
6 | module.exports = async (url = DEFAULT_SERVER, method, param_json, version = '1.0', access_token = '', app_key = '', app_secret = '') => {
7 | const isNewVersion = url === DEFAULT_SERVER;
8 | const qs = {
9 | method,
10 | v: version,
11 | access_token,
12 | app_key,
13 | timestamp: format(new Date(), 'yyyy-mm-dd HH:MM:ss'),
14 | sign_method: 'md5',
15 | format: 'json'
16 | };
17 | const params = typeof param_json === 'object' ? JSON.stringify(param_json) : param_json;
18 | if (isNewVersion) {
19 | qs['360buy_param_json'] = params;
20 | } else {
21 | qs['param_json'] = params;
22 | }
23 | qs.sign = sign(qs, app_key, app_secret, url);
24 | return await req({
25 | uri: url,
26 | qs,
27 | json: true
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/schedule/index.js:
--------------------------------------------------------------------------------
1 | const schedule = require('node-schedule');
2 | // date 参数
3 |
4 | // 其他规则见 https://www.npmjs.com/package/node-schedule
5 | // 规则参数讲解, 规则类似 Linux 下的 crontab ('*'代表通配符)
6 | //
7 | // * * * * * *
8 | // ┬ ┬ ┬ ┬ ┬ ┬
9 | // │ │ │ │ │ |
10 | // │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
11 | // │ │ │ │ └───── month (1 - 12)
12 | // │ │ │ └────────── day of month (1 - 31)
13 | // │ │ └─────────────── hour (0 - 23)
14 | // │ └──────────────────── minute (0 - 59)
15 | // └───────────────────────── second (0 - 59, OPTIONAL)
16 |
17 | // 1. 按固定时间触发
18 | //
19 | // 每分钟的第30秒触发: '30 * * * * *'
20 | //
21 | // 每小时的1分30秒触发 :'30 1 * * * *'
22 | //
23 | // 每天的凌晨1点1分30秒触发 :'30 1 1 * * *'
24 | //
25 | // 每月的1日1点1分30秒触发 :'30 1 1 1 * *'
26 | //
27 | // 每周1的1点1分30秒触发 :'30 1 1 * * 1'
28 |
29 | // 2. 按时间差触发 (日/月/周同理)
30 | //
31 | // 每30分钟触发一次: '0 */30 * * * *'
32 | //
33 | // 每6小时触发一次: '0 0 */6 * * *'
34 |
35 | function setSchedule(date, callback) {
36 | schedule.scheduleJob({ tz: 'Asia/Shanghai', rule: date }, callback);
37 | }
38 |
39 | module.exports = {
40 | setSchedule,
41 | };
42 |
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 pea-cake
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/src/sign.js:
--------------------------------------------------------------------------------
1 | const md5 = require('md5');
2 | const {DEFAULT_SERVER} = require('./constants');
3 |
4 | module.exports = (obj = {}, app_key = '', app_secret = '', url = DEFAULT_SERVER) => {
5 | const isNewVersion = url === DEFAULT_SERVER;
6 | const list = [];
7 | if (typeof obj['app_key'] === 'undefined') {
8 | obj['app_key'] = app_key;
9 | }
10 | const params = obj['360buy_param_json'] || obj['param_json'];
11 | if (isNewVersion) {
12 | delete obj['param_json'];
13 | obj['360buy_param_json'] = params;
14 | } else {
15 | delete obj['360buy_param_json'];
16 | obj['param_json'] = params;
17 | }
18 | Object.keys(obj).sort().forEach(key => {
19 | const value = obj[key];
20 | if (key !== 'access_token' || key!='format' || (typeof value === 'string' && value.length > 0)) {
21 | list.push(`${key}${typeof value === 'object' ? JSON.stringify(value) : value}`);
22 | }
23 | });
24 | // console.log('list--->',list)
25 | const signStr = `${app_secret}${list.join('')}${app_secret}`;
26 | // console.log('signStr--->',signStr)
27 | return md5(signStr).toUpperCase();
28 | };
29 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/index.js:
--------------------------------------------------------------------------------
1 | const getLink = require('./main');
2 | const config = require('../../config');
3 |
4 | const getPromoteLink = function (tb_url) {
5 | return getLink(tb_url).then((res) => {
6 | const resultData = {
7 | goods_name: (res && res.short_title) || '',
8 | platform: '淘宝',
9 | };
10 | if (res) {
11 | if (res.coupon_id) {
12 | resultData.coupon = `${res.coupon_amount}元 (${res.coupon_info})`;
13 | }
14 | resultData.url = `${res.tkl_info.split(' ')[0]}(复制淘口令到淘宝)`;
15 | if (res.commission_rate > 0) {
16 | // eslint-disable-next-line max-len
17 | let money = (Number(res.commission_rate) / (100 * 100)) * (parseFloat(res.zk_final_price) - parseFloat(res.coupon_amount || 0));
18 | money = (Number(money) * config.RAKE).toFixed(2);
19 | if ((!Number.isNaN(money)) && money) {
20 | resultData.money = money;
21 | }
22 | }
23 | return resultData;
24 | }
25 | return null;
26 | });
27 | };
28 | module.exports = {
29 | getPromoteLink,
30 | };
31 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/src/client.js:
--------------------------------------------------------------------------------
1 | const sign = require('./sign');
2 | const request = require('./request');
3 | const {DEFAULT_SERVER} = require('./constants');
4 |
5 | module.exports = class Client {
6 | constructor(obj = {}) {
7 | this.setConfig(obj);
8 | }
9 |
10 | setConfig(obj = {}) {
11 | const {appKey, appSecret, serverUrl} = obj;
12 | if (typeof appKey !== 'string' || appKey.length < 1) {
13 | throw new Error('请正确填写app_key,类型为字符串');
14 | }
15 | if (typeof appSecret !== 'string' || appSecret.length < 1) {
16 | throw new Error('请正确填写appSecret,类型为字符串');
17 | }
18 | this.appKey = appKey;
19 | this.appSecret = appSecret;
20 | this.serverUrl = serverUrl || DEFAULT_SERVER;
21 | }
22 |
23 | async request(method, param_json, version = '1.0', access_token = '') {
24 | const {appKey, appSecret, serverUrl} = this;
25 | return await request(serverUrl, method, param_json, version, access_token, appKey, appSecret);
26 | }
27 |
28 | sign(obj) {
29 | const {appKey, appSecret, serverUrl} = this;
30 | return sign(obj, appKey, appSecret, serverUrl);
31 | }
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.13.1
2 | LABEL author="peacake"
3 | ENV PORT=3333
4 | WORKDIR /
5 | COPY . /tbk
6 | RUN apt-get update
7 | RUN apt-get install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm1 -y
8 | RUN wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-4.4.18.tgz && tar -zxvf mongodb-linux-x86_64-debian10-4.4.18.tgz && rm -rf mongodb-linux-x86_64-debian10-4.4.18.tgz && mv mongodb-linux-x86_64-debian10-4.4.18 /usr/local/mongodb4 && export PATH=/usr/local/mongodb4/bin:$PATH
9 | RUN mkdir -p /var/lib/mongo && mkdir -p /var/log/mongodb && chown `whoami` /var/lib/mongo && chown `whoami` /var/log/mongodb
10 | RUN cd /usr/local/mongodb4/bin && ./mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork
11 | RUN npm install -g cnpm --registry=https://registry.npm.taobao.org
12 | RUN cd /tbk && cnpm install
13 | RUN cnpm install -g pm2
14 | EXPOSE ${PORT}
15 | CMD cd /tbk && pm2 start tbk-api-server/index.js -n tbk-api-server && pm2 start wechat/index.js -n wechat && pm2 log
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tbk",
3 | "version": "1.0.0",
4 | "description": "淘宝客(淘宝、京东、拼多多购物返利系统)",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "server": "cd ./tbk-api-server && node index.js",
9 | "wechat": "cd ./wechat && node index.js",
10 | "lint": "eslint . --ext .js --fix"
11 | },
12 | "keywords": [
13 | "淘宝客"
14 | ],
15 | "author": "pea-cake",
16 | "license": "MIT",
17 | "dependencies": {
18 | "axios": "^0.24.0",
19 | "cheerio": "^1.0.0-rc.2",
20 | "dateformat": "^3.0.3",
21 | "file-box": "^1.4.15",
22 | "form-data": "^0.2.0",
23 | "iconv-lite": "^0.4.7",
24 | "install": "^0.13.0",
25 | "md5": "^2.2.1",
26 | "mime": "~1.3.4",
27 | "moment": "^2.29.1",
28 | "mongodb": "^4.2.2",
29 | "node-machine-id": "^1.1.12",
30 | "node-schedule": "^1.3.2",
31 | "pdd-sdk": "^1.0.13",
32 | "qrcode-terminal": "^0.12.0",
33 | "qs": "^6.10.2",
34 | "request": "^2.88.2",
35 | "request-promise": "^4.2.5",
36 | "urlencode": "^1.1.0",
37 | "ws": "^0.4.32",
38 | "wechaty": "^1.20.2",
39 | "wechaty-puppet": "^1.18.3",
40 | "wechaty-puppet-wechat": "^1.18.4"
41 | },
42 | "devDependencies": {
43 | "eslint": "^8.23.0",
44 | "eslint-config-airbnb-base": "^15.0.0",
45 | "eslint-plugin-import": "^2.26.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | const msgs = require('../wechat/msg');
2 | // 配置文件
3 | module.exports = {
4 | RAKE: 0.92, // 返利给用户的比例
5 | JDconfig: {
6 | // 京东联盟
7 | appKey: "",
8 | appSecret: "",
9 | },
10 | TBconfig: {
11 | // 阿里联盟
12 | appkey: "",
13 | appsecret: "",
14 | adzone_id: "", // 推广位pid 'mm_123_456_789' 的789就是adzone_id 获取链接 https://pub.alimama.com/third/manage/record/adzone.htm
15 | },
16 | PDDconfig: {
17 | // 多多客
18 | clientId: "",
19 | clientSecret: "",
20 | pid: "", // 推广位pid
21 | },
22 | autoAcceptFriend: true, // 自动接受好友
23 | acceptFriendKeyWords: [], // 接受好友认证关键词
24 | newFriendReplys: [{ type: 1, content: msgs.subscribeMsg, url: '' }], // 新好友回复
25 | replyKeywords: [
26 | {
27 | reg: 1,
28 | keywords: ['查', '优惠', '券', '京东', '淘宝', '拼多多'],
29 | replys: [{ type: 1, content: msgs.useMsg, url: '' }],
30 | },
31 | {
32 | reg: 1,
33 | keywords: ['你好', '打招呼', '我是', '已添加', 'hello', '哈喽'],
34 | replys: [{ type: 1, content: msgs.subscribeMsg, url: '' }],
35 | },
36 | {
37 | reg: 1,
38 | keywords: ['你是', '你叫啥'],
39 | replys: [{ type: 1, content: `我是购物您的小助手,${msgs.useMsg}`, url: '' }],
40 | },
41 | ],
42 | heartService: { // 检测登录状态,暂时先别用了,容易封号提醒
43 | SENDDATE: '00 00 * * * *', // 定时发送时间 每小时发送一次,规则见 /schedule/index.js
44 | vx: '', // 微信心跳发送人,微信号或昵称
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/tbk-api-server/index.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const qs = require('qs');
3 | const parseUrl = require('url');
4 | const taobaoSDK = require('./taobao');
5 | const jdSDK = require('./jd');
6 | const pddSDK = require('./pdd');
7 | const { getHttpString, formatReplayStr } = require('../utils');
8 |
9 | // 创建淘宝客api服务
10 | http.createServer(async (request, response) => {
11 | // 解析请求,包括文件名
12 | const router = parseUrl.parse(request.url);
13 | console.log('请求地址:', router.pathname);
14 | const { query } = router;
15 | const params = qs.parse(query);
16 | console.log('接收到的url参数', params);
17 | let responseData = {};
18 | const goal_url = getHttpString(params.url);
19 | if (goal_url.indexOf('tb.cn') != -1 || goal_url.indexOf('taobao.com') != -1) {
20 | // 未处理的url参数直接传入,主要就是'「'和'」'之间的关键词,如'「绿联八类网线万兆cat8七类7六6类千兆超家用宽带电脑路由器网络线」'
21 | const res = await taobaoSDK.getPromoteLink(params.url);
22 | if (res) {
23 | responseData = res;
24 | }
25 | } else if (goal_url.indexOf('jd.com') != -1) {
26 | responseData = await jdSDK.getPromotionReply(goal_url);
27 | } else if (goal_url.indexOf('yangkeduo.com') != -1 || goal_url.indexOf('pinduoduo.com') != -1) {
28 | responseData = await pddSDK.getPromoteLink(goal_url);
29 | } else {
30 | responseData = {};
31 | }
32 | // 输出请求的文件名
33 | response.writeHead(200, { 'Content-Type': 'text/plain;charset=utf-8' });
34 | // 发送响应数据
35 | // 响应文件内容
36 | responseData = formatReplayStr(responseData);
37 | response.write(responseData);
38 | response.end();
39 | }).listen(3333);
40 |
41 | // 控制台会输出以下信息
42 | console.log('Server running at http://127.0.0.1:3333/');
43 |
--------------------------------------------------------------------------------
/wechat/handlers/on-friend.js:
--------------------------------------------------------------------------------
1 | const { Friendship } = require("wechaty");
2 | const { delay } = require("../../utils");
3 | const config = require("../../config");
4 |
5 | /**
6 | * 好友添加
7 | */
8 | async function onFriend(friendship) {
9 | console.log("-------->friendship:", friendship);
10 | try {
11 | let logMsg;
12 | const name = friendship.contact().name();
13 | const hello = friendship.hello();
14 | logMsg = `${name},发送了好友请求`;
15 | console.log(logMsg);
16 | if (config.autoAcceptFriend) {
17 | switch (friendship.type()) {
18 | case Friendship.Type.Receive:
19 | if (config.acceptFriendKeyWords.length === 0) {
20 | console.log("无认证关键词,10秒后将会自动通过好友请求");
21 | await delay(10000);
22 | await friendship.accept();
23 | } else if (
24 | config.acceptFriendKeyWords.length > 0
25 | && config.acceptFriendKeyWords.includes(hello)
26 | ) {
27 | console.log(`触发关键词${hello},10秒后自动通过好友请求`);
28 | await delay(10000);
29 | await friendship.accept();
30 | } else {
31 | console.log("未触发任何关键词,好友自动添加失败");
32 | }
33 | break;
34 | case Friendship.Type.Confirm:
35 | logMsg = `已确认添加好友:${name}`;
36 | console.log(logMsg);
37 | break;
38 | default:
39 | console.log(logMsg);
40 | break;
41 | }
42 | } else {
43 | console.log("未开启自动添加好友功能,忽略好友添加");
44 | }
45 | } catch (e) {
46 | console.log("添加好友出错:", e);
47 | }
48 | }
49 |
50 | module.exports = onFriend;
51 |
--------------------------------------------------------------------------------
/wechat/common/index.js:
--------------------------------------------------------------------------------
1 | const { FileBox, UrlLink, MiniProgram } = require('wechaty');
2 | const { delay } = require('../../utils');
3 | /**
4 | * 私聊发送消息
5 | * @param contact
6 | * @param msg
7 | * @param isRoom
8 | * type 1 文字 2 图片url 3 图片base64 4 url链接 5 小程序 6 名片
9 | */
10 | async function contactSay(contact, msg, isRoom = false) {
11 | try {
12 | if (msg.type === 1 && msg.content) {
13 | // 文字
14 | console.log('回复内容', msg.content);
15 | await contact.say(msg.content);
16 | } else if (msg.type === 2 && msg.url) {
17 | // url文件
18 | const obj = FileBox.fromUrl(msg.url);
19 | await obj.ready();
20 | console.log('回复内容', obj);
21 | if (isRoom) {
22 | await contact.say(`@${contact.name()}`);
23 | await delay(500);
24 | }
25 | await contact.say(obj);
26 | } else if (msg.type === 3 && msg.url) {
27 | // bse64文件
28 | const obj = FileBox.fromDataURL(msg.url, 'user-avatar.jpg');
29 | await contact.say(obj);
30 | } else if (msg.type === 4 && msg.url && msg.title && msg.description && msg.thumbUrl) {
31 | const url = new UrlLink({
32 | description: msg.description,
33 | thumbnailUrl: msg.thumbUrl,
34 | title: msg.title,
35 | url: msg.url,
36 | });
37 | await contact.say(url);
38 | } else if (msg.type === 5
39 | && msg.appid
40 | && msg.title
41 | && msg.pagePath
42 | && msg.description
43 | && msg.thumbUrl
44 | && msg.thumbKey) {
45 | const miniProgram = new MiniProgram({
46 | appid: msg.appid,
47 | title: msg.title,
48 | pagePath: msg.pagePath,
49 | description: msg.description,
50 | thumbUrl: msg.thumbUrl,
51 | thumbKey: msg.thumbKey,
52 | });
53 | await contact.say(miniProgram);
54 | }
55 | } catch (e) {
56 | console.log('私聊发送消息失败', e);
57 | }
58 | }
59 |
60 | module.exports = {
61 | contactSay,
62 | };
63 |
--------------------------------------------------------------------------------
/wechat/service/msg-filter-service.js:
--------------------------------------------------------------------------------
1 | const msgFilter = require('./msg-filters');
2 | const { checkOrder } = require('../../utils');
3 |
4 | const WEIXINOFFICIAL = ['朋友推荐消息', '微信支付', '微信运动', '微信团队', 'recommendation message']; // 微信官方账户,针对此账户不做任何回复
5 | const DELETEFRIEND = '开启了朋友验证'; // 被人删除后,防止重复回复
6 | const REMINDKEY = '提醒';
7 | const NEWADDFRIEND = '你已添加';
8 | const SHOP_SITES = ['tb.cn', 'taobao.com', 'jd.com', 'pinduoduo.com', 'yangkeduo.com']; // 转链支持网站
9 |
10 | function isIncludes(arr, str) {
11 | let res = false;
12 | arr.forEach((item) => {
13 | if (str.includes(item)) {
14 | res = true;
15 | }
16 | });
17 | return res;
18 | }
19 |
20 | async function getMsgReply(resArray, {
21 | that, msg, name, contact, avatar, id, room,
22 | }) {
23 | try {
24 | let msgArr = [];
25 | for (let i = 0; i < resArray.length; i += 1) {
26 | const item = resArray[i];
27 | if (item.bool) {
28 | msgArr = (await msgFilter[item.method]({
29 | that, msg, name, contact, avatar, id, room,
30 | })) || [];
31 | }
32 | if (msgArr.length > 0) {
33 | return msgArr;
34 | }
35 | }
36 | return [];
37 | } catch (e) {
38 | console.log('getMsgReply error', e);
39 | return [];
40 | }
41 | }
42 |
43 | /**
44 | * 微信好友文本消息事件过滤
45 | *
46 | * @param that wechaty实例
47 | * @param {Object} contact 发消息者信息
48 | * @param {string} msg 消息内容
49 | * @returns {number} 返回回复内容
50 | */
51 | async function filterFriendMsg(that, contact, msg) {
52 | let msgArr = [];
53 | try {
54 | const name = contact.name();
55 | const { id } = contact;
56 | const avatar = await contact.avatar();
57 | const resArray = [
58 | { bool: msg === '', method: 'emptyMsg' },
59 | { bool: msg.includes(DELETEFRIEND) || WEIXINOFFICIAL.includes(name), method: 'officialMsg' },
60 | { bool: msg.includes(NEWADDFRIEND), method: 'newFriendMsg' },
61 | { bool: msg.startsWith(REMINDKEY), method: 'scheduleJobMsg' },
62 | { bool: isIncludes(SHOP_SITES, msg), method: 'searchPromoteLink' },
63 | { bool: checkOrder(msg), method: 'bindOrder' },
64 | { bool: true, method: 'keywordsMsg' },
65 | ];
66 | msgArr = await getMsgReply(resArray, {
67 | that, msg, contact, name, avatar, id,
68 | });
69 | } catch (e) {
70 | console.log('filterFriendMsg error', e);
71 | }
72 | return msgArr && msgArr.length > 0 ? msgArr : [];
73 | }
74 |
75 | module.exports = {
76 | filterFriendMsg,
77 | };
78 |
--------------------------------------------------------------------------------
/wechat/db_action/index.js:
--------------------------------------------------------------------------------
1 | // const moment = require('moment')
2 | const { MongoClient } = require('mongodb');
3 |
4 | const con_url = "mongodb://localhost:27017/";
5 |
6 | const connectMongo = (dbase_name) => MongoClient.connect(con_url).then((conn) => {
7 | console.log("数据库已连接");
8 | const dbase = conn.db(dbase_name);
9 | return {
10 | conn,
11 | dbase,
12 | };
13 | }).catch((err) => {
14 | console.log("数据库连接失败");
15 | });
16 |
17 | const insertData = (mongoObj, collection_name, data) => {
18 | const table = mongoObj.dbase.collection(collection_name);
19 | // 增加
20 | return table.insertOne(data).then((res) => {
21 | console.log("已插入数据成功");
22 | }).catch((err) => {
23 | console.log("插入数据错误", err);
24 | });
25 | };
26 |
27 | const updateData = (mongoObj, collection_name, query, update_data) => {
28 | const table = mongoObj.dbase.collection(collection_name);
29 | // 修改
30 | return table.updateOne(query, { $set: update_data }).then((res) => {
31 | console.log("修改数据成功");
32 | }).catch((err) => {
33 | console.log("修改数据错误", err);
34 | });
35 | };
36 |
37 | const selectData = (mongoObj, collection_name = '', data = null) => {
38 | const girls_table = mongoObj.dbase.collection(collection_name);
39 | // 查询
40 | return girls_table.find(data).toArray().then((res) => {
41 | console.log("查询数据成功");
42 | return res;
43 | }).catch((err) => {
44 | console.log("查询数据错误", err);
45 | });
46 | };
47 |
48 | const insertCustomer = (mongoObj, data) => insertData(mongoObj, 'customers', data);
49 | const selectCustomers = (mongoObj, data) => selectData(mongoObj, 'customers', data);
50 | const updateCustomer = (mongoObj, query, data) => updateData(mongoObj, 'customers', query, data);
51 | const insertOrder = (mongoObj, data) => insertData(mongoObj, 'orders', data);
52 | const selectOrders = (mongoObj, data) => selectData(mongoObj, 'orders', data);
53 | const updateOrder = (mongoObj, query, data) => updateData(mongoObj, 'orders', query, data);
54 | const insertTurnLink = (mongoObj, data) => insertData(mongoObj, 'turnlinks', data);
55 | const selectTurnLinks = (mongoObj, data) => selectData(mongoObj, 'turnlinks', data);
56 | const updateTurnLink = (mongoObj, query, data) => updateData(mongoObj, 'turnlinks', query, data);
57 |
58 | async function test() {
59 | const mongoObj = await connectMongo('taobaoke');
60 | const insterData = { name: 'xuhaha', age: 15 };
61 | await insertData(mongoObj, 'users', insterData);
62 | }
63 | module.exports = {
64 | connectMongo,
65 | selectData,
66 | insertData,
67 | insertCustomer,
68 | selectCustomers,
69 | updateCustomer,
70 | insertOrder,
71 | selectOrders,
72 | updateOrder,
73 | insertTurnLink,
74 | selectTurnLinks,
75 | updateTurnLink,
76 | };
77 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/README.md:
--------------------------------------------------------------------------------
1 | ## jd-union
2 | > 京东联盟Nodejs SDK
3 |
4 | ### NPM
5 | [
6 | 
7 | 
8 | 
9 | ](https://www.npmjs.com/package/jd-union)
10 |
11 | ### 安装
12 | ```
13 | npm i -save jd-union
14 | // or
15 | yarn add jd-union
16 | ```
17 |
18 | ### 导入
19 | 整个导入
20 | ```js
21 | const JdUnion = require('jd-union');
22 | ```
23 |
24 | 按需导入
25 | ```js
26 | const {create, sign, request} = require('jd-union');
27 | ```
28 |
29 | ### 使用
30 | 创建Api实例
31 | ```js
32 | const {create} = require('jd-union');
33 |
34 | const api = create(
35 | {
36 | appKey: '<联盟分配给应用的appKey>如:eefc33bDRea044cb8ctre5hycf0ac1934',
37 | appSecret: '<联盟分配给应用的appSecret>如:6d34r0d0kild46460654b42f5e350982',
38 | serverUrl: '默认为 https://api.jd.com/routerjson'
39 | }
40 | );
41 |
42 | // 调用Api
43 | // api.request(API接口名称, 业务参数); 返回promise
44 | const res = await api.request(
45 | 'jd.union.open.goods.jingfen.query',
46 | {
47 | goodsReq: 110
48 | }
49 | );
50 |
51 | // 验签
52 | const signature = api.sign(
53 | {
54 | method: 'jd.union.open.goods.query',
55 | param_json: {'goodsReqDTO': {'keyword': '男装', 'pageSize': 10, 'pageIndex': 1}}, // 0.2.x版本后,key为360buy_param_json或param_json皆可
56 | v: '1.0',
57 | access_token: '',
58 | timestamp: '2018-10-18 11:13:12',
59 | sign_method: 'md5',
60 | format: 'json'
61 | }
62 | );
63 |
64 | console.log(signature);
65 | ```
66 |
67 | 单独验签
68 | ```js
69 | const {sign} = require('jd-union');
70 |
71 | const signature = sign(
72 | {
73 | method: 'jd.union.open.goods.query',
74 | param_json: {"goodsReqDTO":{"keyword":"男装","pageSize":10,"pageIndex":1}}, // 0.2.x版本后,key为360buy_param_json或param_json皆可
75 | v: '1.0',
76 | ccess_token: '',
77 | timestamp: '2018-10-18 11:13:12',
78 | sign_method: 'md5',
79 | format: 'json'
80 | },
81 | 'eefc33bDRea044cb8ctre5hycf0ac1934',
82 | '6d34r0d0kild46460654b42f5e350982',
83 | 'https://api.jd.com/routerjson'
84 | );
85 |
86 | console.log(signature);
87 | ```
88 |
89 | 单独调用Api
90 | ```js
91 | const {request} = require('jd-union');
92 |
93 | /*
94 | const res = await request('https://router.jd.com/api',
95 | API接口名称,
96 | 业务参数,
97 | Api版本号,
98 | access_token = '',
99 | '<联盟分配给应用的appKey>如:eefc33bDRea044cb8ctre5hycf0ac1934',
100 | '<联盟分配给应用的appSecret>如:6d34r0d0kild46460654b42f5e350982');
101 | */
102 | const res = await request(
103 | 'https://api.jd.com/routerjson',
104 | 'jd.union.open.goods.jingfen.query',
105 | {
106 | goodsReq: 110
107 | },
108 | '1.0',
109 | '',
110 | 'eefc33bDRea044cb8ctre5hycf0ac1934',
111 | '6d34r0d0kild46460654b42f5e350982'
112 | );
113 | ```
114 |
--------------------------------------------------------------------------------
/wechat/handlers/on-message.js:
--------------------------------------------------------------------------------
1 | const { contactSay } = require('../common');
2 | const { getContactTextReply } = require('../common/reply');
3 | const { delay, updateWechatFirend } = require('../../utils');
4 | /**
5 | * 根据消息类型过滤私聊消息事件
6 | * @param {*} that bot实例
7 | * @param {*} msg 消息主体
8 | */
9 | async function dispatchFriendFilterByMsgType(that, msg) {
10 | try {
11 | const type = msg.type();
12 | const contact = msg.talker(); // 发消息人
13 | // console.log('联系人===========================》', contact)
14 | const isOfficial = contact.type() === that.Contact.Type.Official;
15 | let content = '';
16 | let replys = [];
17 | const contactName = contact.name();
18 | if (contactName.indexOf('recommendation message') == -1) {
19 | updateWechatFirend(contact);
20 | }
21 | switch (type) {
22 | case that.Message.Type.Text:
23 | content = msg.text();
24 | if (!isOfficial) {
25 | console.log(`发消息人${contactName}:${content}`);
26 | if (content.trim()) {
27 | replys = await getContactTextReply(that, contact, content) || [];
28 | for (let i = 0; i < replys.length; i += 1) {
29 | const reply = replys[i];
30 | await delay(1000);
31 | await contactSay(contact, reply);
32 | }
33 | }
34 | } else {
35 | console.log('公众号消息');
36 | }
37 | break;
38 | case that.Message.Type.Emoticon:
39 | console.log(`发消息人${contactName}:发了一个表情`);
40 | break;
41 | case that.Message.Type.Image:
42 | console.log(`发消息人${contactName}:发了一张图片`);
43 | break;
44 | case that.Message.Type.Url:
45 | console.log(`发消息人${contactName}:发了一个链接`);
46 | break;
47 | case that.Message.Type.Video:
48 | console.log(`发消息人${contactName}:发了一个视频`);
49 | break;
50 | case that.Message.Type.Audio:
51 | console.log(`发消息人${contactName}:发了一个视频`);
52 | break;
53 | default:
54 | break;
55 | }
56 | } catch (error) {
57 | console.log('监听消息错误', error);
58 | }
59 | }
60 |
61 | async function onMessage(msg) {
62 | try {
63 | const room = msg.room(); // 是否为群消息
64 | // console.log('roomroomroomroom----------',room)
65 | const msgSelf = msg.self(); // 是否自己发给自己的消息
66 | if (msgSelf) return;
67 | if (room) {
68 | // 群消息
69 | const contact = msg.talker(); // 发消息人
70 | } else {
71 | await dispatchFriendFilterByMsgType(this, msg);
72 | }
73 | } catch (e) {
74 | console.log('监听消息失败', e);
75 | }
76 | }
77 |
78 | module.exports = onMessage;
79 |
--------------------------------------------------------------------------------
/wechat/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WechatBot
3 | * - https://github.com/gengchen528/wechatBot
4 | */
5 | const { WechatyBuilder } = require('wechaty');
6 | const qrcodeTerminal = require('qrcode-terminal');
7 | const schedule = require('../schedule/index');
8 | const config = require('../config/index');
9 | const onFriend = require('./handlers/on-friend');
10 | const onMessage = require('./handlers/on-message');
11 |
12 | // 延时函数,防止检测出类似机器人行为操作
13 | function delay(ms) {
14 | return new Promise((resolve) => {
15 | setTimeout(() => {
16 | resolve();
17 | }, ms);
18 | });
19 | }
20 |
21 | // 二维码生成
22 | function onScan(qrcode, status) {
23 | if (qrcode) {
24 | console.log(qrcode);
25 | const qrcodeImageUrl = [
26 | 'https://wechaty.js.org/qrcode/',
27 | encodeURIComponent(qrcode),
28 | ].join('');
29 | console.info('StarterBot', 'onScan: %s(%s) - %s', status, qrcodeImageUrl);
30 |
31 | qrcodeTerminal.generate(qrcode, { small: true }); // show qrcode on console
32 | console.info(`[${status}] ${qrcode}\nScan QR Code above to log in: `);
33 | } else {
34 | console.info(`[${status}]`);
35 | }
36 | }
37 |
38 | const bot = WechatyBuilder.build({
39 | name: 'puppet-wechat',
40 | puppetOptions: {
41 | uos: true, // 开启uos协议
42 | },
43 | puppet: 'wechaty-puppet-wechat',
44 | });
45 |
46 | // 创建微信心跳任务
47 | async function initHeart() {
48 | schedule.setSchedule(config.heartService.SENDDATE, async () => {
49 | let logMsg;
50 | const contact = (await bot.Contact.find({ name: config.service.vx }))
51 | || (await bot.Contact.find({ alias: config.heartService.vx }));
52 | const str = 'alive';
53 | try {
54 | logMsg = str;
55 | await delay(2000);
56 | await contact.say(str); // 发送消息
57 | } catch (e) {
58 | logMsg = e.message;
59 | }
60 | console.log(logMsg);
61 | });
62 | }
63 | // 登录
64 | async function onLogin(user) {
65 | console.log(`淘宝客微信bot${user}登录了`);
66 | const date = new Date();
67 | console.log(`当前容器时间:${date}`);
68 | await initHeart();
69 | }
70 |
71 | // 登出
72 | function onLogout(user) {
73 | console.log(`淘宝客微信bot${user} 已经登出`);
74 | }
75 |
76 | async function onReady() {
77 | console.log('ready');
78 | // const contactList = await bot.Contact.findAll()
79 | // console.info('Total number of contacts:', contactList.length)
80 | // for (let i = 0; i < contactList.length; i++) {
81 | // const contact = contactList[i]
82 | // console.log('contact',contact)
83 | // if (contact.type()==bot.Contact.Type.Individual) {
84 | // await utils.updateWechatFirend(contact)
85 | // }
86 | // }
87 | }
88 |
89 | bot.on('scan', onScan);
90 | bot.on('login', onLogin);
91 | bot.on('logout', onLogout);
92 | bot.on('message', onMessage);
93 | bot.on('friendship', onFriend);
94 | bot.on('ready', onReady);
95 |
96 | bot
97 | .start()
98 | .then(() => console.log('开始登陆微信'))
99 | .catch((e) => console.error(e));
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tbk-淘宝客返利系统(nodejs v16)
2 |
3 | ## 现有功能
4 |
5 | * 支持 淘宝、京东、拼多多转链([转链服务源码](/tbk-api-server/README.md)) ([api测试链接](https://tbk-api.xumeng.host/?url=%E3%80%90%E6%B7%98%E5%AE%9D%E3%80%91https://m.tb.cn/h.UKRgdJO?tk=Aa0UdRum9Sd%20CZ3457%20%E3%80%8C%E4%B8%80%E6%AC%A1%E6%80%A7%E9%A5%BA%E5%AD%90%E7%9B%92%E5%A4%96%E5%8D%96%E4%B8%93%E7%94%A8%E9%A4%90%E7%9B%92%E5%95%86%E7%94%A8%E9%80%9F%E5%86%BB%E6%B0%B4%E9%A5%BA%E7%9B%92%E9%A6%84%E9%A5%A8%E6%89%93%E5%8C%85%E7%9B%92%E5%88%86%E6%A0%BC%E5%8C%85%E8%A3%85%E7%9B%92%E5%AD%90%E3%80%8D%20%E7%82%B9%E5%87%BB%E9%93%BE%E6%8E%A5%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%20%E6%88%96%E8%80%85%20%E6%B7%98%E5%AE%9D%E6%90%9C%E7%B4%A2%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80))
6 | * 微信bot(是微信,不是公众号,当然公众号也实现了的)自动回复消息 转链、绑定订单(基于wechaty) ([微信bot源码](/wechat/README.md))
7 | * 返利系统订单管理后台(vue2、element-ui、express、mongoose...)(已开源) ([管理后台](https://github.com/pea-cake/tbk-manage.git))
8 | * ...
9 |
10 | ## 如何运行和部署
11 |
12 | ### 运行
13 |
14 | 1. 配置(./config/index.js) 阿里联盟、京东联盟、多多客 返佣账号appkey appsecret
15 |
16 | ```text
17 | JDconfig: {
18 | // 京东联盟
19 | appKey: "",
20 | appSecret: "",
21 | },
22 | TBconfig: {
23 | // 阿里联盟, 获取配置看这里https://github.com/pea-cake/tbk/issues/5
24 | appkey: "",
25 | appsecret: "",
26 | adzone_id: "", // 推广位pid 'mm_123_456_789' 的789就是adzone_id
27 | },
28 | PDDconfig: {
29 | // 多多客
30 | clientId: "",
31 | clientSecret: "",
32 | pid: "", // 推广位pid
33 | }
34 | ```
35 |
36 | 2. 安装mongodb数据库(自行查找方法)
37 |
38 | 3. 安装node环境(自行查找方法)
39 |
40 | 4. 安装所需包
41 |
42 | ```bash
43 | npm i
44 | ```
45 |
46 | 5. 运行转链api服务
47 |
48 | ```bash
49 | npm run server
50 | ```
51 |
52 | 6. 另启动一个终端,运行微信bot服务
53 |
54 | ```bash
55 | npm run wechat
56 | ```
57 |
58 | 7. 扫码登录微信即可
59 | 8. 还可使用pm2 运行
60 |
61 | ```bash
62 | npm install pm2 -g
63 | pm2 start tbk-api-server/index.js
64 | pm2 start wechat/index.js
65 | ```
66 |
67 | ### 部署
68 |
69 | ## docker
70 |
71 | 1. 已经安装docker
72 | 2. 完成配置(config/index.js)
73 | 3.
74 | * DockerFile
75 |
76 | ```bash
77 | docker build -t tbk:v1 .
78 | docker run -it tbk:v1 /bin/bash
79 | ```
80 |
81 | * docker远程仓库
82 |
83 | ```bash
84 | docker pull peacaker/tbk:1.0.0
85 | docker run -it peacaker/tbk:1.0.0 /bin/bash
86 | ```
87 |
88 | 4. 扫码登录微信即可
89 |
90 | ## 🧐🧐🧐
91 |
92 | * 这是一个能赚点小钱,即使不能赚钱,也能方便你省钱的系统
93 | * 已全部开源,欢迎使用,欢迎star,也期待你的添砖加瓦
94 | * ...
95 |
96 | ## 体验
97 | 1. 转链api体验:https://tbk-api.xumeng.host/?url=商品链接.
98 | 2. 微信扫码(风控暂时停用,可能由于玩chatgpt多了):
99 |
100 |
101 | ## 展示
102 |
103 | 微信消息部分:
104 |
105 |
106 | 管理系统截图:
107 |
108 | 
109 |
110 | 
111 | ...
112 |
113 | ## 感谢
114 |
115 | * 待添加
116 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/topUtil.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var util = require('util');
3 | var stream = require('stream');
4 |
5 | /**
6 | * hash
7 | *
8 | * @param {String} method hash method, e.g.: 'md5', 'sha1'
9 | * @param {String|Buffer} s
10 | * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'.
11 | * @return {String} md5 hash string
12 | * @public
13 | */
14 | exports.hash = function hash(method, s, format) {
15 | var sum = crypto.createHash(method);
16 | var isBuffer = Buffer.isBuffer(s);
17 | if (!isBuffer && typeof s === 'object') {
18 | s = JSON.stringify(sortObject(s));
19 | }
20 | sum.update(s, isBuffer ? 'binary' : 'utf8');
21 | return sum.digest(format || 'hex');
22 | };
23 |
24 | /**
25 | * md5 hash
26 | *
27 | * @param {String|Buffer} s
28 | * @param {String} [format] output string format, could be 'hex' or 'base64'. default is 'hex'.
29 | * @return {String} md5 hash string
30 | * @public
31 | */
32 | exports.md5 = function md5(s, format) {
33 | return exports.hash('md5', s, format);
34 | };
35 |
36 | exports.YYYYMMDDHHmmss = function (d, options) {
37 | d = d || new Date();
38 | if (!(d instanceof Date)) {
39 | d = new Date(d);
40 | }
41 |
42 | var dateSep = '-';
43 | var timeSep = ':';
44 | if (options) {
45 | if (options.dateSep) {
46 | dateSep = options.dateSep;
47 | }
48 | if (options.timeSep) {
49 | timeSep = options.timeSep;
50 | }
51 | }
52 | var date = d.getDate();
53 | if (date < 10) {
54 | date = '0' + date;
55 | }
56 | var month = d.getMonth() + 1;
57 | if (month < 10) {
58 | month = '0' + month;
59 | }
60 | var hours = d.getHours();
61 | if (hours < 10) {
62 | hours = '0' + hours;
63 | }
64 | var mintues = d.getMinutes();
65 | if (mintues < 10) {
66 | mintues = '0' + mintues;
67 | }
68 | var seconds = d.getSeconds();
69 | if (seconds < 10) {
70 | seconds = '0' + seconds;
71 | }
72 | return d.getFullYear() + dateSep + month + dateSep + date + ' ' +
73 | hours + timeSep + mintues + timeSep + seconds;
74 | };
75 |
76 | exports.checkRequired = function (params, keys) {
77 | if (!Array.isArray(keys)) {
78 | keys = [keys];
79 | }
80 | for (var i = 0, l = keys.length; i < l; i++) {
81 | var k = keys[i];
82 | if (!params.hasOwnProperty(k)) {
83 | var err = new Error('`' + k + '` required');
84 | err.name = "ParameterMissingError";
85 | return err;
86 | }
87 | }
88 | };
89 |
90 | exports.getApiResponseName = function(apiName){
91 | var reg = /\./g;
92 | if(apiName.match("^taobao"))
93 | apiName = apiName.substr(7);
94 | return apiName.replace(reg,'_')+"_response";
95 | }
96 |
97 | exports.getLocalIPAdress = function (){
98 | var interfaces = require('os').networkInterfaces();
99 | for(var devName in interfaces){
100 | var iface = interfaces[devName];
101 | for(var i=0;i -1;
10 | }
11 |
12 | /**
13 | * 校验SPI请求签名,不支持带上传文件的HTTP请求。
14 | *
15 | * @param bizParams 业务参数
16 | * @param httpHeaders http头部信息
17 | * @param secret APP密钥
18 | * @param charset 目标编码
19 | * @return boolean
20 | */
21 | exports.checkSignForSpi = function checkSignForSpi(url,body,httpHeaders,secret) {
22 | var ctype = httpHeaders['content-type'];
23 | if(!ctype){
24 | ctype = httpHeaders['Content-Type'];
25 | }
26 | if(!ctype){
27 | return false;
28 | }
29 |
30 | var charset = this.getResponseCharset(ctype);
31 | var urlParams = URL.parse(url).query.split("&");
32 | var bizParams = buildBizParams(urlParams);
33 | return checkSignInternal(bizParams,body,httpHeaders,secret,charset);
34 | }
35 |
36 | function buildBizParams(urlParams){
37 | var bizParams = {};
38 | for(var i =0; i < urlParams.length; i++){
39 | var params = urlParams[i].split("=");
40 | bizParams[params[0]] = params[1];
41 | }
42 | return bizParams;
43 | }
44 |
45 | /**
46 | * 检查发起SPI请求的来源IP是否是TOP机房的出口IP。
47 | *
48 | * @param request HTTP请求
49 | * @param topIpList TOP网关IP出口地址段列表,通过taobao.top.ipout.get获得
50 | *
51 | * @return boolean true表达IP来源合法,false代表IP来源不合法
52 | */
53 | exports.checkRemoteIp = function checkRemoteIp(httpHeaders,topIpList){
54 | var ip = null;
55 | for(var i = 0; i < ipFileds.length; i++){
56 | var realIp = httpHeaders[ipFileds[i]];
57 | if(realIp && 'unknown' != realIp.toLowerCase()){
58 | ip = realIp;
59 | break;
60 | }
61 | }
62 |
63 | if(ip){
64 | for(var i = 0; i < topIpList.length; i++) {
65 | if(ip == topIpList[i]){
66 | return true;
67 | }
68 | }
69 | }
70 | return false;
71 | }
72 |
73 | /**
74 | * 检查SPI请求到达服务器端是否已经超过指定的分钟数,如果超过则拒绝请求。
75 | *
76 | * @return boolean true代表不超过,false代表超过。
77 | */
78 | exports.checkTimestamp = function checkTimestamp(bizParams,minutes){
79 | var timestamp = bizParams['timestamp'];
80 | if(timestamp){
81 | var remove = new Date(timestamp).getTime();
82 | var local = new Date().getTime();
83 | return (local - remove) <= minutes * 60 * 1000;
84 | }
85 | return false;
86 | }
87 |
88 | function arrayConcat(bizParams,signHttpParams){
89 | if(signHttpParams){
90 | for(var i=0; i < signHttpParams.length; i++){
91 | bizParams[signHttpParams[i].key] = signHttpParams[i].value;
92 | }
93 | }
94 | }
95 |
96 | function checkSignInternal(bizParams,body,httpHeaders,secret,charset){
97 | var remoteSign = bizParams['sign'];
98 | arrayConcat(bizParams,getHeaderMap(httpHeaders));
99 | var sorted = Object.keys(bizParams).sort();
100 | var bastString = secret;
101 | var localSign ;
102 | for (var i = 0, l = sorted.length; i < l; i++) {
103 | var k = sorted[i];
104 | var value = bizParams[k];
105 | if(k == 'sign'){
106 | continue;
107 | }
108 | value = urlencode.decode(bizParams[k],charset);
109 |
110 | if(k == 'timestamp'){
111 | value = value.replace('+',' ');
112 | }
113 | k = iconv.encode(k,charset);
114 | bastString += k;
115 | bastString += value;
116 | }
117 | if(body){
118 | bastString += body;
119 | }
120 |
121 | bastString += secret;
122 | var buffer = iconv.encode(bastString,charset);
123 | localSign = util.md5(buffer).toUpperCase();
124 | return localSign == remoteSign;
125 | }
126 |
127 | function getHeaderMap(httpHeaders){
128 | var resultMap = {};
129 | var signList = httpHeaders['top-sign-list'];
130 | if(signList){
131 | var targetKeys = signList.split(",");
132 | targetKeys.forEach(function(target){
133 | resultMap[target] = httpHeaders[target];
134 | })
135 | }
136 | return resultMap;
137 | }
138 |
139 | exports.getResponseCharset = function getResponseCharset(ctype){
140 | var charset = 'UTF-8';
141 | if(ctype){
142 | var params = ctype.split(";");
143 | for(var i = 0; i < params.length; i++){
144 | var param = params[i].trim();
145 | if(param.startsWith('charset')){
146 | var pair = param.split("=");
147 | charset = pair[1].trim().toUpperCase();
148 | }
149 | }
150 | }
151 | if(charset && charset.toLowerCase().startsWith('GB')){
152 | charset = "GBK";
153 | }
154 | return charset;
155 | }
156 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/api/dingtalkClient.js:
--------------------------------------------------------------------------------
1 | var util = require('../topUtil.js');
2 | var RestClient = require('./network.js')
3 | var Stream = require('stream')
4 |
5 | /**
6 | * Dingtalk API Client.
7 | *
8 | * @param {Object} options.
9 | * @constructor
10 | */
11 |
12 | function DingtalkClient(options) {
13 | if (!(this instanceof DingtalkClient)) {
14 | return new DingtalkClient(options);
15 | }
16 | options = options || {};
17 | this.url = options.url || 'https://eco.taobao.com/router/rest';
18 | }
19 |
20 | /**
21 | * Invoke an api by method name.
22 | *
23 | * @param {String} method, method name
24 | * @param {Object} params
25 | * @param {Array} reponseNames, e.g. ['tmall_selected_items_search_response', 'tem_list', 'selected_item']
26 | * @param {Object} defaultResponse
27 | * @param {Function(err, response)} callback
28 | */
29 | DingtalkClient.prototype.invoke = function (type,method, params,reponseNames, callback) {
30 | params.method = method;
31 | this.request(type,params,function (err, result) {
32 | if (err) {
33 | return callback(err);
34 | }
35 | var response = result;
36 | if (reponseNames && reponseNames.length > 0) {
37 | for (var i = 0; i < reponseNames.length; i++) {
38 | var name = reponseNames[i];
39 | response = response[name];
40 | if (response === undefined) {
41 | break;
42 | }
43 | }
44 | }
45 | callback(null, response);
46 | });
47 | };
48 |
49 | /**
50 | * Request API.
51 | *
52 | * @param {Object} params
53 | * @param {String} [type='GET']
54 | * @param {Function(err, result)} callback
55 | * @public
56 | */
57 | DingtalkClient.prototype.request = function (type,params,callback) {
58 | var err = util.checkRequired(params, 'method');
59 | if (err) {
60 | return callback(err);
61 | }
62 | var args = {
63 | timestamp: this.timestamp(),
64 | format: 'json',
65 | v: '2.0',
66 | sign_method: 'md5'
67 | };
68 |
69 | var request = null;
70 | if(type == 'get'){
71 | request = RestClient.get(this.url);
72 | }else{
73 | request = RestClient.post(this.url);
74 | }
75 |
76 | for (var key in params) {
77 | if(typeof params[key] === 'object' && Buffer.isBuffer(params[key])){
78 | request.attach(key,params[key],{knownLength:params[key].length,filename:key})
79 | } else if(typeof params[key] === 'object' && Stream.Readable(params[key]) && !util.is(params[key]).a(String)){
80 | request.attach(key, params[key]);
81 | } else if(typeof params[key] === 'object'){
82 | args[key] = JSON.stringify(params[key]);
83 | } else{
84 | args[key] = params[key];
85 | }
86 | }
87 |
88 | args.sign = this.sign(args);
89 | for(var key in args){
90 | request.field(key, args[key]);
91 | }
92 |
93 | request.end(function(response){
94 | if(response.statusCode == 200){
95 | var data = response.body;
96 | var errRes = data && data.error_response;
97 | if (errRes) {
98 | callback(errRes, data);
99 | }else{
100 | callback(err, data);
101 | }
102 | }else{
103 | err = new Error('NetWork-Error');
104 | err.name = 'NetWork-Error';
105 | err.code = 15;
106 | err.sub_code = response.statusCode;
107 | callback(err, null);
108 | }
109 | })
110 | };
111 |
112 | /**
113 | * Get now timestamp with 'yyyy-MM-dd HH:mm:ss' format.
114 | * @return {String}
115 | */
116 | DingtalkClient.prototype.timestamp = function () {
117 | return util.YYYYMMDDHHmmss();
118 | };
119 |
120 | /**
121 | * Sign API request.
122 | * see http://open.taobao.com/doc/detail.htm?id=111#s6
123 | *
124 | * @param {Object} params
125 | * @return {String} sign string
126 | */
127 | DingtalkClient.prototype.sign = function (params) {
128 | var sorted = Object.keys(params).sort();
129 | var basestring = this.appsecret;
130 | for (var i = 0, l = sorted.length; i < l; i++) {
131 | var k = sorted[i];
132 | basestring += k + params[k];
133 | }
134 | basestring += this.appsecret;
135 | return util.md5(basestring).toUpperCase();
136 | };
137 |
138 | /**
139 | * execute top api
140 | */
141 | DingtalkClient.prototype.execute = function (apiname,params,callback) {
142 | this.invoke('post',apiname, params, [util.getApiResponseName(apiname)], callback);
143 | };
144 |
145 | DingtalkClient.prototype.get = function (apiname,params,callback) {
146 | this.invoke('get',apiname, params, [util.getApiResponseName(apiname)], callback);
147 | };
148 |
149 | exports.DingtalkClient = DingtalkClient;
150 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/tmc/tmcClient.js:
--------------------------------------------------------------------------------
1 | var WebSocket = require('ws');
2 | var Common = require('./common.js').Common;
3 | var TmcCodec = require('./tmcCodec.js').TmcCodec;
4 | var util = require('../topUtil.js');
5 |
6 | var codec = new TmcCodec();
7 | var client ;
8 | var TmcClient = function TmcClient(appkey,appsecret,groupName) {
9 | this._appkey = appkey;
10 | this._appsecret = appsecret;
11 | this._groupName = groupName;
12 | this._uri = 'ws://mc.api.taobao.com/';
13 | this._ws = null;
14 | this.isReconing = false;
15 | this._callback = null;
16 | this._interval = null;
17 | client = this;
18 | }
19 |
20 |
21 | TmcClient.prototype.createSign = function(timestamp){
22 | var basestring = this._appsecret;
23 | basestring += 'app_key' + this._appkey;
24 | basestring += 'group_name' + this._groupName;
25 | basestring += 'timestamp' + timestamp;
26 | basestring += this._appsecret;
27 | return util.md5(basestring).toUpperCase();
28 | }
29 |
30 | TmcClient.prototype.createConnectMessage = function() {
31 | var msg = {};
32 | msg.messageType = Common.enum.MessageType.CONNECT;
33 | var timestamp = Date.now();
34 | var content = {
35 | 'app_key':this._appkey,
36 | 'group_name':this._groupName,
37 | 'timestamp':timestamp+'',
38 | 'sign':this.createSign(timestamp),
39 | 'sdk':'NodeJS-1.2.0',
40 | 'intranet_ip':util.getLocalIPAdress()
41 | };
42 | msg.content = content;
43 | var buffer = codec.writeMessage(msg);
44 | return buffer;
45 | }
46 |
47 | TmcClient.prototype.createPullMessage = function() {
48 | var msg = {};
49 | msg.protocolVersion = 2;
50 | msg.messageType = Common.enum.MessageType.SEND;
51 | var content = {
52 | '__kind':Common.enum.MessageKind.PullRequest
53 | };
54 | msg.token = client._token;
55 | msg.content = content;
56 | var buffer = codec.writeMessage(msg);
57 | return buffer;
58 | }
59 |
60 | TmcClient.prototype.createConfirmMessage = function(id) {
61 | var msg = {};
62 | msg.protocolVersion = 2;
63 | msg.messageType = Common.enum.MessageType.SEND;
64 | var content = {
65 | '__kind':Common.enum.MessageKind.Confirm,
66 | 'id':id
67 | };
68 | msg.token = client._token;
69 | msg.content = content;
70 | var buffer = codec.writeMessage(msg);
71 | return buffer;
72 | }
73 |
74 | TmcClient.prototype.autoPull = function () {
75 | if(client._ws){
76 | client._ws.send(client.createPullMessage(), { binary: true, mask: true });
77 | }
78 | }
79 |
80 | TmcClient.prototype.reconnect = function (duration) {
81 | if(this.isReconing)
82 | return;
83 |
84 | this.isReconing = true;
85 | setTimeout(function timeout() {
86 | client.connect(client._uri,client._callback);
87 | }, duration);
88 | }
89 |
90 | TmcClient.prototype.connect = function(uri,callback) {
91 | this._uri = uri;
92 | this._callback = callback;
93 |
94 | if(client._ws != null){
95 | client._ws.close();
96 | }
97 |
98 | var ws = new WebSocket(this._uri);
99 |
100 | ws.on('open', function open() {
101 | client._ws = ws;
102 | this.send(client.createConnectMessage(), { binary: true, mask: true });
103 | if(!client._interval){
104 | client._interval = setInterval(client.autoPull, 5000);
105 | }
106 | });
107 |
108 | ws.on('message', function(data, flags) {
109 | if(flags.binary){
110 | var message = codec.readMessage(data);
111 | if(message != null && message.messageType == Common.enum.MessageType.CONNECTACK){
112 | if(message.statusCode){
113 | throw new Error(message.statusPhase);
114 | }else{
115 | client._token = message.token;
116 | console.log("top message channel connect success, token = "+message.token);
117 | }
118 | }else if(message != null && message.messageType == Common.enum.MessageType.SEND){
119 | var status = {success:true};
120 | try {
121 | client._callback(message,status);
122 | }catch (err) {
123 | status.success = false;
124 | }
125 | if(status.success){
126 | ws.send(client.createConfirmMessage(message.id), { binary: true, mask: true });
127 | }
128 | }else{
129 | console.log(message);
130 | }
131 | }
132 | });
133 |
134 | ws.on('ping',function(data, flags) {
135 | ws.pong(data,{mask: true },true);
136 | });
137 |
138 | ws.on('error',function(reason, errorCode) {
139 | console.log('tmc client error,reason : '+ reason + ' code : '+ errorCode);
140 | console.log('tmc client channel closed begin reconnect');
141 | client._ws = null;
142 | client.reconnect(15000);
143 | });
144 |
145 | ws.on('close', function close() {
146 | console.log('tmc client channel closed begin reconnect');
147 | client._ws = null;
148 | client.reconnect(3000);
149 | });
150 | this.isReconing = false;
151 | }
152 |
153 | exports.TmcClient = TmcClient;
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/api/topClient.js:
--------------------------------------------------------------------------------
1 | var util = require('../topUtil.js');
2 | var RestClient = require('./network.js')
3 | var Stream = require('stream')
4 |
5 | /**
6 | * TOP API Client.
7 | *
8 | * @param {Object} options, must set `appkey` and `appsecret`.
9 | * @constructor
10 | */
11 |
12 | function TopClient(options) {
13 | if (!(this instanceof TopClient)) {
14 | return new TopClient(options);
15 | }
16 | options = options || {};
17 | if (!options.appkey || !options.appsecret) {
18 | throw new Error('appkey or appsecret need!');
19 | }
20 | this.url = options.url || 'http://gw.api.taobao.com/router/rest';
21 | this.appkey = options.appkey;
22 | this.appsecret = options.appsecret;
23 | }
24 |
25 | /**
26 | * Invoke an api by method name.
27 | *
28 | * @param {String} method, method name
29 | * @param {Object} params
30 | * @param {Array} reponseNames, e.g. ['tmall_selected_items_search_response', 'tem_list', 'selected_item']
31 | * @param {Object} defaultResponse
32 | * @param {Function(err, response)} callback
33 | */
34 | TopClient.prototype.invoke = function (type,method, params,reponseNames,httpHeaders,callback) {
35 | params.method = method;
36 | this.request(type,params,httpHeaders,function (err, result) {
37 | if (err) {
38 | return callback(err);
39 | }
40 | var response = result;
41 | if (reponseNames && reponseNames.length > 0) {
42 | for (var i = 0; i < reponseNames.length; i++) {
43 | var name = reponseNames[i];
44 | response = response[name];
45 | if (response === undefined) {
46 | break;
47 | }
48 | }
49 | }
50 | callback(null, response);
51 | });
52 | };
53 |
54 | /**
55 | * Request API.
56 | *
57 | * @param {Object} params
58 | * @param {String} [type='GET']
59 | * @param {Function(err, result)} callback
60 | * @public
61 | */
62 | TopClient.prototype.request = function (type,params,httpHeaders,callback) {
63 | var err = util.checkRequired(params, 'method');
64 | if (err) {
65 | return callback(err);
66 | }
67 | var args = {
68 | timestamp: this.timestamp(),
69 | format: 'json',
70 | app_key: this.appkey,
71 | v: '2.0',
72 | sign_method: 'md5'
73 | };
74 |
75 | var request = null;
76 | if(type == 'get'){
77 | request = RestClient.get(this.url);
78 | }else{
79 | request = RestClient.post(this.url);
80 | }
81 |
82 | for (var key in params) {
83 | if(typeof params[key] === 'object' && Buffer.isBuffer(params[key])){
84 | request.attach(key,params[key],{knownLength:params[key].length,filename:key})
85 | } else if(typeof params[key] === 'object' && Stream.Readable(params[key]) && !util.is(params[key]).a(String)){
86 | request.attach(key, params[key]);
87 | } else if(typeof params[key] === 'object'){
88 | args[key] = JSON.stringify(params[key]);
89 | } else{
90 | args[key] = params[key];
91 | }
92 | }
93 |
94 | args.sign = this.sign(args);
95 |
96 | for(var key in httpHeaders) {
97 | request.header(key,httpHeaders[key]);
98 | }
99 |
100 | for(var key in args){
101 | request.field(key, args[key]);
102 | }
103 |
104 | request.end(function(response){
105 | if(response.statusCode == 200){
106 | var data = response.body;
107 | var errRes = data && data.error_response;
108 | if (errRes) {
109 | callback(errRes, data);
110 | }else{
111 | callback(err, data);
112 | }
113 | }else{
114 | err = new Error('NetWork-Error');
115 | err.name = 'NetWork-Error';
116 | err.code = 15;
117 | err.sub_code = response.statusCode;
118 | callback(err, null);
119 | }
120 | })
121 | };
122 |
123 | /**
124 | * Get now timestamp with 'yyyy-MM-dd HH:mm:ss' format.
125 | * @return {String}
126 | */
127 | TopClient.prototype.timestamp = function () {
128 | return util.YYYYMMDDHHmmss();
129 | };
130 |
131 | /**
132 | * Sign API request.
133 | * see http://open.taobao.com/doc/detail.htm?id=111#s6
134 | *
135 | * @param {Object} params
136 | * @return {String} sign string
137 | */
138 | TopClient.prototype.sign = function (params) {
139 | var sorted = Object.keys(params).sort();
140 | var basestring = this.appsecret;
141 | for (var i = 0, l = sorted.length; i < l; i++) {
142 | var k = sorted[i];
143 | basestring += k + params[k];
144 | }
145 | basestring += this.appsecret;
146 | return util.md5(basestring).toUpperCase();
147 | };
148 |
149 | /**
150 | * execute top api
151 | */
152 | TopClient.prototype.execute = function (apiname,params,callback) {
153 | this.invoke('post',apiname, params, [util.getApiResponseName(apiname)], [], callback);
154 | };
155 |
156 | TopClient.prototype.executeWithHeader = function (apiname,params,httpHeaders,callback) {
157 | this.invoke('post',apiname, params, [util.getApiResponseName(apiname)], httpHeaders || [], callback);
158 | };
159 |
160 | TopClient.prototype.get = function (apiname,params,callback) {
161 | this.invoke('get',apiname, params, [util.getApiResponseName(apiname)], [], callback);
162 | };
163 |
164 | exports.TopClient = TopClient;
165 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/tmc/tmcCodec.js:
--------------------------------------------------------------------------------
1 | var Common = require('./common.js').Common;
2 |
3 | var TmcCodec = function(){
4 |
5 | }
6 |
7 | TmcCodec.prototype.writeMessage = function(message) {
8 | var buffer = new Buffer(256);
9 | buffer.writeUInt8(2,0);
10 | buffer.writeUInt8(message.messageType,1);
11 | var index = 2;
12 |
13 | if(message.statusCode && message.statusCode > 0){
14 | buffer.writeUInt16LE(Common.enum.HeaderType.StatusCode,index);
15 | buffer.writeUInt32LE(message.statusCode,index+2);
16 | index += 6;
17 | }
18 |
19 | if(message.flag && message.flag > 0){
20 | buffer.writeUInt16LE(Common.enum.HeaderType.Flag,index);
21 | buffer.writeUInt32LE(message.flag,index+2);
22 | index += 6;
23 | }
24 |
25 | if(message.token){
26 | buffer.writeUInt16LE(Common.enum.HeaderType.Token,index);
27 | var length = Buffer.byteLength(message.token);
28 | buffer.writeUInt32LE(length,index+2);
29 | buffer.write(message.token,index+6,'UTF-8');
30 | index = index + length + 6;
31 | }
32 |
33 | if(message.content){
34 | for(var key in message.content){
35 | buffer.writeUInt16LE(Common.enum.HeaderType.Custom,index);
36 | var length = Buffer.byteLength(key);
37 | buffer.writeUInt32LE(length,index+2);
38 | buffer.write(key,index+6,'UTF-8');
39 | index = index + length + 6;
40 |
41 | length = Buffer.byteLength(message.content[key]);
42 | if(length == 0){
43 | buffer.writeUInt8(Common.enum.ValueFormat.Void,index);
44 | }else{
45 | var type = typeof message.content[key];
46 | if(key == '__kind'){
47 | buffer.writeUInt8(Common.enum.ValueFormat.Byte,index);
48 | buffer.writeUInt8(message.content[key],index+1);
49 | index += 2;
50 | } else if(type == 'number'){
51 | buffer.writeUInt8(Common.enum.ValueFormat.Int64,index);
52 | const big = ~~(message.content[key] / (0xFFFFFFFF + 1));
53 | const low = (message.content[key] % (0xFFFFFFFF + 1));
54 | buffer.writeUInt32LE(low,index + 1);
55 | buffer.writeUInt32LE(big,index + 5);
56 | index += 9;
57 | } else{
58 | buffer.writeUInt8(Common.enum.ValueFormat.CountedString,index);
59 | buffer.writeUInt32LE(length,index+1);
60 | buffer.write(message.content[key],index+5,'UTF-8');
61 | index = index + length + 5;
62 | }
63 | }
64 | }
65 | }
66 | buffer.writeUInt16LE(Common.enum.HeaderType.EndOfHeaders,index);
67 | return buffer.slice(0,index+2);
68 | }
69 |
70 | TmcCodec.prototype.readMessage = function(buffer) {
71 | var message = {};
72 | message.protocolVersion = buffer.readUInt8(0);
73 | message.messageType = buffer.readUInt8(1);
74 | try{
75 | var headerType = buffer.readUInt16LE(2);
76 | var index = 4;
77 | while(headerType != Common.enum.HeaderType.EndOfHeaders){
78 | if(headerType === Common.enum.HeaderType.StatusCode){
79 | message.statusCode = buffer.readUInt32LE(index);
80 | index += 4;
81 | } else if(headerType === Common.enum.HeaderType.StatusPhrase){
82 | var length = buffer.readUInt32LE(index);
83 | message.statusPhase = buffer.toString('UTF-8',index+4,index+length+4);
84 | index = index + length + 4;
85 | } else if(headerType === Common.enum.HeaderType.Flag){
86 | message.flag = buffer.readUInt32LE(index);
87 | index += 4;
88 | } else if(headerType === Common.enum.HeaderType.Token){
89 | var length = buffer.readUInt32LE(index);
90 | message.token = buffer.toString('UTF-8',index+4,index+length+4);
91 | index = index + length + 4;
92 | } else if(headerType === Common.enum.HeaderType.Custom){
93 | var length = buffer.readUInt32LE(index);
94 | var key = buffer.toString('UTF-8',index+4,index+length+4);
95 | index = index + length + 4;
96 |
97 | var format = buffer.readUInt8(index);
98 | index += 1;
99 | if(format == Common.enum.ValueFormat.Int64 || format == Common.enum.ValueFormat.Date){
100 | message[key] = buffer.readUInt32LE(index) + buffer.readUInt32LE(index+4) * 4294967296;
101 | index += 8;
102 | }else if(format == Common.enum.ValueFormat.CountedString){
103 | length = buffer.readUInt32LE(index);
104 | message[key] = buffer.toString('UTF-8',index+4,index+length+4);
105 | index = index + length + 4;
106 | }else if(format == Common.enum.ValueFormat.Byte){
107 | message[key] = buffer.readUInt8(index);
108 | index += 1;
109 | }else if(format == Common.enum.ValueFormat.Int32){
110 | message[key] = buffer.readUInt32LE(index);
111 | index += 4;
112 | }else if(format == Common.enum.ValueFormat.Int16){
113 | message[key] = buffer.readUInt16LE(index);
114 | index += 2;
115 | }
116 | }
117 | headerType = buffer.readUInt16LE(index);
118 | index += 2;
119 | }
120 | }catch (err) {
121 | console.log(err);
122 | return null;
123 | }
124 | return message;
125 | }
126 |
127 | exports.TmcCodec = TmcCodec;
128 |
129 |
--------------------------------------------------------------------------------
/utils/index.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 | const config = require('../config');
3 | const db_action = require('../wechat/db_action');
4 |
5 | function getHttpString(s) {
6 | let res = '';
7 | const reg = /(https?|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g;
8 | if (s) {
9 | res = s.match(reg) || '';
10 | }
11 | console.log(res);
12 | if (res && res.length > 0) {
13 | return res[0];
14 | }
15 | return res;
16 | }
17 | function getDay(date) {
18 | const date2 = new Date();
19 | const date1 = new Date(date);
20 | const iDays = parseInt((Math.abs(date2.getTime() - date1.getTime()) / 1000 / 60 / 60 / 24), 10);
21 | return iDays;
22 | }
23 |
24 | function formatDate(date) {
25 | const tempDate = new Date(date);
26 | const year = tempDate.getFullYear();
27 | const month = tempDate.getMonth() + 1;
28 | const day = tempDate.getDate();
29 | let hour = tempDate.getHours();
30 | let min = tempDate.getMinutes();
31 | let second = tempDate.getSeconds();
32 | const week = tempDate.getDay();
33 | let str = '';
34 | if (week === 0) {
35 | str = '星期日';
36 | } else if (week === 1) {
37 | str = '星期一';
38 | } else if (week === 2) {
39 | str = '星期二';
40 | } else if (week === 3) {
41 | str = '星期三';
42 | } else if (week === 4) {
43 | str = '星期四';
44 | } else if (week === 5) {
45 | str = '星期五';
46 | } else if (week === 6) {
47 | str = '星期六';
48 | }
49 | if (hour < 10) {
50 | hour = `0${hour}`;
51 | }
52 | if (min < 10) {
53 | min = `0${min}`;
54 | }
55 | if (second < 10) {
56 | second = `0${second}`;
57 | }
58 | return `${year}-${month}-${day}日 ${hour}:${min} ${str}`;
59 | }
60 | /**
61 | * 延时函数
62 | * @param {*} ms 毫秒
63 | */
64 | async function delay(ms) {
65 | return new Promise((resolve) => { setTimeout(() => { resolve(); }, ms); });
66 | }
67 | function checkIsJDOrder(str) {
68 | const re = /^[0-9]{12}$/;
69 | return re.test(str);
70 | }
71 | function checkIsTBOrder(str) {
72 | const re = /^[0-9]{19}$/;
73 | return re.test(str);
74 | }
75 | function checkIsPDDOrder(str) {
76 | if (str && str.length > 6 && str[6] === '-') {
77 | const re = /^[0-9|-]{22}$/;
78 | return re.test(str);
79 | }
80 | return false;
81 | }
82 | function checkOrder(str) {
83 | console.log('--------', str);
84 | if (str) {
85 | // eslint-disable-next-line no-param-reassign
86 | str = str.trim();
87 | if (checkIsJDOrder(str)) {
88 | return true;
89 | }
90 | if (checkIsTBOrder(str)) {
91 | return true;
92 | }
93 | if (checkIsPDDOrder(str)) {
94 | return true;
95 | }
96 | return false;
97 | }
98 | return false;
99 | }
100 | function getPlatform(url) {
101 | if (!url) {
102 | return '';
103 | }
104 | if (url.indexOf('jd.com') != -1) {
105 | return 'jd';
106 | }
107 | if (url.indexOf('tb.cn') != -1) {
108 | return 'tb';
109 | }
110 | if (url.indexOf('yangkeduo.com') != -1) {
111 | return 'pdd';
112 | }
113 | return '';
114 | }
115 | function formatReplayStr(content) {
116 | let result = `------【粉丝福利购】------
117 |
118 | 【商品名】:${content.goods_name || '你的宝贝'}
119 |
120 | 【电商平台】:${content.platform}
121 |
122 | 【优惠券】:${content.coupon || '无'}
123 |
124 | 【预计补贴】:${content.money || '无'}
125 |
126 | 【下单链接(或口令)】:
127 | ${content.url || '无'}
128 |
129 | 【注】:
130 | 1.通过打开以上链接下单 或 复制口令到对应平台app下单才能领补贴哦
131 |
132 | 2.下单完成后将订单号发送给我,用来绑定补贴订单
133 |
134 | 3.收货完成后,补贴将以微信🧧方式发送给您
135 |
136 | `;
137 | if (!content.coupon && !content.money) {
138 | result = '';
139 | }
140 | return result;
141 | }
142 |
143 | const updateWechatFirend = function (contact) {
144 | // eslint-disable-next-line no-async-promise-executor
145 | return new Promise(async (resolve, reject) => {
146 | const contactName = contact.name();
147 | const db_obj = await db_action.connectMongo('taobaoke');
148 | const customers = await db_action.selectCustomers(db_obj, { wx_id: contact.id });
149 | if (customers.length > 0) {
150 | // 客户已存在
151 | // 更新last_time字段
152 | const update_user_data = {
153 | new_name: contactName,
154 | last_time: moment().format('YYYY-MM-DD HH:mm:ss'),
155 | is_friend: contact.friend(),
156 | gender: contact.gender(),
157 | city: contact.city(),
158 | province: contact.province(),
159 | from: 'wechat',
160 | };
161 | const phone = contact.phone() || contact.payload.phone;
162 | if (phone && Array.isArray(phone) && phone.length > 0) {
163 | update_user_data.new_phone = phone;
164 | }
165 |
166 | let new_alias = await contact.alias();
167 | if (contact.payload.alias) {
168 | new_alias = contact.payload.alias;
169 | }
170 | if (new_alias) {
171 | update_user_data.new_alias = new_alias;
172 | }
173 |
174 | let avatar = await contact.avatar();
175 | if (contact.payload.avatar) {
176 | avatar = contact.payload.avatar;
177 | }
178 | if (avatar) {
179 | update_user_data.avatar = avatar;
180 | }
181 |
182 | const type = contact.type() || contact.payload.type;
183 | if (type != '') {
184 | update_user_data.type = type;
185 | }
186 | await db_action.updateCustomer(
187 | db_obj,
188 | {
189 | wx_id: contact.id,
190 | },
191 | update_user_data,
192 | );
193 | } else {
194 | // 插入新客户
195 | await db_action.insertCustomer(db_obj, {
196 | wx_id: contact.id,
197 | name: contactName,
198 | phone: contact.phone() || contact.payload.phone,
199 | alias: contact.alias() || contact.payload.alias,
200 | avatar: contact.avatar() || contact.payload.avatar,
201 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
202 | is_friend: contact.friend(),
203 | gender: contact.gender(),
204 | city: contact.city(),
205 | province: contact.province(),
206 | type: contact.type() || contact.payload.type,
207 | from: 'wechat',
208 | });
209 | }
210 | db_obj.conn.close();
211 | resolve();
212 | });
213 | };
214 | module.exports = {
215 | getHttpString,
216 | getDay,
217 | formatDate,
218 | delay,
219 | checkOrder,
220 | getPlatform,
221 | checkIsJDOrder,
222 | checkIsTBOrder,
223 | checkIsPDDOrder,
224 | formatReplayStr,
225 | updateWechatFirend,
226 | };
227 |
--------------------------------------------------------------------------------
/tbk-api-server/jd/index.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 | const { create } = require('./main');
3 | const db_action = require('../../wechat/db_action');
4 | const { setSchedule } = require('../../schedule');
5 | const config = require('../../config');
6 | const { delay } = require('../../utils');
7 |
8 | const { appKey } = config.JDconfig; // 京东联盟分配给你的app_key
9 | const { appSecret } = config.JDconfig; // 京东联盟分配给你的app_secret
10 |
11 | const api = create(
12 | {
13 | appKey,
14 | appSecret,
15 | serverUrl: 'https://api.jd.com/routerjson',
16 | },
17 | );
18 |
19 | const getSkuId = (url) => {
20 | const res = url.match(/product\/(\S*)\./);
21 | if (res && res.length > 1) {
22 | return res[1];
23 | }
24 | return '';
25 | };
26 |
27 | const getGoodsDetail = async (skuId) => api.request(
28 | 'jd.union.open.goods.promotiongoodsinfo.query',
29 | {
30 | skuIds: skuId,
31 | },
32 | ).then((res) => {
33 | // eslint-disable-next-line max-len
34 | const result = JSON.parse(res.jd_union_open_goods_promotiongoodsinfo_query_responce.queryResult);
35 | if (result.data && Array.isArray(result.data)) {
36 | return result.data[0];
37 | }
38 | return null;
39 | });
40 |
41 | const getLink = async (url) => api.request(
42 | 'jd.union.open.promotion.common.get',
43 | {
44 | promotionCodeReq: {
45 | materialId: url,
46 | siteId: '4100464321',
47 | },
48 | },
49 | ).then((res) => {
50 | const result = JSON.parse(res.jd_union_open_promotion_common_get_responce.getResult);
51 | console.log('jd_result', result);
52 | if (result.data) {
53 | return result.data;
54 | }
55 | return null;
56 | });
57 |
58 | const getOrderList = async (query = {}) => api.request(
59 | 'jd.union.open.order.row.query',
60 | {
61 | orderReq: {
62 | pageIndex: 1,
63 | pageSize: 500,
64 | type: 3,
65 | startTime: query.startTime || moment().subtract(1, 'hours').format('YYYY-MM-DD HH:mm:ss'),
66 | endTime: query.endTime || moment().subtract(5, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
67 | },
68 | },
69 | ).then((res) => {
70 | // console.log(res)
71 | const result = JSON.parse(res.jd_union_open_order_row_query_responce.queryResult);
72 | console.log(result);
73 | if (result.data) {
74 | return result.data;
75 | }
76 | return null;
77 | });
78 |
79 | const updateOrderDB = async function (orderList) {
80 | if (orderList && Array.isArray(orderList) && orderList.length > 0) {
81 | const db_obj = await db_action.connectMongo('taobaoke');
82 | for (let i = 0; i < orderList.length; i += 1) {
83 | const item = orderList[i];
84 | const orders = await db_action.selectOrders(db_obj, { order_id: item.orderId });
85 | if (orders.length > 0) {
86 | await db_action.updateOrder(db_obj, { order_id: item.orderId }, {
87 | order_id: item.orderId,
88 | last_time: moment().format('YYYY-MM-DD HH:mm:ss'),
89 | platform: 'jd',
90 | parentId: item.parentId,
91 | order_detail: item,
92 | real: 1,
93 | goods_name: item.skuName,
94 | goods_num: item.skuNum,
95 | orderTime: item.orderTime,
96 | finishTime: item.finishTime,
97 | modifyTime: item.modifyTime,
98 | subsidyFee: item.actualFee * config.RAKE,
99 | estimateCosPrice: item.estimateCosPrice, // 预估计佣金额
100 | estimateFee: item.estimateFee, // 预估全部佣金金额
101 | estimateCustomerFee: item.estimateFee * config.RAKE, // 预估客户获得佣金金额
102 | });
103 | } else {
104 | await db_action.insertOrder(db_obj, {
105 | order_id: item.orderId,
106 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
107 | platform: 'jd',
108 | parentId: item.parentId,
109 | order_detail: item,
110 | real: 1,
111 | goods_name: item.skuName,
112 | goods_num: item.skuNum,
113 | orderTime: item.orderTime,
114 | finishTime: item.finishTime,
115 | modifyTime: item.modifyTime,
116 | actualFee: item.actualFee,
117 | subsidyFee: item.actualFee * config.RAKE,
118 | estimateCosPrice: item.estimateCosPrice, // 预估计佣金额
119 | estimateFee: item.estimateFee, // 预估全部佣金金额
120 | estimateCustomerFee: item.estimateFee * config.RAKE, // 预估客户获得佣金金额
121 |
122 | });
123 | }
124 | }
125 | db_obj.conn.close();
126 | }
127 | };
128 | const updateOrders = async () => {
129 | console.log('更新订单中...');
130 | const orderList = await getOrderList();
131 | await updateOrderDB(orderList);
132 | console.log('更新订单完毕...');
133 | };
134 | const updateOrdersEveryMinute = () => {
135 | setSchedule('0 * * * * *', updateOrders);
136 | };
137 | async function updateOrderByDay(day = 1) {
138 | let result = [];
139 | for (let i = 24 * day; i > 0; i -= 1) {
140 | const startTime = moment().subtract(i, 'hours').format('YYYY-MM-DD HH:mm:ss');
141 | const endTime = moment().subtract(i - 1, 'hours').format('YYYY-MM-DD HH:mm:ss');
142 | const res = await getOrderList({ startTime, endTime });
143 | await delay(500);
144 | console.log(startTime, endTime, res);
145 | if (res) {
146 | result = result.concat(res);
147 | }
148 | }
149 | await updateOrderDB(result);
150 | }
151 | const getPromotionReply = async (url) => {
152 | const skuId = getSkuId(url);
153 | let result = '';
154 | const resultData = {
155 | platform: '京东',
156 | };
157 | let goods_detail = null;
158 | if (skuId) {
159 | goods_detail = await getGoodsDetail(skuId);
160 | } else {
161 | return '🙁🙁🙁该宝贝无优惠,试试其他宝贝';
162 | }
163 | if (!goods_detail) {
164 | return '🙁🙁🙁该宝贝无优惠,试试其他宝贝';
165 | }
166 | resultData.goods_name = goods_detail.goodsName || '';
167 | const linkInfo = await getLink(url);
168 | let price = goods_detail.unitPrice || 0;
169 | if (goods_detail.wlUnitPrice) {
170 | price = goods_detail.wlUnitPrice;
171 | }
172 | let rate = goods_detail.commisionRatioPc || 1;
173 | if (goods_detail.commisionRatioWl) {
174 | rate = goods_detail.commisionRatioWl;
175 | }
176 | const money = Number(price * (rate / 100) * config.RAKE).toFixed(2);
177 | if (money > 0) {
178 | resultData.money = money;
179 | result += `福利购 🧧🧧🧧 该商品有补贴${money}元 🧧🧧🧧`;
180 | }
181 | console.log('linkInfo', linkInfo);
182 | if (linkInfo.jCommand) {
183 | resultData.url = linkInfo.jCommand;
184 | result += `${linkInfo.jCommand}复制本条消息下单`;
185 | } else {
186 | resultData.url = linkInfo.clickURL;
187 | result += ` 下单点击:${linkInfo.clickURL}`;
188 | }
189 | result += "下单成功不要忘记发送订单号给我,领取补贴奖励哦";
190 | return resultData;
191 | };
192 |
193 | module.exports = {
194 | getPromotionReply,
195 | getOrderList,
196 | updateOrders,
197 | updateOrdersEveryMinute,
198 | updateOrderByDay,
199 | };
200 | // getPromotionReply('https://item.m.jd.com/product/4718621.html?&utm_source=iosapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=CopyURL&ad_od=share&gx=RnFmlWdYb2GKmdRP--tzDnAdULrU3SjFqz2Z')
201 |
--------------------------------------------------------------------------------
/tbk-api-server/pdd/index.js:
--------------------------------------------------------------------------------
1 | const PddSdk = require('pdd-sdk').default;
2 | const moment = require('moment');
3 | const db_action = require('../../wechat/db_action');
4 | const config = require('../../config');
5 | // const { setSchedule } = require('../schedule')
6 |
7 | // 以调用商品标准类目接口为例
8 | // 商品标准类目接口文档(https://open.pinduoduo.com/application/document/api?id=pdd.goods.cats.get)
9 | const apiClient = new PddSdk({
10 | clientId: config.PDDconfig.clientId,
11 | clientSecret: config.PDDconfig.clientSecret,
12 | });
13 |
14 | /**
15 | * 从链接里提取参数值
16 | * @param {String} url
17 | * @param {String} key
18 | * @returns {String}
19 | */
20 | const getUrlParamValByKey = (url, key) => {
21 | let clone_url = url;
22 | if (!clone_url) {
23 | return null;
24 | }
25 | clone_url = clone_url.replace(/&/g, "&");
26 | const str = clone_url.split('?')[1];
27 | const arr = str.split('&');
28 | let res = '';
29 | arr.forEach((element) => {
30 | const k = element.split('=')[0];
31 | const v = element.split('=')[1];
32 | if (key === k) {
33 | res = v;
34 | }
35 | });
36 | return res;
37 | };
38 |
39 | /**
40 | * 搜索商品 通过关键词
41 | * @param {*} keyword
42 | * @returns
43 | */
44 | const searchGoods = function (keyword) {
45 | return apiClient.execute('pdd.ddk.goods.search', { pid: config.PDDconfig.pid, keyword, page_size: 100 }).then((res) => {
46 | let goods_list = [];
47 | if (res.goods_search_response && res.goods_search_response.goods_list) {
48 | goods_list = res.goods_search_response.goods_list;
49 | }
50 | return goods_list;
51 | }).catch((error) => {
52 | console.log(error);
53 | });
54 | };
55 |
56 | /**
57 | * 多多进宝推广链接生成,通过goods_sign
58 | * @param {*} goods_sign
59 | * @returns
60 | */
61 | const generateUrl = function (goods_sign) {
62 | return apiClient.execute('pdd.ddk.goods.promotion.url.generate', { p_id: config.PDDconfig.pid, goods_sign_list: [goods_sign] }).then((res) => {
63 | let goods_list = [];
64 | // eslint-disable-next-line max-len
65 | if (res.goods_promotion_url_generate_response && res.goods_promotion_url_generate_response.goods_promotion_url_list) {
66 | goods_list = res.goods_promotion_url_generate_response.goods_promotion_url_list;
67 | }
68 | if (goods_list.length > 0) {
69 | return goods_list[0].short_url;
70 | }
71 | return null;
72 | }).catch((error) => {
73 | console.log(error);
74 | });
75 | };
76 |
77 | // 转链
78 | const getLink = function (source_url) {
79 | return apiClient.execute('pdd.ddk.goods.zs.unit.url.gen', { pid: config.PDDconfig.pid, source_url }).then((res) => res.goods_zs_unit_generate_response).catch((error) => {
80 | console.log(error);
81 | });
82 | };
83 |
84 | // 获取商品详情
85 | const getGoodsDetail = function (goods_sign) {
86 | return apiClient.execute('pdd.ddk.goods.detail', { pid: config.PDDconfig.pid, goods_sign }).then((res) => {
87 | if (res.goods_detail_response && res.goods_detail_response.goods_details) {
88 | return res.goods_detail_response.goods_details[0];
89 | }
90 | return null;
91 | }).catch((error) => {
92 | console.log(error);
93 | });
94 | };
95 |
96 | const getPromoteLink = async function (source_url) {
97 | const links = await getLink(source_url);
98 | if (!links) {
99 | return Promise.resolve('该宝贝暂无优惠');
100 | }
101 | const goods_sign = getUrlParamValByKey(links.url, 'goods_sign');
102 | if (!goods_sign) {
103 | return Promise.resolve('该宝贝暂无优惠');
104 | }
105 | const short_url = await generateUrl(goods_sign);
106 | const goods_detail = await getGoodsDetail(goods_sign);
107 | // const goods_name = goods_detail.goods_name || '';
108 | // const search_goods_list = await searchGoods(goods_name);
109 | return new Promise((resolve, reject) => {
110 | const resultData = {
111 | platform: '拼多多',
112 | };
113 | if (!goods_detail) {
114 | resolve('该宝贝暂无优惠');
115 | return;
116 | }
117 | resultData.goods_name = goods_detail.goods_name || '';
118 | const promotion_rate = goods_detail.promotion_rate > 0
119 | ? goods_detail.promotion_rate / 1000
120 | : 0;
121 | const goods_price = goods_detail.min_group_price / 100;
122 | const money = parseFloat(goods_price * promotion_rate).toFixed(2);
123 |
124 | let result = `您查询的拼多多宝贝:${goods_detail.goods_name} `;
125 | if (goods_detail.extra_coupon_amount > 0) {
126 | resultData.coupon = `${goods_detail.extra_coupon_amount / 100}元`;
127 | result += `有内部优惠券:${goods_detail.extra_coupon_amount / 100}元 `;
128 | }
129 | if (money > 0) {
130 | resultData.money = money || '';
131 | result += `预计补贴:${money}元 🧧🧧🧧 `;
132 | }
133 | resultData.url = short_url || links.short_url;
134 | result += `点击下单(有补贴):${resultData.url} 下单完成请将订单号发送给我,绑定补贴哦!`;
135 | // console.log(result);
136 | resolve(resultData);
137 | });
138 | };
139 |
140 | // 获取订单详情
141 | const getOrderDetail = function (order_sn) {
142 | return apiClient.execute('pdd.ddk.order.detail.get', { order_sn }).then((res) => {
143 | console.log(res.order_detail_response);
144 | if (res.order_detail_response) {
145 | return res.order_detail_response;
146 | }
147 | return null;
148 | }).catch((error) => {
149 | console.log(error);
150 | });
151 | };
152 | // 获取订单列表
153 | const getOrderList = function () {
154 | return apiClient.execute('pdd.ddk.order.list.range.get', {
155 | start_time: moment().subtract(30, 'days').format('YYYY-MM-DD HH:mm:ss'),
156 | end_time: moment().subtract(5, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
157 | }).then((res) => {
158 | console.log(res.order_list_get_response.order_list);
159 | if (res.order_list_get_response.order_list) {
160 | return res.order_list_get_response.order_list;
161 | }
162 | return null;
163 | }).catch((error) => {
164 | console.log(error);
165 | });
166 | };
167 | const updateOrders = async () => {
168 | console.log('更新订单中...');
169 | const orderList = await getOrderList();
170 | if (orderList && Array.isArray(orderList) && orderList.length > 0) {
171 | const db_obj = await db_action.connectMongo('taobaoke');
172 | for (let i = 0; i < orderList.length; i += 1) {
173 | const item = orderList[i];
174 | const orders = await db_action.selectOrders(db_obj, { order_id: item.order_sn });
175 | if (orders.length > 0) {
176 | await db_action.updateOrder(db_obj, { order_id: item.order_sn }, {
177 | order_id: item.order_sn,
178 | last_time: moment().format('YYYY-MM-DD HH:mm:ss'),
179 | platform: 'pdd',
180 | order_detail: item,
181 | real: 1,
182 | orderTime: item.order_create_time ? moment(item.order_create_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
183 | finishTime: item.order_receive_time ? moment(item.order_receive_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
184 | modifyTime: item.order_modify_at ? moment(item.order_modify_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
185 | actualFee: Number(item.promotion_amount / 100).toFixed(2) || '',
186 | subsidyFee: Number((item.promotion_amount / 100) * config.RAKE).toFixed(2) || '',
187 | });
188 | } else {
189 | await db_action.insertOrder(db_obj, {
190 | order_id: item.order_sn,
191 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
192 | platform: 'pdd',
193 | order_detail: item,
194 | real: 1,
195 | orderTime: item.order_create_time ? moment(item.order_create_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
196 | orderPayTime: item.order_pay_time ? moment(item.order_pay_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
197 | finishTime: item.order_receive_time ? moment(item.order_receive_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
198 | settleTime: item.order_settle_time ? moment(item.order_settle_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
199 | modifyTime: item.order_modify_at ? moment(item.order_modify_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
200 | actualFee: Number(item.promotion_amount / 100).toFixed(2) || '',
201 | subsidyFee: Number((item.promotion_amount / 100) * config.RAKE).toFixed(2) || '',
202 | });
203 | }
204 | }
205 | db_obj.conn.close();
206 | }
207 | console.log('更新订单完毕...');
208 | };
209 | // const updateOrdersEveryMinute = () => {
210 | // setSchedule('0 * * * * *', updateOrders)
211 | // }
212 |
213 | // let url = 'https://mobile.yangkeduo.com/goods2.html?_wvx=10&refer_share_uin=AKDNTPXMYINS5JHETQKJVRGXXM_GEXDA&refer_share_uid=8111396033479&share_uin=AKDNTPXMYINS5JHETQKJVRGXXM_GEXDA&page_from=205&_wv=41729&refer_share_id=Lr3nfeaNxcPdL0pcKcoKwkpBJzD7q090&refer_share_channel=copy_link&share_uid=8111396033479&pxq_secret_key=IK45E6VZ3JDTOZKAD6PP2WJAGVOZ6JBDUUY6CBTE2VKUMRFSQ54A&goods_id=261568776516'
214 | // getPromoteLink(url)
215 | // getOrderDetail('211221-345820404612637')
216 | module.exports = {
217 | getPromoteLink,
218 | getOrderDetail,
219 | updateOrders,
220 | // updateOrdersEveryMinute
221 | };
222 |
--------------------------------------------------------------------------------
/wechat/service/msg-filters.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const moment = require('moment');
3 | const config = require('../../config');
4 | const db_action = require('../db_action');
5 | const pdd = require('../../tbk-api-server/pdd');
6 | const jd = require('../../tbk-api-server/jd');
7 | const utils = require('../../utils');
8 | const msgs = require('../msg');
9 |
10 | function emptyMsg() {
11 | const msgArr = []; // 返回的消息列表
12 | const obj = { type: 1, content: '我在呢', url: '' }; // 消息主体
13 | msgArr.push(obj);
14 | return msgArr;
15 | }
16 |
17 | function officialMsg() {
18 | console.log('无效或官方消息,不做回复');
19 | return [{ type: 1, content: '', url: '' }];
20 | }
21 |
22 | function newFriendMsg({ name }) {
23 | console.log(`新添加好友:${name},默认回复`);
24 | return config.newFriendReplys || [{ type: 1, content: msgs.subscribeMsg, url: '' }];
25 | }
26 |
27 | function searchPromoteLink({
28 | that, msg, id, contact, name,
29 | }) {
30 | // 查转链链接
31 | return axios({
32 | url: 'http://127.0.0.1:3333',
33 | method: 'GET',
34 | params: { url: msg },
35 | }).then(async (res) => {
36 | console.log('转链结果', res.data);
37 | if (!res.data) {
38 | return [{ type: 1, content: '☹该商品暂无优惠和补贴,换一个试试', url: '' }];
39 | }
40 | const db_obj = await db_action.connectMongo('taobaoke');
41 | const customers = await db_action.selectCustomers(db_obj, { wx_id: id });
42 | const customer = customers[0];
43 | console.log('转链人:', customer, id);
44 | db_action.insertTurnLink(db_obj, {
45 | origin_url: msg,
46 | platform: utils.getPlatform(msg),
47 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
48 | turn_content: res.data,
49 | customer_id: customer._id.toString(),
50 | customer_name: customer.name || '',
51 | from: 'wechat',
52 | }).finally(() => {
53 | db_obj.conn.close();
54 | });
55 | return [
56 | { type: 1, content: res.data, url: '' },
57 | { type: 1, content: `下单完成后将【订单号】发送给我!`, url: '' },
58 | ];
59 | }).catch((err) => {
60 | console.log('err', err);
61 | });
62 | }
63 |
64 | async function bindOrder({ messages, id }) {
65 | // msg用户发的订单号
66 | let msg = messages;
67 | if (msg) {
68 | msg = String(msg).trim();
69 | }
70 | let replay_str = '请检查订单号';
71 | const db_obj = await db_action.connectMongo('taobaoke');
72 | const customers = await db_action.selectCustomers(db_obj, { wx_id: id });
73 | const customer = customers[0];
74 | console.log('绑定订单人:', customer, id);
75 | if (utils.checkIsJDOrder(msg)) {
76 | const orders = await db_action.selectOrders(db_obj, { order_id: msg });
77 | if (orders.length > 0 && orders[0].is_bind) {
78 | replay_str = '该京东补贴订单已被绑定,不要重复绑定!';
79 | } else {
80 | const real_orders = await jd.getOrderList();
81 | let real_order = null;
82 | if (real_orders && real_orders.length > 0) {
83 | real_orders.forEach((item) => {
84 | if (item.orderId == msg) {
85 | real_order = item;
86 | } else if (item.parentId == msg) {
87 | real_order = item;
88 | }
89 | });
90 | }
91 | if (real_order) {
92 | await db_action.insertOrder(db_obj, {
93 | order_id: msg,
94 | is_bind: 1,
95 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
96 | customer_id: customer._id.toString(),
97 | customer_name: customer.name || '',
98 | platform: 'jd',
99 | parentId: real_order ? real_order.parentId : null,
100 | order_detail: real_order,
101 | real: real_order ? 1 : 0,
102 | goods_name: real_order.skuName,
103 | goods_num: real_order.skuNum,
104 | orderTime: real_order.orderTime || null,
105 | finishTime: real_order.finishTime || null,
106 | modifyTime: real_order.modifyTime || null,
107 | actualFee: real_order.actualFee,
108 | subsidyFee: real_order.actualFee * config.RAKE,
109 | estimateCosPrice: real_order.estimateCosPrice, // 预估计佣金额
110 | estimateFee: real_order.estimateFee, // 预估全部佣金金额
111 | estimateCustomerFee: real_order.estimateFee * config.RAKE, // 预估客户获得佣金金额
112 | from: 'wechat',
113 | });
114 | replay_str = '您的京东补贴订单绑定成功!';
115 | if (real_order.estimateCosPrice) {
116 | replay_str += `预计本单补贴:${real_order.estimateFee * config.RAKE}元,收货后(5-15天)将以微信🧧方式发送给您,请注意查收!`;
117 | }
118 | } else {
119 | await db_action.insertOrder(db_obj, {
120 | order_id: msg,
121 | is_bind: 1,
122 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
123 | customer_id: customer._id.toString(),
124 | customer_name: customer.name || '',
125 | real: real_order ? 1 : 0,
126 | platform: 'jd',
127 | from: 'wechat',
128 | });
129 | replay_str = '您的京东补贴订单绑定成功!收货后(5-15天)将以微信🧧方式发送给您,请注意查收!';
130 | }
131 | }
132 | }
133 | // 淘宝订单号
134 | if (utils.checkIsTBOrder(msg)) {
135 | const orders = await db_action.selectOrders(db_obj, { order_id: msg });
136 | if (orders.length > 0) {
137 | replay_str = '该淘宝补贴订单已被绑定,不要重复绑定!';
138 | } else {
139 | await db_action.insertOrder(db_obj, {
140 | order_id: msg,
141 | is_bind: 1,
142 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
143 | customer_id: customer._id.toString(),
144 | customer_name: customer.name || '',
145 | platform: 'tb',
146 | from: 'wechat',
147 | });
148 | replay_str = '您的淘宝补贴订单绑定成功!收货后(5-15天)将以微信🧧方式发送给您,请注意查收!';
149 | }
150 | }
151 | // 拼多多订单号
152 | if (utils.checkIsPDDOrder(msg)) {
153 | const orders = await db_action.selectOrders(db_obj, { order_id: msg });
154 | if (orders.length > 0 && orders[0].is_bind) {
155 | replay_str = '该拼多多补贴订单已被绑定,不要重复绑定!';
156 | } else {
157 | const order_detail = await pdd.getOrderDetail(msg);
158 | if (order_detail) {
159 | await db_action.insertOrder(db_obj, {
160 | order_id: msg,
161 | is_bind: 1,
162 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
163 | customer_id: customer._id.toString(),
164 | customer_name: customer.name || '',
165 | platform: 'pdd',
166 | real: order_detail ? 1 : 0,
167 | order_detail,
168 | orderTime: order_detail.order_create_time ? moment(order_detail.order_create_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
169 | orderPayTime: order_detail.order_pay_time ? moment(order_detail.order_pay_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
170 | finishTime: order_detail.order_receive_time ? moment(order_detail.order_receive_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
171 | settleTime: order_detail.order_settle_time ? moment(order_detail.order_settle_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
172 | modifyTime: order_detail.order_modify_at ? moment(order_detail.order_modify_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '',
173 | actualFee: Number(order_detail.promotion_amount / 100).toFixed(2) || '',
174 | subsidyFee: Number((order_detail.promotion_amount / 100) * config.RAKE).toFixed(2) || '',
175 | from: 'wechat',
176 | });
177 | replay_str = '您的拼多多补贴订单绑定成功!';
178 | const subsidyFee = Number((order_detail.promotion_amount / 100) * config.RAKE)
179 | .toFixed(2);
180 | if (subsidyFee) {
181 | replay_str += `预计本单补贴:${subsidyFee}元,收货后(5-15天)将以微信🧧方式发送给您,请注意查收!`;
182 | }
183 | } else {
184 | await db_action.insertOrder(db_obj, {
185 | order_id: msg,
186 | is_bind: 1,
187 | create_time: moment().format('YYYY-MM-DD HH:mm:ss'),
188 | customer_id: customer._id.toString(),
189 | customer_name: customer.name || '',
190 | platform: 'pdd',
191 | real: order_detail ? 1 : 0,
192 | order_detail: null,
193 | from: 'wechat',
194 | });
195 | replay_str = '您的拼多多补贴订单绑定成功!收货后(5-15天)将以微信🧧方式发送给您,请注意查收!';
196 | }
197 | }
198 | }
199 | db_obj.conn.close();
200 | return [{ type: 1, content: replay_str, url: '' }];
201 | }
202 | /**
203 | * 关键词回复
204 | * @returns {Promise<*>}
205 | */
206 |
207 | async function keywordsMsg({ msg }) {
208 | let res = [];
209 | try {
210 | if (config.replyKeywords && config.replyKeywords.length > 0) {
211 | for (let i = 0; i < Object.keys(config.replyKeywords).length; i += 1) {
212 | const item = config.replyKeywords[i];
213 | if (item.reg === 2 && item.keywords.includes(msg)) {
214 | console.log(`精确匹配到关键词${msg},正在回复用户`);
215 | res = item.replys;
216 | }
217 | if (item.reg === 1) {
218 | for (let j = 0; j < Object.keys(item.keywords).length; j += 1) {
219 | const key = item.keywords[j];
220 | if (msg.includes(key)) {
221 | console.log(`模糊匹配到关键词${msg},正在回复用户`);
222 | res = item.replys;
223 | }
224 | }
225 | }
226 | }
227 | }
228 | return res;
229 | } catch (e) {
230 | console.log('keywordsMsg error:', e);
231 | return [];
232 | }
233 | }
234 |
235 | module.exports = {
236 | emptyMsg,
237 | officialMsg,
238 | newFriendMsg,
239 | keywordsMsg,
240 | searchPromoteLink,
241 | bindOrder,
242 | };
243 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/main.js:
--------------------------------------------------------------------------------
1 | const TopClient = require("./lib/api/topClient");
2 | const config = require("../../config");
3 |
4 | const client = new TopClient.TopClient({
5 | appkey: config.TBconfig.appkey,
6 | appsecret: config.TBconfig.appsecret,
7 | REST_URL: "http://gw.api.taobao.com/router/rest",
8 | });
9 |
10 | /**
11 | * 获取淘口令
12 | * @param {String} url 商品推广链接
13 | * @returns {String} 淘口令
14 | */
15 | const getTKL = (url) => {
16 | console.log("查询tkl:", url);
17 | return new Promise((resolve, reject) => {
18 | client.execute(
19 | "taobao.tbk.tpwd.create",
20 | {
21 | url,
22 | // 'text':'数据结构JSON示例',
23 | // 'logo':'数据结构JSON示例',
24 | // 'ext':'数据结构JSON示例',
25 | // 'user_id':'数据结构JSON示例'
26 | },
27 | (error, response) => {
28 | console.log("查询tkl_res", response);
29 | if (!error) {
30 | let res = "";
31 | if (response && response.data) {
32 | if (response.data.model) {
33 | res = response.data.model;
34 | }
35 | if (response.data.password_simple) {
36 | res = response.data.password_simple;
37 | }
38 | } else {
39 | res = null;
40 | }
41 | resolve(res);
42 | } else {
43 | resolve(null);
44 | }
45 | },
46 | );
47 | });
48 | };
49 |
50 | const getGoodsId = (click_url) => new Promise((resolve, reject) => {
51 | client.execute(
52 | "taobao.tbk.item.click.extract",
53 | {
54 | click_url,
55 | // 'platform':'1',
56 | // 'ip':'11.22.33.43'
57 | },
58 | (error, response) => {
59 | if (!error) {
60 | // console.log("extract====>", response);
61 | resolve();
62 | // eslint-disable-next-line max-len
63 | // if (response.results && Array.isArray(response.results.n_tbk_item) && response.results.n_tbk_item.length > 0) {
64 | // resolve(response.results.n_tbk_item[0]);
65 | // } else {
66 | // resolve(null);
67 | // }
68 | } else {
69 | reject(error);
70 | }
71 | },
72 | );
73 | });
74 |
75 | /**
76 | * 淘宝客商品详情查询(简版)
77 | * @param {String} id 商品id
78 | * @returns {Object}
79 | */
80 | const getGoodsDetail = (id) => new Promise((resolve, reject) => {
81 | client.execute(
82 | "taobao.tbk.item.info.get",
83 | {
84 | num_iids: id,
85 | // 'platform':'1',
86 | // 'ip':'11.22.33.43'
87 | },
88 | (error, response) => {
89 | if (!error) {
90 | // console.log("====>", response);
91 | // eslint-disable-next-line max-len
92 | if (
93 | response.results
94 | && Array.isArray(response.results.n_tbk_item)
95 | && response.results.n_tbk_item.length > 0
96 | ) {
97 | resolve(response.results.n_tbk_item[0]);
98 | } else {
99 | resolve(null);
100 | }
101 | } else {
102 | reject(error);
103 | }
104 | },
105 | );
106 | });
107 |
108 | /**
109 | * 通过商品id对比得到转链商品信息
110 | * @param {String} goods_id 商品id
111 | * @param {Array} goods_list 商品列表
112 | * @returns {String}
113 | */
114 | const getPromotionInfo = (goods_id, goods_list) => {
115 | let result = "";
116 | for (let i = 0; i < goods_list.length; i += 1) {
117 | if (goods_list[i].item_id == goods_id) {
118 | result = goods_list[i];
119 | break;
120 | }
121 | }
122 | return result;
123 | };
124 |
125 | /**
126 | * 通过关键词搜索商品
127 | * @param {String} keyword 关键词
128 | * @param {String} seller_ids 商家ids
129 | * @param {Object} search_goods_detail 查询的商品详情信息
130 | * @param {String} page_result_key 本地化业务入参-分页唯一标识,非首页的请求必传,值为上一页返回结果中的page_result_key字段值
131 | * @returns {Array}
132 | */
133 | const getGoodsListByKeyWord = (keyword, page = 1, page_result_key = "") =>
134 | // console.log("keywordkeywordkeywordkeyword", keyword);
135 | new Promise((resolve, reject) => {
136 | client.execute(
137 | "taobao.tbk.dg.material.optional",
138 | {
139 | // 'start_dsr': '10',
140 | page_size: "100",
141 | page_no: page,
142 | // 'platform': '1',
143 | // 'end_tk_rate': '500',
144 | // 'start_tk_rate': '6800',
145 | // 'end_price':'10',
146 | // 'start_price':'10',
147 | // 'is_overseas':'false',
148 | // 'is_tmall':'false',
149 | sort: "match",
150 | // 'itemloc':search_goods_detail.provcity.split(' ')[1]||'',
151 | // 'cat':'16,18',
152 | q: keyword,
153 | // 'material_id': '17004',
154 | // 'has_coupon': 'true',
155 | // 'ip':'13.2.33.4',
156 | adzone_id: config.TBconfig.adzone_id,
157 | // 'need_free_shipment':'true',
158 | // 'need_prepay': 'true',
159 | // 'include_pay_rate_30': 'true',
160 | // 'include_good_rate': 'true',
161 | // 'include_rfd_rate': 'true',
162 | // 'npx_level': '2',
163 | // 'end_ka_tk_rate': '0',
164 | // 'start_ka_tk_rate': '0',
165 | // 'device_encrypt': 'MD5',
166 | // 'device_value':'xxx',
167 | // 'device_type': 'IMEI',
168 | // 'lock_rate_end_time':'1567440000000',
169 | // 'lock_rate_start_time':'1567440000000',
170 | // 'longitude':'121.473701',
171 | // 'latitude':'31.230370',
172 | // 'city_code':'310000',
173 | // 'seller_ids':search_goods_detail.seller_id||'',
174 | // 'special_id':'2323',
175 | // 'relation_id':'3243',
176 | // 'page_result_key': page_result_key,
177 | // 'ucrowd_id':'1',
178 | // 'ucrowd_rank_items':'数据结构JSON示例',
179 | get_topn_rate: "0",
180 | },
181 | (error, response) => {
182 | if (!error) {
183 | const result_list = response.result_list || null;
184 | const map_data = result_list.map_data || null;
185 | if (Array.isArray(map_data) && map_data.length > 0) {
186 | // console.log(response.total_results);
187 | // console.log(map_data);
188 | resolve({
189 | total_results: response.total_results,
190 | map_data,
191 | });
192 | } else {
193 | resolve(null);
194 | }
195 | } else {
196 | reject(error);
197 | }
198 | },
199 | );
200 | });
201 |
202 | /**
203 | * 从链接里提取参数值
204 | * @param {String} url
205 | * @param {String} key
206 | * @returns {String}
207 | */
208 | const getUrlParamValByKey = (url, key) => {
209 | let clone_url = url;
210 | if (!clone_url) {
211 | return null;
212 | }
213 | clone_url = clone_url.replace(/&/g, "&");
214 | const str = clone_url.split("?")[1];
215 | const arr = str.split("&");
216 | let res = "";
217 | arr.forEach((element) => {
218 | const k = element.split("=")[0];
219 | const v = element.split("=")[1];
220 | if (key == k) {
221 | res = v;
222 | }
223 | });
224 | return res;
225 | };
226 |
227 | const httpString = (s) => {
228 | let res = s;
229 | const reg = /(https?|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g;
230 | res = res.match(reg);
231 | if (res && res.length > 0) {
232 | return res[0];
233 | }
234 | return res;
235 | };
236 | const getLastPromotionInfo = async (into_goods_url) => {
237 | let keyword = '';
238 | try {
239 | keyword = into_goods_url.match(/「([\S|\s]*)」/)[1];
240 | } catch (error) {
241 | return null;
242 | }
243 | let result_item = null;
244 | let is_err = false;
245 | const jsonData = await getGoodsListByKeyWord(keyword).catch((err) => {
246 | console.log("=====", err);
247 | is_err = true;
248 | });
249 | if (is_err) {
250 | return null;
251 | }
252 | const my_goods_list = jsonData.map_data;
253 | const total = jsonData.total_results;
254 | console.log("总数", total);
255 | console.log(my_goods_list)
256 | if (Array.isArray(my_goods_list) && my_goods_list.length > 0) {
257 | my_goods_list.some((item) => {
258 | if (item.title === keyword) {
259 | result_item = my_goods_list[0];
260 | return true;
261 | }
262 | });
263 | }
264 | if (!result_item) {
265 | let pages = 1;
266 | if (total > 100) {
267 | pages = Math.ceil(total / 100);
268 | }
269 | if (pages > 10) {
270 | pages = 10;
271 | }
272 | if (pages > 1) {
273 | for (let i = 2; i <= pages; i += 1) {
274 | let is_continue = false;
275 | const res = await getGoodsListByKeyWord(keyword, i).catch((err) => {
276 | is_continue = true;
277 | });
278 | if (!is_continue && res && res.map_data && res.map_data.length > 0) {
279 | res.map_data.some((item) => {
280 | if (item.title === keyword) {
281 | result_item = my_goods_list[0];
282 | return true;
283 | }
284 | });
285 | if (result_item) {
286 | break;
287 | }
288 | }
289 | }
290 | }
291 | }
292 | if (!result_item) {
293 | return null;
294 | }
295 | let goods_url = "https:";
296 | if (result_item.coupon_share_url) {
297 | goods_url += result_item.coupon_share_url;
298 | } else if (result_item.url) {
299 | goods_url += result_item.url;
300 | } else {
301 | return null;
302 | }
303 | const tkl_info = await getTKL(goods_url);
304 | result_item.tkl_info = tkl_info || null;
305 | return result_item;
306 | };
307 |
308 | module.exports = getLastPromotionInfo;
309 |
--------------------------------------------------------------------------------
/tbk-api-server/taobao/lib/api/network.js:
--------------------------------------------------------------------------------
1 | var StringDecoder = require('string_decoder').StringDecoder
2 | var FormData = require('form-data')
3 | var Stream = require('stream')
4 | var mime = require('mime')
5 | var path = require('path')
6 | var URL = require('url')
7 | var fs = require('fs')
8 |
9 | /**
10 | * Define form mime type
11 | */
12 | mime.define({
13 | 'application/x-www-form-urlencoded': ['form', 'urlencoded', 'form-data']
14 | })
15 |
16 | /**
17 | * Initialize our Rest Container
18 | */
19 | var RestClient = function (method, uri, headers, body, callback) {
20 | var restClient = function (uri, headers, body, callback) {
21 | var $this = {
22 | /**
23 | * Stream Multipart form-data request
24 | *
25 | * @type {Boolean}
26 | */
27 | _stream: false,
28 |
29 | /**
30 | * Container to hold multipart form data for processing upon request.
31 | *
32 | * @type {Array}
33 | * @private
34 | */
35 | _multipart: [],
36 |
37 | /**
38 | * Container to hold form data for processing upon request.
39 | *
40 | * @type {Array}
41 | * @private
42 | */
43 | _form: [],
44 |
45 | /**
46 | * Request option container for details about the request.
47 | *
48 | * @type {Object}
49 | */
50 | options: {
51 | /**
52 | * Url obtained from request method arguments.
53 | *
54 | * @type {String}
55 | */
56 | url: uri,
57 |
58 | /**
59 | * Method obtained from request method arguments.
60 | *
61 | * @type {String}
62 | */
63 | method: method,
64 |
65 | /**
66 | * List of headers with case-sensitive fields.
67 | *
68 | * @type {Object}
69 | */
70 | headers: {}
71 | },
72 |
73 | hasHeader: function (name) {
74 | var headers
75 | var lowercaseHeaders
76 |
77 | name = name.toLowerCase()
78 | headers = Object.keys($this.options.headers)
79 | lowercaseHeaders = headers.map(function (header) {
80 | return header.toLowerCase()
81 | })
82 |
83 | for (var i = 0; i < lowercaseHeaders.length; i++) {
84 | if (lowercaseHeaders[i] === name) {
85 | return headers[i]
86 | }
87 | }
88 |
89 | return false
90 | },
91 |
92 | field: function (name, value, options) {
93 | return handleField(name, value, options)
94 | },
95 |
96 | attach: function (name, path, options) {
97 | options = options || {}
98 | options.attachment = true
99 | return handleField(name, path, options)
100 | },
101 |
102 | rawField: function (name, value, options) {
103 | $this._multipart.push({
104 | name: name,
105 | value: value,
106 | options: options,
107 | attachment: options.attachment || false
108 | })
109 | },
110 |
111 | header: function (field, value) {
112 | if (is(field).a(Object)) {
113 | for (var key in field) {
114 | if (field.hasOwnProperty(key)) {
115 | $this.header(key, field[key])
116 | }
117 | }
118 |
119 | return $this
120 | }
121 |
122 | var existingHeaderName = $this.hasHeader(field)
123 | $this.options.headers[existingHeaderName || field] = value
124 |
125 | return $this
126 | },
127 |
128 | type: function (type) {
129 | $this.header('Content-Type', does(type).contain('/')
130 | ? type
131 | : mime.lookup(type))
132 | return $this
133 | },
134 |
135 | send: function (data) {
136 | var type = $this.options.headers[$this.hasHeader('content-type')]
137 |
138 | if ((is(data).a(Object) || is(data).a(Array)) && !Buffer.isBuffer(data)) {
139 | if (!type) {
140 | $this.type('form')
141 | type = $this.options.headers[$this.hasHeader('content-type')]
142 | $this.options.body = RestClient.serializers.form(data)
143 | } else if (~type.indexOf('json')) {
144 | $this.options.json = true
145 |
146 | if ($this.options.body && is($this.options.body).a(Object)) {
147 | for (var key in data) {
148 | if (data.hasOwnProperty(key)) {
149 | $this.options.body[key] = data[key]
150 | }
151 | }
152 | } else {
153 | $this.options.body = data
154 | }
155 | } else {
156 | $this.options.body = RestClient.Request.serialize(data, type)
157 | }
158 | } else if (is(data).a(String)) {
159 | if (!type) {
160 | $this.type('form')
161 | type = $this.options.headers[$this.hasHeader('content-type')]
162 | }
163 |
164 | if (type === 'application/x-www-form-urlencoded') {
165 | $this.options.body = $this.options.body
166 | ? $this.options.body + '&' + data
167 | : data
168 | } else {
169 | $this.options.body = ($this.options.body || '') + data
170 | }
171 | } else {
172 | $this.options.body = data
173 | }
174 |
175 | return $this
176 | },
177 |
178 | end: function (callback) {
179 | var Request
180 | var header
181 | var parts
182 | var form
183 |
184 | function handleRequestResponse (error, response, body) {
185 | var result = {}
186 | // Handle pure error
187 | if (error && !response) {
188 | result.error = error
189 |
190 | if (callback) {
191 | callback(result)
192 | }
193 |
194 | return
195 | }
196 |
197 | if (!response) {
198 | console.log('This is odd, report this action / request to: http://github.com/mashape/RestClient-nodejs')
199 |
200 | result.error = {
201 | message: 'No response found.'
202 | }
203 |
204 | if (callback) {
205 | callback(result)
206 | }
207 |
208 | return
209 | }
210 |
211 | // Create response reference
212 | result = response
213 |
214 | body = body || response.body
215 | result.raw_body = body
216 | result.headers = response.headers
217 |
218 | if (body) {
219 | type = RestClient.type(result.headers['content-type'], true)
220 | if (type) data = RestClient.Response.parse(body, type)
221 | else data = body
222 | }
223 | result.body = data
224 |
225 | ;(callback) && callback(result)
226 | }
227 |
228 | function handleGZIPResponse (response) {
229 | if (/^(deflate|gzip)$/.test(response.headers['content-encoding'])) {
230 | var unzip = zlib.createUnzip()
231 | var stream = new Stream()
232 | var _on = response.on
233 | var decoder
234 |
235 | // Keeping node happy
236 | stream.req = response.req
237 |
238 | // Make sure we emit prior to processing
239 | unzip.on('error', function (error) {
240 | // Catch the parser error when there is no content
241 | if (error.errno === zlib.Z_BUF_ERROR || error.errno === zlib.Z_DATA_ERROR) {
242 | stream.emit('end')
243 | return
244 | }
245 |
246 | stream.emit('error', error)
247 | })
248 |
249 | // Start the processing
250 | response.pipe(unzip)
251 |
252 | // Ensure encoding is captured
253 | response.setEncoding = function (type) {
254 | decoder = new StringDecoder(type)
255 | }
256 |
257 | // Capture decompression and decode with captured encoding
258 | unzip.on('data', function (buffer) {
259 | if (!decoder) return stream.emit('data', buffer)
260 | var string = decoder.write(buffer)
261 | if (string.length) stream.emit('data', string)
262 | })
263 |
264 | // Emit yoself
265 | unzip.on('end', function () {
266 | stream.emit('end')
267 | })
268 |
269 | response.on = function (type, next) {
270 | if (type === 'data' || type === 'end') {
271 | stream.on(type, next)
272 | } else if (type === 'error') {
273 | _on.call(response, type, next)
274 | } else {
275 | _on.call(response, type, next)
276 | }
277 | }
278 | }
279 | }
280 |
281 | function handleFormData (form) {
282 | for (var i = 0; i < $this._multipart.length; i++) {
283 | var item = $this._multipart[i]
284 |
285 | if (item.attachment && is(item.value).a(String)) {
286 | if (does(item.value).contain('http://') || does(item.value).contain('https://')) {
287 | item.value = RestClient.request(item.value)
288 | } else {
289 | item.value = fs.createReadStream(path.resolve(item.value))
290 | }
291 | }
292 | form.append(item.name, item.value, item.options)
293 | }
294 |
295 | return form
296 | }
297 |
298 | if ($this._multipart.length && !$this._stream && $this.options.method != 'get') {
299 | header = $this.options.headers[$this.hasHeader('content-type')]
300 | parts = URL.parse($this.options.url)
301 | form = new FormData()
302 |
303 | if (header) {
304 | $this.options.headers['content-type'] = header.split(';')[0] + '; boundary=' + form.getBoundary()
305 | } else {
306 | $this.options.headers['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary()
307 | }
308 |
309 | return handleFormData(form).submit({
310 | protocol: parts.protocol,
311 | port: parts.port,
312 | host: parts.hostname,
313 | path: parts.path,
314 | method: $this.options.method,
315 | headers: $this.options.headers
316 | }, function (error, response) {
317 | var decoder = new StringDecoder('utf8')
318 |
319 | if (error) {
320 | return handleRequestResponse(error, response)
321 | }
322 |
323 | if (!response.body) {
324 | response.body = ''
325 | }
326 |
327 | // Node 10+
328 | response.resume()
329 |
330 | // GZIP, Feel me?
331 | handleGZIPResponse(response)
332 |
333 | // Fallback
334 | response.on('data', function (chunk) {
335 | if (typeof chunk === 'string') response.body += chunk
336 | else response.body += decoder.write(chunk)
337 | })
338 |
339 | // After all, we end up here
340 | response.on('end', function () {
341 | return handleRequestResponse(error, response)
342 | })
343 | })
344 | }
345 |
346 | Request = RestClient.request($this.options, handleRequestResponse)
347 | Request.on('response', handleGZIPResponse)
348 |
349 | if ($this._multipart.length && $this._stream) {
350 | handleFormData(Request.form())
351 | }
352 |
353 | return Request
354 | }
355 | }
356 |
357 | /**
358 | * Alias for _.header_
359 | * @type {Function}
360 | */
361 | $this.headers = $this.header
362 |
363 | /**
364 | * Alias for _.header_
365 | *
366 | * @type {Function}
367 | */
368 | $this.set = $this.header
369 |
370 | /**
371 | * Alias for _.end_
372 | *
373 | * @type {Function}
374 | */
375 | $this.complete = $this.end
376 |
377 | /**
378 | * Aliases for _.end_
379 | *
380 | * @type {Object}
381 | */
382 |
383 | $this.as = {
384 | json: $this.end,
385 | binary: $this.end,
386 | string: $this.end
387 | }
388 |
389 | /**
390 | * Handles Multipart Field Processing
391 | *
392 | * @param {String} name
393 | * @param {Mixed} value
394 | * @param {Object} options
395 | */
396 | function handleField (name, value, options) {
397 | var serialized
398 | var length
399 | var key
400 | var i
401 |
402 | options = options || { attachment: false }
403 |
404 | if (is(name).a(Object)) {
405 | for (key in name) {
406 | if (name.hasOwnProperty(key)) {
407 | handleField(key, name[key], options)
408 | }
409 | }
410 | } else {
411 | if (is(value).a(Array)) {
412 | for (i = 0, length = value.length; i < length; i++) {
413 | serialized = handleFieldValue(value[i])
414 | if (serialized) {
415 | $this.rawField(name, serialized, options)
416 | }
417 | }
418 | } else if (value != null) {
419 | $this.rawField(name, handleFieldValue(value), options)
420 | }
421 | }
422 |
423 | return $this
424 | }
425 |
426 | /**
427 | * Handles Multipart Value Processing
428 | *
429 | * @param {Mixed} value
430 | */
431 | function handleFieldValue (value) {
432 | if (!(value instanceof Buffer || typeof value === 'string')) {
433 | if (is(value).a(Object)) {
434 | if (value instanceof Stream.Readable) {
435 | return value
436 | } else {
437 | return RestClient.serializers.json(value)
438 | }
439 | } else {
440 | return value.toString()
441 | }
442 | } else return value
443 | }
444 |
445 | if (headers && typeof headers === 'function') {
446 | callback = headers
447 | headers = null
448 | } else if (body && typeof body === 'function') {
449 | callback = body
450 | body = null
451 | }
452 |
453 | if (headers) $this.set(headers)
454 | if (body) $this.send(body)
455 |
456 | return callback ? $this.end(callback) : $this
457 | }
458 |
459 | return uri ? restClient(uri, headers, body, callback) : restClient
460 | }
461 |
462 | /**
463 | * Expose the underlying layer.
464 | */
465 | RestClient.request = require('request')
466 | RestClient.pipe = RestClient.request.pipe
467 |
468 |
469 | RestClient.type = function (type, parse) {
470 | if (typeof type !== 'string') return false
471 | return parse ? type.split(/ *; */).shift() : (RestClient.types[type] || type)
472 | }
473 |
474 |
475 | RestClient.trim = ''.trim
476 | ? function (s) { return s.trim() }
477 | : function (s) { return s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '') }
478 |
479 | RestClient.parsers = {
480 | string: function (data) {
481 | var obj = {}
482 | var pairs = data.split('&')
483 | var parts
484 | var pair
485 |
486 | for (var i = 0, len = pairs.length; i < len; ++i) {
487 | pair = pairs[i]
488 | parts = pair.split('=')
489 | obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1])
490 | }
491 |
492 | return obj
493 | },
494 |
495 | json: function (data) {
496 | try {
497 | data = JSON.parse(data)
498 | } catch (e) {}
499 |
500 | return data
501 | }
502 | }
503 |
504 | /**
505 | * Serialization methods for different data types.
506 | *
507 | * @type {Object}
508 | */
509 | RestClient.serializers = {
510 | form: function (obj) {
511 | return QueryString.stringify(obj)
512 | },
513 |
514 | json: function (obj) {
515 | return JSON.stringify(obj)
516 | }
517 | }
518 |
519 | /**
520 | * RestClient Request Utility Methods
521 | *
522 | * @type {Object}
523 | */
524 | RestClient.Request = {
525 | serialize: function (string, type) {
526 | var serializer = RestClient.firstMatch(type, RestClient.enum.serialize)
527 | return serializer ? serializer(string) : string
528 | }
529 | }
530 |
531 | RestClient.Response = {
532 | parse: function (string, type) {
533 | var parser = RestClient.firstMatch(type, RestClient.enum.parse)
534 | return parser ? parser(string) : string
535 | }
536 | }
537 |
538 | /**
539 | * Enum Structures
540 | *
541 | * @type {Object}
542 | */
543 | RestClient.enum = {
544 | serialize: {
545 | 'application/x-www-form-urlencoded': RestClient.serializers.form,
546 | 'application/json': RestClient.serializers.json,
547 | 'text/javascript': RestClient.serializers.json
548 | },
549 |
550 | parse: {
551 | 'application/x-www-form-urlencoded': RestClient.parsers.string,
552 | 'application/json': RestClient.parsers.json,
553 | 'text/javascript': RestClient.parsers.json
554 | },
555 |
556 | methods: [
557 | 'GET',
558 | 'HEAD',
559 | 'PUT',
560 | 'POST',
561 | 'PATCH',
562 | 'DELETE',
563 | 'OPTIONS'
564 | ]
565 | }
566 |
567 | RestClient.matches = function matches (string, map) {
568 | var results = []
569 |
570 | for (var key in map) {
571 | if (typeof map.length !== 'undefined') {
572 | key = map[key]
573 | }
574 |
575 | if (string.indexOf(key) !== -1) {
576 | results.push(map[key])
577 | }
578 | }
579 |
580 | return results
581 | }
582 |
583 | RestClient.firstMatch = function firstMatch (string, map) {
584 | return RestClient.matches(string, map)[0]
585 | }
586 |
587 | /**
588 | * Generate sugar for request library.
589 | *
590 | * This allows us to mock super-agent chaining style while using request library under the hood.
591 | */
592 | function setupMethod (method) {
593 | RestClient[method] = RestClient(method)
594 | }
595 |
596 | for (var i = 0; i < RestClient.enum.methods.length; i++) {
597 | var method = RestClient.enum.methods[i].toLowerCase()
598 | setupMethod(method)
599 | }
600 |
601 | function is (value) {
602 | return {
603 | a: function (check) {
604 | if (check.prototype) check = check.prototype.constructor.name
605 | var type = Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
606 | return value != null && type === check.toLowerCase()
607 | }
608 | }
609 | }
610 |
611 | /**
612 | * Simple Utility Methods for checking information about a value.
613 | *
614 | * @param {Mixed} value Could be anything.
615 | * @return {Object}
616 | */
617 | function does (value) {
618 | var arrayIndexOf = (Array.indexOf ? function (arr, obj, from) {
619 | return arr.indexOf(obj, from)
620 | } : function (arr, obj, from) {
621 | var l = arr.length
622 | var i = from ? parseInt((1 * from) + (from < 0 ? l : 0), 10) : 0
623 | i = i < 0 ? 0 : i
624 | for (; i < l; i++) if (i in arr && arr[i] === obj) return i
625 | return -1
626 | })
627 |
628 | return {
629 | contain: function (field) {
630 | if (is(value).a(String)) return value.indexOf(field) > -1
631 | if (is(value).a(Object)) return value.hasOwnProperty(field)
632 | if (is(value).a(Array)) return !!~arrayIndexOf(value, field)
633 | return false
634 | }
635 | }
636 | }
637 |
638 | /**
639 | * Expose the RestClient Container
640 | */
641 | module.exports = exports = RestClient
642 |
--------------------------------------------------------------------------------