Files
2023-09-24 08:50:43 +08:00

528 lines
13 KiB
Go

package core
import (
"bufio"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"time"
"github.com/cdle/sillyplus/core/common"
"github.com/cdle/sillyplus/core/storage"
"github.com/cdle/sillyplus/utils"
"github.com/dop251/goja"
"github.com/fsnotify/fsnotify"
"github.com/google/uuid"
cron "github.com/robfig/cron/v3"
)
func init() {
go initNodePlugins()
}
var processes sync.Map
func initNodePlugins() {
initLanguage()
root := strings.ReplaceAll(utils.ExecPath+"/plugins", "\\", "/")
plugins := []string{root}
os.Mkdir(root, 0755)
// fmt.Println("root", root)
files, _ := ioutil.ReadDir(root)
for _, file := range files {
if !file.IsDir() {
continue
}
name := file.Name()
path := root + "/" + name
plugins = append(plugins, path)
index, class := FindMainIndex(path)
if index != "" {
AddNodePlugin(index, name, class)
}
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Println("创建监视器失败:", err)
return
}
defer watcher.Close()
// 要监控的文件夹路径
for _, dir := range plugins {
err = watcher.Add(dir)
if err != nil {
fmt.Println("添加监视目录失败:", err)
return
}
}
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// fmt.Println(event.Name, "op", event.Op.String())
event.Name = strings.ReplaceAll(event.Name, "\\", "/")
files := strings.Split(strings.Replace(event.Name, root+"/", "", 1), "/")
var plugin_dir = false
var plugin_index = false
var plugin_name = ""
var class = ""
switch len(files) {
case 1:
plugin_dir = true
// fmt.Println("目录事件")
plugin_name = files[0]
case 2:
class, plugin_index = CheckMainIndex(files[1])
// if files[1] == "main.js" {
// if files[1] == "main.js" {
// plugin_index = true
// }
// // fmt.Println("入口文件事件")
// }
plugin_name = files[0]
}
if plugin_name == "." {
continue
}
switch event.Op.String() {
case "CREATE":
if plugin_dir {
info, err := os.Stat(event.Name)
// fmt.Println(err)
if err == nil && info.IsDir() {
index, class := FindMainIndex(event.Name)
switch class {
case NODE:
AddNodePlugin(index, plugin_name, class)
case PYTHON:
case "":
// time.Sleep(time.Millisecond * 100) //如果没有
// if info, err := os.Stat(event.Name + "/demo.main.js"); err == nil && !info.IsDir() {
// }
}
// event_name := event.Name + "/main.js"
// if info, err := os.Stat(event_name); err == nil && !info.IsDir() {
// AddNodePlugin(event_name, plugin_name)
// } else {
// time.Sleep(time.Millisecond * 100)
// if info, err := os.Stat(event_name); err == nil && !info.IsDir() {
// AddNodePlugin(event_name, plugin_name)
// }
// }
watcher.Add(event.Name)
// fmt.Println("增加插件目录", event.Name)
} else {
// fmt.Println("非插件目录", event.Name)
}
tf := event.Name + "/node_modules/sillygirl.d.ts"
ti := event.Name + "/demo.main.js"
if _, err := os.Stat(tf); err != nil {
os.Mkdir(event.Name+"/node_modules", 0700)
os.WriteFile(tf, []byte(typeat), 0700)
}
go func() {
time.Sleep(time.Second)
if _, err := os.Stat(ti); err != nil {
os.Mkdir(event.Name+"/node_modules", 0700)
os.WriteFile(ti, []byte(defaultScript(plugin_name)), 0700)
}
}()
} else if plugin_index {
// fmt.Println("增加插件", event.Name)
// RemNodePlugin(plugin_name)
AddNodePlugin(event.Name, plugin_name, class)
}
case "REMOVE", "RENAME", "REMOVE|RENAME", "REMOVE|WRITE":
if plugin_dir {
watcher.Remove(event.Name)
// fmt.Println("移除插件目录", event.Name)
// fmt.Println("移除插件", plugin_name)
AddNodePlugin(event.Name, plugin_name, UNKNOWN)
} else if plugin_index {
// fmt.Println("移除插件", plugin_name)
AddNodePlugin(event.Name, plugin_name, class)
}
case "WRITE": //, "CHMOD"
if plugin_index {
AddNodePlugin(event.Name, plugin_name, class)
// fmt.Println("变更插件", event.Name, plugin_name)
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
fmt.Println("错误:", err)
}
}
}
func nameUuid(name string) string {
hash := sha1.Sum([]byte(name))
return strings.ReplaceAll(uuid.NewSHA1(uuid.Nil, hash[:]).String(), "-", "_")
}
func isNameUuid(uuid string) bool {
return strings.Contains(uuid, "_")
}
func AddNodePlugin(path, name, class string) error {
if name == "" {
return nil
}
uuid := nameUuid(name)
pluginLock.Lock()
defer pluginLock.Unlock()
//移除
var rf *common.Function
for i := range Functions {
if Functions[i].UUID == uuid {
rf = Functions[i]
DestroyAdapterByUUID(uuid)
Functions[i].Running = false
if len(Functions[i].CronIds) != 0 {
for _, id := range Functions[i].CronIds {
CRON.Remove(cron.EntryID(id))
}
}
Functions = append(Functions[:i], Functions[i+1:]...)
CancelPluginCrons(uuid)
CancelPluginWebs(uuid)
CancelPluginlistening(uuid)
CancelHttpListen(uuid)
remStatic(uuid)
storage.DisableHandle(uuid)
break
}
}
file, err := os.Open(path)
if err != nil {
if rf != nil {
console.Log("已卸载 %s%s", rf.Title, rf.Suffix)
}
return err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return err
}
script := string(data)
if script == "" {
return nil
}
// plugins_id.Store(uuid, path)
// fmt.Println("add,", uuid, name)
f, cbs := pluginParse(script, uuid)
f.Reload = func() { //重载
AddNodePlugin(path, name, class)
}
f.Type = class
switch f.Type {
case NODE:
f.Suffix = ".js"
case PYTHON:
f.Suffix = ".py"
}
f.Path = path
f.Handle = func(s common.Sender, _ func(vm *goja.Runtime)) interface{} {
console := &Console{UUID: uuid}
s.SetPluginID(uuid)
plt := s.GetImType()
bin := ""
var cmd *exec.Cmd
switch class {
case NODE:
bin = utils.ExecPath + "/language/node/node"
cmd = exec.Command(bin, path)
case PYTHON:
bin = "python3"
cmd = exec.Command(bin, "-u", path)
cmd.Env = append(cmd.Env, "PYTHONPATH=/Users/a1-6/Code/sillyplus/proto3")
}
cmd.Dir = filepath.Dir(path)
RUNTIME_ID := utils.GenUUID()
cmd.Env = append(cmd.Env, "RUNTIME_ID="+RUNTIME_ID)
cmd.Env = append(cmd.Env, "PLUGIN_ID="+uuid)
// 获取标准输出和标准错误输出的管道
stdout, err := cmd.StdoutPipe()
if err != nil {
// fmt.Printf("获取标准输出管道失败:%v\n", err)
// os.Exit(1)
}
stderr, err := cmd.StderrPipe()
if err != nil {
// fmt.Printf("获取标准错误输出管道失败:%v\n", err)
// os.Exit(1)
}
// file, err := os.Create("output.log")
// if err != nil {
// fmt.Printf("创建文件失败:%v\n", err)
// os.Exit(1)
// }
// defer file.Close()
var wg sync.WaitGroup
wg.Add(2)
// 处理标准输出
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
data := scanner.Text()
fmt.Println(data)
// if _, err := file.WriteString(data + "\n"); err != nil {
// fmt.Printf("写入文件失败:%v\n", err)
// }
}
}()
// 处理标准错误输出
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stderr)
if f.OnStart {
for scanner.Scan() {
fmt.Println(scanner.Text())
}
} else {
lines := []string{}
for scanner.Scan() {
data := scanner.Text()
lines = append(lines, data)
}
if len(lines) != 0 {
console.Error(strings.Join(lines, "\n"))
}
}
}()
processes.Store(cmd, s)
register := createSenderRegister(RUNTIME_ID)
if (plt) != "*" {
cmd.Env = append(cmd.Env, "SENDER_ID="+register(s))
err = cmd.Start()
if err != nil {
}
defer deleteSenderRegister(RUNTIME_ID)
defer processes.Delete(cmd)
err = cmd.Wait()
if err != nil {
fmt.Println("命令执行失败:", err)
return nil
}
} else {
err = cmd.Start()
if err != nil {
}
processes.Range(func(key, value any) bool {
p := key.(*exec.Cmd)
if p == cmd {
return true
}
s := value.(common.Sender)
if s.GetPluginID() == uuid {
func() {
defer func() {
recover()
}()
if p.Process.Kill() == nil {
processes.Delete(key)
}
}()
}
return true
})
go func() {
defer deleteSenderRegister(RUNTIME_ID)
defer processes.Delete(cmd)
err = cmd.Wait()
}()
}
return nil
}
for _, cb := range cbs {
cb()
}
if !f.Disable { //!f.OnStart &&
if rf == nil {
// console.Log("已加载 %s%s", f.Title, f.Suffix)
} else {
console.Log("已重载 %s%s", f.Title, f.Suffix)
}
}
AddCommand([]*common.Function{f})
return nil
}
var typeat = `declare class Sender {
private uuid;
private destoried;
constructor(uuid: string);
destroy(): void;
getUserId(): Promise<string>;
getUserName(): Promise<string>;
getChatId(): Promise<string>;
getChatName(): Promise<string>;
getMessageId(): Promise<string>;
getPlatform(): Promise<string>;
getBotId(): Promise<string>;
getContent(): Promise<string>;
isAdmin(): Promise<boolean>;
param(key: number | string): Promise<string>;
setContent(content: string): Promise<undefined>;
continue(): Promise<undefined>;
getAdapter(): Promise<Adapter>;
listen(options?: {
rules?: string[];
timeout?: number;
handle?: (s: Sender) => Promise<string | void> | string | void;
listen_private?: boolean;
listen_group?: boolean;
allow_platforms?: string[];
prohibit_platforms?: string[];
allow_groups?: string[];
prohibit_groups?: string[];
allow_users?: string[];
prohibit_users?: string[];
}): Promise<Sender | undefined>;
holdOn(str: string): string;
reply(content: string): Promise<string>;
doAction(options: Record<string, any>): Promise<any>;
getEvent(): Promise<Record<string, any>>;
}
declare class Bucket {
private name;
constructor(name: string);
transform(v: string | undefined): string | number | boolean | undefined;
reverseTransform(value: any): string;
get(key: string, defaultValue?: any): Promise<any>;
set(key: string, value: any): Promise<{
message?: string;
changed?: boolean;
}>;
getAll(): Promise<Record<string, any>>;
delete(key: string): Promise<{
message?: string;
changed?: boolean;
}>;
deleteAll(): Promise<undefined>;
keys(): Promise<string[]>;
len(): Promise<number>;
buckets(): Promise<string[]>;
watch(key: string, handle: (old: any, now: any, key: string) => StorageModifier | void): void;
getName(): Promise<string>;
}
interface StorageModifier {
echo?: string;
now?: any;
message?: string;
error?: string;
}
interface Message {
message_id?: string;
user_id: string;
chat_id?: string;
content: string;
user_name?: string;
chat_name?: string;
}
declare class Adapter {
platform: string;
bot_id: string;
call: any;
constructor(options: {
platform: string;
bot_id: string;
replyHandler?: (message: Message) => Promise<string | undefined>;
actionHandler?: (message: Message) => Promise<string | undefined>;
});
receive(message: Message): Promise<undefined>;
push(message: Message): Promise<string>;
destroy(): Promise<void>;
sender(options: any): Promise<Sender>;
}
declare let sender: Sender;
declare function sleep(ms?: number): Promise<unknown>;
interface CQItem {
type: string;
params: Record<string, string>;
}
interface CQParams {
[key: string]: string | number | boolean;
}
declare let utils: {
buildCQTag: (type: string, params: CQParams, prefix?: string) => string;
parseCQText: (text: string, prefix?: string) => (string | CQItem)[];
image: (url: string) => string;
video: (url: string) => string;
};
declare let console: {
log(...args: any[]): void;
info(...args: any[]): void;
error(...args: any[]): void;
debug(...args: any[]): void;
};
export { Adapter, Bucket, sender, sleep, utils, console };
`
func defaultScript(title string) string {
create_at := time.Now().Format("2006-01-02 15:04:05")
return `/**
* @title ` + title + `
* @create_at ` + create_at + `
* @description 🐒这个人很懒什么都没有留下
* @author ` + sillyGirl.GetString("author", "佚名") + `
* @version v1.0.0
*/
const {
sender: s,
Bucket,
utils: { buildCQTag, image, video },
} = require("sillygirl");
`
}
const (
NODE = "node"
PYTHON = "python3"
UNKNOWN = "unknown"
)
func FindMainIndex(home string) (string, string) {
if info, err := os.Stat(home + "/main.js"); err == nil && !info.IsDir() {
return home + "/main.js", NODE
}
if info, err := os.Stat(home + "/main.py"); err == nil && !info.IsDir() {
return home + "/main.py", PYTHON
}
return "", ""
}
func CheckMainIndex(filename string) (string, bool) {
switch filename {
case "main.js":
return NODE, true
case "main.py":
return PYTHON, true
}
return "", false
}