# adapter 3个文件的作用

# api.js

这个文件的代码,系统启动后会变成全局 bot 对象,使用 module.exports 将对象暴露出去,bot 对象方法就取决于 module.exports 属性的值

一般这里导入 HTTP 模块,调用对接平台的 HTTP 接口

var okhttp = require('ok-http').create({
	connectTimeout: 5000,										// 连接服务器超时时间, 5秒
	// followRedirects: 'normal'								// 重定向规则
	// 以下是请求参数的默认配置
	baseUrl: 'http://' + require('environment').getProperty('bot.address'),		// 基础url, 设置后, 每次请求都会拼接
	headers: {													// 请求头, 设置后, 每次请求都会携带
		'Content-Type': 'application/json',
		'X-Application-Controller': 'yoyo-bot'
	}
})



/**
 * 默认发送信息
 * @param Event event: 事件
 * @param String message: 回复
 */
function defaultSendMessage(event, content) {
	if (event.isTeam) {
		sendGroup(event.groupId, content)
	} else {
		sendUser(event.userId, content)
	}
}


/**
 * 发送用户消息
 * sendUser(String userId, String message)
 * @return String messageId: 消息id
 */
function sendUser(userId, message) {
	let res = okhttp.post({
		url: '/send_private_msg',
		data: {
			user_id: String(userId),
			message: String(message)
		}
	})
	if (res.data.retcode === 0) {
		return String(res.data.data.message_id)
	} else {
		console.warn('用户消息发送失败(API适配器)\n' + res.data.wording)
		return ''
	}
}

// 此处省略一万行代码...

module.exports = {
	sendUser, 发送用户消息: sendUser,
	sendGroup, 发送群消息: sendGroup,
	recallMessage, 撤回消息: recallMessage,
	deleteGroupMember, 踢出群成员: deleteGroupMember,
	muteGroupUser, 禁言群成员: muteGroupUser,
	muteGroup, 禁言群: muteGroup,
	setGroupAdmin, 设置群管理员: setGroupAdmin,
	setGroupUserCard, 设置群成员名片: setGroupUserCard,
	setGroupName, 设置群名: setGroupName,
	leaveGroup, 离开群: leaveGroup,
	dissolveGroup, 解散群: dissolveGroup,
	handleFriendRequest, 处理好友申请: handleFriendRequest,
	handleGroupRequest, 处理群申请: handleGroupRequest,
	handleGroupInvitation, 处理群邀请: handleGroupInvitation,
	deleteFriend, 删除好友: deleteFriend,
	getSelf, 获取自身信息: getSelf,
	getUser, 获取用户信息: getUser,
	listFriend, 获取好友列表: listFriend,
	getGroup, 获取群: getGroup,
	listGroup, 获取群列表: listGroup,
	getGroupMember, 获取群成员信息: getGroupMember,
	listGroupMember, 获取群成员列表: listGroupMember,
	getAtCode, 获取at码: getAtCode, atCode: getAtCode,
	getImageCode, 获取图片码: getImageCode, imageCode: getImageCode
}

# router.js

描述了事件该如何路由,路由到哪个组件中,事件会经过 route 函数,他的返回值中的 event 对象,一般作为 action 函数的第一个参数

/*
	本套事件解析器和路由器, 根据多数聊天平台事件模型, 抽象出来一套标准事件模型
	开发指南: 
		必须遵循 ES5.1 和 ES6.0(部分), ES6如解构赋值, class不被支持, 将来将支持ES11(2020)及以上(画饼)
		模块使用require()导入
		必须实现以下函数
	1. Route route(String json)			所有的event都会经过此函数, 对event进行路由, 无法路由的event返回undefined或null
	Route 对象结构, 对事件进行路由
		Route = {
			component: string;	// 路由目标组件, 系统组件有: message-user, message-group, notice-user, notice-group, 共计 4 个
			log: log;			// 下面有说明
			event: object;		// 这个对象可以随便放任何属性
			type: string;		// 类型
			groupId: string;	// 群id, 没有就为undefined
			teamId: string;		// 团队id, 没有就为undefined
			userId: string;		// 用户id, 没有就为undefined
			content: string;	// 内容, message-user和message-group组件的type为text/文本需要, 主要为关键词过滤服务
		}
	Log 对象结构, 主要就是为web日志端渲染日志
		Log = {
			label: string;		// 标签
			color: string; 		// 标签的颜色, 16进制
			title: string;		// 标题
			time: string;		// 时间
			content: string;	// 内容
			origin: string;		// 来源
		}
	Event 一般定义如下, 可以随便添加属性, 所有属性非强制
		Event = {
			isTeam: boolean;	// 是否是团队消息, function defaultSendMessage() 用到
			groupId: string;	// 群id, 没有就为undefined
			teamId: string;		// 团队id, 没有就为undefined
			content: string;	// 消息内容
			userId: string;		// 用户id
			messageId: string;	// 消息id, 用于撤回消息等, 非消息类型为undefined
			selfId: string;		// 自身id
			userName: string;	// 用户名
			groupName: string;	// 群名
			selfName: string;	// 自身名
			image: object;		// 如果是图片消息, 可以选择ocr, 识别出图片内容, 如携带text, url, base64, 鉴黄指数等
		}
*/

