wangrong
9 小时以前 0d90e20ec20f0aeee642756a7ebf9e8514174124
微信小程序插件支持
1个文件已添加
19个文件已修改
538 ■■■■■ 已修改文件
src/modules/app/controller/app/complain.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/app/controller/app/feedback.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/db.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/cs/controller/app/msg.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/cs/controller/app/session.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/cs/service/session.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/cs/socket/middleware/token.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/goods/controller/app/comment.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/controller/app/address.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/controller/app/info.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/controller/app/login.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/middleware/app.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/service/address.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/service/info.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/service/login.ts 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/service/wx.ts 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
typings/pay-wx.d.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
typings/plugin.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
typings/wx.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
typings/wxBaseMessage.d.ts 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/app/controller/app/complain.ts
@@ -11,13 +11,13 @@
  entity: AppComplainEntity,
  insertParam: ctx => {
    return {
      userId: ctx.user.id,
      userId: ctx.user.userId,
    };
  },
  pageQueryOp: {
    fieldEq: ['a.type'],
    where: ctx => {
      const userId = ctx.user.id;
      const userId = ctx.user.userId;
      return [['a.userId = :userId', { userId }]];
    },
  },
@@ -31,7 +31,7 @@
  @Post('/submit', { summary: '提交投诉举报' })
  async submit(@Body() info) {
    info.userId = this.ctx.user.id;
    info.userId = this.ctx.user.userId;
    await this.appComplainService.submit(info);
    return this.ok();
  }
src/modules/app/controller/app/feedback.ts
@@ -11,13 +11,13 @@
  entity: AppFeedbackEntity,
  insertParam: ctx => {
    return {
      userId: ctx.user.id,
      userId: ctx.user.userId,
    };
  },
  pageQueryOp: {
    fieldEq: ['a.type'],
    where: ctx => {
      const userId = ctx.user.id;
      const userId = ctx.user.userId;
      return [['a.userId = :userId', { userId }]];
    },
  },
@@ -31,7 +31,7 @@
  @Post('/submit', { summary: '提交意见反馈' })
  async submit(@Body() info) {
    info.userId = this.ctx.user.id;
    info.userId = this.ctx.user.userId;
    await this.appFeedbackService.submit(info);
    return this.ok();
  }
src/modules/base/db.json
@@ -118,6 +118,20 @@
          "data": "400",
          "dataType": 0,
          "remark": "逐台查询设备时的间隔,避免触发 QPS 限流"
      },
      {
          "keyName": "wx.mini.appId",
          "name": "小程序AppId",
          "data": "wx88c344d1b0db243a",
          "dataType": 0,
          "remark": "微信小程序 AppId,用于手机号登录"
      },
      {
          "keyName": "wx.mini.appSecret",
          "name": "小程序AppSecret",
          "data": "",
          "dataType": 0,
          "remark": "微信公众平台-开发管理-开发设置中的 AppSecret,用于用授权码换取手机号"
      }
  ],
  "base_sys_conf": [
src/modules/cs/controller/app/msg.ts
@@ -38,7 +38,7 @@
      },
    ],
    where: ctx => {
      return [['d.userId = :userId', { userId: ctx.user?.id }]];
      return [['d.userId = :userId', { userId: ctx.user?.userId }]];
    },
  },
})
@@ -51,7 +51,9 @@
  @Get('/unreadCount', { summary: '未读消息数' })
  async unreadCount() {
    return this.ok(await this.csMsgService.unreadCount(this.ctx.user?.id, 1));
    return this.ok(
      await this.csMsgService.unreadCount(this.ctx.user?.userId, 1)
    );
  }
  @Post('/read', { summary: '标记已读' })
