mirror of https://github.com/msgbyte/tailchat
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			164 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			164 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
import { TcService, config, TcContext, call } from 'tailchat-server-sdk';
 | 
						|
import { isValidStr, isValidUrl } from '../../lib/utils';
 | 
						|
import type { OpenApp } from '../../models/openapi/app';
 | 
						|
import got from 'got';
 | 
						|
import _ from 'lodash';
 | 
						|
 | 
						|
class OpenBotService extends TcService {
 | 
						|
  get serviceName(): string {
 | 
						|
    return 'openapi.bot';
 | 
						|
  }
 | 
						|
 | 
						|
  onInit(): void {
 | 
						|
    if (!config.enableOpenapi) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this.registerEventListener('chat.inbox.append', async (payload, ctx) => {
 | 
						|
      const userInfo = await call(ctx).getUserInfo(String(payload.userId));
 | 
						|
 | 
						|
      if (!userInfo) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (userInfo.type !== 'openapiBot') {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // 开放平台机器人
 | 
						|
      const botId: string | null = await ctx.call('user.findOpenapiBotId', {
 | 
						|
        email: userInfo.email,
 | 
						|
      });
 | 
						|
 | 
						|
      if (!(isValidStr(botId) && botId.startsWith('open_'))) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // 是合法的机器人id
 | 
						|
 | 
						|
      const appId = botId.replace('open_', '');
 | 
						|
      const appInfo: OpenApp | null = await ctx.call('openapi.app.get', {
 | 
						|
        appId,
 | 
						|
      });
 | 
						|
      const callbackUrl = _.get(appInfo, 'bot.callbackUrl');
 | 
						|
 | 
						|
      if (!isValidUrl(callbackUrl)) {
 | 
						|
        this.logger.info('机器人回调地址不是一个可用的url, skip.');
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      got
 | 
						|
        .post(callbackUrl, {
 | 
						|
          json: payload,
 | 
						|
          headers: {
 | 
						|
            'X-TC-Payload-Type': 'inbox',
 | 
						|
          },
 | 
						|
        })
 | 
						|
        .then(() => {
 | 
						|
          this.logger.info('调用机器人通知接口回调成功');
 | 
						|
        })
 | 
						|
        .catch((err) => {
 | 
						|
          this.logger.error('调用机器人通知接口回调失败:', err);
 | 
						|
        });
 | 
						|
    });
 | 
						|
 | 
						|
    this.registerAction('login', this.login, {
 | 
						|
      params: {
 | 
						|
        appId: 'string',
 | 
						|
        token: 'string',
 | 
						|
      },
 | 
						|
    });
 | 
						|
    this.registerAction('getOrCreateBotAccount', this.getOrCreateBotAccount, {
 | 
						|
      params: {
 | 
						|
        appId: 'string',
 | 
						|
      },
 | 
						|
      visibility: 'public',
 | 
						|
    });
 | 
						|
 | 
						|
    this.registerAuthWhitelist(['/login']);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 登录
 | 
						|
   *
 | 
						|
   * 并自动创建机器人账号
 | 
						|
   */
 | 
						|
  async login(ctx: TcContext<{ appId: string; token: string }>) {
 | 
						|
    const { appId, token } = ctx.params;
 | 
						|
    const valid = await ctx.call('openapi.app.authToken', {
 | 
						|
      appId,
 | 
						|
      token,
 | 
						|
      capability: ['bot'],
 | 
						|
    });
 | 
						|
 | 
						|
    if (!valid) {
 | 
						|
      throw new Error('Auth failed.');
 | 
						|
    }
 | 
						|
 | 
						|
    // 校验通过, 获取机器人账号存在
 | 
						|
    const { userId, email, nickname, avatar } = await this.localCall(
 | 
						|
      'getOrCreateBotAccount',
 | 
						|
      {
 | 
						|
        appId,
 | 
						|
      }
 | 
						|
    );
 | 
						|
 | 
						|
    const jwt: string = await ctx.call('user.generateUserToken', {
 | 
						|
      userId,
 | 
						|
      email,
 | 
						|
      nickname,
 | 
						|
      avatar,
 | 
						|
    });
 | 
						|
 | 
						|
    return { jwt, userId, email, nickname, avatar };
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 获取或创建机器人账号
 | 
						|
   */
 | 
						|
  async getOrCreateBotAccount(ctx: TcContext<{ appId: string }>): Promise<{
 | 
						|
    userId: string;
 | 
						|
    email: string;
 | 
						|
    nickname: string;
 | 
						|
    avatar: string;
 | 
						|
  }> {
 | 
						|
    const appId = ctx.params.appId;
 | 
						|
    await this.waitForServices(['user']);
 | 
						|
 | 
						|
    const appInfo: OpenApp = await ctx.call('openapi.app.get', {
 | 
						|
      appId,
 | 
						|
    });
 | 
						|
 | 
						|
    try {
 | 
						|
      const botId = 'open_' + appInfo.appId;
 | 
						|
      const nickname = appInfo.appName;
 | 
						|
      const avatar = appInfo.appIcon;
 | 
						|
      const { _id: botUserId, email } = await ctx.call<
 | 
						|
        {
 | 
						|
          _id: string;
 | 
						|
          email: string;
 | 
						|
        },
 | 
						|
        any
 | 
						|
      >('user.ensureOpenapiBot', {
 | 
						|
        botId,
 | 
						|
        nickname,
 | 
						|
        avatar,
 | 
						|
      });
 | 
						|
 | 
						|
      this.logger.info('[getOrCreateBotAccount] Bot Id:', botUserId);
 | 
						|
 | 
						|
      return {
 | 
						|
        userId: String(botUserId),
 | 
						|
        email,
 | 
						|
        nickname,
 | 
						|
        avatar,
 | 
						|
      };
 | 
						|
    } catch (e) {
 | 
						|
      this.logger.error(e);
 | 
						|
      throw e;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export default OpenBotService;
 |