let remarkConfig = {				// 配置对象, 这个对象主要是做用户名和群名缓存, 减少对接口的访问
	enableCache: true,				// 开启缓存
	map: new Map(),					// 缓存容器
	enableRegularRefresh: true,		// 开启定时刷新
	lastRefreshTime: 0,				// 最后刷新时间
	duration: 259200000				// 持续时间
}

let enableImageOcr = false										// 开启图片文字识别功能
let ocr = enableImageOcr ? require('yoyo-ocr') : undefined		// 导入ocr模块

let logColor = {				// log label color 对log对象的标签颜色声明
	groupMessage: '#CE93D8',	// 淡紫色
	groupNotice: '#AB47BC',		// 深紫色
	userMessage: '#64B5F6',		// 淡蓝色
	userNotice: '#039BE5' 		// 深蓝色
}

let okhttp = require('ok-http').create({		// 导入模块, 并创建http请求客户端
	connectTimeout: 5000
})

// 事件路由, 总的来说就办了3件事, 设置route对象, 设置event对象, 设置log对象
function route(json) {
	let data = JSON.parse(json)
	let route = {}
	let event = {}
	let log = {}
	route.event = event
	route.log = log
	// 设置事件的共有属性
	// set self
	event.selfId = String(data.self_id)
	event.selfName = getRemarkFormCache('user', event.selfId)
	event.selfDescription = event.selfName + '(' + event.selfId + ')'
	// set user
	event.userId = String(data.user_id)
	event.userName = getRemarkFormCache('user', event.userId)
	event.userDescription = event.userName + '(' + event.userId + ')'
	// set group
	event.groupId = data.group_id ? String(data.group_id) : undefined
	event.groupName = event.groupId ? getRemarkFormCache('group', event.groupId) : undefined
	event.groupDescription = event.groupId ? event.groupName + '(' + event.groupId + ')' : undefined
	// set chat
	event.chatId = event.groupId ? event.groupId : event.userId    // 设置聊天id, 在qq里这个属性并不常见
	event.chatName = event.groupName ? event.groupName : event.userName
	event.chatDescription = event.chatName + '(' + event.chatId + ')'

	// set log 设置日志
	log.time = getNowTime()
	log.title = event.chatDescription
	log.origin = event.selfDescription
	// log.label
	// log.color
	// log.content

	// set route 设置路由
	route.groupId = event.groupId
	// route.teamId = ??, 给groupId, 系统会自动识别出teamId

	if (data.post_type === 'message') {
		event.content = data.message
		event.messageId = String(data.message_id)
		event.isTeam = Boolean(event.groupId)

		route.component = event.groupId ? 'message-team' : 'message-user'
		route.content = event.content
		event.component = 'message'

		// set log
		log.color = event.groupId ? logColor.groupMessage : logColor.userMessage
		log.content = event.groupId ? event.userDescription + ': ' + event.content : event.content

		// 先交给自定义事件消息处理, 他处理不了就是文本消息, 除了不了返回false
		let isHandle = parseCustomMessage(data, route, event, log)
		if (!isHandle) {
			parseTextMessage(data, route, event, log)
		}
		return route
	} else if (data.post_type === 'notice') {
		route.component = event.groupId ? 'notice-team' : 'notice-user'
		event.component = 'notice'
		// set log
		log.color = event.groupId ? logColor.groupNotice : logColor.userNotice

		if (data.notice_type == 'friend_add') {
			parseFriendIncrease(data, route, event, log)
		} else if (data.notice_type == 'friend_recall' || data.notice_type == 'group_recall') {
			parseRecallMessage(data, route, event, log)
		} else if(data.notice_type == 'notify' && data.sub_type == 'poke') {
			parsePokeEvent(data, route, event, log)
		} else if(data.notice_type == 'group_admin' && data.sub_type == 'set') {
			parseGroupAdminIncrease(data, route, event, log)
		} else if(data.notice_type == 'group_admin' && data.sub_type == 'unset') {
			parseGroupAdminDecrease(data, route, event, log)
		} else if(data.notice_type == 'group_upload') {
			parseGroupFileUpload(data, route, event, log)
		} else if(data.notice_type == 'group_increase') {
			parseGroupMemberIncrease(data, route, event, log)
		} else if(data.notice_type == 'group_decrease') {
			parseGroupMemberDecrease(data, route, event, log)
		} else if(data.notice_type == 'group_card') {
			parseGroupMemberCardUpdate(data, route, event, log)
		} else if(data.notice_type == 'group_ban' && data.sub_type == 'ban'/* && data.hasOwnProperty('user_id') == false*/) {
			parseGroupUserMuteEvent(data, route, event, log)
		} else if(data.notice_type == 'group_ban' && data.sub_type == 'lift_ban') {
			parseGroupMemberUnmute(data, route, event, log)
		}
		return route
	} else if (data.post_type === 'request') {
		route.component = event.groupId ? 'notice-team' : 'notice-user'
		event.component = 'notice'
		// set log
		route.log.color = event.groupId ? logColor.groupNotice : logColor.userNotice

		if (data.request_type == 'friend') {
			parseFriendRequest(data, route, event, log)
		} else if (data.request_type == 'group' && data.sub_type == 'add') {
			parseGroupRequest(data, route, event, log)
		} else if (data.request_type == 'group' && data.sub_type == 'invite') {
			parseGroupInvite(data, route, event, log)
		}
		return route
	}
}

