mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Merge pull request #133 from HollisMeynell/main
add: support for file download
This commit is contained in:
commit
9ed67628bc
13
package-lock.json
generated
13
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"level": "^8.0.1",
|
"level": "^8.0.1",
|
||||||
|
"node-stream-zip": "^1.15.0",
|
||||||
"silk-wasm": "^3.2.3",
|
"silk-wasm": "^3.2.3",
|
||||||
"utf-8-validate": "^6.0.3",
|
"utf-8-validate": "^6.0.3",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
@ -4930,6 +4931,18 @@
|
|||||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-stream-zip": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/antelle"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-url": {
|
"node_modules/normalize-url": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"level": "^8.0.1",
|
"level": "^8.0.1",
|
||||||
|
"node-stream-zip": "^1.15.0",
|
||||||
"silk-wasm": "^3.2.3",
|
"silk-wasm": "^3.2.3",
|
||||||
"utf-8-validate": "^6.0.3",
|
"utf-8-validate": "^6.0.3",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
@ -2,4 +2,6 @@ export const CHANNEL_GET_CONFIG = 'llonebot_get_config'
|
|||||||
export const CHANNEL_SET_CONFIG = 'llonebot_set_config'
|
export const CHANNEL_SET_CONFIG = 'llonebot_set_config'
|
||||||
export const CHANNEL_LOG = 'llonebot_log'
|
export const CHANNEL_LOG = 'llonebot_log'
|
||||||
export const CHANNEL_ERROR = 'llonebot_error'
|
export const CHANNEL_ERROR = 'llonebot_error'
|
||||||
|
export const CHANNEL_UPDATE = 'llonebot_update'
|
||||||
|
export const CHANNEL_CHECKVERSION = 'llonebot_checkversion'
|
||||||
export const CHANNEL_SELECT_FILE = 'llonebot_select_ffmpeg'
|
export const CHANNEL_SELECT_FILE = 'llonebot_select_ffmpeg'
|
||||||
|
@ -9,7 +9,10 @@ export interface OB11Config {
|
|||||||
enableWsReverse?: boolean
|
enableWsReverse?: boolean
|
||||||
messagePostFormat?: 'array' | 'string'
|
messagePostFormat?: 'array' | 'string'
|
||||||
}
|
}
|
||||||
|
export interface CheckVersion {
|
||||||
|
result: boolean,
|
||||||
|
version: string
|
||||||
|
}
|
||||||
export interface Config {
|
export interface Config {
|
||||||
ob11: OB11Config
|
ob11: OB11Config
|
||||||
token?: string
|
token?: string
|
||||||
|
@ -7,6 +7,8 @@ import fs from 'fs';
|
|||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import ffmpeg from "fluent-ffmpeg"
|
import ffmpeg from "fluent-ffmpeg"
|
||||||
|
import * as https from "node:https";
|
||||||
|
import { version } from "../version";
|
||||||
|
|
||||||
export const DATA_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
export const DATA_DIR = global.LiteLoader.plugins["LLOneBot"].path.data;
|
||||||
|
|
||||||
@ -35,8 +37,66 @@ function truncateString(obj: any, maxLength = 500) {
|
|||||||
export function isNumeric(str: string) {
|
export function isNumeric(str: string) {
|
||||||
return /^\d+$/.test(str);
|
return /^\d+$/.test(str);
|
||||||
}
|
}
|
||||||
|
// 判断是否为最新版本
|
||||||
|
export async function checkVersion() {
|
||||||
|
const latestVersionText = await getRemoteVersion();
|
||||||
|
const latestVersion = latestVersionText.split(".");
|
||||||
|
const currentVersion = version.split(".");
|
||||||
|
for (let k in [0, 1, 2]) {
|
||||||
|
if (latestVersion[k] > currentVersion[k]) {
|
||||||
|
return { result: false, version: latestVersionText };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { result: true, version: version };
|
||||||
|
}
|
||||||
|
export async function updateLLOneBot() {
|
||||||
|
let mirrorGithubList = ["https://mirror.ghproxy.com"];
|
||||||
|
const latestVersion = await getRemoteVersion();
|
||||||
|
if (latestVersion && latestVersion != "") {
|
||||||
|
const downloadUrl = "https://github.com/LLOneBot/LLOneBot/releases/download/v" + latestVersion + "/LLOneBot.zip";
|
||||||
|
const realUrl = mirrorGithubList[0] + downloadUrl;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export async function getRemoteVersion() {
|
||||||
|
let mirrorGithubList = ["https://521github.com"];
|
||||||
|
let Version = "";
|
||||||
|
for (let i = 0; i < mirrorGithubList.length; i++) {
|
||||||
|
let mirrorGithub = mirrorGithubList[i];
|
||||||
|
let tVersion = await getRemoteVersionByMirror(mirrorGithub);
|
||||||
|
if (tVersion && tVersion != "") {
|
||||||
|
Version = tVersion;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Version;
|
||||||
|
}
|
||||||
|
export async function getRemoteVersionByMirror(mirrorGithub: string) {
|
||||||
|
let releasePage = "error";
|
||||||
|
let reqPromise = async function (): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(mirrorGithub + "/LLOneBot/LLOneBot/releases", res => {
|
||||||
|
let list = [];
|
||||||
|
res.on('data', chunk => {
|
||||||
|
list.push(chunk);
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve(Buffer.concat(list).toString());
|
||||||
|
});
|
||||||
|
}).on('error', err => {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
releasePage = await reqPromise();
|
||||||
|
if (releasePage === "error") return "";
|
||||||
|
return releasePage.match(new RegExp('(?<=(tag/v)).*?(?=("))'))[0];
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return "";
|
||||||
|
|
||||||
|
}
|
||||||
export function log(...msg: any[]) {
|
export function log(...msg: any[]) {
|
||||||
if (!getConfigUtil().getConfig().log) {
|
if (!getConfigUtil().getConfig().log) {
|
||||||
return //console.log(...msg);
|
return //console.log(...msg);
|
||||||
|
@ -7,11 +7,13 @@ import {
|
|||||||
CHANNEL_ERROR,
|
CHANNEL_ERROR,
|
||||||
CHANNEL_GET_CONFIG,
|
CHANNEL_GET_CONFIG,
|
||||||
CHANNEL_LOG,
|
CHANNEL_LOG,
|
||||||
|
CHANNEL_CHECKVERSION,
|
||||||
CHANNEL_SELECT_FILE,
|
CHANNEL_SELECT_FILE,
|
||||||
CHANNEL_SET_CONFIG,
|
CHANNEL_SET_CONFIG,
|
||||||
|
CHANNEL_UPDATE,
|
||||||
} from "../common/channels";
|
} from "../common/channels";
|
||||||
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
|
import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
|
||||||
import {checkFfmpeg, DATA_DIR, getConfigUtil, log} from "../common/utils";
|
import {checkFfmpeg, checkVersion, DATA_DIR, getConfigUtil, log, updateLLOneBot} from "../common/utils";
|
||||||
import {
|
import {
|
||||||
friendRequests,
|
friendRequests,
|
||||||
getFriend,
|
getFriend,
|
||||||
@ -47,7 +49,12 @@ let running = false;
|
|||||||
// 加载插件时触发
|
// 加载插件时触发
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
log("llonebot main onLoad");
|
log("llonebot main onLoad");
|
||||||
|
ipcMain.handle(CHANNEL_CHECKVERSION, async (event, arg) => {
|
||||||
|
return checkVersion();
|
||||||
|
});
|
||||||
|
ipcMain.handle(CHANNEL_UPDATE, async (event, arg) => {
|
||||||
|
return updateLLOneBot();
|
||||||
|
});
|
||||||
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
|
ipcMain.handle(CHANNEL_SELECT_FILE, async (event, arg) => {
|
||||||
const selectPath = new Promise<string>((resolve, reject) => {
|
const selectPath = new Promise<string>((resolve, reject) => {
|
||||||
dialog
|
dialog
|
||||||
@ -309,7 +316,6 @@ function onLoad() {
|
|||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
log("llonebot pid", process.pid)
|
log("llonebot pid", process.pid)
|
||||||
|
|
||||||
startTime = Date.now();
|
startTime = Date.now();
|
||||||
startReceiveHook().then();
|
startReceiveHook().then();
|
||||||
NTQQGroupApi.getGroups(true).then()
|
NTQQGroupApi.getGroups(true).then()
|
||||||
|
103
src/onebot11/action/go-cqhttp/DownloadFile.ts
Normal file
103
src/onebot11/action/go-cqhttp/DownloadFile.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import BaseAction from "../BaseAction";
|
||||||
|
import {ActionName} from "../types";
|
||||||
|
import fs from "fs";
|
||||||
|
import {join as joinPath} from "node:path";
|
||||||
|
import {calculateFileMD5, DATA_DIR} from "../../../common/utils";
|
||||||
|
|
||||||
|
interface Payload {
|
||||||
|
thread_count?: number
|
||||||
|
url?: string
|
||||||
|
base64?: string
|
||||||
|
name?: string
|
||||||
|
headers?: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileResponse {
|
||||||
|
file: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPath = joinPath(DATA_DIR, "file_cache")
|
||||||
|
export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileResponse> {
|
||||||
|
actionName = ActionName.GoCQHTTP_DownloadFile
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
if (!fs.existsSync(localPath)) {
|
||||||
|
fs.mkdirSync(localPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _handle(payload: Payload): Promise<FileResponse> {
|
||||||
|
let name = payload.name || "";
|
||||||
|
const isRandomName = !payload.name
|
||||||
|
|
||||||
|
if (isRandomName) {
|
||||||
|
do {
|
||||||
|
name = this.generateRandomString(10);
|
||||||
|
// 使用循环防止极低概率的情况下随机出已有的文件, 导致覆盖
|
||||||
|
} while (fs.existsSync(joinPath(localPath, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = joinPath(localPath, name);
|
||||||
|
|
||||||
|
if (payload.base64) {
|
||||||
|
fs.writeFileSync(filePath, payload.base64, 'base64')
|
||||||
|
} else if (payload.url) {
|
||||||
|
const headers = this.getHeaders(payload.headers);
|
||||||
|
|
||||||
|
const result = await fetch(payload.url, {headers})
|
||||||
|
if (! result.ok) throw new Error(`下载文件失败: ${result.statusText}`)
|
||||||
|
|
||||||
|
const blob = await result.blob();
|
||||||
|
let buffer = await blob.arrayBuffer();
|
||||||
|
fs.writeFileSync(filePath, Buffer.from(buffer), 'binary');
|
||||||
|
} else {
|
||||||
|
throw new Error("不存在任何文件, 无法下载")
|
||||||
|
}
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
|
||||||
|
if (isRandomName) {
|
||||||
|
// 默认实现要名称未填写时文件名为文件 md5
|
||||||
|
const md5 = await calculateFileMD5(filePath);
|
||||||
|
const newPath = joinPath(localPath, md5);
|
||||||
|
fs.renameSync(filePath, newPath);
|
||||||
|
return { file: newPath }
|
||||||
|
}
|
||||||
|
return { file: filePath }
|
||||||
|
} else {
|
||||||
|
throw new Error("文件写入失败, 检查权限")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRandomString(length: number): string {
|
||||||
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let randomString = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
|
randomString += characters.charAt(randomIndex);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders(headersIn?: string | string[]): any {
|
||||||
|
const headers = {};
|
||||||
|
if (typeof headersIn == 'string') {
|
||||||
|
headersIn = headersIn.split('[\\r\\n]');
|
||||||
|
}
|
||||||
|
if (Array.isArray(headersIn)) {
|
||||||
|
for (const headerItem of headersIn) {
|
||||||
|
const spilt = headerItem.indexOf('=');
|
||||||
|
if (spilt < 0) {
|
||||||
|
headers[headerItem] = "";
|
||||||
|
} else {
|
||||||
|
const key = headerItem.substring(0, spilt);
|
||||||
|
headers[key] = headerItem.substring(0, spilt + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!headers['Content-Type']) {
|
||||||
|
headers['Content-Type'] = 'application/octet-stream';
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@ import GoCQHTTPUploadGroupFile from "./go-cqhttp/UploadGroupFile";
|
|||||||
import {GetConfigAction, SetConfigAction} from "./llonebot/Config";
|
import {GetConfigAction, SetConfigAction} from "./llonebot/Config";
|
||||||
import GetGroupAddRequest from "./llonebot/GetGroupAddRequest";
|
import GetGroupAddRequest from "./llonebot/GetGroupAddRequest";
|
||||||
import SetQQAvatar from './llonebot/SetQQAvatar'
|
import SetQQAvatar from './llonebot/SetQQAvatar'
|
||||||
|
import GoCQHTTPDownloadFile from "./go-cqhttp/DownloadFile";
|
||||||
|
|
||||||
export const actionHandlers = [
|
export const actionHandlers = [
|
||||||
new Debug(),
|
new Debug(),
|
||||||
@ -72,6 +73,7 @@ export const actionHandlers = [
|
|||||||
new GoCQHTTPSendGroupForwardMsg(),
|
new GoCQHTTPSendGroupForwardMsg(),
|
||||||
new GoCQHTTPSendPrivateForwardMsg(),
|
new GoCQHTTPSendPrivateForwardMsg(),
|
||||||
new GoCQHTTPGetStrangerInfo(),
|
new GoCQHTTPGetStrangerInfo(),
|
||||||
|
new GoCQHTTPDownloadFile(),
|
||||||
new GetGuildList(),
|
new GetGuildList(),
|
||||||
new GoCQHTTPMarkMsgAsRead(),
|
new GoCQHTTPMarkMsgAsRead(),
|
||||||
new GoCQHTTPUploadGroupFile(),
|
new GoCQHTTPUploadGroupFile(),
|
||||||
|
@ -54,4 +54,5 @@ export enum ActionName {
|
|||||||
GetGuildList = "get_guild_list",
|
GetGuildList = "get_guild_list",
|
||||||
GoCQHTTP_MarkMsgAsRead = "mark_msg_as_read",
|
GoCQHTTP_MarkMsgAsRead = "mark_msg_as_read",
|
||||||
GoCQHTTP_UploadGroupFile = "upload_group_file",
|
GoCQHTTP_UploadGroupFile = "upload_group_file",
|
||||||
|
GoCQHTTP_DownloadFile = "download_file",
|
||||||
}
|
}
|
@ -1,12 +1,14 @@
|
|||||||
// Electron 主进程 与 渲染进程 交互的桥梁
|
// Electron 主进程 与 渲染进程 交互的桥梁
|
||||||
|
|
||||||
import {Config, LLOneBotError} from "./common/types";
|
import {CheckVersion, Config, LLOneBotError} from "./common/types";
|
||||||
import {
|
import {
|
||||||
CHANNEL_ERROR,
|
CHANNEL_ERROR,
|
||||||
CHANNEL_GET_CONFIG,
|
CHANNEL_GET_CONFIG,
|
||||||
CHANNEL_LOG,
|
CHANNEL_LOG,
|
||||||
|
CHANNEL_CHECKVERSION,
|
||||||
CHANNEL_SELECT_FILE,
|
CHANNEL_SELECT_FILE,
|
||||||
CHANNEL_SET_CONFIG,
|
CHANNEL_SET_CONFIG,
|
||||||
|
CHANNEL_UPDATE,
|
||||||
} from "./common/channels";
|
} from "./common/channels";
|
||||||
|
|
||||||
const {contextBridge} = require("electron");
|
const {contextBridge} = require("electron");
|
||||||
@ -16,6 +18,12 @@ const llonebot = {
|
|||||||
log: (data: any) => {
|
log: (data: any) => {
|
||||||
ipcRenderer.send(CHANNEL_LOG, data);
|
ipcRenderer.send(CHANNEL_LOG, data);
|
||||||
},
|
},
|
||||||
|
checkVersion:async (): Promise<CheckVersion> => {
|
||||||
|
return ipcRenderer.invoke(CHANNEL_CHECKVERSION);
|
||||||
|
},
|
||||||
|
updateLLOneBot:async (): Promise<boolean> => {
|
||||||
|
return ipcRenderer.invoke(CHANNEL_UPDATE);
|
||||||
|
},
|
||||||
setConfig: (config: Config) => {
|
setConfig: (config: Config) => {
|
||||||
ipcRenderer.send(CHANNEL_SET_CONFIG, config);
|
ipcRenderer.send(CHANNEL_SET_CONFIG, config);
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user