mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
a45c56bd85
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,6 +1,15 @@
|
||||
node_modules/
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
package-lock.json
|
||||
dist/
|
||||
out/
|
||||
yarn.lock
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
13
README.md
13
README.md
@ -1,6 +1,6 @@
|
||||
# LLOneBot
|
||||
|
||||
LiteLoaderQQNT插件,使你的NTQQ支持OneBot11协议进行QQ机器人开发
|
||||
LiteLoaderQQNT 插件,实现 OneBot 11 协议,用以 QQ 机器人开发
|
||||
|
||||
> [!CAUTION]\
|
||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: B站,微博,知乎,抖音等)发布和讨论*任何*与本插件存在相关性的信息**
|
||||
@ -13,13 +13,13 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
|
||||
## 设置界面
|
||||
|
||||
<img src="./doc/image/setting.png" width="500px" alt="图片名称"/>
|
||||
<img src="./doc/image/setting.png" width="400px" alt="设置界面"/>
|
||||
|
||||
## HTTP 调用示例
|
||||
|
||||

|
||||
<img src="./doc/image/example.jpg" width="500px" alt="HTTP调用示例"/>
|
||||
|
||||
## 支持的 api 和功能详情
|
||||
## 支持的 API
|
||||
|
||||
见 <https://llonebot.github.io/zh-CN/develop/api>
|
||||
|
||||
@ -35,13 +35,8 @@ TG群:<https://t.me/+nLZEnpne-pQ1OWFl>
|
||||
- [x] 群禁言事件上报
|
||||
- [x] 优化加群成功事件上报
|
||||
- [x] 清理缓存api
|
||||
- [ ] 无头模式
|
||||
- [ ] 框架对接文档
|
||||
|
||||
## onebot11文档
|
||||
|
||||
<https://11.onebot.dev/>
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/LLOneBot/LLOneBot)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import cp from 'vite-plugin-cp'
|
||||
import './scripts/gen-version'
|
||||
import path from 'node:path'
|
||||
import './scripts/gen-manifest'
|
||||
|
||||
const external = [
|
||||
'silk-wasm',
|
||||
@ -32,6 +32,7 @@ let config = {
|
||||
external,
|
||||
input: 'src/main/main.ts',
|
||||
},
|
||||
minify: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@ -44,8 +45,8 @@ let config = {
|
||||
targets: [
|
||||
...external.map(genCpModule),
|
||||
{ src: './manifest.json', dest: 'dist' },
|
||||
{ src: './icon.jpg', dest: 'dist' },
|
||||
{ src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||
{ src: './icon.webp', dest: 'dist' },
|
||||
// { src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
||||
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
||||
],
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.27.4",
|
||||
"name": "LLOneBot",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发",
|
||||
"description": "实现 OneBot 11 协议,帮助进行 QQ 机器人开发",
|
||||
"version": "3.27.4",
|
||||
"icon": "./icon.jpg",
|
||||
"icon": "./icon.webp",
|
||||
"authors": [
|
||||
{
|
||||
"name": "linyuchen",
|
||||
|
15
package.json
15
package.json
@ -31,16 +31,11 @@
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/ws": "^8.5.12",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||
"electron": "^29.0.1",
|
||||
"electron-vite": "^2.0.0",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-n": "^15.0.0 || ^16.0.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "*",
|
||||
"vite": "^5.1.4",
|
||||
"electron-vite": "^2.3.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.5",
|
||||
"vite-plugin-cp": "^4.0.8"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.4.0"
|
||||
}
|
||||
|
38
scripts/gen-manifest.ts
Normal file
38
scripts/gen-manifest.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { version } from '../src/version'
|
||||
import { writeFileSync } from 'node:fs'
|
||||
|
||||
const manifest = {
|
||||
manifest_version: 4,
|
||||
type: 'extension',
|
||||
name: 'LLOneBot',
|
||||
slug: 'LLOneBot',
|
||||
description: '实现 OneBot 11 协议,用以 QQ 机器人开发',
|
||||
version,
|
||||
icon: './icon.webp',
|
||||
authors: [
|
||||
{
|
||||
name: 'linyuchen',
|
||||
link: 'https://github.com/linyuchen'
|
||||
}
|
||||
],
|
||||
repository: {
|
||||
repo: 'linyuchen/LiteLoaderQQNT-OneBotApi',
|
||||
branch: 'main',
|
||||
release: {
|
||||
tag: 'latest',
|
||||
name: 'LLOneBot.zip'
|
||||
}
|
||||
},
|
||||
platform: [
|
||||
'win32',
|
||||
'linux',
|
||||
'darwin'
|
||||
],
|
||||
injects: {
|
||||
renderer: './renderer/index.js',
|
||||
main: './main/main.cjs',
|
||||
preload: './preload/preload.cjs'
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync('manifest.json', JSON.stringify(manifest, null, 2))
|
@ -1,22 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { version } from '../src/version'
|
||||
|
||||
const manifestPath = path.join(__dirname, '../manifest.json')
|
||||
|
||||
function readManifest(): any {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
||||
}
|
||||
}
|
||||
|
||||
function writeManifest(manifest: any) {
|
||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const manifest = readManifest()
|
||||
if (version !== manifest.version) {
|
||||
manifest.version = version
|
||||
manifest.name = `LLOneBot v${version}`
|
||||
writeManifest(manifest)
|
||||
}
|
@ -53,7 +53,6 @@ export class ConfigUtil {
|
||||
reportSelfMessage: false,
|
||||
autoDeleteFile: false,
|
||||
autoDeleteFileSecond: 60,
|
||||
enablePoke: false,
|
||||
musicSignUrl: '',
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ export interface Config {
|
||||
autoDeleteFile?: boolean
|
||||
autoDeleteFileSecond?: number
|
||||
ffmpeg?: string // ffmpeg路径
|
||||
enablePoke?: boolean
|
||||
musicSignUrl?: string
|
||||
ignoreBeforeLoginMsg?: boolean
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
import fs from 'fs'
|
||||
import fsPromise from 'fs/promises'
|
||||
import util from 'util'
|
||||
import fs from 'node:fs'
|
||||
import fsPromise from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { log, TEMP_DIR } from './index'
|
||||
import { dbUtil } from '../db'
|
||||
import * as fileType from 'file-type'
|
||||
import { net } from 'electron'
|
||||
import { randomUUID, createHash } from 'node:crypto'
|
||||
|
||||
export function isGIF(path: string) {
|
||||
@ -36,7 +34,6 @@ export function checkFileReceived(path: string, timeout: number = 3000): Promise
|
||||
}
|
||||
|
||||
export async function file2base64(path: string) {
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
let result = {
|
||||
err: '',
|
||||
data: '',
|
||||
@ -52,7 +49,7 @@ export async function file2base64(path: string) {
|
||||
result.err = e.toString()
|
||||
return result
|
||||
}
|
||||
const data = await readFile(path)
|
||||
const data = await fsPromise.readFile(path)
|
||||
// 转换为Base64编码
|
||||
result.data = data.toString('base64')
|
||||
} catch (err) {
|
||||
@ -90,7 +87,6 @@ export interface HttpDownloadOptions {
|
||||
headers?: Record<string, string> | string
|
||||
}
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
let chunks: Buffer[] = []
|
||||
let url: string
|
||||
let headers: Record<string, string> = {
|
||||
'User-Agent':
|
||||
@ -108,12 +104,10 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
const fetchRes = await net.fetch(url, { headers })
|
||||
const fetchRes = await fetch(url, { headers })
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
||||
|
||||
const blob = await fetchRes.blob()
|
||||
let buffer = await blob.arrayBuffer()
|
||||
return Buffer.from(buffer)
|
||||
return Buffer.from(await fetchRes.arrayBuffer())
|
||||
}
|
||||
|
||||
type Uri2LocalRes = {
|
||||
@ -152,7 +146,7 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
let base64Data = uri.split('base64://')[1]
|
||||
try {
|
||||
const buffer = Buffer.from(base64Data, 'base64')
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
await fsPromise.writeFile(filePath, buffer)
|
||||
} catch (e: any) {
|
||||
res.errMsg = `base64文件下载失败,` + e.toString()
|
||||
return res
|
||||
@ -178,7 +172,7 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_')
|
||||
res.fileName = fileName
|
||||
filePath = path.join(TEMP_DIR, randomUUID() + fileName)
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
await fsPromise.writeFile(filePath, buffer)
|
||||
} catch (e: any) {
|
||||
res.errMsg = `${url}下载失败,` + e.toString()
|
||||
return res
|
||||
@ -217,7 +211,7 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
||||
let ext: string = (await fileType.fileTypeFromFile(filePath)).ext
|
||||
if (ext) {
|
||||
log('获取文件类型', ext, filePath)
|
||||
fs.renameSync(filePath, filePath + `.${ext}`)
|
||||
await fsPromise.rename(filePath, filePath + `.${ext}`)
|
||||
filePath += `.${ext}`
|
||||
res.fileName += `.${ext}`
|
||||
res.ext = ext
|
||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -3,6 +3,6 @@ import { type LLOneBot } from './preload'
|
||||
declare global {
|
||||
interface Window {
|
||||
llonebot: LLOneBot
|
||||
LiteLoader: any
|
||||
LiteLoader: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ import { dbUtil } from '../common/db'
|
||||
import { setConfig } from './setConfig'
|
||||
import { NTQQUserApi } from '../ntqqapi/api/user'
|
||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||
import { crychic } from '../ntqqapi/native/crychic'
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent'
|
||||
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
||||
import { log } from '../common/utils/log'
|
||||
@ -209,26 +208,7 @@ function onLoad() {
|
||||
}
|
||||
|
||||
async function startReceiveHook() {
|
||||
startHook().then()
|
||||
if (getConfigUtil().getConfig().enablePoke) {
|
||||
if (qqPkgInfo.buildVersion > '23873') {
|
||||
log(`当前版本${qqPkgInfo.buildVersion}不支持发送戳一戳模块`)
|
||||
}
|
||||
else {
|
||||
crychic.loadNode()
|
||||
crychic.registerPokeHandler((id, isGroup) => {
|
||||
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
||||
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent
|
||||
if (isGroup) {
|
||||
pokeEvent = new OB11GroupPokeEvent(parseInt(id))
|
||||
}
|
||||
else {
|
||||
pokeEvent = new OB11FriendPokeEvent(parseInt(selfInfo.uin), parseInt(id))
|
||||
}
|
||||
postOb11Event(pokeEvent)
|
||||
})
|
||||
}
|
||||
}
|
||||
startHook()
|
||||
registerReceiveHook<{
|
||||
msgList: Array<RawMessage>
|
||||
}>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], async (payload) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import type { BrowserWindow } from 'electron'
|
||||
import { NTQQApiClass, NTQQApiMethod } from './ntcall'
|
||||
import { NTQQMsgApi, sendMessagePool } from './api/msg'
|
||||
import { CategoryFriend, ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User } from './types'
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
OB11MessageVideo,
|
||||
OB11PostSendMsg,
|
||||
} from '../../types'
|
||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
||||
import { NTQQMsgApi } from '../../../ntqqapi/api/msg'
|
||||
import { SendMsgElementConstructor } from '../../../ntqqapi/constructor'
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName, BaseCheckResult } from '../types'
|
||||
@ -37,6 +37,7 @@ import { uri2local } from '../../../common/utils'
|
||||
import { crychic } from '../../../ntqqapi/native/crychic'
|
||||
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
||||
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
||||
import { Peer } from '../../../ntqqapi/types/msg'
|
||||
|
||||
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||
function checkUri(uri: string): boolean {
|
||||
|
@ -4,6 +4,7 @@ import { SettingButton, SettingItem, SettingList, SettingSwitch, SettingSelect }
|
||||
// @ts-ignore
|
||||
import StyleRaw from './style.css?raw'
|
||||
import { iconSvg } from './icon'
|
||||
import { version } from '../version'
|
||||
|
||||
// 打开设置界面时触发
|
||||
|
||||
@ -186,11 +187,6 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingItem('', null, SettingButton('保存', 'config-ob11-save', 'primary')),
|
||||
]),
|
||||
SettingList([
|
||||
SettingItem(
|
||||
'戳一戳消息, 暂时只支持Windows版的LLOneBot',
|
||||
`重启QQ后生效,如果导致QQ崩溃请勿开启此项, 群戳一戳只能收到群号`,
|
||||
SettingSwitch('enablePoke', config.enablePoke),
|
||||
),
|
||||
SettingItem(
|
||||
'使用 Base64 编码获取文件',
|
||||
'调用 /get_image、/get_record、/get_file 时,没有 url 时添加 Base64 字段',
|
||||
@ -404,7 +400,7 @@ async function onSettingWindowCreated(view: Element) {
|
||||
const buttonDom = view.querySelector<HTMLButtonElement>('#llonebot-update-button')!
|
||||
|
||||
if (ResultVersion.version === '') {
|
||||
titleDom.innerHTML = '检查更新失败'
|
||||
titleDom.innerHTML = `当前版本为 v${version},检查更新失败`
|
||||
buttonDom.innerHTML = '点击重试'
|
||||
|
||||
buttonDom.addEventListener('click', async () => {
|
||||
@ -414,10 +410,10 @@ async function onSettingWindowCreated(view: Element) {
|
||||
return
|
||||
}
|
||||
if (!ResultVersion.result) {
|
||||
titleDom.innerHTML = '当前已是最新版本 v' + ResultVersion.version
|
||||
titleDom.innerHTML = '当前已是最新版本 v' + version
|
||||
buttonDom.innerHTML = '无需更新'
|
||||
} else {
|
||||
titleDom.innerHTML = '已检测到最新版本 v' + ResultVersion.version
|
||||
titleDom.innerHTML = `当前版本为 v${version},最新版本为 v${ResultVersion.version}`
|
||||
buttonDom.innerHTML = '点击更新'
|
||||
buttonDom.dataset.type = 'primary'
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user