// 此处省略一万行代码...

# weber.js

这里定义了 HTTP 接口,函数无需暴露,由系统调度

/**
 * 获取全部团队通知事件名
 * @return String[] names: 全部群通知事件名
 */
function listTeamNoticeName() {
    return [
        '群文件上传',
        '群管理员增加',
        '群管理员减少',
        '群成员增加',
        '群成员减少',
        '群成员禁言',
        '群成员取消禁言',
        '戳一戳',
        '群成员荣誉更新',
        '群成员名片更新',
        '群申请',
        '消息撤回'
    ]
}

/**
 * 获取全部好友通知事件名
 * @return Object[] objects
 */
function listUserNoticeName() {
    return [
        '好友增加',
        '消息撤回',
        '戳一戳',
        '好友请求',
        '群邀请'
    ]
}

/**
 * 获取全部自定义消息类型
 * @return Object[] objects
 */
function listCustomMessageType() {
    return [
        '图片',
        '表情包',
        'json'
    ]
}

/**
 * 获取全部好友
 * @return User[]
 * User{
 *     id: String,
 *     name: String,
 *     profilePhoto: String
 * }
 */
function listFriend() {
    return bot.listFriend()
}

/**
 * 获取全部团队
 * @return Team[]
 * Team{
 *     id: String,
 *     name: String,
 *     profilePhoto: String
 * }
 */
function listTeam() {
    return bot.listGroup()
}

/**
 * 获取生产者列表
 * @return String[]
 */
function listProducer() {
    var self = bot.getSelf()
    return [
        {
            tags: [
                {
                    color: '#32CD99',
                    text:'QQ'
                }
            ],
            text: self.name + '(' + self.id + ')'
        }
    ]
}

function getAdapter() {
    return {
        version: '0.0.1',
        name: 'go-cqhttp',
        author: 'undefined'
    }
}

function deleteFriend(id) {
    bot.deleteFriend(id)
}

function leaveGroup(id) {
    bot.leaveGroup(id)
    bot.dissolveGroup(id)
}