Skip to content

Commit

Permalink
feat: 新增 input 适配器 完善通知、请求事件
Browse files Browse the repository at this point in the history
  • Loading branch information
CakmLexi committed Jun 30, 2024
1 parent 75e2583 commit 8ce4ade
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 82 deletions.
11 changes: 11 additions & 0 deletions config/defSet/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ multi_progress: false
# 控制台触发插件日志颜色 十六进制 默认#FFFF00 不支持热更新
log_color: "#E1D919"

# input适配器配置 以下所有配置均不支持热更新
AdapterInput:
# 是否启用
enable: true
# 是否将语音、图片、视频消息转为文件 转为文件后可通过url访问
msgToFile: true
# url访问token 如果为 AdapterInput 每次启动后会重新生成
token: "AdapterInput"
# 访问ip
ip: 127.0.0.1

# ffmpeg配置 用于音视频处理
ffmpeg_path:
ffprobe_path:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"dependencies": {
"@grpc/grpc-js": "1.10.10",
"@grpc/proto-loader": "0.7.13",
"@inquirer/prompts": "^5.0.7",
"art-template": "4.13.2",
"axios": "1.7.2",
"chalk": "5.3.0",
Expand Down
206 changes: 206 additions & 0 deletions src/adapter/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import fs from 'fs'
import { randomUUID } from 'crypto'
import { listener } from 'karin/core'
import { KarinMessage } from 'karin/event'
import { KarinAdapter } from 'karin/types/adapter'
import { contact, KarinElement } from 'karin/types'
import { config, common, YamlEditor } from 'karin/utils'

const { enable, msgToFile, token: oldToken, ip } = config.Config.AdapterInput

let token = oldToken

if (oldToken === 'AdapterInput') {
try {
token = randomUUID()
const yaml = new YamlEditor('./config/config/config.yaml')
const data = yaml.get('AdapterInput')
if (!data) {
const yaml1 = new YamlEditor('./config/defSet/config.yaml')
const data1 = yaml1.get('AdapterInput')
data1.token = token
yaml.set('AdapterInput', data1)
} else {
data.token = token
yaml.set('AdapterInput', data)
}

yaml.save()
} catch (e) {
logger.error('AdapterInput token更换失败,请手动更换token')
}
}

// 清空文件夹
fs.readdirSync('./temp/input').forEach((file) => {
fs.unlinkSync(`./temp/input/${file}`)
})

/**
* - 标准输入输出适配器
*/
export class AdapterInput implements KarinAdapter {
#stdin: boolean
socket!: WebSocket
account: KarinAdapter['account']
adapter: KarinAdapter['adapter']
version: KarinAdapter['version']
constructor () {
this.#stdin = false
this.account = { uid: 'input', uin: 'input', name: 'input' }
this.adapter = { id: 'shell', name: 'input', type: 'internal', sub_type: 'internal', start_time: Date.now(), connect: '' }
this.version = { name: 'input', app_name: 'input', version: '1.0.0' }
}

get self_id () {
return this.account.uid
}

stdin () {
if (this.#stdin) return
this.#stdin = true
process.stdin.on('data', data => this.#input(data.toString()))
process.once('stdin.close', () => process.stdin.removeAllListeners('data'))
}

logger (level: 'info' | 'error' | 'trace' | 'debug' | 'mark' | 'warn' | 'fatal', ...args: any[]) {
logger.bot(level, this.account.uid || this.account.uin, ...args)
}

async #input (elements: string) {
const message = {
event: 'message' as 'message' | 'message_sent',
self_id: 'input',
user_id: 'input',
time: Date.now(),
message_id: `input.${Date.now()}`,
message_seq: '',
sender: {
uid: 'input',
uin: 'input',
nick: 'input',
role: 'member' as 'member',
},
elements: [{ type: 'text', text: elements }] as KarinElement[],
contact: {
scene: 'private' as 'private' | 'group',
peer: 'input',
sub_peer: '',
},
group_id: '',
raw_message: elements,
}

const e = new KarinMessage(message)
e.bot = this
/**
* 快速回复 开发者不应该使用这个方法,应该使用由karin封装过后的reply方法
*/
e.replyCallback = async elements => {
this.SendMessage(e.contact, elements)
return { message_id: e.message_id }
}

listener.emit('message', e)
}

async #MsgToFile (type: 'image' | 'voice', file: Uint8Array | string): Promise<string> {
if (!msgToFile) return ''

// 判断是否为string 如果是则继续判断是否为url、path
if (typeof file === 'string') {
if (file.startsWith('http')) return file
if (common.exists(file)) return file
}

const buffer = await common.buffer(file) as Uint8Array
// 生成文件名 根据type生成不同的文件后缀
const name = `${Date.now()}.${type === 'image' ? 'jpg' : type === 'voice' ? 'mp3' : 'file'}`
// 写入文件
fs.writeFileSync(`./temp/input/${name}`, buffer)
return `[${type === 'image' ? '图片' : '语音'}: http://${ip}:${config.Server.http.port}/api/input?name=${name}&token=${token} ]`
}

async GetVersion () {
const data = this.version
delete (data as { name?: string }).name
return data
}

async SendMessage (_contact: contact, elements: Array<KarinElement>) {
const text = []
for (const v of elements) {
switch (v.type) {
case 'at':
text.push(`@${v.uid}`)
break
case 'face':
text.push(`[表情:${v.id}]`)
break
case 'text':
text.push(v.text)
break
case 'image':
case 'voice':
text.push(await this.#MsgToFile(v.type, v.file))
break
default:
text.push(`[未知消息类型:${JSON.stringify(v)}]`)
}
}
this.logger('info', text.join(''))
return { message_id: 'input' }
}

getAvatarUrl () {
return 'https://p.qlogo.cn/gh/967068507/967068507/0'
}

getGroupAvatar () {
return 'https://p.qlogo.cn/gh/967068507/967068507/0'
}

async GetCurrentAccount () {
return { account_uid: 'input', account_uin: 'input', account_name: 'input' }
}

async GetEssenceMessageList (): Promise<any> { throw new Error('Method not implemented.') }
async DownloadForwardMessage (): Promise<any> { throw new Error('Method not implemented.') }
async SetEssenceMessage (): Promise<any> { throw new Error('Method not implemented.') }
async DeleteEssenceMessage (): Promise<any> { throw new Error('Method not implemented.') }
async SetFriendApplyResult (): Promise<any> { throw new Error('Method not implemented.') }
async SetGroupApplyResultRequest (): Promise<any> { throw new Error('Method not implemented.') }
async SetInvitedJoinGroupResult (): Promise<any> { throw new Error('Method not implemented.') }
async ReactMessageWithEmojiRequest (): Promise<any> { throw new Error('Method not implemented.') }
async UploadPrivateFile (): Promise<any> { throw new Error('Method not implemented.') }
async UploadGroupFile (): Promise<any> { throw new Error('Method not implemented.') }
async UploadForwardMessage (): Promise<any> { throw new Error('Method not implemented.') }
async sendForwardMessage (): Promise<any> { throw new Error('Method not implemented.') }
async SendMessageByResId (): Promise<any> { throw new Error('Method not implemented.') }
async RecallMessage (): Promise<any> { throw new Error('Method not implemented.') }
async GetMessage (): Promise<any> { throw new Error('Method not implemented.') }
async GetHistoryMessage (): Promise<any> { throw new Error('Method not implemented.') }
async VoteUser (): Promise<any> { throw new Error('Method not implemented.') }
async KickMember (): Promise<any> { throw new Error('Method not implemented.') }
async BanMember (): Promise<any> { throw new Error('Method not implemented.') }
async SetGroupWholeBan (): Promise<any> { throw new Error('Method not implemented.') }
async SetGroupAdmin (): Promise<any> { throw new Error('Method not implemented.') }
async ModifyMemberCard (): Promise<any> { throw new Error('Method not implemented.') }
async ModifyGroupName (): Promise<any> { throw new Error('Method not implemented.') }
async LeaveGroup (): Promise<any> { throw new Error('Method not implemented.') }
async SetGroupUniqueTitle (): Promise<any> { throw new Error('Method not implemented.') }
async GetStrangerProfileCard (): Promise<any> { throw new Error('Method not implemented.') }
async GetFriendList (): Promise<any> { throw new Error('Method not implemented.') }
async GetGroupInfo (): Promise<any> { throw new Error('Method not implemented.') }
async GetGroupList (): Promise<any> { throw new Error('Method not implemented.') }
async GetGroupMemberInfo (): Promise<any> { throw new Error('Method not implemented.') }
async GetGroupMemberList (): Promise<any> { throw new Error('Method not implemented.') }
async GetGroupHonor (): Promise<any> { throw new Error('Method not implemented.') }
}

if (enable) {
const bot = new AdapterInput()
bot.stdin()
/** 注册bot */
listener.emit('bot', { type: 'internal', bot })
}
Loading

0 comments on commit 8ce4ade

Please sign in to comment.