src/modules/cs/controller/app/session.ts
@@ -15,11 +15,11 @@
  @Get('/detail', { summary: '会话详情' })
  async detail() {
    return this.ok(await this.csSessionService.detail(this.ctx.user?.id));
    return this.ok(await this.csSessionService.detail(this.ctx.user?.userId));
  }
  @Post('/create', { summary: '创建会话' })
  async create() {
    return this.ok(await this.csSessionService.create(this.ctx.user?.id));
    return this.ok(await this.csSessionService.create(this.ctx.user?.userId));
  }
}
src/modules/cs/service/session.ts
@@ -1,5 +1,5 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { CsSessionEntity } from '../entity/session';
@@ -26,6 +26,9 @@
   * @returns
   */
  async create(userId: number) {
    if (!userId) {
      throw new CoolCommException('用户不存在');
    }
    const check = await this.detail(userId);
    if (check) {
      return check;
src/modules/cs/socket/middleware/token.ts
@@ -1,6 +1,7 @@
import { Config, Middleware } from '@midwayjs/core';
import { Config, Inject, Middleware } from '@midwayjs/core';
import { NextFunction } from '@midwayjs/socketio';
import * as jwt from 'jsonwebtoken';
import { UserInfoService } from '../../../user/service/info';
@Middleware()
export class CsSocketTokenMiddleware {
@@ -9,6 +10,9 @@
  @Config('module.base.jwt')
  adminJwtConfig;
  @Inject()
  userInfoService: UserInfoService;
  resolve() {
    return async (ctx, next: NextFunction) => {
@@ -20,8 +24,13 @@
          token,
          isAdmin ? this.adminJwtConfig.secret : this.appJwtConfig.secret
        );
        let userId = isAdmin ? data.userId : data.id;
        if (!isAdmin) {
          const info = await this.userInfoService.person(data.id);
          userId = info?.id;
        }
        ctx.connData = {
          userId: isAdmin ? data.userId : data.id,
          userId,
          isAdmin,
        };
      } catch (error) {
src/modules/goods/controller/app/comment.ts
@@ -21,7 +21,7 @@
  entity: GoodsCommentEntity,
  insertParam: ctx => {
    return {
      userId: ctx.user.id,
      userId: ctx.user.userId,
    };
  },
  pageQueryOp: {
@@ -49,7 +49,7 @@
    @Body('orderId') orderId: number
  ) {
    return this.ok(
      await this.goodsCommentService.submit(this.ctx.user.id, orderId, data)
      await this.goodsCommentService.submit(this.ctx.user.userId, orderId, data)
    );
  }
}
src/modules/user/controller/app/address.ts
@@ -13,12 +13,12 @@
  service: UserAddressService,
  insertParam: ctx => {
    return {
      userId: ctx.user.id,
      userId: ctx.user.userId,
    };
  },
  pageQueryOp: {
    where: async ctx => {
      return [['userId =:userId', { userId: ctx.user.id }]];
      return [['userId =:userId', { userId: ctx.user.userId }]];
    },
    addOrderBy: {
      isDefault: 'DESC',
@@ -34,6 +34,8 @@
  @Get('/default', { summary: '默认地址' })
  async default() {
    return this.ok(await this.userAddressService.default(this.ctx.user.id));
    return this.ok(
      await this.userAddressService.default(this.ctx.user.userId)
    );
  }
}
src/modules/user/controller/app/info.ts
@@ -1,4 +1,10 @@
import { CoolController, BaseController } from '@cool-midway/core';
import {
  CoolController,
  BaseController,
  CoolUrlTag,
  CoolTag,
  TagTypes,
} from '@cool-midway/core';
import { Body, Get, Inject, Post } from '@midwayjs/core';
import { UserInfoService } from '../../service/info';
import { UserInfoEntity } from '../../entity/info';
@@ -6,6 +12,7 @@
/**
 * 用户信息
 */
@CoolUrlTag()
@CoolController({
  api: [],
  entity: UserInfoEntity,
@@ -63,9 +70,9 @@
    );
  }
  @CoolTag(TagTypes.IGNORE_TOKEN)
   @Post('/dashboard', { summary: '获取用户碳积分信息' })
  async dashboard(@Body() body) {
    console.log(body)
    const {unionid} = body;
    return this.ok(
      await this.userInfoService.dashboard(
src/modules/user/controller/app/login.ts
@@ -59,15 +59,21 @@
  }
  @CoolTag(TagTypes.IGNORE_TOKEN)
  @Post('/miniPhone', { summary: '绑定小程序手机号' })
  @Post('/miniPhone', { summary: '小程序手机号授权登录' })
  async miniPhone(@Body() body) {
    const { code, encryptedData, iv } = body;
    const { code, encryptedData, iv, loginCode } = body;
    return this.ok(
      await this.userLoginService.miniPhone(code, encryptedData, iv)
      await this.userLoginService.miniPhone(code, encryptedData, iv, loginCode)
    );
  }
  @CoolTag(TagTypes.IGNORE_TOKEN)
  @Post('/miniAuto', { summary: '小程序静默登录' })
  async miniAuto(@Body('code') code: string) {
    return this.ok(await this.userLoginService.miniAuto(code));
  }
  @CoolTag(TagTypes.IGNORE_TOKEN)
  @Get('/captcha', { summary: '图片验证码' })
  async captcha(
    @Query('width') width: number,
src/modules/user/middleware/app.ts
@@ -5,6 +5,7 @@
import * as _ from 'lodash';
import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core';
import { Utils } from '../../../comm/utils';
import { UserInfoService } from '../service/info';
/**
 * 用户
@@ -38,14 +39,30 @@
      let { url } = ctx;
      url = url.replace(this.prefix, '').split('?')[0];
      if (_.startsWith(url, '/app/')) {
        const token = ctx.get('Authorization');
        let token = ctx.get('Authorization') || '';
        token = token.replace(/^Bearer\s+/i, '').trim();
        try {
          ctx.user = jwt.verify(token, this.jwtConfig.secret);
          if (ctx.user.isRefresh) {
            throw new CoolCommException('登录失效~');
        } catch (error) {
          ctx.user = undefined;
          if (token) {
            console.error('[user] token无效:', (error as Error).message);
          }
        }
        if (ctx.user?.isRefresh) {
          ctx.user = undefined;
        } else if (ctx.user?.id) {
          try {
            // 令牌里的 id 是 unionid(手机号登录时就是手机号),整型 userId 列要用用户主键
            const userInfoService = await ctx.requestContext.getAsync(
              UserInfoService
            );
            const info = await userInfoService.person(ctx.user.id);
            if (info?.id) {
              ctx.user.userId = info.id;
          }
        } catch (error) {}
        }
        // 使用matchUrl方法来检查URL是否应该被忽略
        const isIgnored = this.ignoreUrls.some(pattern =>
          this.utils.matchUrl(pattern, url)
src/modules/user/service/address.ts
@@ -27,7 +27,7 @@
  async list() {
    return this.userAddressEntity
      .createQueryBuilder()
      .where('userId = :userId ', { userId: this.ctx.user.id })
      .where('userId = :userId ', { userId: this.ctx.user.userId })
      .addOrderBy('isDefault', 'DESC')
      .getMany();
  }
@@ -44,7 +44,7 @@
          .createQueryBuilder()
          .update()
          .set({ isDefault: false })
          .where('userId = :userId ', { userId: this.ctx.user.id })
          .where('userId = :userId ', { userId: this.ctx.user.userId })
          .andWhere('id != :id', { id: data.id })
          .execute();
      }
src/modules/user/service/info.ts
@@ -50,8 +50,17 @@
   * @returns
   */
  async person(id) {
    const info = await this.userInfoEntity.findOneBy({ id: Equal(id) });
    //delete info.password;
    const idText = id === undefined || id === null ? '' : String(id);
    // 手机号登录签发的是 unionid(手机号),不能拿去查整型主键
    let info = null;
    if (/^\d{1,10}$/.test(idText)) {
      info = await this.userInfoEntity.findOneBy({ id: Equal(Number(idText)) });
    }
    if (!info && idText) {
      info = await this.userInfoEntity.findOneBy({
        unionid: Equal(idText),
      });
    }
    return info;
  }
@@ -60,8 +69,12 @@
   * @param userId
   */
  async logoff(userId: number) {
    const info = await this.person(userId);
    if (!info) {
      throw new CoolCommException('用户不存在');
    }
    await this.userInfoEntity.update(
      { id: userId },
      { id: info.id },
      {
        status: 2,
        phone: "",
@@ -92,7 +105,7 @@
      }
    } catch (err) { }
    try {
      return await this.userInfoEntity.update({ id }, param);
      return await this.userInfoEntity.update({ id: info.id }, param);
    } catch (err) {
      throw new CoolCommException('更新失败,参数错误或者手机号已存在');
    }
@@ -116,7 +129,7 @@
      throw new CoolCommException('验证码错误');
    }
    await this.userInfoEntity.update(user.unionid, {
    await this.userInfoEntity.update(user.id, {
      password: md5(password),
    });
  }
@@ -132,7 +145,11 @@
    if (!check) {
      throw new CoolCommException('验证码错误');
    }
    await this.userInfoEntity.update({ id: userId }, { phone });
    const info = await this.person(userId);
    if (!info) {
      throw new CoolCommException('用户不存在');
    }
    await this.userInfoEntity.update({ id: info.id }, { phone });
  }
  /**
src/modules/user/service/login.ts
@@ -78,12 +78,85 @@
   * @param encryptedData
   * @param iv
   */
  async miniPhone(code, encryptedData, iv) {
  async miniPhone(code, encryptedData, iv, loginCode?) {
    const phone = await this.userWxService.miniPhone(code, encryptedData, iv);
    if (phone) {
      return await this.phone(phone);
    } else {
    if (!phone) {
      throw new CoolCommException('获得手机号失败,请检查配置');
    }
    const token = await this.phone(phone);
    await this.bindMiniOpenid(phone, loginCode);
    return {
      ...token,
      phone,
    };
  }
  /**
   * 小程序再次打开时,用 openid 静默登录
   */
  async miniAuto(code: string) {
    if (!code) {
      return null;
    }
    const session = await this.userWxService.miniSession(code);
    if (!session?.openid || session.errcode) {
      return null;
    }
    const wx = await this.userWxEntity.findOneBy({
      openid: Equal(session.openid),
    });
    if (!wx?.unionid) {
      return null;
    }
    const user = await this.userInfoEntity.findOneBy({
      unionid: Equal(wx.unionid),
    });
    if (!user?.phone || user.status === 2) {
      return null;
    }
    const token = await this.token({ id: user.unionid });
    return {
      ...token,
      phone: user.phone,
      nickName: user.nickName,
      avatarUrl: user.avatarUrl,
      unionid: user.unionid,
      id: user.id,
    };
  }
  /**
   * 把当前微信 openid 记到手机号账号上,供下次静默登录
   */
  async bindMiniOpenid(phone: string, loginCode?: string) {
    if (!loginCode) {
      return;
    }
    try {
      const session = await this.userWxService.miniSession(loginCode);
      if (!session?.openid || session.errcode) {
        return;
      }
      const user = await this.userInfoEntity.findOneBy({
        phone: Equal(phone),
      });
      if (!user) {
        return;
      }
      const unionid = user.unionid || session.unionid || phone;
      const exist = await this.userWxEntity.findOneBy({
        openid: Equal(session.openid),
      });
      await this.userWxEntity.save({
        id: exist?.id,
        openid: session.openid,
        unionid,
        nickName: user.nickName,
        avatarUrl: user.avatarUrl,
        type: 0,
      });
    } catch (err) {
      // 绑定 openid 失败不影响本次手机号登录
    }
  }
@@ -115,7 +188,8 @@
      user = {
        phone,
        unionid: phone,
        loginType: 2,
        loginType: 0,
        departmentId: 1,
        nickName: phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2'),
      };
      await this.userInfoEntity.insert(user);
src/modules/user/service/wx.ts
@@ -6,6 +6,7 @@
import * as moment from 'moment';
import { Equal, Repository } from 'typeorm';
import { v1 as uuid } from 'uuid';
import { BaseSysParamEntity } from '../../base/entity/sys/param';
import { PluginService } from '../../plugin/service/info';
import { UserInfoEntity } from '../entity/info';
import { UserWxEntity } from '../entity/wx';
@@ -18,14 +19,22 @@
  @Config('module.user')
  config;
  @Config('wxMini')
  wxMini: { appId?: string; appSecret?: string };
  @InjectEntityModel(UserInfoEntity)
  userInfoEntity: Repository<UserInfoEntity>;
  @InjectEntityModel(UserWxEntity)
  userWxEntity: Repository<UserWxEntity>;
  @InjectEntityModel(BaseSysParamEntity)
  baseSysParamEntity: Repository<BaseSysParamEntity>;
  @Inject()
  pluginService: PluginService;
  private miniTokenCache: { appId: string; token: string; expireAt: number };
  /**
   * 获得插件实例
@@ -49,6 +58,68 @@
  async getMiniApp() {
    const wxPlugin: any = await this.getPlugin();
    return wxPlugin.MiniApp();
  }
  async readParam(key: string) {
    const row = await this.baseSysParamEntity.findOneBy({ keyName: key });
    return (row?.data || '').trim();
  }
  /**
   * 小程序账号。优先插件,其次系统参数 wx.mini.appId / wx.mini.appSecret
   */
  async getMiniCredential() {
    try {
      const app = await this.getMiniApp();
      const account = app.getAccount();
      const appId = account.getAppId?.() || account.appId;
      const secret = account.getSecret?.() || account.appSecret || account.secret;
      if (appId && secret) {
        return { appId, secret };
      }
    } catch (error) {
      // 未安装微信插件时改用系统参数
    }
    const paramAppId = await this.readParam('wx.mini.appId');
    const paramSecret = await this.readParam('wx.mini.appSecret');
    const appId = paramAppId || this.wxMini?.appId || '';
    const secret = paramSecret || this.wxMini?.appSecret || '';
    if (!appId || !secret) {
      throw new CoolCommException(
        '未配置小程序密钥。请在系统参数填写 wx.mini.appSecret(微信公众平台-开发管理-开发设置)'
      );
    }
    return { appId, secret };
  }
  async getMiniAccessToken() {
    const { appId, secret } = await this.getMiniCredential();
    const now = Date.now();
    if (
      this.miniTokenCache &&
      this.miniTokenCache.appId === appId &&
      this.miniTokenCache.expireAt > now
    ) {
      return this.miniTokenCache.token;
    }
    const { data } = await axios.get('https://api.weixin.qq.com/cgi-bin/token', {
      params: {
        grant_type: 'client_credential',
        appid: appId,
        secret,
      },
    });
    if (!data?.access_token) {
      throw new CoolCommException(data?.errmsg || '获取微信凭证失败,请检查 AppSecret');
    }
    this.miniTokenCache = {
      appId,
      token: data.access_token,
      expireAt: now + Math.max(60, (data.expires_in || 7200) - 120) * 1000,
    };
    return data.access_token;
  }
  /**
@@ -215,10 +286,20 @@
   * @param conf 配置
   */
  async miniSession(code) {
    const app = await this.getMiniApp();
    const utils = app.getUtils();
    const result = await utils.codeToSession(code);
    return result;
    const { appId, secret } = await this.getMiniCredential();
    const { data } = await axios.get(
      'https://api.weixin.qq.com/sns/jscode2session',
      {
        params: {
          appid: appId,
          secret,
          js_code: code,
          grant_type: 'authorization_code',
        },
      }
    );
    console.log('[miniSession] appId=', appId, 'code前6位=', String(code).slice(0,6), 'ret=', JSON.stringify(data));
    return data;
  }
  /**
@@ -255,6 +336,11 @@
   * @param iv
   */
  async miniPhone(code, encryptedData, iv) {
    // 新版获取手机号只返回动态令牌,由服务端向微信换取手机号
    if (!encryptedData || !iv) {
      return await this.getPhoneNumber(code);
    }
    const session = await this.miniSession(code);
    if (session.errcode) {
      throw new CoolCommException('获取手机号失败,请刷新重试');
@@ -268,6 +354,41 @@
  }
  /**
   * 用手机号动态令牌换取手机号
   * @param code getPhoneNumber 返回的 code
   */
  async getPhoneNumber(code: string) {
    if (!code) {
      throw new CoolCommException('未获取到手机号授权');
    }
    const accessToken = await this.getMiniAccessToken();
    const { data } = await axios.post(
      'https://api.weixin.qq.com/wxa/business/getuserphonenumber',
      { code },
      {
        params: {
          access_token: accessToken,
        },
      }
    );
    if (data?.errcode) {
      throw new CoolCommException(data.errmsg || '获取手机号失败');
    }
    const phone =
      data?.phone_info?.purePhoneNumber || data?.phone_info?.phoneNumber;
    if (!phone) {
      throw new CoolCommException('获得手机号失败,请检查配置');
    }
    return phone;
  }
  /**
   * 小程序信息解密
   * @param encryptedData
   * @param iv
typings/pay-wx.d.ts
@@ -23,9 +23,16 @@
    mchid: string;
    key: string;
    notify_url: string;
    publicKey: Buffer;
    privateKey: Buffer;
    publicKey: string;
    privateKey: string;
    verifyPublicKey?: string;
  }): Promise<any>;
  /**
   * 获得Buffer
   * @param path
   * @returns
   */
  getBuffer(path: string): Promise<Buffer<any>>;
  /**
   * 根据给定的 URL 下载文件并返回一个 Buffer。
   * @param {string} url 文件的 URL
@@ -45,8 +52,9 @@
      mchid: string;
      key: string;
      notify_url: string;
      publicKey: Buffer;
      privateKey: Buffer;
      publicKey: string;
      privateKey: string;
      verifyPublicKey?: string;
    },
  ): Promise<any>;
}
typings/plugin.d.ts
@@ -1,3 +1,4 @@
import * as wxBaseMessage from './wxBaseMessage';
import * as wuliu from './wuliu';
import * as pay_wx from './pay-wx';
import * as wx from './wx';
@@ -15,4 +16,5 @@
  wx: wx.CoolPlugin;
  'pay-wx': pay_wx.CoolPlugin;
  wuliu: wuliu.CoolPlugin;
  wxBaseMessage: wxBaseMessage.CoolPlugin;
}
typings/wx.d.ts
@@ -7,6 +7,7 @@
  Work,
  OpenWork,
} from 'node-easywechat';
import { IncomingMessage } from 'http';
/**
 * 微信
 */
@@ -18,6 +19,12 @@
   */
  OfficialAccount(config?: any): Promise<any>;
  /**
   * 创建请求
   * @param req
   * @returns
   */
  createRequest(req: IncomingMessage): Promise<any>;
  /**
   * 小程序
   * @param config
   * @returns
typings/wxBaseMessage.d.ts
New file
@@ -0,0 +1,153 @@
import { BasePlugin } from '@cool-midway/plugin-cli';
interface TemplateDataItem {
  value: string;
  color?: string;
}
interface TemplateData {
  [key: string]: TemplateDataItem;
}
interface MiniProgramParams {
  appid?: string;
  pagepath?: string;
}
interface SendTemplateMessageParams {
  touser: string;
  template_id: string;
  url?: string;
  miniprogram?: MiniProgramParams;
  data: TemplateData;
  client_msg_id?: string;
}
interface SendResult {
  msgid: number;
  errcode: number;
  errmsg: string;
}
interface MassSendFilter {
  is_to_all?: boolean;
  tag_id?: string;
}
interface MassSendByOpenIdParams {
  touser: string[];
  msgtype: string;
  mpnews?: {
    media_id: string;
  };
  text?: {
    content: string;
  };
  voice?: {
    media_id: string;
  };
  image?: {
    media_ids: string[];
    recommend?: string;
    title?: string;
    need_open_comment?: number;
    only_fans_can_comment?: number;
  };
  mpvideo?: {
    media_id: string;
    title?: string;
    description?: string;
  };
  wxcard?: {
    card_id: string;
  };
  send_ignore_reprint?: number;
  clientmsgid?: string;
}
interface MassSendByTagParams {
  filter: MassSendFilter;
  msgtype: string;
  mpnews?: {
    media_id: string;
  };
  text?: {
    content: string;
  };
  voice?: {
    media_id: string;
  };
  image?: {
    media_ids: string[];
    recommend?: string;
    title?: string;
    need_open_comment?: number;
    only_fans_can_comment?: number;
  };
  mpvideo?: {
    media_id: string;
    title?: string;
    description?: string;
  };
  wxcard?: {
    card_id: string;
  };
  clientmsgid?: string;
}
interface MassSendResult {
  type: string;
  msg_id: number;
  msg_data_id: number;
  errcode: number;
  errmsg: string;
}
interface SubscribeDataItem {
  value: string;
}
interface SubscribeData {
  [key: string]: SubscribeDataItem;
}
interface SendSubscribeMsgParams {
  template_id: string;
  touser: string;
  page?: string;
  data: SubscribeData;
  miniprogram_state: string;
  lang?: string;
}
interface SubscribeResult {
  errcode: number;
  errmsg: string;
}
interface OneTimeMiniProgram {
  appid: string;
  pagepath: string;
}
interface OneTimeMsgData {
  content: {
    value: string;
    color?: string;
  };
}
interface SendOneTimeSubscribeParams {
  touser: string;
  template_id: string;
  url?: string;
  miniprogram?: OneTimeMiniProgram;
  scene: string;
  title: string;
  data: OneTimeMsgData;
}
interface OneTimeSubscribeResult {
  errcode: number;
  errmsg: string;
}
export declare class CoolPlugin extends BasePlugin {
  private getAccessToken;
  private handleApiError;
  private validateMassSendParams;
  massSendByOpenId(params: MassSendByOpenIdParams): Promise<MassSendResult>;
  massSendByTag(params: MassSendByTagParams): Promise<MassSendResult>;
  sendTemplateMessage(params: SendTemplateMessageParams): Promise<SendResult>;
  batchSendTemplateMessage(
    paramsList: SendTemplateMessageParams[],
  ): Promise<SendResult[]>;
  sendSubscribeMsg(params: SendSubscribeMsgParams): Promise<SubscribeResult>;
  sendOneTimeSubscribeMsg(
    params: SendOneTimeSubscribeParams,
  ): Promise<OneTimeSubscribeResult>;
}
export declare const Plugin: typeof CoolPlugin;
export {};