1 Commits

Author SHA1 Message Date
cdle b6cb7726f8 last commit 2024-10-19 19:04:34 +08:00
210 changed files with 1714 additions and 87005 deletions
-63
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
-23
View File
@@ -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
+119 -505
View File
@@ -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!
- 用户可以前往插件市场,一键安装自己想要接入聊天工具,简单配置即可使用
![开箱即用的适配器](md/image/use/adapters.png)
- 开发者可以通过 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 函数响应式编程 :占用小,响应快,开发易 ;
- 极简的插件设计模式
- 系统高度封装,提供简便人性化的系统方法,随心所欲开发插件;
- 用户可以轻松安装已发布插件,免去部署环境等繁琐操作
![插件市场](md/image/use/market.png)
- 支持多种开发语言
- 内置 Javascript 引擎,完整支持 ECMAScript 5.1,快速、便捷、门槛低;
- 支持 NodeJs,尽情使用其提供的 async/await/Promise 语法糖,可以自由控制异步同步;
- 支持 Python3,简洁明了的语法、强大的标准库以及社区优势;
- 即将支持 PHP,对对对,这是世界上最好的语言;
- 不仅仅是 Chat RoBot : 如果你愿意,可以在本框架上搭建网站、图片服务器、资源共享平台、并发请求等服务,尽情释放你的创造力.
```ts
CryptoBuffer
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]
> 后台目前只有一个蹩脚的前端(本人),如果有前端工程师对此感兴趣,请联系我~
### 项目赞助
# 安装
打开微信扫一扫,深入了解作者~
![](https://raw.githubusercontent.com/cdle/sillyGirl/main/appreciate.jpg)
参见 [安装文档](./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 开源许可协议。
-171
View File
@@ -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("傻+ 已离线")
-313
View File
@@ -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, "&#91;", "[", -1)
content = strings.Replace(content, "&#93;", "]", -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)
}
})
}()
}
-180
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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

-13
View File
@@ -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
View File
@@ -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
View File
@@ -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
-5
View File
@@ -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

-202
View File
@@ -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
-241
View File
@@ -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
}
-259
View File
@@ -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,
})
})
}
-563
View File
@@ -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
View File
@@ -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
View File
@@ -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 ""
}
-72
View File
@@ -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"`
// }
-68
View File
@@ -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
}
-64
View File
@@ -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
}
-3
View File
@@ -1,3 +0,0 @@
package core
var compiled_at = ""
-10
View File
@@ -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()
}
-98
View File
@@ -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
}
-692
View File
@@ -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
}
-25
View File
@@ -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)
}
}()
}
-138
View File
@@ -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
}
-205
View File
@@ -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
}
-118
View File
@@ -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
}
-527
View File
@@ -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
View File
@@ -1 +0,0 @@
package core
-111
View File
@@ -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
}
-283
View File
@@ -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), &params)
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
View File
@@ -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()
}
-70
View File
@@ -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)
```
-93
View File
@@ -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
}
-38
View File
@@ -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)
}
-205
View File
@@ -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 {
// TopicLogGroup
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)
}
-13
View File
@@ -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
-42
View File
@@ -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
}
-813
View File
@@ -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
}
-270
View File
@@ -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
}
-91
View File
@@ -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
}
-62
View File
@@ -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
}
-111
View File
@@ -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
}
-141
View File
@@ -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)
}
-96
View File
@@ -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)
}
-125
View File
@@ -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)
}
-84
View File
@@ -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)
}
-123
View File
@@ -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)
}
-39
View File
@@ -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())
}
-33
View File
@@ -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)
}
-442
View File
@@ -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)
}
-503
View File
@@ -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)
}
-91
View File
@@ -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
}
-116
View File
@@ -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