From 70bad0cc52a6173169e2a460e933fa78a7fe4aaf Mon Sep 17 00:00:00 2001 From: 1-6 Date: Wed, 9 Aug 2023 10:21:56 +0800 Subject: [PATCH] x --- .dockerfile | 79 +++++++ core/grpc_bucket.go | 7 + core/grpc_plugins.go | 142 ++++++++---- core/grpc_runtime.go | 9 + core/grpc_sender.go | 25 +- core/plugin_core.go | 4 +- main.go | 3 + package.json | 5 + proto3/set_up.py | 12 + proto3/sillygirl.py | 526 ++++++++++++++++++++++++++++++++++++------- yarn.lock | 42 ++++ 11 files changed, 725 insertions(+), 129 deletions(-) create mode 100644 .dockerfile create mode 100644 package.json create mode 100644 proto3/set_up.py create mode 100644 yarn.lock diff --git a/.dockerfile b/.dockerfile new file mode 100644 index 0000000..7d10057 --- /dev/null +++ b/.dockerfile @@ -0,0 +1,79 @@ +# 使用Alpine镜像作为基础镜像 +FROM alpine:latest + +# 将APK源更换为国内源(这里使用阿里云的源) +RUN echo "https://mirrors.aliyun.com/alpine/latest-stable/main" > /etc/apk/repositories && \ + echo "https://mirrors.aliyun.com/alpine/latest-stable/community" >> /etc/apk/repositories + +# 更新软件包索引并安装必要的工具 +RUN apk update && apk add --no-cache \ + python3 \ + py3-pip \ + php \ + php-json \ + php-phar \ + php-openssl \ + php-pdo \ + php-mysqli \ + php-session \ + php-ctype \ + php-tokenizer \ + php-dom \ + php-xml \ + php-xmlwriter \ + php-mbstring \ + php-simplexml \ + php-fileinfo \ + php-opcache \ + php-zlib \ + php-curl \ + php-ftp \ + php-gd \ + php-xmlreader \ + php-pdo_mysql \ + php-pdo_sqlite \ + php-pdo_pgsql \ + php-posix \ + php-sockets \ + php-bcmath \ + php-redis \ + php-pear \ + php-dev \ + php-pear-grpc \ + curl \ + git + +# 安装gRPC扩展 +RUN pecl install grpc + +# 清理缓存和临时文件 +RUN rm -rf /tmp/* /var/cache/apk/* + +# 下载文件 +RUN mkdir -p /usr/local/sillyGirl \ + && ARCH=$(uname -m) \ + && DOWNLOAD_URL="" \ + && if [ "$ARCH" = "x86_64" ]; then \ + DOWNLOAD_URL="https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_linux_amd64"; \ + elif [ "$ARCH" = "aarch64" ]; then \ + DOWNLOAD_URL="https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_linux_arm64"; \ + elif [ "$ARCH" = "armv7l" ]; then \ + DOWNLOAD_URL="https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_linux_armv7"; \ + else \ + echo "Unsupported architecture: $ARCH"; \ + exit 1; \ + fi \ + && curl -sSL --connect-timeout 20 -f "$DOWNLOAD_URL" -o /usr/local/sillyGirl/sillyGirl \ + || (echo "Download from original address failed, trying proxy address..." \ + && curl -sSL --connect-timeout 60 -f https://ghproxy.com/"$DOWNLOAD_URL" -o /usr/local/sillyGirl/sillyGirl) \ + && chmod +x /usr/local/sillyGirl/sillyGirl + +# 设置工作目录 +WORKDIR /usr/local/sillyGirl + + + + + +# 指定容器启动时要运行的命令 +CMD ["/usr/local/sillyGirl/sillyGirl"] \ No newline at end of file diff --git a/core/grpc_bucket.go b/core/grpc_bucket.go index d309a5d..9681518 100644 --- a/core/grpc_bucket.go +++ b/core/grpc_bucket.go @@ -18,6 +18,9 @@ type SillyGirlService struct { func (sg *SillyGirlService) BucketWatch(stream srpc.SillyGirlService_BucketWatchServer) error { var watcher func(old, new, key string) *storage.Final var echos sync.Map + defer func() { + watcher = nil + }() for { req, err := stream.Recv() if err != nil { @@ -25,6 +28,9 @@ func (sg *SillyGirlService) BucketWatch(stream srpc.SillyGirlService_BucketWatch } if watcher == nil { watcher = func(old, new, key string) *storage.Final { + if watcher == nil { + return nil + } echo := utils.GenUUID() ch := make(chan *storage.Final) echos.Store(echo, ch) @@ -64,6 +70,7 @@ func (sg *SillyGirlService) BucketWatch(stream srpc.SillyGirlService_BucketWatch } } } + } // Get implements BucketServiceServer.Get. diff --git a/core/grpc_plugins.go b/core/grpc_plugins.go index f298264..a086367 100644 --- a/core/grpc_plugins.go +++ b/core/grpc_plugins.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" "sync" "time" @@ -32,7 +33,6 @@ func initNodePlugins() { plugins := []string{root} os.Mkdir(root, 0755) // fmt.Println("root", root) - files, _ := ioutil.ReadDir(root) for _, file := range files { if !file.IsDir() { @@ -41,9 +41,10 @@ func initNodePlugins() { name := file.Name() path := root + "/" + name plugins = append(plugins, path) - index := path + "/main.js" - if info, err := os.Stat(index); err == nil && !info.IsDir() { - AddNodePlugin(index, name) + index, class := FindMainIndex(path) + + if index != "" { + AddNodePlugin(index, name, class) } } watcher, err := fsnotify.NewWatcher() @@ -73,18 +74,20 @@ func initNodePlugins() { 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: - if files[1] == "main.ts" || files[1] == "main.js" { - if files[1] == "main.js" { - plugin_index = true - } - // fmt.Println("入口文件事件") - } + 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 == "." { @@ -96,52 +99,62 @@ func initNodePlugins() { info, err := os.Stat(event.Name) // fmt.Println(err) if 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) - } + 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 + "/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) - } - }() + // 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) + 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) - + AddNodePlugin(event.Name, plugin_name, UNKNOWN) } else if plugin_index { // fmt.Println("移除插件", plugin_name) - AddNodePlugin(event.Name, plugin_name) + AddNodePlugin(event.Name, plugin_name, class) } case "WRITE": //, "CHMOD" if plugin_index { - AddNodePlugin(event.Name, plugin_name) + AddNodePlugin(event.Name, plugin_name, class) // fmt.Println("变更插件", event.Name, plugin_name) } } @@ -163,7 +176,8 @@ func isNameUuid(uuid string) bool { return strings.Contains(uuid, "_") } -func AddNodePlugin(path, name string) error { +func AddNodePlugin(path, name, class string) error { + if name == "" { return nil } @@ -212,17 +226,34 @@ func AddNodePlugin(path, name string) error { // fmt.Println("add,", uuid, name) f, cbs := pluginParse(script, uuid) f.Reload = func() { //重载 - AddNodePlugin(path, name) + AddNodePlugin(path, name, class) + } + + f.Type = class + switch f.Type { + case NODE: + f.Suffix = ".js" + case PYTHON: + f.Suffix = ".py" } - f.Suffix = ".js" - f.Type = "node" f.Path = path f.Handle = func(s common.Sender, _ func(vm *goja.Runtime)) interface{} { console := &Console{UUID: uuid} s.SetPluginID(uuid) plt := s.GetImType() - cmd := exec.Command("./node", path) - cmd.Dir = utils.ExecPath + "/language/node" + 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) @@ -249,6 +280,7 @@ func AddNodePlugin(path, name string) error { // 处理标准输出 go func() { defer wg.Done() + scanner := bufio.NewScanner(stdout) for scanner.Scan() { data := scanner.Text() @@ -467,3 +499,29 @@ const { } = 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 +} diff --git a/core/grpc_runtime.go b/core/grpc_runtime.go index c181440..14e5b3c 100644 --- a/core/grpc_runtime.go +++ b/core/grpc_runtime.go @@ -14,6 +14,15 @@ import ( var senderRegisters sync.Map func getRegisterSenderByCtx(ctx context.Context, uuid string) (common.Sender, error) { + // if uuid == "" { //临时---====+++测试 + // plt, _ := GetAdapter("terminal", "default") + // return plt.Sender2(map[string]string{ + // CONETNT: "hello", + // USER_ID: "user_id", + // CHAT_ID: "chat_id", + // MESSAGE_ID: "MESSAGE_ID", + // }), nil + // } md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, fmt.Errorf("missing metadata") diff --git a/core/grpc_sender.go b/core/grpc_sender.go index b27348c..1e67221 100644 --- a/core/grpc_sender.go +++ b/core/grpc_sender.go @@ -114,9 +114,12 @@ func (sg *SillyGirlService) SenderListen(stream srpc.SillyGirlService_SenderList var carry *Carry var echos sync.Map var persistent bool - // defer fmt.Println("已关闭,", "===") + ctx := stream.Context() + + defer fmt.Println("已关闭,", "===") for { req, err := stream.Recv() + // fmt.Println("req", string(utils.JsonMarshal(req))) // fmt.Println("carry", carry, err) if err == io.EOF { break // 如果流已经关闭,则退出循环 @@ -187,30 +190,42 @@ func (sg *SillyGirlService) SenderListen(stream srpc.SillyGirlService_SenderList persistent = req.Persistent options = append(options, "persistent") } else { - options = append(options, func(err error) { - stream.Send(&srpc.SenderListenResponse{Echo: ""}) + // fmt.Println("???") + options = append(options, func(err error) { //超时 + stream.Send(&srpc.SenderListenResponse{Echo: "END"}) }) + // fmt.Println("?????") } level := s.GetLevel() - go s.Await(s, func(s common.Sender) interface{} { s.SetLevel(level + 1) echo := utils.GenUUID() ch := make(chan string) echos.Store(echo, ch) defer echos.Delete(echo) + // fmt.Println("tosend") stream.Send(&srpc.SenderListenResponse{Echo: echo, Uuid: register(s)}) - value := <-ch + // fmt.Println("sended") + value := "" + select { + case <-ctx.Done(): + return nil + case value = <-ch: + } + // fmt.Println("value := <-ch") if !persistent { if strings.HasPrefix(value, "go_again_") { value = strings.Replace(value, "go_again_", "", 1) return GoAgain(value) } else { + // fmt.Println("toend") stream.Send(&srpc.SenderListenResponse{Echo: "END"}) + // fmt.Println("ended") } } else { value = strings.Replace(value, "go_again_", "", 1) } + // fmt.Println("watch pver") return value }, options...) } diff --git a/core/plugin_core.go b/core/plugin_core.go index 4d035ef..a039830 100644 --- a/core/plugin_core.go +++ b/core/plugin_core.go @@ -121,7 +121,7 @@ func initPlugins() { fin = &storage.Final{ Now: storage.EMPTY, } - + } if isNameUuid(key) { if new == "install" { @@ -201,7 +201,7 @@ func initPlugins() { // fmt.Println(strings.Join(ss, " ")) l := len(ss) if l > 2 { - go AddNodePlugin(filename, ss[l-1]) + go AddNodePlugin(filename, ss[l-1], f.Type) } } else { err := os.WriteFile(filename, []byte(new), 0755) diff --git a/main.go b/main.go index 961c574..cdb08ba 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,9 @@ func main() { a := &core.Factory{} a.Init("terminal", "default", nil) i := 0 + a.SetIsAdmin(func(s string) bool { + return true + }) a.SetReplyHandler(func(m map[string]interface{}) string { i++ fmt.Printf("\x1b[%dm%s \x1b[0m\n", 31, m[core.CONETNT]) diff --git a/package.json b/package.json new file mode 100644 index 0000000..1797372 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "node-fetch": "^3.3.2" + } +} diff --git a/proto3/set_up.py b/proto3/set_up.py new file mode 100644 index 0000000..cc936c5 --- /dev/null +++ b/proto3/set_up.py @@ -0,0 +1,12 @@ +from distutils.core import setup +from Cython.Build import cythonize + +setup( + ext_modules = cythonize("sillygirl.py"), + libraries=[ + "srpc_pb2", + "srpc_pb2_grpc", + "grpc", + "google.protobuf", + ] # 指定第三方依赖项所在的库 +) \ No newline at end of file diff --git a/proto3/sillygirl.py b/proto3/sillygirl.py index 2bb65ce..1583f89 100644 --- a/proto3/sillygirl.py +++ b/proto3/sillygirl.py @@ -1,20 +1,26 @@ import asyncio import json, pickle, base64, os, grpc -import time -import random -import threading -from queue import Queue import srpc_pb2, srpc_pb2_grpc plugin_id = os.environ.get("PLUGIN_ID", "") runtime_id = os.environ.get("RUNTIME_ID", "") -metadata = (("RUNTIME_ID", runtime_id),) -metadata = grpc.metadata_call_credentials(metadata) +metadata = [("runtime_id", runtime_id)] +channel = None +stub = None -channel = grpc.insecure_channel("localhost:50051") -stub = srpc_pb2_grpc.SillyGirlServiceStub(channel) + +def get_stub(): # 异步 + global stub, channel + if stub == None: + channel = grpc.insecure_channel("localhost:50051") + stub = srpc_pb2_grpc.SillyGirlServiceStub(channel) + return stub + + +channel2 = grpc.aio.insecure_channel("localhost:50051") +stub2 = srpc_pb2_grpc.SillyGirlServiceStub(channel2) def transform(value): @@ -57,136 +63,496 @@ def reverseTransform(value): class Bucket: def __init__(self, name): self.__name = name - print("name=", name, "self.__name=", self.__name) def __getitem__(self, key): - return self.get(key) + return self.__get(key) def __getattr__(self, name): - return self.get(name) + return self.__get(name) def __setitem__(self, key, value): - self.set(key, value) + self.__set(key, value) + # asyncio.ensure_future(self.set(key, value)) def __setattr__(self, name, value): if name in ["data", "_Bucket__name"]: # 使用object类的方法设置属性,避免无限递归 object.__setattr__(self, name, value) else: - self.set(name, value) + self.__set(name, value) + # asyncio.ensure_future(self.set(name, value)) - def get(self, key, defaultValue=None): + async def get(self, key, defaultValue=None): request = srpc_pb2.BucketKeyRequest(name=self.__name, key=key) - response = stub.BucketGet(request) + response = await stub2.BucketGet(request) if response.value != "": return transform(response.value) else: return defaultValue - def set(self, key, value): - request = srpc_pb2.BucketSetRequest( - name=self.__name, key=key, value=reverseTransform(value) - ) - response = stub.BucketSet(request) - return {"message": response.message, "changed": response.changed} + def __get(self, key, defaultValue=None): + request = srpc_pb2.BucketKeyRequest(name=self.__name, key=key) + response = get_stub().BucketGet(request) + if response.value != "": + return transform(response.value) + else: + return defaultValue - def delete(self, key): - return self.set(key, None) + def __set(self, key, value): + try: + request = srpc_pb2.BucketSetRequest( + name=self.__name, key=key, value=reverseTransform(value) + ) + response = get_stub().BucketSet(request) + return {"message": response.message, "changed": response.changed} + except Exception as e: + return {"error": "fail to set value"} - def deleteAll(self): + async def set(self, key, value): + try: + request = srpc_pb2.BucketSetRequest( + name=self.__name, key=key, value=reverseTransform(value) + ) + response = await stub2.BucketSet(request) + return {"message": response.message, "changed": response.changed} + except Exception as e: + return {"error": "fail to set value"} + + async def delete(self, key): + return await self.set(key, None) + + async def deleteAll(self): request = srpc_pb2.BucketRequest(name=self.__name) - stub.BucketDelete(request) + await stub2.BucketDelete(request) - def keys(self): + async def keys(self): print("name", self.__name) request = srpc_pb2.BucketKeyRequest(name=self.__name) - response = stub.BucketKeys(request) + response = await stub2.BucketKeys(request) return list(response.keys) - def len(self): + async def len(self): request = srpc_pb2.BucketRequest(name=self.__name) - response = stub.BucketLen(request) + response = await stub2.BucketLen(request) return response.length - def buckets(self): + async def buckets(self): request = srpc_pb2.Empty() - response = stub.BucketBuckets(request) + response = await stub2.BucketBuckets(request) return list(response.buckets) - def watch(self): - def watch_thread(self, key, handle): - queue = Queue() - def entry_request_iterator(): + def watch(self, key, handle): + async def __watch(self, key, handle): + queue = asyncio.Queue() + + async def entry_request_iterator(): yield srpc_pb2.BucketWatchRequest( name=self.__name, key=key, plugin_id=plugin_id ) while True: - yield queue.get() - print("queue.get()") - # 阻塞A区 等待B区发送数据 + yield await queue.get() generator = entry_request_iterator() - for response in stub.BucketWatch(generator): - print("Server response:", response) + response_iterator = stub2.BucketWatch(generator) + async for response in response_iterator: old = transform(response.old) now = transform(response.now) - result = handle(old, now, response.key) try: - fin = result + result = await handle(old, now, response.key) except Exception as e: print(e) continue storage_modifier = { "echo": response.echo, } - if not fin: + if not result: storage_modifier["error"] = "VOID" else: - if "now" in fin: - storage_modifier["now"] = reverseTransform(fin["now"]) - if "message" in fin: - storage_modifier["message"] = fin["message"] - if "error" in fin: - storage_modifier["error"] = fin["error"] - queue.put(srpc_pb2.BucketWatchRequest(**storage_modifier)) + if "now" in result: + storage_modifier["now"] = reverseTransform(result["now"]) + if "message" in result: + storage_modifier["message"] = result["message"] + if "error" in result: + storage_modifier["error"] = result["error"] + await queue.put(srpc_pb2.BucketWatchRequest(**storage_modifier)) + + asyncio.ensure_future(__watch(self, key, handle)) -app = Bucket("test") +# test = Bucket("test") +# test.name = "777" +# print(test.name) + +# async def handle(old_value, new_value, key): +# print("Bucket value changed!") +# print("Key:", key) +# print("Old value:", old_value) +# print("New value:", new_value) +# return { +# "now": "shit", +# "message": "hehe", +# "error": "xxx", +# } -def handle(old_value, new_value, key): - print("Bucket value changed!") - print("Key:", key) - print("Old value:", old_value) - print("New value:", new_value) - return { - "now": "shit", - } +# async def main(): +# asyncio.ensure_future(test.watch(key="*", handle=handle)) +# await asyncio.sleep(0.1) +# print("test.set", await test.set("name", random.randint(1, 10))) +# print("test.name", await test.get("name")) +# print("test.keys", await test.keys()) +# print("test.buckets", await test.buckets()) +# # print("test.delete", await test.delete("name")) +# # print("test.deleteAll", await test.deleteAll()) +# print("test.name", await test.get("name")) - -app.watch(key="*", handle=handle) -# app.name = 1 -# print(app.name) -# app["name"] = (1,3, {3,4,5}) -# print(app["name"]) -# app.set("name", 1.23) -# print(app.get("name")) -# print("keys", app.keys()) -# print("len", app.len()) -# print("buckets", app.buckets()) -# # app.delete("name") -# app.name = 0 -# print(app.name) -# print("==") -# app.name = random.randrange(1,100,1) -# print("==") -# app.deleteAll() -print("app.name", app.name) +# loop = asyncio.get_event_loop() +# loop.run_until_complete(main()) class Sender: - pass + def __init__(self, uuid): + self.__uuid = uuid + self.destoried = False + + async def destroy(self): + if self.destoried: + return + self.destoried = True + await stub2.SenderDestroy( + srpc_pb2.ReplyRequest(uuid=self.__uuid), metadata=metadata + ) + + async def getUserId(self): + response = await stub2.SenderGetUserId( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getUserName(self): + response = await stub2.SenderGetUserName( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getChatId(self): + response = await stub2.SenderGetChatId( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getChatName(self): + response = await stub2.SenderGetChatName( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getMessageId(self): + response = await stub2.SenderGetMessageId( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getPlatform(self): + response = await stub2.SenderGetPlatform( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getBotId(self): + response = await stub2.SenderGetBotId( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def getContent(self): + response = await stub2.SenderGetContent( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def isAdmin(self): + response = await stub2.SenderIsAdmin( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + return response.value + + async def param(self, key): + response = await stub2.SenderParam( + request=srpc_pb2.ReplyRequest(uuid=self.__uuid, content=str(key)), + metadata=metadata, + ) + return response.value + + async def setContent(self, content): + await stub2.SenderSetContent( + request=srpc_pb2.SenderContentRequest(uuid=self.__uuid, content=content), + metadata=metadata, + ) + + async def next(self): + await stub2.SenderContinue( + request=srpc_pb2.SenderRequest(uuid=self.__uuid), metadata=metadata + ) + + async def getAdapter(self): + bot_id = await self.getBotId() + platform = await self.getPlatform() + return Adapter(bot_id, platform) + + async def listen( + self, + timeout=0, + rules=[], + handle=None, + listen_private=False, + listen_group=False, + allow_platforms=[], + prohibit_platforms=[], + allow_groups=[], + prohibit_groups=[], + allow_users=[], + prohibit_users=[], + ): + queue = asyncio.Queue() + + async def entry_request_iterator(): + yield srpc_pb2.SenderListenRequest( + uuid=self.__uuid, + timeout=timeout, + rules=rules, + listen_private=listen_private, + listen_group=listen_group, + allow_platforms=allow_platforms, + prohibit_platforms=prohibit_platforms, + allow_groups=allow_groups, + prohibit_groups=prohibit_groups, + allow_users=allow_users, + prohibit_users=prohibit_users, + persistent=self.__uuid == "", + plugin_id=plugin_id, + ) + while True: + next_request = await queue.get() + if next_request is None: + return + yield next_request + + generator = entry_request_iterator() + response_iterator = stub2.SenderListen(generator, metadata=metadata) + s = None + async for response in response_iterator: + if response.echo == "END": + break + s = Sender(response.uuid) if response.uuid != "" else None + if handle is not None and s is not None: + try: + value = await handle(s) + await queue.put( + srpc_pb2.SenderListenRequest( + uuid=response.echo, + value=value, + ) + ) + except Exception as e: + print(e) + else: + await queue.put( + srpc_pb2.SenderListenRequest( + uuid=response.echo, + value="", + ) + ) + + await queue.put(None) + return s + + def holdOn(self, string): + return "go_again_" + string + + async def reply(self, content): + return ( + await stub2.SenderReply( + request=srpc_pb2.ReplyRequest(uuid=self.__uuid, content=content), + metadata=metadata, + ) + ).value + + async def doAction(self, properties): + # Perform action based on the provided properties + # ... + pass + + +sender = Sender(os.environ.get("SENDER_ID", "")) class Adapter: - pass + def __init__(self, platform, bot_id, replyHandler=None, actionHandler=None): + self.platform = platform + self.bot_id = bot_id + + if replyHandler is not None: + asyncio.create_task(self.__run(replyHandler, actionHandler)) + + async def __run(self, replyHandler, actionHandler): + self.queue = asyncio.Queue() + + async def entry_request_iterator(): + yield srpc_pb2.AdapterRegistRequest( + bot_id=self.bot_id, platform=self.platform + ) + while True: + next_request = await self.queue.get() + if next_request is None: + return + yield next_request + + generator = entry_request_iterator() + + response_iterator = stub2.AdapterRegist(generator, metadata=metadata) + try: + async for response in response_iterator: + message = json.loads(response.value) + echo = message["echo"] + __type__ = message["__type__"] + del message["__type__"] + del message["echo"] + if __type__ == "reply": + try: + v = replyHandler(message) or "" + await self.queue.put( + srpc_pb2.AdapterRegistRequest(bot_id=echo, platform=v) + ) + except Exception as e: + print(e) + + if __type__ == "action" and actionHandler is not None: + try: + v = actionHandler(message) or "" + await self.queue.put( + srpc_pb2.AdapterRegistRequest(bot_id=echo, platform=v) + ) + except Exception as e: + print(e) + except: + pass + + async def receive(self, message): + # 投递消息 + return await stub2.AdapterReceive( + srpc_pb2.AdapterRequest( + platform=self.platform, bot_id=self.bot_id, value=json.dumps(message) + ), + metadata=metadata, + ) + + async def push(self, message): + # 推送消息 + response = await stub2.AdapterPush( + srpc_pb2.AdapterRequest( + platform=self.platform, bot_id=self.bot_id, value=json.dumps(message) + ), + metadata=metadata, + ) + return response.value or "" + + async def destroy(self): + if not hasattr(self, "self.__destroyed"): + await self.queue.put(None) + self.__destroyed = True + + async def sender(self, options={}): + response = await stub2.AdapterSender( + srpc_pb2.AdapterRequest( + platform=self.platform, bot_id=self.bot_id, value=json.dumps(options) + ), + metadata=metadata, + ) + if response.value: + return Sender(response.value) + + +import re + + +class Utils: + def buildCQTag(self, type, params, prefix="CQ"): + param_strings = [] + for key, value in params.items(): + param_string = f"{key}={value}" + param_strings.append(param_string) + param_string = ",".join(param_strings) + cq_string = f"[{prefix}:{type}{',' + param_string if param_string else ''}]" + return cq_string + + def parseCQText(self, text, prefix="CQ"): + cq_regex = re.compile(rf"\[{prefix}:(\w+)(.*?)\]", re.DOTALL) + cq_matches = cq_regex.finditer(text) + result = [] + + last_index = 0 + for match in cq_matches: + # 添加 CQ 码前的文本 + match_index = text.index(match.group(0), last_index) + if match_index > last_index: + result.append(text[last_index:match_index]) + + # 解析 CQ 码 + params = {} + param_regex = re.compile(r"(\w+)=([^,]+)") + param_matches = param_regex.findall(match.group(2)) + for param_match in param_matches: + params[param_match[0]] = param_match[1].strip() + result.append( + { + "type": match.group(1), + "params": params, + } + ) + + last_index = match_index + len(match.group(0)) + + if last_index < len(text): + result.append(text[last_index:]) + + return result + + def image(self, url): + return self.buildCQTag("image", {"url": url}) + + def video(self, url): + return self.buildCQTag("video", {"url": url}) + + +utils = Utils() + +import srpc_pb2 +import srpc_pb2_grpc + + +class Console: + def __init__(self, plugin_id): + self.plugin_id = plugin_id + + def log(self, *args): + self.send_console_request("log", *args) + + def info(self, *args): + self.send_console_request("info", *args) + + def error(self, *args): + self.send_console_request("error", *args) + + def debug(self, *args): + self.send_console_request("debug", *args) + + def send_console_request(self, console_type, *args): + content = " ".join(map(str, args)) + request = srpc_pb2.ConsoleRequest( + type=console_type, content=content, plugin_id=self.plugin_id + ) + stub2.Console(request) + + +console = Console(plugin_id) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..eb66a12 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,42 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==