diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index d646888b..fa694cc8 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,6 @@ -# Created by .ignore support plugin (hsz.mobi) -### Example user template template -### Example user template - -# IntelliJ project files -.idea -out -gen -config/ -cq_data/ -composer.lock \ No newline at end of file +.idea/ +/src/test/ +/src/webconsole/config/ +/vendor/ +zm.json +/zm_data/ \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100755 index 96f69fec..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Blue Whale Network. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 803c7858..4f355096 --- a/README.md +++ b/README.md @@ -1,141 +1,41 @@ -# CQBot-swoole - -## 此分支停止维护! +# zhamao-framework [![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]() [![license](https://img.shields.io/badge/license-MIT-blue.svg)]() [![版本](https://img.shields.io/badge/version-2019.2.9-green.svg)]() -一个异步、多平台兼容的**聊天机器人**框架。 +一个异步、多平台兼容的 **聊天机器人** 框架。 -**当前正在重构,框架可能会有很大的变化,旧框架将创建old分支,重构完成后新款框架为master分支。** -**框架同时将更名为zhamao-framework(炸毛框架),与旧版本兼容性不并存,届时需要根据文档进行模块的升级。** + ![](resources/images/logo.png) -## CQBot-swoole 文档 -本项目的文档正在努力编写中:[https://cqbot.crazywhale.org/](https://cqbot.crazywhale.org/) +## 简介 +zhamao-framework 是一个基于 酷Q 的 PHP 机器人框架,它会对 QQ 机器人收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。 -## 什么是Swoole -PHP原生对多线程、多进程、异步等特性支持不是很好,有了Swoole,你可以非常简单自由地写出优雅的高性能服务器。 +除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。 -本项目原生支持多机器人连接,故选择了反向Websocket连接方式。同时更适用于高并发、多机器人同时连接以及对接**微信公众号**和**web前端**等场景。 -[Swoole官网](https://www.swoole.com/) - -## 框架简介 -cqbot-swoole是一个聊天机器人框架,同时兼容酷Q(需安装[cqhttp插件](https://cqhttp.cc)),微信公众号,支持多QQ账号对接。 - -## 说在前面 -本框架目前还有一些缺陷,说在前面的原因是如果你有更好的想法或发现的问题,可以提issue或联系作者。 - -> 如果你对swoole、PHP研究较深,推荐尝试学习框架的源码和运行原理后自己动手写一个。也欢迎star本项目给予支持! +## 文档 +本项目文档正在努力编写中:[https://framework.zhamao.xin/](https://framework.zhamao.xin/) ## 特点 -- 多账号单后端式框架 -- 采用模块式编写,功能之间独立性高,可分别开关各个模块和设置响应优先级 -- 全局缓存,随处使用 -- 协程开发,传统同步写法实现高并发 -- 除swoole外不依赖composer其他项目 -- 自带HTTP、websocket服务器,可对接其他服务 -- 支持协程MySQL +- 支持多账号 +- 灵活的注解事件绑定机制 +- 采用模块化编写,功能之间高内聚低耦合 +- 常驻内存,全局缓存变量随处使用 +- 自带 MySQL 查询器、数据库连接池等数据库连接方案 +- 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口 -## 环境部署 +## 从 cqbot-swoole 升级 +目前新的框架采用了全新的注解机制,所以旧版的框架上写的模块到新框架需要重新编写。当然为了减少工作量,新的框架也最大限度地保留了旧版框架编写的风格,一般情况下根据新版框架的文档仅需修改少量地方即可完成重写。 -### 酷Q和HTTP API插件 -由于框架是独立于酷Q运行的,故你可以在多台主机上部署酷Q的docker。 +旧版框架并入了 `old` 分支,如果想继续使用旧版框架请移步分支。升级过程中如果遇到问题可以找作者。 -如果你是新用户或重新安装含有HTTPAPI插件的**酷Q-Docker**的话,可以在你需要部署酷Q的Linux主机下使用下面的脚本快速构建酷Q环境,此脚本会引导进行相关的cqhttp插件设置。每台部署酷Q的主机均可直接使用下方的命令(服务器需要提前安装Docker) - -```shell -#第一次部署酷Q-httpapi docker运行下面的代码 -bash -c "$(wget https://raw.githubusercontent.com/crazywhalecc/cqbot-swoole/master/start-coolq.sh -O -)" - -#以后每次启动/停止/重启酷Q容器执行的命令 -docker start coolq -docker stop coolq -docker restart coolq - -#以上指令非root用户可能需要sudo -``` -### 微信公众号 -很快将兼容微信公众平台,敬请期待。 - - -## 框架部署 -### 手动安装到Linux主机上 -``` shell -# 安装PHP(ubuntu/debian) -apt-get install software-properties-common -add-apt-repository ppa:ondrej/php -apt-get update -apt-get install php7.2 php7.2-dev php7.2 php7.2-mbstring php7.2-json php7.2 php-pear - -#安装PHP(CentOS) -rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm -yum makecache fast -yum install php72w-devel.x86_64 php72w-mbstring.x86_64 php72w-pear.noarch gcc gcc-c++ -y - - -# 安装Swoole -pecl install swoole -echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}') - -# 部署框架 -git clone https://github.com/crazywhalecc/cqbot-swoole.git - -# 以上指令可能需要sudo执行 -``` - - -### 使用Docker快速构建并启动 -``` shell -sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole - -# 可以将命令添加为alias方便以后快速启动 -echo "alias cqbot='sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole'" >> ~/.bash_profile -source ~/.bash_profile -cqbot -``` - - -## 启动 -#### 直接安装后启动框架 - -```shell -cd cqbot-swoole/ -php start.php -``` - -#### 在screen中运行框架 - -```shell -screen -R cqbot -cd cqbot-swoole/ -php start.php -# Ctrl A + D 将screen放到后台运行 -``` - -#### 使用Docker在screen中运行框架 - -```shell -screen -R cqbot -sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole -# Ctrl A + D 将screen放到后台运行 -``` - -## MacOS与Windows兼容性 -#### MacOS下运行cqbot-swoole -mac下运行和Linux整体相同,使用brew安装好PHP后通过源码编译`swoole`组件安装,或使用docker。 -> Docker for mac 运行需要手动指定端口`-p 20000:20000`,不能使用`--net=host`网络模式。 - -#### Windows下运行cqbot-swoole -因为swoole使用了Linux的特性,故**不推荐**在Windows电脑或服务器使用,Windows可以使用Docker运行,使用 `cygwin` 环境或 `WSL` 环境。 -> 不推荐原因有不能使用`reload`指令进行重启服务和不能使用全部的swoole特性。 +## 贡献 +如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。 ## 关于 +框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人(3276124472)是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。 -框架和SDK部分代码直接从 [炸毛机器人](https://github.com/zhamao-robot/) 中移植而来,炸毛机器人(3290004669)是作者写的一个高性能的机器人,曾获全国计算机设计大赛一等奖。 +欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ(627577391)或提交 Issue 进行疑难解答。 -欢迎随时在HTTP-API插件群提问,当然更好的话可以加作者QQ(627577391)或提交issue进行疑难解答。 - -本项目在有更新内容时,请及时关注GitHub的动态,更新前请将自己的**模块**代码做好备份。 +本项目在更行内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。 \ No newline at end of file diff --git a/bin/start b/bin/start new file mode 100755 index 00000000..b141b3f2 --- /dev/null +++ b/bin/start @@ -0,0 +1,44 @@ +#!/usr/bin/env php + 30000, +]); + +date_default_timezone_set("Asia/Shanghai"); + +switch ($argv[1] ?? '') { + case 'scheduler': + case 'timer': + go(function () { + try { + new Scheduler(Scheduler::REMOTE); + } catch (Exception $e) { + die($e->getMessage()); + } + }); + break; + case '': + case 'framework': + case 'server': + if(!is_dir(__DIR__.'/../vendor/')){ + echo "Warning: you have not update composer!\n"; + echo "You need to run \"composer update\" at root of zhamao-framework!\n"; + echo "Or if you are using docker or composer installed, just run \"sh bin/update-composer\"\n"; + echo "In China, if your composer downloading slowly, you can get latest vendor.tar.gz from HERE:\n"; + echo "https://dl2.zhamao.xin/zhamao-framework/vendor.tar.gz\n"; + die; + } + $loader = new FrameworkLoader($argv); + break; + default: + echo "Unknown option \"{$argv[1]}\"!\n"; + break; +} + diff --git a/bin/update-composer b/bin/update-composer new file mode 100644 index 00000000..454e504a --- /dev/null +++ b/bin/update-composer @@ -0,0 +1,3 @@ +#!/bin/bash + +composer update \ No newline at end of file diff --git a/composer.json b/composer.json index 6e6abe7b..cead0423 100644 --- a/composer.json +++ b/composer.json @@ -1,19 +1,31 @@ { + "name": "zhamao/framework", "description": "high-performance intelligent assistant", "minimum-stability": "stable", - "license": "MIT", + "license": "proprietary", + "authors": [ + { + "name": "whale", + "email": "crazysnowcc@gmail.com" + }, + { + "name": "swift", + "email": "hugo_swift@yahoo.com" + } + ], "require": { - "php": ">=7.0.0", - "ext-mbstring": "^7.1", + "swoole/ide-helper": "@dev", + "ext-mbstring": "*", + "swlib/saber": "^1.0", + "doctrine/annotations": "^1.8", "ext-json": "*", - "ext-iconv": "*", - "ext-mysqli": "*", - "ext-dom": "20031129", - "ext-gd": "^7.1", - "ext-curl": "^7.1", - "ext-openssl": "^7.1" + "ext-posix": "*", + "ext-ctype": "*" }, - "require-dev": { - "eaglewu/swoole-ide-helper": "dev-master" + "repositories": { + "packagist": { + "type": "composer", + "url": "https://mirrors.aliyun.com/composer/" + } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..3e192231 --- /dev/null +++ b/composer.lock @@ -0,0 +1,395 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fb2cdfc2c51cc4f5c2758916f7a3b628", + "packages": [ + { + "name": "doctrine/annotations", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2019-10-01T18:55:10+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "time": "2019-10-30T14:39:59+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "swlib/http", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/swlib/http.git", + "reference": "d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swlib/http/zipball/d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060", + "reference": "d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "~1.0" + }, + "require-dev": { + "eaglewu/swoole-ide-helper": "dev-master", + "phpunit/phpunit": "~7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Swlib\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "twosee", + "email": "twose@qq.com" + } + ], + "description": "Swlib-HTTP base class repository, PSR implementation", + "keywords": [ + "http", + "php", + "psr7", + "swoole" + ], + "time": "2019-09-24T06:38:56+00:00" + }, + { + "name": "swlib/saber", + "version": "1.0.10", + "source": { + "type": "git", + "url": "https://github.com/swlib/saber.git", + "reference": "10b3a1b4e30edce6de583c44cb7d2185f33a8a1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swlib/saber/zipball/10b3a1b4e30edce6de583c44cb7d2185f33a8a1f", + "reference": "10b3a1b4e30edce6de583c44cb7d2185f33a8a1f", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "swlib/http": "1.0.5", + "swlib/util": "1.0.0" + }, + "require-dev": { + "eaglewu/swoole-ide-helper": "dev-master", + "phpunit/phpunit": "~7" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/Saber.php", + "src/SaberGM.php" + ], + "psr-4": { + "Swlib\\Saber\\": "src" + }, + "files": [ + "src/include/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "twosee", + "email": "twose@qq.com" + } + ], + "description": "Swoole coroutine HTTP client", + "keywords": [ + "ajax", + "axios", + "client", + "coroutine", + "curl", + "http", + "php", + "psr7", + "requests", + "swoole" + ], + "time": "2019-09-26T08:29:29+00:00" + }, + { + "name": "swlib/util", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/swlib/util.git", + "reference": "f9aaa9028ea16a489a46b9ad09f0e844997cffe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swlib/util/zipball/f9aaa9028ea16a489a46b9ad09f0e844997cffe7", + "reference": "f9aaa9028ea16a489a46b9ad09f0e844997cffe7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "eaglewu/swoole-ide-helper": "dev-master", + "phpunit/phpunit": "~7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swlib\\Util\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "twosee", + "email": "twose@qq.com" + } + ], + "description": "Swlib Toolkit", + "keywords": [ + "php", + "swlib", + "swoole", + "util" + ], + "time": "2018-07-23T04:36:19+00:00" + }, + { + "name": "swoole/ide-helper", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "a7bae643171ff4176ff37868a40276819e5789aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/a7bae643171ff4176ff37868a40276819e5789aa", + "reference": "a7bae643171ff4176ff37868a40276819e5789aa", + "shasum": "" + }, + "require-dev": { + "guzzlehttp/guzzle": "~6.5.0", + "squizlabs/php_codesniffer": "~3.5.0", + "symfony/filesystem": "~4.3.0", + "zendframework/zend-code": "~3.3.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole.", + "time": "2020-01-15T08:00:05+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "swoole/ide-helper": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-mbstring": "*", + "ext-json": "*", + "ext-posix": "*" + }, + "platform-dev": [] +} diff --git a/config/global.php b/config/global.php new file mode 100644 index 00000000..1e991c5b --- /dev/null +++ b/config/global.php @@ -0,0 +1,64 @@ +set参数 */ +$config['swoole'] = [ + 'log_file' => $config['crash_dir'].'swoole_error.log', + 'worker_num' => 1, + 'dispatch_mode' => 2, + 'task_worker_num' => 0 +]; + +/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ +$config['sql_config'] = [ + 'sql_host' => '', + 'sql_port' => 3306, + 'sql_username' => 'name', + 'sql_database' => 'db_name', + 'sql_password' => '', + 'sql_enable_cache' => true, + 'sql_reset_cache' => '0300' +]; + +/** CQHTTP连接约定的token */ +$config["access_token"] = ""; + +/** HTTP服务器固定请求头的返回 */ +$config['http_header'] = [ + 'X-Powered-By' => 'zhamao-framework', + 'Content-Type' => 'text/html; charset=utf-8' +]; + +/** HTTP服务器在指定状态码下回复的页面(默认) */ +$config['http_default_code_page'] = [ + '404' => '404.html' +]; + +/** zhamao-framework在框架启动时初始化的atomic们 */ +$config['init_atomics'] = [ + 'in_count' => 0, //消息接收message的统计数量 + 'out_count' => 0, //消息发送(调用send_*_msg的统计数量) + 'reload_time' => 0, //调用reload功能统计数量 + 'wait_msg_id' => 0, //协程挂起id自增 + 'info_level' => 0, //终端显示的log等级 +]; + +/** 自动保存的缓存保存时间(秒) */ +$config['auto_save_interval'] = 900; + +return $config; \ No newline at end of file diff --git a/cqbot.json b/cqbot.json deleted file mode 100644 index 3b4a5f42..00000000 --- a/cqbot.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "robot_name": "CQBot", - "cqbot_version": "1.0.0", - "config_dir": "cq_data\/config\/", - "crash_dir": "cq_data\/crash\/", - "user_dir": "cq_data\/users\/", - "cq_data": "cq_data\/", - "admin_group": "", - "access_token": "", - "info_level": 0, - "admin": [], - "save_user_data": true, - "swoole_host": "", - "swoole_port": "", - "swoole_worker_num": 1, - "swoole_dispatch_mode": 2, - "swoole_log_file": "cq_data\/crash\/swoole.log", - "swoole_use_tick": true, - "swoole_tick_interval": 1000 -} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index f9dbe4f4..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM php:7.2 -WORKDIR /root -RUN echo "Asia/Shanghai" > /etc/timezone -RUN dpkg-reconfigure -f noninteractive tzdata -ENV LANG C.UTF-8 -RUN apt-get update && apt-get install curl libxml2 libzip-dev openssl git -y - -# Install php extensions -RUN docker-php-ext-install zip -RUN docker-php-ext-install mysqli -RUN docker-php-ext-install iconv -RUN docker-php-ext-install mbstring - -RUN pecl install swoole -RUN docker-php-ext-enable swoole - -VOLUME ["/root/"] - -CMD if [ ! -d "CQBot-swoole" ]; then git clone https://github.com/crazywhalecc/CQBot-swoole.git; fi && cd CQBot-swoole/ && bash -c "php start.php" \ No newline at end of file diff --git a/resources/html/404.html b/resources/html/404.html new file mode 100644 index 00000000..a0538554 --- /dev/null +++ b/resources/html/404.html @@ -0,0 +1,11 @@ + + + + + Sorry + + +Nothing here.
+Please check your url. (404) + + \ No newline at end of file diff --git a/resources/images/logo.png b/resources/images/logo.png new file mode 100644 index 00000000..c7e36850 Binary files /dev/null and b/resources/images/logo.png differ diff --git a/src/Custom/Annotation/Example.php b/src/Custom/Annotation/Example.php new file mode 100644 index 00000000..8d41fd51 --- /dev/null +++ b/src/Custom/Annotation/Example.php @@ -0,0 +1,19 @@ +get(), [1, 2])) { + $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; + $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; + } + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n"); + } + + static function warning($obj, $head = null) { + if ($head === null) $head = date("[H:i:s") . " WARN] "; + if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { + $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; + $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; + } + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n"); + } + + static function info($obj, $head = null) { + if ($head === null) $head = date("[H:i:s ") . "INFO] "; + if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { + $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; + $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; + } + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n"); + } + + static function log($obj, $color = "") { + if (!is_string($obj)) var_dump($obj); + else echo(self::setColor($obj, $color) . "\n"); + } + + static function msg($obj, $self_id = "") { + if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) { + if (!isset($obj["post_type"])) { + switch ($obj["action"]) { + case "send_private_msg": + $msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue"); + $msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue"); + $msg .= Console::setColor(" > ", "gray"); + $msg .= $obj["params"]["message"]; + Console::log($msg); + break; + case "send_group_msg": + //TODO: 写新的控制台消息(API消息处理) + Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); + break; + case "send_discuss_msg": + Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); + break; + case "send_msg": + $obj["action"] = "send_" . $obj["message_type"] . "_msg"; + self::msg($obj); + break; + case "send_wechat_msg": + Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); + break; + default: + break; + } + } else { + if ($obj["post_type"] == "message") { + switch ($obj["message_type"]) { + case "group": + // + //TODO: 写新的控制台消息(event处理) + case "private": + case "discuss": + case "wechat": + } + } + } + } + } + + static function stackTrace(){ + $log = "Stack trace:\n"; + $trace = debug_backtrace(); + //array_shift($trace); + foreach ($trace as $i => $t) { + if (!isset($t['file'])) { + $t['file'] = 'unknown'; + } + if (!isset($t['line'])) { + $t['line'] = 0; + } + if (!isset($t['function'])) { + $t['function'] = 'unknown'; + } + $log .= "#$i {$t['file']}({$t['line']}): "; + if (isset($t['object']) and is_object($t['object'])) { + $log .= get_class($t['object']) . '->'; + } + $log .= "{$t['function']}()\n"; + } + $log = Console::setColor($log, "gray"); + echo $log; + } + + static function listenConsole(){ + go(function () { + while (true) { + $cmd = trim(co::fread(STDIN)); + if (self::executeCommand($cmd) === false) break; + } + }); + } + + /** + * @param string $cmd + * @return bool + */ + private static function executeCommand(string $cmd) { + $it = explodeMsg($cmd); + switch ($it[0] ?? '') { + case 'call': + $class_name = $it[1]; + $function_name = $it[2]; + $class = new $class_name([]); + call_user_func_array([$class, $function_name],[]); + return true; + case 'bc': + $code = base64_decode($it[1] ?? '', true); + try { + eval($code); + } catch (Exception $e) { + } + return true; + case 'echo': + Console::info($it[1]); + return true; + case 'stop': + ZMUtil::stop(); + return false; + case 'reload': + case 'r': + ZMUtil::reload(); + return false; + case '': + return true; + default: + Console::info("Command not found: " . $it[0]); + return true; + } + } + + public static function withSleep(string $string, int $int) { + self::info($string); + sleep($int); + } +} \ No newline at end of file diff --git a/src/Framework/FrameworkLoader.php b/src/Framework/FrameworkLoader.php new file mode 100644 index 00000000..b1e49884 --- /dev/null +++ b/src/Framework/FrameworkLoader.php @@ -0,0 +1,106 @@ +requireGlobalFunctions(); + $this->registerAutoloader('classLoader'); + self::$settings = new GlobalConfig(); + ZMBuf::$globals = self::$settings; + if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file"); + $this->defineProperties(); + + //start swoole Framework + $this->selfCheck(); + try { + $this->server = new Server(self::$settings->get("host"), self::$settings->get("port")); + if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1"); + $this->server->set(self::$settings->get("swoole")); + $this->server->on("WorkerStart", [$this, "onWorkerStart"]); + $this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); }); + $this->server->on("request", function ($request, $response) { + $response = new Response($response); + EventHandler::callSwooleEvent("request", $request, $response); + }); + $this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); }); + $this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); }); + ZMBuf::initAtomic(); + Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port")); + $this->server->start(); + } catch (Exception $e) { + Console::error("Framework初始化出现错误,请检查!"); + Console::error($e->getMessage()); + die; + } + } + + private function requireGlobalFunctions() { + require __DIR__ . '/global_functions.php'; + } + + private function registerAutoloader(string $string) { + if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !"); + } + + private function defineProperties() { + define("ZM_START_TIME", microtime(true)); + define("ZM_DATA", self::$settings->get("zm_data")); + define("CONFIG_DIR", self::$settings->get("config_dir")); + define("CRASH_DIR", self::$settings->get("crash_dir")); + @mkdir(ZM_DATA); + @mkdir(CONFIG_DIR); + @mkdir(CRASH_DIR); + define("ZM_MATCH_ALL", 0); + define("ZM_MATCH_FIRST", 1); + define("ZM_MATCH_NUMBER", 2); + define("ZM_MATCH_SECOND", 3); + } + + private function selfCheck() { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); + if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n"); + if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); + //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); + //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); + return true; + } + + public function onWorkerStart(\Swoole\Server $server, $worker_id) { + self::$instance = $this; + self::$run_time = microtime(true); + EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id); + } +} \ No newline at end of file diff --git a/src/Framework/GlobalConfig.php b/src/Framework/GlobalConfig.php new file mode 100644 index 00000000..c6daaf1b --- /dev/null +++ b/src/Framework/GlobalConfig.php @@ -0,0 +1,33 @@ +success = true; + $this->config = $config; + } + + public function get($key) { + $r = $this->config[$key] ?? null; + if ($r === null) return null; + return $r; + } +} \ No newline at end of file diff --git a/src/Framework/InfoLevel.php b/src/Framework/InfoLevel.php new file mode 100644 index 00000000..58de3ab9 --- /dev/null +++ b/src/Framework/InfoLevel.php @@ -0,0 +1,10 @@ +listen($host, $port, SWOOLE_SOCK_TCP); + if (!$port) { + throw new Exception("listen fail."); + } + $port->set(array( + "open_eof_split" => true, + 'package_eof' => "\r\n", + )); + $port->on("Connect", array(__CLASS__, 'onConnect')); + $port->on("Close", array(__CLASS__, 'onClose')); + $port->on("Receive", array(__CLASS__, 'onReceive')); + if (method_exists($serv, 'getCallback')) { + self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage'); + } + $serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage')); + self::$serv = $serv; + } + + static function onConnect($serv, $fd, $reactor_id) { + self::$contexts[$fd]['worker_id'] = $serv->worker_id; + self::output($fd, implode("\r\n", self::$menu)); + } + + static function output($fd, $msg) { + if (!isset(self::$contexts[$fd]['worker_id'])) { + $msg .= "\r\nworker#" . self::$serv->worker_id . "$ "; + } else { + $msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ "; + } + self::$serv->send($fd, $msg); + } + + static function onClose($serv, $fd, $reactor_id) { + unset(self::$contexts[$fd]); + } + + static function onPipeMessage($serv, $src_worker_id, $message) { + //不是 debug 消息 + if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) { + if (self::$oriPipeMessageCallback == null) { + trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING); + return true; + } + return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message); + } else { + $request = unserialize(substr($message, strlen(self::STX))); + self::call($request['fd'], $request['func'], $request['args']); + } + return true ; + } + + static protected function call($fd, $func, $args) { + ob_start(); + call_user_func_array($func, $args); + self::output($fd, ob_get_clean()); + } + + static protected function exec($fd, $func, $args) { + //不在当前Worker进程 + if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) { + self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']); + } else { + self::call($fd, $func, $args); + } + } + + static function getCoros() { + var_export(iterator_to_array(Coroutine::listCoroutines())); + } + + static function getBackTrace($_cid) { + $info = Co::getBackTrace($_cid); + if (!$info) { + echo "coroutine $_cid not found."; + } else { + echo get_debug_print_backtrace($info); + } + } + + static function printVariant($var) { + $var = ltrim($var, '$ '); + var_dump($var); + var_dump($$var); + } + + static function evalCode($code) { + eval($code . ';'); + } + + /** + * @param $serv server + * @param $fd + * @param $reactor_id + * @param $data + */ + static function onReceive($serv, $fd, $reactor_id, $data) { + $args = explode(" ", $data, 2); + $cmd = trim($args[0]); + unset($args[0]); + switch ($cmd) { + case 'w': + case 'worker': + if (!isset($args[1])) { + self::output($fd, "invalid command."); + break; + } + $dstWorkerId = intval($args[1]); + self::$contexts[$fd]['worker_id'] = $dstWorkerId; + self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]"); + break; + case 'e': + case 'exec': + if (!isset($args[1])) { + self::output($fd, "invalid command."); + break; + } + $var = trim($args[1]); + self::exec($fd, 'self::evalCode', [$var]); + break; + case 'p': + case 'print': + $var = trim($args[1]); + self::exec($fd, 'self::printVariant', [$var]); + break; + case 'h': + case 'help': + self::output($fd, implode("\r\n", self::$menu)); + break; + case 's': + case 'stats': + $stats = $serv->stats(); + self::output($fd, var_export($stats, true)); + break; + case 'c': + case 'coros': + self::exec($fd, 'self::getCoros', []); + break; + /** + * 查看协程堆栈 + */ + case 'bt': + case 'b': + case 'backtrace': + if (empty($args[1])) { + self::output($fd, "invalid command [" . trim($args[1]) . "]."); + break; + } + $_cid = intval($args[1]); + self::exec($fd, 'self::getBackTrace', [$_cid]); + break; + case 'i': + case 'info': + if (empty($args[1])) { + self::output($fd, "invalid command [" . trim($args[1]) . "]."); + break; + } + $_fd = intval($args[1]); + $info = $serv->getClientInfo($_fd); + if (!$info) { + self::output($fd, "connection $_fd not found."); + } else { + self::output($fd, var_export($info, true)); + } + break; + case 'l': + case 'list': + $tmp = array(); + foreach ($serv->connections as $fd) { + $tmp[] = $fd; + if (count($tmp) > self::PAGESIZE) { + self::output($fd, json_encode($tmp)); + $tmp = array(); + } + } + if (count($tmp) > 0) { + self::output($fd, json_encode($tmp)); + } + break; + case 'q': + case 'quit': + $serv->close($fd); + break; + default: + self::output($fd, "unknow command[$cmd]"); + break; + } + } +} + +function get_debug_print_backtrace($traces) { + $ret = array(); + foreach ($traces as $i => $call) { + $object = ''; + if (isset($call['class'])) { + $object = $call['class'] . $call['type']; + if (is_array($call['args'])) { + foreach ($call['args'] as &$arg) { + get_arg($arg); + } + } + } + $ret[] = '#' . str_pad($i, 3, ' ') + . $object . $call['function'] . '(' . implode(', ', $call['args']) + . ') called at [' . $call['file'] . ':' . $call['line'] . ']'; + } + return implode("\n", $ret); +} + +function get_arg(&$arg) { + if (is_object($arg)) { + $arr = (array)$arg; + $args = array(); + foreach ($arr as $key => $value) { + if (strpos($key, chr(0)) !== false) { + $key = ''; // Private variable found + } + $args[] = '[' . $key . '] => ' . get_arg($value); + } + $arg = get_class($arg) . ' Object (' . implode(',', $args) . ')'; + } +} \ No newline at end of file diff --git a/src/Framework/ZMBuf.php b/src/Framework/ZMBuf.php new file mode 100755 index 00000000..e60c840f --- /dev/null +++ b/src/Framework/ZMBuf.php @@ -0,0 +1,95 @@ +get($key); } + + public static function resetCache() { + self::$cache = []; + self::$connect = []; + self::$time_nlp = null; + } + + /** + * 初始化atomic计数器 + */ + public static function initAtomic() { + foreach(ZMBuf::globals("init_atomics") as $k => $v) { + self::$atomics[$k] = new Atomic($v); + } + } +} \ No newline at end of file diff --git a/src/Framework/global_functions.php b/src/Framework/global_functions.php new file mode 100644 index 00000000..2a4d0026 --- /dev/null +++ b/src/Framework/global_functions.php @@ -0,0 +1,146 @@ + $v) { + if (trim($v) == "") continue; + $ls[] = trim($v); + } + return $ls; +} + +function unicode_decode($str) { + return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { + return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); + }, $str); +} + +/** + * 获取模块文件夹下的每个类文件的类名称 + * @param $dir + * @param string $indoor_name + * @return array + */ +function getAllClasses($dir, $indoor_name) { + $list = scandir($dir); + $classes = []; + unset($list[0], $list[1]); + foreach ($list as $v) { + //echo "Finding " . $dir . $v . PHP_EOL; + //echo "At " . $indoor_name . PHP_EOL; + if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v)); + elseif (mb_substr($v, -4) == ".php") { + $class_name = $indoor_name . "\\" . mb_substr($v, 0, -4); + $classes [] = $class_name; + } + } + return $classes; +} + +function matchPattern($pattern, $context) { + if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "") + return true; + if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1)) + return false; + if (mb_substr($pattern, 0, 1) == mb_substr($context, 0, 1)) + return matchPattern(mb_substr($pattern, 1), mb_substr($context, 1)); + if (mb_substr($pattern, 0, 1) == "*") + return matchPattern(mb_substr($pattern, 1), $context) || matchPattern($pattern, mb_substr($context, 1)); + return false; +} + +function split_explode($del, $str, $divide_en = false) { + $str = explode($del, $str); + for ($i = 0; $i < mb_strlen($str[0]); $i++) { + if ( + is_numeric(mb_substr($str[0], $i, 1)) && + ( + !is_numeric(mb_substr($str[0], $i - 1, 1)) && + mb_substr($str[0], $i - 1, 1) != ' ' && + ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false + ) + ) { + $str[0] = mb_substr($str[0], 0, $i) . " " . mb_substr($str[0], $i); + } elseif ( + $divide_en && + ctype_alnum(mb_substr($str[0], $i, 1)) && + !ctype_alnum(mb_substr($str[0], $i - 1, 1)) && + mb_substr($str[0], $i - 1, 1) != ' ' + ) { + $str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i); + } + } + $str = implode($del, $str); + //echo $str."\n"; + $ls = []; + foreach (explode($del, $str) as $k => $v) { + if (trim($v) == "") continue; + $ls[] = $v; + } + //var_dump($ls); + return $ls == [] ? [""] : $ls; +} + +function matchArgs($pattern, $context) { + $result = []; + if (matchPattern($pattern, $context)) { + if (mb_strpos($pattern, "*") === false) return []; + $exp = explode("*", $pattern); + $i = 0; + foreach ($exp as $k => $v) { + //echo "[MATCH$k] " . $v . PHP_EOL; + if ($v == "" && $k == 0) continue; + elseif ($v == "" && $k == count($exp) - 1) { + $context = $context . "^EOL"; + $v = "^EOL"; + } + $cur_var = ""; + //echo mb_substr($context, $i) . "|" . $v . PHP_EOL; + $ori = $i; + while (($a = mb_substr($context, $i, mb_strlen($v))) != $v && $a != "") { + $cur_var .= mb_substr($context, $i, 1); + ++$i; + } + if ($i != $ori || $k == 1 || $k == count($exp) - 1) { + //echo $cur_var . PHP_EOL; + $result[] = $cur_var; + } + $i += mb_strlen($v); + } + return $result; + } else return false; +} \ No newline at end of file diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php new file mode 100644 index 00000000..d8dc46e8 --- /dev/null +++ b/src/Module/Example/Hello.php @@ -0,0 +1,34 @@ +getQQ()." 已连接!"); + } + /** + * @CQCommand("你好") + */ + public function hello(){ + return "你好啊,我是由炸毛框架构建的机器人!"; + } +} \ No newline at end of file diff --git a/src/Module/TestMod/CQTest.php b/src/Module/TestMod/CQTest.php new file mode 100644 index 00000000..e352d4d2 --- /dev/null +++ b/src/Module/TestMod/CQTest.php @@ -0,0 +1,45 @@ +getQQ()." connected."); + } + + /** + * @CQCommand("多命令a") + * @CQCommand(regexMatch="*是什么") + * @param $arg + * @return string + * @throws \ZM\Exception\DbException + */ + public function hello($arg) { + return "我也不知道".$arg[0]."是什么呀"; + } + + /** + * @CQNotice(notice_type="group_admin") + */ + public function onNotice(){ + + } +} \ No newline at end of file diff --git a/src/Module/TestMod/Hola.php b/src/Module/TestMod/Hola.php new file mode 100644 index 00000000..f1a0e615 --- /dev/null +++ b/src/Module/TestMod/Hola.php @@ -0,0 +1,28 @@ +connection->close(); + } +} \ No newline at end of file diff --git a/src/Scheduler/MessageEvent.php b/src/Scheduler/MessageEvent.php new file mode 100644 index 00000000..b3e67f6e --- /dev/null +++ b/src/Scheduler/MessageEvent.php @@ -0,0 +1,29 @@ +client = $client; + $this->frame = $frame; + } + + public function onActivate() { + //TODO: 写Scheduler计时器内的处理逻辑 + } +} \ No newline at end of file diff --git a/src/Scheduler/Scheduler.php b/src/Scheduler/Scheduler.php new file mode 100644 index 00000000..3d8a1deb --- /dev/null +++ b/src/Scheduler/Scheduler.php @@ -0,0 +1,140 @@ +initProcess(); + elseif ($method == self::REMOTE) $this->initRemote(); + } + + private function initProcess() { //TODO: 完成Process模式的代码 + $m_pid = posix_getpid(); + $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true); + $this->pid = $this->process->start(); + while (1) { + $ret = Process::wait(); + if ($ret) { + $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true); + $this->pid = $this->process->start(); + echo "Reboot done.\n"; + } + } + } + + private function initRemote() { + define('WORKING_DIR', __DIR__ . '../..'); + $this->requireGlobalFunctions(); + $this->registerAutoloader('classLoader'); + $this->settings = new GlobalConfig(); + if (!$this->settings->success) die("Failed to load global config. Please check config/global.php file"); + $this->defineProperties(); + + //start swoole Framework + $this->selfCheck(); + try { + $host = $this->settings->get("scheduler")["host"]; + $port = $this->settings->get("scheduler")["port"]; + $token = $this->settings->get("scheduler")["token"]; + $this->client = new Client($host, $port); + $path = "/" . ($token != "" ? ("?token=" . urlencode($token)) : ""); + while (true) { + if ($this->client->upgrade($path)) { + while (true) { + $recv = $this->client->recv(); + if ($recv instanceof Frame) { + (new MessageEvent($this->client, $recv))->onActivate(); + } else { + break; + } + } + } else { + Console::warning("无法连接Framework,将在5秒后重连..."); + Coroutine::sleep(5); + } + } + } catch (Exception $e) { + Console::error($e); + } + } + + private function requireGlobalFunctions() { + /** @noinspection PhpIncludeInspection */ + require WORKING_DIR . '/src/Framework/global_functions.php'; + } + + private function registerAutoloader(string $string) { + if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !"); + } + + private function defineProperties() { + define("ZM_START_TIME", microtime(true)); + define("ZM_DATA", $this->settings->get("zm_data")); + //define("CONFIG_DIR", $this->settings->get("config_dir")); + define("CRASH_DIR", $this->settings->get("crash_dir")); + } + + private function selfCheck() { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n"); + if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); + if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); + return true; + } + + private static function onWork(Process $worker, $m_pid) { + swoole_set_process_name('php-scheduler'); + for ($j = 0; $j < 16000; $j++) { + self::checkMpid($worker, $m_pid); + echo "msg: {$j}\n"; + sleep(1); + } + } + + private static function checkMpid(Process $worker, $m_pid) { + if (!Process::kill($m_pid, 0)) { + $worker->exit(); //主进程死了我也死 + // 这句提示,实际是看不到的.需要写到日志中 + echo "Master process exited, I [{$worker['pid']}] also quit\n"; + } + } +} \ No newline at end of file diff --git a/src/cqbot/api/CQ.php b/src/ZM/API/CQ.php similarity index 83% rename from src/cqbot/api/CQ.php rename to src/ZM/API/CQ.php index 58b99f23..b62a6338 100644 --- a/src/cqbot/api/CQ.php +++ b/src/ZM/API/CQ.php @@ -1,10 +1,11 @@ $v) { - $ss = explode("=", $v); - $sk = array_shift($ss); - $array[$sk] = implode("=", $ss); - } - return ["type" => $type, "params" => $array, "start" => $start, "end" => $end]; + /** + * 转义CQ码 + * @param $msg + * @return mixed + */ + public static function escape($msg) { + $msg = str_replace("&", "&", $msg); + $msg = str_replace("[", "[", $msg); + $msg = str_replace("]", "]", $msg); + return $msg; } + public static function removeCQ($msg) { + while (($cq = ZMUtil::getCQ($msg)) !== null) { + $msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg); + } + return $msg; + } } \ No newline at end of file diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php new file mode 100644 index 00000000..cd6edb56 --- /dev/null +++ b/src/ZM/API/CQAPI.php @@ -0,0 +1,249 @@ + $data["group_id"], "message" => $msg], $yield); + case "private": + return self::send_private_msg($conn, ["user_id" => $data["user_id"], "message" => $msg], $yield); + case "discuss": + return self::send_discuss_msg($conn, ["discuss_id" => $data["discuss_id"], "message" => $msg], $yield); + } + return null; + } + + public static function __callStatic($name, $arg) { + $all = self::getSupportedAPIs(); + $find = null; + if (in_array($name, $all)) $find = $name; + else { + foreach ($all as $v) { + if (strtolower($name) == strtolower(str_replace("_", "", $v))) { + $find = $v; + break; + } + } + } + if ($find === null) { + Console::error("Unknown API " . $name); + return false; + } + $reply = ["action" => $find]; + if (!is_array($arg[1])) { + Console::error("Error when parsing params. Please make sure your params is an array."); + return false; + } + if ($arg[1] != []) { + $reply["params"] = $arg[1]; + } + if (!($arg[0] instanceof CQConnection)) { + $robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]); + if ($robot == []) { + Console::error("发送错误,机器人连接不存在!"); + return false; + } + $arg[0] = $robot[0]; + } + return self::processAPI($arg[0], $reply, $arg[2] ?? null); + } + + /********************** non-API Part **********************/ + + private static function getSupportedAPIs() { + return [ + "send_private_msg", + "send_group_msg", + "send_discuss_msg", + "send_msg", + "delete_msg", + "send_like", + "set_group_kick", + "set_group_ban", + "set_group_anonymous_ban", + "set_group_whole_ban", + "set_group_admin", + "set_group_anonymous", + "set_group_card", + "set_group_leave", + "set_group_special_title", + "set_discuss_leave", + "set_friend_add_request", + "set_group_add_request", + "get_login_info", + "get_stranger_info", + "get_group_list", + "get_group_member_info", + "get_group_member_list", + "get_cookies", + "get_csrf_token", + "get_credentials", + "get_record", + "get_status", + "get_version_info", + "set_restart", + "set_restart_plugin", + "clean_data_dir", + "clean_plugin_log", + "_get_friend_list", + "_get_group_info", + "_get_vip_info", + //异步API + "send_private_msg_async", + "send_group_msg_async", + "send_discuss_msg_async", + "send_msg_async", + "delete_msg_async", + "set_group_kick_async", + "set_group_ban_async", + "set_group_anonymous_ban_async", + "set_group_whole_ban_async", + "set_group_admin_async", + "set_group_anonymous_async", + "set_group_card_async", + "set_group_leave_async", + "set_group_special_title_async", + "set_discuss_leave_async", + "set_friend_add_request_async", + "set_group_add_request_async" + ]; + } + + public static function getLoggedAPIs() { + return [ + "send_private_msg", + "send_group_msg", + "send_discuss_msg", + "send_msg", + "send_private_msg_async", + "send_group_msg_async", + "send_discuss_msg_async", + "send_msg_async" + ]; + } + + /** + * @param WSConnection $connection + * @param $reply + * @param |null $function + * @return bool + */ + private static function processAPI($connection, $reply, $function = null) { + $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); + $reply["echo"] = $api_id; + ZMBuf::$atomics["wait_msg_id"]->add(1); + + if (is_callable($function)) { + ZMBuf::appendKey("sent_api", $api_id, [ + "data" => $reply, + "time" => microtime(true), + "func" => $function, + "self_id" => $connection->getQQ() + ]); + } elseif ($function === true) { + ZMBuf::appendKey("sent_api", $api_id, [ + "data" => $reply, + "time" => microtime(true), + "coroutine" => Co::getuid(), + "self_id" => $connection->getQQ() + ]); + } else { + ZMBuf::appendKey("sent_api", $api_id, [ + "data" => $reply, + "time" => microtime(true), + "self_id" => $connection->getQQ() + ]); + } + if ($connection->push(json_encode($reply))) { + Console::msg($reply, $connection->getQQ()); + ZMBuf::$atomics["out_count"]->add(1); + if ($function === true) { + Co::suspend(); + $data = ZMBuf::get("sent_api")[$api_id]; + ZMBuf::unsetByValue("sent_api", $reply["echo"]); + return isset($data['result']) ? $data['result'] : null; + } + return true; + } else { + $response = [ + "status" => "failed", + "retcode" => 999, + "data" => null, + "self_id" => $connection->getQQ() + ]; + $s = ZMBuf::get("sent_api")[$reply["echo"]]; + if (($s["func"] ?? null) !== null) + call_user_func($s["func"], $response, $reply); + ZMBuf::unsetByValue("sent_api", $reply["echo"]); + if ($function === true) return null; + return false; + } + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/AnnotationBase.php b/src/ZM/Annotation/AnnotationBase.php new file mode 100644 index 00000000..3c6ab548 --- /dev/null +++ b/src/ZM/Annotation/AnnotationBase.php @@ -0,0 +1,29 @@ + $v) { + $str .= "\n\t" . $k . " => "; + if (is_string($v)) $str .= "\"$v\""; + elseif (is_numeric($v)) $str .= $v; + elseif (is_bool($v)) $str .= ($v ? "TRUE" : "FALSE"); + elseif (is_array($v)) $str .= json_encode($v, JSON_UNESCAPED_UNICODE); + elseif ($v instanceof Closure) $str .= "@AnonymousFunction"; + elseif (is_null($v)) $str .= "NULL"; + else $str .= "@Unknown"; + } + return $str; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php new file mode 100644 index 00000000..428d6d17 --- /dev/null +++ b/src/ZM/Annotation/AnnotationParser.php @@ -0,0 +1,223 @@ +getMethods(ReflectionMethod::IS_PUBLIC); + $class_annotations = $reader->getClassAnnotations($reflection_class); + foreach ($class_annotations as $vs) { + if ($vs instanceof Closed) { + continue 2; + } elseif ($vs instanceof Controller) { + $class_prefix = $vs->prefix; + } elseif ($vs instanceof SaveBuffer) { + DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder); + } + } + foreach ($methods as $vs) { + $method_annotations = $reader->getMethodAnnotations($vs); + foreach ($method_annotations as $vss) { + if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class); + else $vss = self::registerMethod($vss, $vs, $reflection_class); + + if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss; + elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss; + elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss; + elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss; + elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss; + elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss; + elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss; + elseif ($vss instanceof RequestMapping) self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix); + elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss; + elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss; + elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss; + } + } + } + + //给支持level的排个序 + foreach (ZMBuf::$events as $class_name => $v) { + if ((new $class_name()) instanceof Level) { + for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) { + for ($j = 0; $j < count(ZMBuf::$events[$class_name]) - $i - 1; ++$j) { + $l1 = ZMBuf::$events[$class_name][$j]->level; + $l2 = ZMBuf::$events[$class_name][$j + 1]->level; + if ($l1 < $l2) { + $t = ZMBuf::$events[$class_name][$j + 1]; + ZMBuf::$events[$class_name][$j + 1] = ZMBuf::$events[$class_name][$j]; + ZMBuf::$events[$class_name][$j] = $t; + } + } + } + } + } + } + + private static function getRuleCallback($rule_str) { + $func = null; + $rule = $rule_str; + if ($rule != "") { + $asp = explode(":", $rule); + $asp_name = array_shift($asp); + $rest = implode(":", $asp); + //Swoole 事件时走此switch + switch ($asp_name) { + case "connectType": //websocket连接类型 + $func = function (WSConnection $connection) use ($rest) { + return $connection->getType() == $rest ? true : false; + }; + break; + case "containsGet": //handle http request事件时才能用 + case "containsPost": + $get_list = explode(",", $rest); + if ($asp_name == "containsGet") + $func = function ($request) use ($get_list) { + foreach ($get_list as $v) if (!isset($request->get[$v])) return false; + return true; + }; + else + $func = function ($request) use ($get_list) { + foreach ($get_list as $v) if (!isset($request->post[$v])) return false; + return true; + }; + /* + if ($controller_prefix != '') { + $p = ZMBuf::$req_mapping_node; + $prefix_exp = explode("/", $controller_prefix); + foreach ($prefix_exp as $k => $v) { + if ($v == "" || $v == ".." || $v == ".") { + unset($prefix_exp[$k]); + } + } + while (($shift = array_shift($prefix_exp)) !== null) { + $p->addRoute($shift, new MappingNode($shift)); + $p = $p->getRoute($shift); + } + if ($p->getNodeName() != "/") { + $p->setMethod($method->getName()); + $p->setClass($class->getName()); + $p->setRule($func); + return "mapped"; + } + }*/ + break; + case "containsJson": //handle http request事件时才能用 + $json_list = explode(",", $rest); + $func = function ($json) use ($json_list) { + foreach ($json_list as $v) if (!isset($json[$v])) return false; + return true; + }; + break; + case "dataEqual": //handle websocket message事件时才能用 + $func = function ($data) use ($rest) { return $data == $rest; }; + break; + } + switch ($asp_name) { + case "msgMatch": //handle cq message事件时才能用 + $func = function ($msg) use ($rest) { return matchPattern($rest, $msg); }; + break; + case "msgEqual": //handle cq message事件时才能用 + $func = function ($msg) use ($rest) { return trim($msg) == $rest; }; + break; + + } + } + return $func; + } + + private static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) { + $vss->callback = self::getRuleCallback($vss->getRule()); + $vss->method = $method->getName(); + $vss->class = $class->getName(); + return $vss; + } + + private static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) { + $vss->method = $method->getName(); + $vss->class = $class->getName(); + return $vss; + } + + private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) { + $prefix_exp = explode("/", $prefix); + $route_exp = explode("/", $vss->route); + foreach ($prefix_exp as $k => $v) { + if ($v == "" || $v == ".." || $v == ".") { + unset($prefix_exp[$k]); + } + } + foreach ($route_exp as $k => $v) { + if ($v == "" || $v == ".." || $v == ".") { + unset($route_exp[$k]); + } + } + $a = ZMBuf::$req_mapping_node; + $p = $a; + if ($prefix_exp == [] && $route_exp == []) { + $p->setMethod($method->getName()); + $p->setClass($class->getName()); + $p->setRequestMethod($vss->request_method); + return; + } + while (($shift = array_shift($prefix_exp)) !== null) { + $p->addRoute($shift, new MappingNode($shift)); + $p = $p->getRoute($shift); + } + while (($shift = array_shift($route_exp)) !== null) { + if (mb_substr($shift, 0, 1) == "{" && mb_substr($shift, -1, 1) == "}") { + $p->removeAllRoute(); + } + $p->addRoute($shift, new MappingNode($shift)); + $p = $p->getRoute($shift); + } + $p->setMethod($method->getName()); + $p->setClass($class->getName()); + $p->setRequestMethod($vss->request_method); + } + + private static function loadAnnotationClasses() { + $class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation"); + foreach ($class as $v) { + $s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php"; + require_once $s; + } + $class = getAllClasses(WORKING_DIR . "/src/Custom/Annotation/", "Custom\\Annotation"); + foreach ($class as $v) { + $s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php"; + require_once $s; + } + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQAfter.php b/src/ZM/Annotation/CQ/CQAfter.php new file mode 100644 index 00000000..3bfd3c35 --- /dev/null +++ b/src/ZM/Annotation/CQ/CQAfter.php @@ -0,0 +1,22 @@ +level; + } + + /** + * @param mixed $level + */ + public function setLevel($level): void { + $this->level = $level; + } + +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php new file mode 100644 index 00000000..55848d13 --- /dev/null +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -0,0 +1,35 @@ +level; } + + /** + * @param int $level + */ + public function setLevel(int $level) { $this->level = $level; } + +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQMessage.php b/src/ZM/Annotation/CQ/CQMessage.php new file mode 100644 index 00000000..70917033 --- /dev/null +++ b/src/ZM/Annotation/CQ/CQMessage.php @@ -0,0 +1,41 @@ +level; } + + public function setLevel(int $level) { + $this->level = $level; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQMetaEvent.php b/src/ZM/Annotation/CQ/CQMetaEvent.php new file mode 100644 index 00000000..000f922b --- /dev/null +++ b/src/ZM/Annotation/CQ/CQMetaEvent.php @@ -0,0 +1,40 @@ +level; } + + /** + * @param int $level + */ + public function setLevel(int $level): void { + $this->level = $level; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQNotice.php b/src/ZM/Annotation/CQ/CQNotice.php new file mode 100644 index 00000000..b24fcc88 --- /dev/null +++ b/src/ZM/Annotation/CQ/CQNotice.php @@ -0,0 +1,42 @@ +level; + } + + /** + * @param int $level + */ + public function setLevel(int $level): void { + $this->level = $level; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/CQ/CQRequest.php b/src/ZM/Annotation/CQ/CQRequest.php new file mode 100644 index 00000000..bdf6cd91 --- /dev/null +++ b/src/ZM/Annotation/CQ/CQRequest.php @@ -0,0 +1,42 @@ +level; + } + + /** + * @param int $level + */ + public function setLevel(int $level): void { + $this->level = $level; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/Http/Controller.php b/src/ZM/Annotation/Http/Controller.php new file mode 100644 index 00000000..9556a6bd --- /dev/null +++ b/src/ZM/Annotation/Http/Controller.php @@ -0,0 +1,23 @@ +node = $node_name; } + + public function addRoute(string $route_name, MappingNode $route_node) { $this->route[$route_name] = $route_node; } + + /** + * @param string $shift + * @return MappingNode|null + */ + public function getRoute(string $shift) { + return $this->route[$shift] ?? null; + } + + public function getRealRoute(string $shift, array &$bind_params) { + if (mb_substr(key($this->route), 0, 1) == "{" && mb_substr(key($this->route), -1, 1) == "}") { + $param_name = mb_substr(current($this->route)->getNodeName(), 1, -1); + $bind_params[$param_name] = $shift; + return current($this->route); + } else return $this->route[$shift] ?? null; + } + + public function setMethod($method) { $this->method = $method; } + + public function setClass($class) { $this->class = $class; } + + public function setRequestMethod($method) { + if (is_string($method)) $this->request_method = [$method]; + else $this->request_method = $method; + } + + public function getNodeName() { return $this->node; } + + public function getRule() { return $this->rule; } + + public function setRule(Closure $rule): void { $this->rule = $rule; } + + public function removeAllRoute() { $this->route = []; } + + /** + * @return null + */ + public function getMethod() { + return $this->method; + } + + /** + * @return array + */ + public function getRequestMethod(): array { + return $this->request_method; + } + + /** + * @return null + */ + public function getClass() { + return $this->class; + } + + /** + * @return string + */ + public function getNode(): string { + return $this->node; + } + + public function __toString() { + $str = "[" . $this->node . "] => "; + if ($this->class != "" && $this->class != null) + $str .= "\n\t" . $this->class . "->" . $this->method . ": " . implode(", ", $this->request_method); + $str .= "\n\t[Route] => ["; + foreach ($this->route as $k => $v) { + $r = $v; + $r = explode("\n", $r); + foreach ($r as $ks => $vs) { + $r[$ks] = "\t" . $r[$ks]; + } + $r = implode("\n", $r); + $str .= "\n\t" . $r; + } + $str .= "\n]"; + return $str; + } +} \ No newline at end of file diff --git a/src/ZM/Annotation/Module/Closed.php b/src/ZM/Annotation/Module/Closed.php new file mode 100644 index 00000000..d4e6a143 --- /dev/null +++ b/src/ZM/Annotation/Module/Closed.php @@ -0,0 +1,18 @@ +type; + } + + /** + * @param string $type + */ + public function setType(string $type): void { + $this->type = $type; + } + + /** + * @return string + */ + public function getRule(): string { + return $this->rule; + } + + /** + * @param string $rule + */ + public function setRule(string $rule): void { + $this->rule = $rule; + } + + /** + * @return int + */ + public function getLevel(): int { + return $this->level; + } + + /** + * @param int $level + */ + public function setLevel(int $level): void { + $this->level = $level; + } + + +} \ No newline at end of file diff --git a/src/ZM/Annotation/Swoole/SwooleEventAt.php b/src/ZM/Annotation/Swoole/SwooleEventAt.php new file mode 100644 index 00000000..7f91df13 --- /dev/null +++ b/src/ZM/Annotation/Swoole/SwooleEventAt.php @@ -0,0 +1,76 @@ +type; + } + + /** + * @param string $type + */ + public function setType(string $type): void { + $this->type = $type; + } + + /** + * @return string + */ + public function getRule(): string { + return $this->rule; + } + + /** + * @param string $rule + */ + public function setRule(string $rule): void { + $this->rule = $rule; + } + + /** + * @return int + */ + public function getLevel(): int { + return $this->level; + } + + /** + * @param int $level + */ + public function setLevel(int $level): void { + $this->level = $level; + } + +} \ No newline at end of file diff --git a/src/ZM/Connection/CQConnection.php b/src/ZM/Connection/CQConnection.php new file mode 100644 index 00000000..a3f2a483 --- /dev/null +++ b/src/ZM/Connection/CQConnection.php @@ -0,0 +1,23 @@ +self_id = $self_id; + } + + public function getQQ(){ + return $this->self_id; + } + + public function getType() { + return "qq"; + } +} \ No newline at end of file diff --git a/src/ZM/Connection/ConnectionManager.php b/src/ZM/Connection/ConnectionManager.php new file mode 100644 index 00000000..d4290c29 --- /dev/null +++ b/src/ZM/Connection/ConnectionManager.php @@ -0,0 +1,73 @@ +fd == $fd) return $v; + } + return null; + } + + /** + * @param string $type + * @param array $option + * @return WSConnection[] + */ + public static function getByType(string $type, $option = []) { + $conn = []; + foreach (ZMBuf::$connect as $v) { + foreach ($option as $ks => $vs) { + if (($v->$ks ?? "") == $vs) continue; + else continue 2; + } + if ($v->getType() == $type) $conn[] = $v; + } + return $conn; + } + + public static function getTypeClassName(string $type) { + switch (strtolower($type)) { + case "qq": + case "universal": + return CQConnection::class; + case "webconsole": + return WCConnection::class; + case "proxy": + return ProxyConnection::class; + default: + foreach (ZMBuf::$custom_connection_class as $v) { + /** @var WSConnection $r */ + $r = new $v(ZMBuf::$server, -1); + if ($r->getType() == strtolower($type)) return $v; + } + return UnknownConnection::class; + } + } + + public static function close($fd) { + foreach (ZMBuf::$connect as $k => $v) { + if ($v->fd == $fd) { + ZMBuf::$server->close($fd); + unset(ZMBuf::$connect[$k]); + break; + } + } + } + + public static function registerCustomClass() { + $classes = getAllClasses(WORKING_DIR . "/src/Custom/Connection/", "Custom\\Connection"); + ZMBuf::$custom_connection_class = $classes; + } +} \ No newline at end of file diff --git a/src/ZM/Connection/ProxyConnection.php b/src/ZM/Connection/ProxyConnection.php new file mode 100644 index 00000000..f43083f0 --- /dev/null +++ b/src/ZM/Connection/ProxyConnection.php @@ -0,0 +1,13 @@ +server = $server; + $this->fd = $fd; + } + + public abstract function getType(); + + public function exists() { + return $this->available = $this->server->exist($this->fd); + } + + public function close() { + ConnectionManager::close($this->fd); + } + + public function push($data, $push_error_record = true) { + if ($data === null || $data == "") { + Console::warning("推送了空消息"); + return false; + } + if (!$this->server->exist($this->fd)) { + Console::warning("Swoole 原生 websocket连接池中无此连接"); + return false; + } + if ($this->server->push($this->fd, $data) === false) { + $data = unicode_decode($data); + if ($push_error_record) Logger::writeSwooleLog("API push failed. Data: " . $data); + Console::error("websocket数据未成功推送,长度:" . strlen($data)); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php new file mode 100644 index 00000000..65c11aef --- /dev/null +++ b/src/ZM/DB/DB.php @@ -0,0 +1,120 @@ +get(); + if ($conn === false) { + throw new DbException("无法连接SQL!" . $line); + } + $result = $conn->query($line) === false ? false : ($conn->errno != 0 ? false : true); + ZMBuf::$sql_pool->put($conn); + return $result; + } catch (DBException $e) { + if (ZMBuf::get("sql_log") === true) { + $log = + "[" . date("Y-m-d H:i:s") . + " " . round(microtime(true) - $starttime, 5) . + "] " . $line . " (Error:" . $e->getMessage() . ")\n"; + Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); + } + Console::error($e->getMessage()); + return false; + } + } + + public static function rawQuery(string $line, $params) { + if (ZMBuf::get("sql_log") === true) { + $starttime = microtime(true); + } + try { + $conn = ZMBuf::$sql_pool->get(); + if ($conn === false) { + throw new DbException("无法连接SQL!" . $line); + } + $ps = $conn->prepare($line); + if ($ps === false) { + $conn->close(); + ZMBuf::$sql_pool->connect_cnt -= 1; + throw new DbException("SQL语句查询错误," . $line . ",错误信息:" . $conn->error); + } else { + if (!($ps instanceof Statement)) { + throw new DbException("语句查询错误!" . $line); + } + if ($params == []) $result = $ps->execute(); + elseif (!is_array($params)) { + $result = $ps->execute([$params]); + } else $result = $ps->execute($params); + ZMBuf::$sql_pool->put($conn); + if ($ps->errno != 0) { + throw new DBException("语句[$line]错误!" . $ps->error); + //echo json_encode(debug_backtrace(), 128 | 256); + } + if (ZMBuf::get("sql_log") === true) { + $log = + "[" . date("Y-m-d H:i:s") . + " " . round(microtime(true) - $starttime, 4) . + "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n"; + Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); + } + return $result; + } + } catch (DBException $e) { + if (ZMBuf::get("sql_log") === true) { + $log = + "[" . date("Y-m-d H:i:s") . + " " . round(microtime(true) - $starttime, 4) . + "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; + Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); + } + Console::error($e->getMessage()); + return false; + } + } +} \ No newline at end of file diff --git a/src/ZM/DB/DeleteBody.php b/src/ZM/DB/DeleteBody.php new file mode 100644 index 00000000..bc6e590b --- /dev/null +++ b/src/ZM/DB/DeleteBody.php @@ -0,0 +1,28 @@ +table = $table; + } + + public function save() { + list($sql, $param) = $this->getWhereSQL(); + return DB::rawQuery("DELETE FROM " . $this->table->getTableName() . " WHERE " . $sql, $param); + } +} \ No newline at end of file diff --git a/src/ZM/DB/InsertBody.php b/src/ZM/DB/InsertBody.php new file mode 100644 index 00000000..a8a130d4 --- /dev/null +++ b/src/ZM/DB/InsertBody.php @@ -0,0 +1,28 @@ +table = $table; + $this->row = $row; + } + + public function save() { + DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, 5, '?')).')', $this->row); + } +} \ No newline at end of file diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php new file mode 100644 index 00000000..d4daab19 --- /dev/null +++ b/src/ZM/DB/SelectBody.php @@ -0,0 +1,88 @@ +table = $table; + $this->select_thing = $select_thing; + } + + public function get() { return $this->fetchAll(); } + + public function fetchAll() { + if ($this->table->isCacheEnabled()) { + $rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing)); + if (array_key_exists($rr, $this->table->cache)) { + Console::info('SQL query cached: ' . $rr, date("[H:i:s ") . 'DB] '); + return $this->table->cache[$rr]->getResult(); + } + } + $this->execute(); + if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) { + $this->table->cache[$rr] = $this; + } + return $this->getResult(); + } + + public function fetchFirst() { + return $this->fetchAll()[0] ?? null; + } + + public function value() { + $r = $this->fetchFirst(); + if ($r === null) return null; + return current($r); + } + + public function execute() { + $str = $this->queryPrepare(); + $this->result = DB::rawQuery($str[0], $str[1]); + } + + public function getResult() { return $this->result; } + + public function equals(SelectBody $body) { + if ($this->select_thing != $body->getSelectThing()) return false; + elseif ($this->where_thing == $body->getWhereThing()) return false; + else return true; + } + + /** + * @return mixed + */ + public function getSelectThing() { return $this->select_thing; } + + /** + * @return array + */ + public function getWhereThing() { return $this->where_thing; } + + private function queryPrepare() { + $msg = "SELECT " . implode(", ", $this->select_thing) . " FROM " . $this->table->getTableName(); + $sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '='); + if ($sql[0] != '') { + $msg .= " WHERE " . $sql[0]; + $array = $sql[1]; + $sql = $this->table->paintWhereSQL($this->where_thing['!='] ?? [], '!='); + if ($sql[0] != '') $msg .= " AND " . $sql[0]; + $array = array_merge($array, $sql[1]); + } + return [$msg, $array ?? []]; + } +} \ No newline at end of file diff --git a/src/ZM/DB/Table.php b/src/ZM/DB/Table.php new file mode 100644 index 00000000..cf85d4cd --- /dev/null +++ b/src/ZM/DB/Table.php @@ -0,0 +1,79 @@ +enable_cache = $enable_cache; + $this->table_name = $table_name; + self::$table_instance[$table_name] = $this; + } + + public static function getTableInstance($table_name) { + if (isset(self::$table_instance[$table_name])) return self::$table_instance[$table_name]; + else return null; + } + + public function select($what = []) { + return new SelectBody($this, $what == [] ? ["*"] : $what); + } + + public function where($column, $operation_or_value, $value = null){ + return (new SelectBody($this, ["*"]))->where($column, $operation_or_value, $value); + } + + public function insert($row) { + $this->cache = []; + return new InsertBody($this, $row); + } + + public function update(array $set_value) { + $this->cache = []; + return new UpdateBody($this, $set_value); + } + + public function delete() { + $this->cache = []; + return new DeleteBody($this); + } + + public function statement($line){ + $this->cache = []; + //TODO: 无返回的statement语句 + } + + public function paintWhereSQL($rule, $operator) { + if ($rule == []) return ["", []]; + $msg = ""; + $param = []; + foreach ($rule as $k => $v) { + if ($msg == "") { + $msg .= $k . " $operator ? "; + } else { + $msg .= "AND " . $k . " $operator ?"; + } + $param[] = $v; + } + return [$msg, $param]; + } + + /** + * @return mixed + */ + public function getTableName() { return $this->table_name; } + + public function isCacheEnabled() { return $this->enable_cache; } +} \ No newline at end of file diff --git a/src/ZM/DB/UpdateBody.php b/src/ZM/DB/UpdateBody.php new file mode 100644 index 00000000..f9681758 --- /dev/null +++ b/src/ZM/DB/UpdateBody.php @@ -0,0 +1,50 @@ +table = $table; + $this->set_value = $set_value; + } + + /** + * @throws DbException + */ + public function save(){ + $arr = []; + $msg = []; + foreach($this->set_value as $k => $v) { + $msg []= $k .' = ?'; + $arr[]=$v; + } + if(($msg ?? []) == []) throw new DbException('update value sets can not be empty!'); + $line = 'UPDATE '.$this->table->getTableName().' SET '.implode(', ', $msg); + if($this->where_thing != []) { + list($sql, $param) = $this->getWhereSQL(); + $arr = array_merge($arr, $param); + $line .= ' WHERE '.$sql; + } + return DB::rawQuery($line, $arr); + } +} \ No newline at end of file diff --git a/src/ZM/DB/WhereBody.php b/src/ZM/DB/WhereBody.php new file mode 100644 index 00000000..eb2b651f --- /dev/null +++ b/src/ZM/DB/WhereBody.php @@ -0,0 +1,33 @@ +where_thing['='][$column] = $operation_or_value; + elseif ($value !== null) $this->where_thing[$operation_or_value][$column] = $value; + else $this->where_thing['='][$column] = $operation_or_value; + return $this; + } + + protected function getWhereSQL(){ + $param = []; + $msg = ''; + foreach($this->where_thing as $k => $v) { + foreach($v as $ks => $vs) { + if($param != []) { + $msg .= ' AND '.$ks ." $k ?"; + } else { + $msg .= "$ks $k ?"; + } + $param []=$vs; + } + } + return [$msg, $param]; + } +} \ No newline at end of file diff --git a/src/ZM/DBCache/CourseCache.php b/src/ZM/DBCache/CourseCache.php new file mode 100644 index 00000000..82681c3d --- /dev/null +++ b/src/ZM/DBCache/CourseCache.php @@ -0,0 +1,18 @@ +data = $data; + $this->swoole_event = $event; + $this->circle = $circle; + } + + public function onBefore() { + foreach (ZMBuf::$events[CQBefore::class][CQMessage::class] ?? [] as $v) { + $c = $v->class; + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_MESSAGE); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + foreach (ZMBuf::get("wait_api", []) as $k => $v) { + if($this->data["user_id"] == $v["user_id"] && + $this->data["self_id"] == $v["self_id"] && + $this->data["message_type"] == $v["message_type"] && + ($this->data[$this->data["message_type"]."_id"] ?? $this->data["user_id"]) == + ($v[$v["message_type"]."_id"] ?? $v["user_id"])){ + $v["result"] = $this->data["message"]; + ZMBuf::appendKey("wait_api", $k, $v); + Co::resume($v["coroutine"]); + return false; + } + } + return true; + } + + /** @noinspection PhpRedundantCatchClauseInspection */ + public function onActivate() { + try { + $word = split_explode(" ", str_replace("\r", "", $this->data["message"])); + if (count(explode("\n", $word[0])) >= 2) { + $enter = explode("\n", $this->data["message"]); + $first = split_explode(" ", array_shift($enter)); + $word = array_merge($first, $enter); + foreach ($word as $k => $v) { + $word[$k] = trim($word[$k]); + } + } + /** @var ModBase[] $obj */ + $obj = []; + foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) { + /** @var CQCommand $v */ + if ($v->match == "" && $v->regexMatch == "") continue; + else { + $c = $v->class; + if (!isset($obj[$c])) + $obj[$c] = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_MESSAGE); + if ($word[0] != "" && $v->match == $word[0]) { + $r = call_user_func([$obj[$c], $v->method], $word); + if (is_string($r)) $obj[$c]->reply($r); + $this->function_call = true; + return; + } elseif (($args = matchArgs($v->regexMatch, $this->data["message"])) !== false) { + $r = call_user_func([$obj[$c], $v->method], $args); + if (is_string($r)) $obj[$c]->reply($r); + $this->function_call = true; + return; + } + } + } + foreach (ZMBuf::$events[CQMessage::class] ?? [] as $v) { + /** @var CQMessage $v */ + if ( + ($v->message == '' || ($v->message != '' && $v->message == $this->data["message"])) && + ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $this->data["user_id"])) && + ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) && + ($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == ($this->data["discuss_id"] ?? 0))) && + ($v->message_type == '' || ($v->message_type != '' && $v->message_type == $this->data["message_type"])) && + ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == $this->data["raw_message"]))) { + $c = $v->class; + if (!isset($obj[$c])) + $obj[$c] = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_MESSAGE); + $r = call_user_func([$obj[$c], $v->method], $this->data["message"]); + if (is_string($r)) $obj[$c]->reply($r); + if ($obj[$c]->block_continue) return; + } + } + } catch (WaitTimeoutException $e) { + + $e->module->finalReply($e->getMessage()); + } + } + + /** + * 在调用完事件后执行的 + */ + public function onAfter() { + foreach (ZMBuf::$events[CQAfter::class][CQMessage::class] ?? [] as $v) { + $c = $v->class; + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_MESSAGE); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } + + public function hasReply() { + return $this->function_call; + } +} \ No newline at end of file diff --git a/src/ZM/Event/CQ/MetaEvent.php b/src/ZM/Event/CQ/MetaEvent.php new file mode 100644 index 00000000..1460464e --- /dev/null +++ b/src/ZM/Event/CQ/MetaEvent.php @@ -0,0 +1,71 @@ +data = $data; + $this->swoole_event = $event; + $this->circle = $circle; + } + + public function onBefore() { + foreach (ZMBuf::$events[CQBefore::class][CQMetaEvent::class] ?? [] as $v) { + $c = $v->class; + /** @var CQMetaEvent $v */ + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_META_EVENT); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } + + /** @noinspection PhpRedundantCatchClauseInspection */ + public function onActivate() { + try { + /** @var ModBase[] $obj */ + $obj = []; + foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) { + /** @var CQMetaEvent $v */ + if ( + ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == $this->data["meta_event_type"])) && + ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) { + $c = $v->class; + if (!isset($obj[$c])) + $obj[$c] = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_META_EVENT); + $r = call_user_func([$obj[$c], $v->method]); + if (is_string($r)) $obj[$c]->reply($r); + if ($obj[$c]->block_continue) return; + } + } + } catch (WaitTimeoutException $e) { + $e->module->finalReply($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/ZM/Event/CQ/NoticeEvent.php b/src/ZM/Event/CQ/NoticeEvent.php new file mode 100644 index 00000000..fe7c62fb --- /dev/null +++ b/src/ZM/Event/CQ/NoticeEvent.php @@ -0,0 +1,88 @@ +data = $data; + $this->swoole_event = $event; + $this->circle = $circle; + } + + public function onBefore() { + foreach (ZMBuf::$events[CQBefore::class][CQNotice::class] ?? [] as $v) { + $c = $v->class; + /** @var CQNotice $v */ + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_NOTICE); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } + + public function onActivate() { + try { + /** @var ModBase[] $obj */ + $obj = []; + foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) { + /** @var CQNotice $v */ + if ( + ($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == $this->data["notice_type"])) && + ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) && + ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) && + ($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) { + $c = $v->class; + if (!isset($obj[$c])) + $obj[$c] = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_NOTICE); + $r = call_user_func([$obj[$c], $v->method]); + if (is_string($r)) $obj[$c]->reply($r); + if ($obj[$c]->block_continue) return; + } + } + } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { + $e->module->finalReply($e->getMessage()); + } + } + + public function onAfter() { + foreach (ZMBuf::$events[CQAfter::class][CQNotice::class] ?? [] as $v) { + $c = $v->class; + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_NOTICE); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/CQ/RequestEvent.php b/src/ZM/Event/CQ/RequestEvent.php new file mode 100644 index 00000000..ad99b7c3 --- /dev/null +++ b/src/ZM/Event/CQ/RequestEvent.php @@ -0,0 +1,89 @@ +data = $data; + $this->swoole_event = $event; + $this->circle = $circle; + } + + public function onBefore() { + foreach (ZMBuf::$events[CQBefore::class][CQRequest::class] ?? [] as $v) { + $c = $v->class; + /** @var CQRequest $v */ + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_REQUEST); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } + + /** @noinspection PhpRedundantCatchClauseInspection */ + public function onActivate() { + try { + /** @var ModBase[] $obj */ + $obj = []; + foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) { + /** @var CQRequest $v */ + if ( + ($v->request_type == '' || ($v->request_type != '' && $v->request_type == $this->data["request_type"])) && + ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) && + ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ($this->data["user_id"] ?? 0))) && + ($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) { + $c = $v->class; + if (!isset($obj[$c])) + $obj[$c] = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_REQUEST); + $r = call_user_func([$obj[$c], $v->method]); + if (is_string($r)) $obj[$c]->reply($r); + if ($obj[$c]->block_continue) return; + } + } + } catch (WaitTimeoutException $e) { + $e->module->finalReply($e->getMessage()); + } + } + + public function onAfter() { + foreach (ZMBuf::$events[CQAfter::class][CQRequest::class] ?? [] as $v) { + $c = $v->class; + $class = new $c([ + "data" => $this->data, + "frame" => $this->swoole_event->frame, + "server" => $this->swoole_event->server, + "connection" => ConnectionManager::get($this->swoole_event->frame->fd) + ], ModHandleType::CQ_REQUEST); + $r = call_user_func_array([$class, $v->method], []); + if (!$r || $class->block_continue) return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/Event.php b/src/ZM/Event/Event.php new file mode 100644 index 00000000..f78aff33 --- /dev/null +++ b/src/ZM/Event/Event.php @@ -0,0 +1,11 @@ +onActivate()->onAfter(); + Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold"); + } catch (Exception $e) { + Console::error("Worker加载出错!停止服务!"); + Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); + + ZMUtil::stop(); + return; + } + break; + case "message": + (new MessageEvent($param0, $param1))->onActivate()->onAfter(); + break; + case "request": + try { + (new RequestEvent($param0, $param1))->onActivate()->onAfter(); + } catch (Exception $e) { + /** @var Response $param1 */ + $param1->status(500); + if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage()); + Console::error("Internal server error (500), caused by uncaught exception."); + Console::log($e->getTraceAsString(), "gray"); + } + break; + case "open": + (new WSOpenEvent($param0, $param1))->onActivate()->onAfter(); + break; + case "close": + (new WSCloseEvent($param0, $param1))->onActivate()->onAfter(); + break; + } + Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold")); + } + + public static function callCQEvent($event_data, MessageEvent $event, $level = 0) { + if ($level >= 5) { + Console::warning("Recursive call reached " . $level . " times"); + Console::stackTrace(); + return false; + } + $starttime = microtime(true); + switch ($event_data["post_type"]) { + case "message": + $event = new CQ\MessageEvent($event_data, $event, $level); + if ($event->onBefore()) $event->onActivate(); + $event->onAfter(); + return $event->hasReply(); + break; + case "notice": + $event = new CQ\NoticeEvent($event_data, $event, $level); + if($event->onBefore()) $event->onActivate(); + $event->onAfter(); + return true; + case "request": + $event = new CQ\RequestEvent($event_data, $event, $level); + if($event->onBefore()) $event->onActivate(); + $event->onAfter(); + return true; + case "meta_event": + $event = new CQ\MetaEvent($event_data, $event, $level); + if($event->onBefore()) $event->onActivate(); + return true; + } + unset($starttime); + return false; + } + + public static function callCQResponse($req) { + //Console::info("收到来自API连接的回复:".json_encode($req, 128|256)); + if(isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) { + $status = $req["status"]; + $retcode = $req["retcode"]; + $data = $req["data"]; + $origin = ZMBuf::get("sent_api")[$req["echo"]]; + $self_id = $origin["self_id"]; + $response = [ + "status" => $status, + "retcode" => $retcode, + "data" => $data, + "self_id" => $self_id + ]; + if (($origin["func"] ?? null) !== null) { + call_user_func($origin["func"], $response, $origin["data"]); + } elseif (($origin["coroutine"] ?? false) !== false) { + $p = ZMBuf::get("sent_api"); + $p[$req["echo"]]["result"] = $response; + ZMBuf::set("sent_api", $p); + Co::resume($origin['coroutine']); + } + ZMBuf::unsetByValue("sent_api", $req["echo"]); + } + } +} \ No newline at end of file diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php new file mode 100644 index 00000000..c30279af --- /dev/null +++ b/src/ZM/Event/Swoole/MessageEvent.php @@ -0,0 +1,94 @@ +server = $server; + $this->frame = $frame; + } + + /** + * @inheritDoc + */ + public function onActivate() { + ZMUtil::checkWait(); + try { + if (ConnectionManager::get($this->frame->fd)->getType() == "qq") { + $data = json_decode($this->frame->data, true); + if (isset($data["post_type"])) + EventHandler::callCQEvent($data, $this, 0); + else + EventHandler::callCQResponse($data); + } + foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) { + $conn = ConnectionManager::get($this->frame->fd); + $c = $v->class; + /** @var ModBase $class */ + $class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE); + call_user_func_array([$class, $v->method], [$conn]); + if ($class->block_continue) break; + } + } + } catch (Exception $e) { + Console::error("出现错误: " . $e->getMessage()); + } + return $this; + } + + /** + * @inheritDoc + */ + public function onAfter() { + foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { + if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) { + $conn = ConnectionManager::get($this->frame->fd); + $c = $v->class; + /** @var ModBase $class */ + $class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE); + call_user_func_array([$class, $v->method], []); + if ($class->block_continue) break; + } + } + return $this; + } + + private function parseSwooleRule($v) { + switch (explode(":", $v->rule)[0]) { + case "connectType": //websocket连接类型 + if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->frame->fd)); + break; + case "dataEqual": //handle websocket message事件时才能用 + if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data); + break; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/Swoole/RequestEvent.php b/src/ZM/Event/Swoole/RequestEvent.php new file mode 100644 index 00000000..b91eb3d2 --- /dev/null +++ b/src/ZM/Event/Swoole/RequestEvent.php @@ -0,0 +1,132 @@ +request = $request; + $this->response = $response; + } + + /** + * @inheritDoc + */ + public function onActivate() { + ZMUtil::checkWait(); + foreach (ZMBuf::globals("http_header") as $k => $v) { + $this->response->setHeader($k, $v); + } + $uri = $this->request->server["request_uri"]; + if ($uri != "/") { + $uri = explode("/", $uri); + $uri = array_diff($uri, ["..", "", "."]); + $node = ZMBuf::$req_mapping_node; + $params = []; + while (true) { + $r = array_shift($uri); + if ($r === null) break; + if (($node2 = $node->getRealRoute($r, $params)) !== null) { + $node = $node2; + continue; + } else { + $this->responseStatus(404); + return $this; + } + } + if ($node->getRule() === null || call_user_func($node->getRule(), $this->request) === true) { //判断规则是否存在,如果有规则则走一遍规则 + if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面 + $c_name = $node->getClass(); + /** @var ModBase $class */ + $class = new $c_name(["request" => $this->request, "response" => $this->response, "params" => $params], ModHandleType::SWOOLE_REQUEST); + $r = call_user_func_array([$class, $node->getMethod()], [$params]); + if (is_string($r) && !$this->response->isEnd()) $this->response->end($r); + if ($class->block_continue) return $this; + if ($this->response->isEnd()) return $this; + } + } + } else { + if (($node = ZMBuf::$req_mapping_node)->getMethod() !== null) { + if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面 + $c_name = $node->getClass(); + /** @var ModBase $class */ + $class = new $c_name(["request" => $this->request, "response" => $this->response], ModHandleType::SWOOLE_REQUEST); + $r = call_user_func_array([$class, $node->getMethod()], []); + if (is_string($r) && !$this->response->isEnd()) $this->response->end($r); + if ($class->block_continue) return $this; + if ($this->response->isEnd()) return $this; + } + } + foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) { + $c = $v->class; + $class = new $c(["request" => $this->request, "response" => $this->response]); + $r = call_user_func_array([$class, $v->method], []); + if ($class->block_continue) break; + } + } + } + if (!$this->response->isEnd()) { + $this->response->status(404); + $this->response->end(ZMUtil::getHttpCodePage(404)); + } + return $this; + } + + /** + * @inheritDoc + */ + public function onAfter() { + foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { + if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) { + $c = $v->class; + $class = new $c(["request" => $this->request, "response" => $this->response]); + call_user_func_array([$class, $v->method], []); + if ($class->block_continue) break; + } + } + return $this; + } + + private function responseStatus(int $int) { + $this->response->status($int); + $this->response->end(); + } + + private function parseSwooleRule($v) { + switch ($v->rule) { + case "containsGet": + case "containsPost": + if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request); + break; + case "containsJson": + $content = $this->request->rawContent(); + $content = json_decode($content, true); + if ($content === null) return false; + if ($v->callback instanceof Closure) return call_user_func($v->callback, $content); + break; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/Swoole/SwooleEvent.php b/src/ZM/Event/Swoole/SwooleEvent.php new file mode 100644 index 00000000..73b1d54e --- /dev/null +++ b/src/ZM/Event/Swoole/SwooleEvent.php @@ -0,0 +1,20 @@ +server = $server; + $this->fd = $fd; + } + + /** + * @inheritDoc + */ + public function onActivate() { + ZMUtil::checkWait(); + foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) { + $c = $v->class; + $class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE); + call_user_func_array([$class, $v->method], []); + if($class->block_continue) break; + } + } + return $this; + } + + /** + * @inheritDoc + */ + public function onAfter() { + foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { + if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) { + $c = $v->class; + /** @var ModBase $class */ + $class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE); + call_user_func_array([$class, $v->method], []); + if($class->block_continue) break; + } + } + return $this; + } + + private function parseSwooleRule($v) { + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/Swoole/WSOpenEvent.php b/src/ZM/Event/Swoole/WSOpenEvent.php new file mode 100644 index 00000000..af6ef95b --- /dev/null +++ b/src/ZM/Event/Swoole/WSOpenEvent.php @@ -0,0 +1,93 @@ +server = $server; + $this->request = $request; + } + + /** + * @inheritDoc + */ + public function onActivate() { + ZMUtil::checkWait(); + $type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? ""); + $type_conn = ConnectionManager::getTypeClassName($type); + if ($type_conn == CQConnection::class) { + $qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? ""; + $self_token = ZMBuf::globals("access_token") ?? ""; + $remote_token = $this->request->get["token"] ?? (isset($header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : ""); + if ($qq != "" && ($self_token == $remote_token)) $this->conn = new CQConnection($this->server, $this->request->fd, $qq); + else $this->conn = new UnknownConnection($this->server, $this->request->fd); + } else { + $this->conn = new $type_conn($this->server, $this->request->fd); + } + ZMBuf::$connect[$this->request->fd] = $this->conn; + foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { + $c = $v->class; + $class = new $c(["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN); + call_user_func_array([$class, $v->method], [$this->conn]); + if ($class->block_continue) break; + } + } + return $this; + } + + /** + * @inheritDoc + */ + public function onAfter() { + if (!$this->conn->exists()) return $this; + foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { + if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { + /** @var ModBase $class */ + $class = new $v["class"](["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN); + call_user_func_array([$class, $v["method"]], [$this->conn]); + if ($class->block_continue) break; + } + } + return $this; + } + + private function parseSwooleRule($v) { + switch (explode(":", $v->rule)[0]) { + case "connectType": //websocket连接类型 + if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->conn); + break; + } + return true; + } +} \ No newline at end of file diff --git a/src/ZM/Event/Swoole/WorkerStartEvent.php b/src/ZM/Event/Swoole/WorkerStartEvent.php new file mode 100644 index 00000000..01736459 --- /dev/null +++ b/src/ZM/Event/Swoole/WorkerStartEvent.php @@ -0,0 +1,137 @@ +server = $server; + $this->worker_id = $worker_id; + } + + /** + * @return WorkerStartEvent + * @throws AnnotationException + * @throws ReflectionException + */ + public function onActivate(): WorkerStartEvent { + Console::info("Worker启动中"); + ZMBuf::resetCache(); //清空变量缓存 + ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 + DBCacheManager::freeAllCache(); // 清空数据库缓存 + $this->resetConnections();//释放所有与framework的连接 + + //设置炸毛buf中储存的对象 + ZMBuf::$globals = new GlobalConfig(); + if (ZMBuf::globals("sql_config")["sql_host"] != "") { + Console::info("新建SQL连接池中"); + ZMBuf::$sql_pool = new SQLPool(); + DB::initTableList(); + } + ZMBuf::$server = $this->server; + ZMBuf::$atomics['reload_time']->add(1); + ZMBuf::$req_mapping_node = new MappingNode("/"); + + Console::info("监听console输入"); + Console::listenConsole(); //这个方法只能在这里调用,且如果worker_num不为1的话,此功能不可用 + + $this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等 + + $this->setAutosaveTimer(ZMBuf::globals("auto_save_interval")); + + return $this; + } + + public function onAfter(): WorkerStartEvent { + foreach (ZMBuf::get("wait_start") as $v) { + Coroutine::resume($v); + } + ZMBuf::unsetCache("wait_start"); + foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { + /** @var AnnotationBase $v */ + if (strtolower($v->type) == "workerstart") { + $class_name = $v->class; + /** @var ModBase $class */ + $class = new $class_name(["server" => $this->server, "worker_id" => $this->worker_id], ModHandleType::SWOOLE_WORKER_START); + call_user_func_array([$class, $v->method], []); + if ($class->block_continue) break; + } + } + return $this; + } + + private function resetConnections() { + foreach ($this->server->connections as $v) { + $this->server->close($v); + } + if (ZMBuf::$sql_pool instanceof SqlPool) { + ZMBuf::$sql_pool->destruct(); + ZMBuf::$sql_pool = null; + } + } + + /** + * @throws AnnotationException + * @throws ReflectionException + */ + private function loadAllClass() { + //加载phar包 + Console::info("加载外部phar包中"); + $dir = WORKING_DIR . "/resources/package/"; + if (is_dir($dir)) { + $list = scandir($dir); + unset($list[0], $list[1]); + foreach ($list as $v) { + if (is_dir($dir . $v)) continue; + if (pathinfo($dir . $v, 4) == "phar") require_once($dir . $v); + } + } + + //加载composer类 + Console::info("加载composer资源中"); + /** @noinspection PhpIncludeInspection */ + require_once WORKING_DIR . "/vendor/autoload.php"; + + //加载各个模块的注解类,以及反射 + Console::info("检索Module中"); + AnnotationParser::registerMods(); + + //加载Custom目录下的自定义的内部类 + ConnectionManager::registerCustomClass(); + } + + private function setAutosaveTimer($globals) { + DataProvider::$buffer_list = []; + Timer::tick($globals * 1000, function() { + DataProvider::saveBuffer(); + }); + } +} \ No newline at end of file diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php new file mode 100644 index 00000000..f5575ce2 --- /dev/null +++ b/src/ZM/Exception/DbException.php @@ -0,0 +1,12 @@ +module = $module; + } +} \ No newline at end of file diff --git a/src/ZM/Http/Response.php b/src/ZM/Http/Response.php new file mode 100644 index 00000000..7eae9d97 --- /dev/null +++ b/src/ZM/Http/Response.php @@ -0,0 +1,229 @@ +response = $response; + $this->fd = $response->fd; + $this->socket = $response->socket; + $this->header = $response->header; + $this->cookie = $response->cookie; + if (isset($response->trailer)) + $this->trailer = $response->trailer; + } + + /** + * @return mixed + */ + public function initHeader() { + return $this->response->initHeader(); + } + + /** + * @param $name + * @param $value + * @param $expires + * @param $path + * @param $domain + * @param $secure + * @param $httponly + * @param $samesite + * @return mixed + */ + public function cookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) { + return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite); + } + + /** + * @param $name + * @param $value + * @param $expires + * @param $path + * @param $domain + * @param $secure + * @param $httponly + * @param $samesite + * @return mixed + */ + public function setCookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) { + return $this->response->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite); + } + + /** + * @param $name + * @param $value + * @param $expires + * @param $path + * @param $domain + * @param $secure + * @param $httponly + * @param $samesite + * @return mixed + */ + public function rawcookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) { + return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite); + } + + /** + * @param $http_code + * @param $reason + * @return mixed + */ + public function status($http_code, $reason = null) { + return $this->response->status($http_code, $reason); + } + + /** + * @param $http_code + * @param $reason + * @return mixed + */ + public function setStatusCode($http_code, $reason = null) { + return $this->response->setStatusCode($http_code, $reason); + } + + /** + * @param $key + * @param $value + * @param $ucwords + * @return mixed + */ + public function header($key, $value, $ucwords = null) { + return $this->response->header($key, $value, $ucwords); + } + + /** + * @param $key + * @param $value + * @param $ucwords + * @return mixed + */ + public function setHeader($key, $value, $ucwords = null) { + return $this->response->setHeader($key, $value, $ucwords); + } + + /** + * @param $key + * @param $value + * @return mixed + */ + public function trailer($key, $value) { + return $this->response->trailer($key, $value); + } + + /** + * @return mixed + */ + public function ping() { + return $this->response->ping(); + } + + /** + * @param $content + * @return mixed + */ + public function write($content) { + return $this->response->write($content); + } + + /** + * @param $content + * @return mixed + */ + public function end($content = null) { + $this->is_end = true; + return $this->response->end($content); + } + + public function isEnd() { return $this->is_end; } + + /** + * @param $filename + * @param $offset + * @param $length + * @return mixed + */ + public function sendfile($filename, $offset = null, $length = null) { + return $this->response->sendfile($filename, $offset, $length); + } + + /** + * @param $location + * @param $http_code + * @return mixed + */ + public function redirect($location, $http_code = null) { + return $this->redirect($location, $http_code); + } + + /** + * @return mixed + */ + public function detach() { + return $this->response->detach(); + } + + /** + * @param $fd + * @return mixed + */ + public static function create($fd) { + return \Swoole\Http\Response::create($fd); + } + + /** + * @return mixed + */ + public function upgrade() { + return $this->response->upgrade(); + } + + /** + * @param $data + * @param null $opcode + * @param null $flags + * @return mixed + */ + public function push($data, $opcode = null, $flags = null) { + return $this->response->push($data, $opcode, $flags); + } + + /** + * @return mixed + */ + public function recv() { + return $this->response->recv(); + } + + /** + * @return mixed + */ + public function close() { + return $this->response->close(); + } + + public function __destruct() { + } + + +} \ No newline at end of file diff --git a/src/ZM/ModBase.php b/src/ZM/ModBase.php new file mode 100644 index 00000000..e14649f6 --- /dev/null +++ b/src/ZM/ModBase.php @@ -0,0 +1,165 @@ +server = $param0["server"]; + if (isset($param0["frame"])) $this->frame = $param0["frame"]; + if (isset($param0["data"])) $this->data = $param0["data"]; + if (isset($param0["request"])) $this->request = $param0["request"]; + if (isset($param0["response"])) $this->response = $param0["response"]; + if (isset($param0["fd"])) $this->fd = $param0["fd"]; + if (isset($param0["worker_id"])) $this->worker_id = $param0["worker_id"]; + if (isset($param0["connection"])) $this->connection = $param0["connection"]; + $this->handle_type = $handle_type; + } + + /** + * only can used by cq->message event function + * @param $msg + * @param bool $yield + * @return mixed + */ + public function reply($msg, $yield = false) { + switch ($this->data["message_type"]) { + case "group": + case "private": + case "discuss": + return CQAPI::quick_reply($this->connection, $this->data, $msg, $yield); + } + return false; + } + + public function finalReply($msg, $yield = false) { + $this->block_continue = true; + if ($msg == "") return true; + return $this->reply($msg, $yield); + } + + /** + * @param string $prompt + * @param int $timeout + * @param string $timeout_prompt + * @return string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") { + if ($prompt != "") $this->reply($prompt); + if (!isset($this->data["user_id"], $this->data["message"], $this->data["self_id"])) + throw new InvalidArgumentException("协程等待参数缺失"); + $cid = Co::getuid(); + $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); + ZMBuf::$atomics["wait_msg_id"]->add(1); + $hang = [ + "coroutine" => $cid, + "user_id" => $this->data["user_id"], + "message" => $this->data["message"], + "self_id" => $this->data["self_id"], + "message_type" => $this->data["message_type"], + "result" => null + ]; + if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { + $hang[$hang["message_type"] . "_id"] = $this->data[$this->data["message_type"] . "_id"]; + } + ZMBuf::appendKey("wait_api", $api_id, $hang); + $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { + $r = ZMBuf::get("wait_api")[$api_id] ?? null; + if ($r !== null) { + Co::resume($r["coroutine"]); + } + }); + + Co::suspend(); + $sess = ZMBuf::get("wait_api")[$api_id]; + ZMBuf::unsetByValue("wait_api", $api_id); + $result = $sess["result"]; + if (isset($id)) swoole_timer_clear($id); + if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); + return $result; + } + + /** + * @param $arg + * @param $mode + * @param $prompt_msg + * @return mixed|string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function getArgs(&$arg, $mode, $prompt_msg) { + switch ($mode) { + case ZM_MATCH_ALL: + $p = $arg; + array_shift($p); + return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p)); + case ZM_MATCH_NUMBER: + foreach ($arg as $k => $v) { + if (is_numeric($v)) { + array_splice($arg, $k, 1); + return $v; + } + } + return $this->waitMessage($prompt_msg); + case ZM_MATCH_FIRST: + if (isset($arg[1])) { + $a = $arg[1]; + array_splice($arg, 1, 1); + return $a; + } else { + return $this->waitMessage($prompt_msg); + } + } + throw new InvalidArgumentException(); + } + + public function getMessage() { return $this->data["message"] ?? null; } + + public function getUserId() { return $this->data["user_id"] ?? null; } + + public function getGroupId() { return $this->data["group_id"] ?? null; } + + public function getMessageType() { return $this->data["message_type"] ?? null; } + + public function getRobotId() { return $this->data["self_id"]; } + + public function getConnection() { return $this->connection; } + + public function setBlock($result = true) { $this->block_continue = $result; } +} \ No newline at end of file diff --git a/src/ZM/ModHandleType.php b/src/ZM/ModHandleType.php new file mode 100644 index 00000000..1082dfc7 --- /dev/null +++ b/src/ZM/ModHandleType.php @@ -0,0 +1,20 @@ + $v) { + self::setJsonData($v, ZMBuf::get($k)); + } + echo Console::setColor("saved", "lightblue").PHP_EOL; + } + + private static function getJsonData(string $string) { + if(!file_exists(self::getDataFolder().$string)) return []; + return json_decode(Co::readFile(self::getDataFolder().$string), true); + } + + private static function setJsonData($filename, array $args) { + Co::writeFile(self::getDataFolder() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING)); + } + + private static function getDataFolder() { + return CONFIG_DIR; + } +} \ No newline at end of file diff --git a/src/ZM/Utils/SQLPool.php b/src/ZM/Utils/SQLPool.php new file mode 100644 index 00000000..db5805fa --- /dev/null +++ b/src/ZM/Utils/SQLPool.php @@ -0,0 +1,101 @@ +pool = new SplQueue; + $this->info = [ + "host" => ZMBuf::globals("sql_config")["sql_host"], + "port" => ZMBuf::globals("sql_config")["sql_port"], + "user" => ZMBuf::globals("sql_config")["sql_username"], + "password" => ZMBuf::globals("sql_config")["sql_password"], + "database" => ZMBuf::globals("sql_config")["sql_database"] + ]; + } + + /** + * 将利用过的连接入队 + * @param $mysql + */ + public function put($mysql) { + $this->pool->push($mysql); + if (($a = array_shift($this->co_list)) !== null) { + Coroutine::resume($a); + } + } + + /** + * 获取队中的连接,如果不存在则创建新的 + * @param bool $no_new_conn + * @return bool|mixed|Mysql + */ + public function get($no_new_conn = false) { + if (count($this->pool) == 0 && $this->connect_cnt <= 70) { + if($no_new_conn) return false; + $this->connect_cnt += 1; + $r = $this->newConnect(); + if ($r !== false) { + return $r; + } else { + $this->connect_cnt -= 1; + return false; + } + } elseif (count($this->pool) > 0) { + $con = $this->pool->pop(); + if ($con->connected !== false) return $con; + } elseif ($this->connect_cnt > 70) { + $this->co_list[]=Coroutine::getuid(); + Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 ".Coroutine::stats()["coroutine_num"]); + Coroutine::suspend(); + return $this->get($no_new_conn); + } + return false; + } + + public function getCount() { + return $this->pool->count(); + } + + public function destruct() { + // 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出 + $this->available = false; + while (!$this->pool->isEmpty()) { + $this->pool->pop(); + } + } + + private function newConnect() { + //无空闲连接,创建新连接 + $mysql = new Mysql(); + + Console::info("创建SQL连接中,当前有" . $this->connect_cnt . "个连接"); + $res = $mysql->connect($this->info); + if ($res == false) { + echo $mysql->error . PHP_EOL; + return false; + } else { + return $mysql; + } + } +} \ No newline at end of file diff --git a/src/ZM/Utils/ScheduleManager.php b/src/ZM/Utils/ScheduleManager.php new file mode 100644 index 00000000..e87e15ce --- /dev/null +++ b/src/ZM/Utils/ScheduleManager.php @@ -0,0 +1,10 @@ +connections as $v) { + ZMBuf::$server->close($v); + } + DataProvider::saveBuffer(); + ZMBuf::$server->shutdown(); + ZMBuf::$server->stop(); + } + + public static function getHttpCodePage(int $http_code) { + if(isset(ZMBuf::globals("http_default_code_page")[$http_code])) { + return Co::readFile(DataProvider::getResourceFolder()."html/".ZMBuf::globals("http_default_code_page")[$http_code]); + } else return null; + } + + public static function reload() { + Console::info(Console::setColor("Reloading server...", "gold")); + foreach (ZMBuf::$server->connections as $v) { + ZMBuf::$server->close($v); + } + DataProvider::saveBuffer(); + ZMBuf::$server->reload(); + } + + /** + * 解析CQ码 + * @param $msg + * @return array|null + * 0123456 + * [CQ:at] + */ + static function getCQ($msg) { + if (($start = mb_strpos($msg, '[')) === false) return null; + if (($end = mb_strpos($msg, ']')) === false) return null; + $msg = mb_substr($msg, $start + 1, $end - $start - 1); + if (mb_substr($msg, 0, 3) != "CQ:") return null; + $msg = mb_substr($msg, 3); + $msg2 = explode(",", $msg); + $type = array_shift($msg2); + $array = []; + foreach ($msg2 as $k => $v) { + $ss = explode("=", $v); + $sk = array_shift($ss); + $array[$sk] = implode("=", $ss); + } + return ["type" => $type, "params" => $array, "start" => $start, "end" => $end]; + } +} \ No newline at end of file diff --git a/src/cqbot/CQBot.php b/src/cqbot/CQBot.php deleted file mode 100755 index fa2eaa7c..00000000 --- a/src/cqbot/CQBot.php +++ /dev/null @@ -1,142 +0,0 @@ -circle = $circle; - $this->starttime = microtime(true); - $this->framework = $framework; - $this->data = $package; - $this->self_id = $this->data["self_id"]; - } - - public function execute() { - if ($this->circle >= 5) return false; - if ($this->data === null) return false; - if (isset($it["user_id"]) && CQUtil::isRobot($this->data["user_id"])) return false; - if (isset($it["group_id"]) && $this->data["group_id"] == Cache::get("admin_group")) { - if ($this->getRobotId() != Cache::get("admin_active")) { - return false; - } - } - if ($this->data["message"] == "") - return false; - foreach (Cache::get("mods") as $v) { - /** @var ModBase $r */ - $r = new $v($this, $this->data); - if ($r->split_execute === true) { - $msg = trim($this->data["message"]); - $msg = explodeMsg($msg); - $r->execute($msg); - } - } - $this->endtime = microtime(true); - return $this->function_called; - } - - /** - * 快速回复消息 - * @param $msg - * @param callable|null $callback - * @param bool $async - * @return bool - */ - public function reply($msg, callable $callback = null, $async = false) { - $this->function_called = true; - switch ($this->data["message_type"]) { - case "group": - $this->function_called = true; - if (!$async) return CQAPI::send_group_msg($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback); - else return CQAPI::send_group_msg_async($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback); - case "private": - $this->function_called = true; - if (!$async) return CQAPI::send_private_msg($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback); - else return CQAPI::send_private_msg_async($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback); - case "discuss": - $this->function_called = true; - if (!$async) return CQAPI::send_discuss_msg($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback); - else return CQAPI::send_discuss_msg_async($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback); - case "wechat": - //TODO: add wechat account support in the future - break; - } - return false; - } - - public function isAdmin($user) { - if (in_array($user, Cache::get("admin"))) return true; - else return false; - } - - public function replace($msg, $dat) { - $msg = str_replace("{at}", '[CQ:at,qq=' . $dat["user_id"] . ']', $msg); - $msg = str_replace("{and}", '&', $msg); - while (strpos($msg, '{') !== false && strpos($msg, '}') !== false) { - if (strpos($msg, '{') > strpos($msg, '}')) return $msg; - $start = strpos($msg, '{'); - $end = strpos($msg, '}'); - $sub = explode("=", substr($msg, $start + 1, $end - $start - 1)); - switch ($sub[0]) { - case "at": - $qq = $sub[1]; - $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:at,qq=' . $qq . ']', $msg); - break; - case "image": - case "record": - $pictFile = $sub[1]; - $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:' . $sub[0] . ',file=' . $pictFile . ']', $msg); - break; - case "dice": - $file = $sub[1]; - $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:dice,type=' . $file . ']', $msg); - break; - case "shake": - $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:shake]', $msg); - break; - case "music": - $id = $sub[1]; - $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:music,type=163,id=' . $id . ']', $msg); - break; - case "internet": - array_shift($sub); - $id = implode("=", $sub); - if (substr($id, 0, 7) != "http://") $id = "http://" . $id; - $is = file_get_contents($id, false, NULL, 0, 1024); - if ($is == false) $is = "[请求时发生了错误] 如有疑问,请联系管理员"; - $msg = str_replace(substr($msg, $start, $end - $start + 1), $is, $msg); - break 2; - default: - break 2; - } - } - return $msg; - } - - /** - * 返回当前机器人的id - * @return string|null - */ - public function getRobotId() { - return $this->data["self_id"] ?? null; - } -} \ No newline at end of file diff --git a/src/cqbot/api/CQAPI.php b/src/cqbot/api/CQAPI.php deleted file mode 100644 index 00155a3d..00000000 --- a/src/cqbot/api/CQAPI.php +++ /dev/null @@ -1,232 +0,0 @@ - $msg, "group_id" => Cache::get("admin_group")]); - } - return false; - } - - public static function __callStatic($name, $arg) { - if(mb_substr($name, -6) == "_after"){ - $all = self::getSupportedAPIs(); - $find = null; - $true_name = mb_substr($name, 0, -6); - if(!in_array($true_name, $all)){ - Console::error("Unknown API " . $name); - return false; - } - $ms = array_shift($arg); - Scheduler::after($ms, function() use ($true_name, $arg){ - $reply = ["action" => $true_name]; - if (!is_array($arg[1])) { - Console::error("Error when parsing params. Please make sure your params is an array."); - return false; - } - if ($arg[1] != []) { - $reply["params"] = $arg[1]; - } - return self::processAPI($arg[0], $reply, $arg[2]); - }); - return true; - } - $all = self::getSupportedAPIs(); - $find = null; - if (in_array($name, $all)) $find = $name; - else { - foreach ($all as $v) { - if (strtolower($name) == strtolower(str_replace("_", "", $v))) { - $find = $v; - break; - } - } - } - if ($find === null) { - Console::error("Unknown API " . $name); - return false; - } - $reply = ["action" => $find]; - if (!is_array($arg[1])) { - Console::error("Error when parsing params. Please make sure your params is an array."); - return false; - } - if ($arg[1] != []) { - $reply["params"] = $arg[1]; - } - return self::processAPI($arg[0], $reply, $arg[2]); - } - - /********************** non-API Part **********************/ - - private static function getSupportedAPIs() { - return [ - "send_private_msg", - "send_group_msg", - "send_discuss_msg", - "send_msg", - "delete_msg", - "send_like", - "set_group_kick", - "set_group_ban", - "set_group_anonymous_ban", - "set_group_whole_ban", - "set_group_admin", - "set_group_anonymous", - "set_group_card", - "set_group_leave", - "set_group_special_title", - "set_discuss_leave", - "set_friend_add_request", - "set_group_add_request", - "get_login_info", - "get_stranger_info", - "get_group_list", - "get_group_member_info", - "get_group_member_list", - "get_cookies", - "get_csrf_token", - "get_credentials", - "get_record", - "get_status", - "get_version_info", - "set_restart", - "set_restart_plugin", - "clean_data_dir", - "clean_plugin_log", - "_get_friend_list", - "_get_group_info", - "_get_vip_info", - //异步API - "send_private_msg_async", - "send_group_msg_async", - "send_discuss_msg_async", - "send_msg_async", - "delete_msg_async", - "set_group_kick_async", - "set_group_ban_async", - "set_group_anonymous_ban_async", - "set_group_whole_ban_async", - "set_group_admin_async", - "set_group_anonymous_async", - "set_group_card_async", - "set_group_leave_async", - "set_group_special_title_async", - "set_discuss_leave_async", - "set_friend_add_request_async", - "set_group_add_request_async" - ]; - } - - public static function getLoggedAPIs() { - return [ - "send_private_msg", - "send_group_msg", - "send_discuss_msg", - "send_msg", - "send_private_msg_async", - "send_group_msg_async", - "send_discuss_msg_async", - "send_msg_async" - ]; - } - - /** - * @param $self_id - * @param $reply - * @param callable|null $function - * @return bool - */ - private static function processAPI($self_id, $reply, callable $function = null) { - $api_id = Cache::$api_id->get(); - $reply["echo"] = $api_id; - Cache::$api_id->add(1); - if ($self_id instanceof RobotWSConnection) { - $connection = $self_id; - $self_id = $connection->getQQ(); - } else $connection = ConnectionManager::getRobotConnection($self_id); - if ($connection instanceof NullConnection) { - Console::error("未找到qq号:" . $self_id . "的API连接"); - return false; - } - if ($connection->push(json_encode($reply))) { - Cache::appendKey("sent_api", $api_id, [ - "data" => $reply, - "time" => microtime(true), - "func" => $function, - "self_id" => $self_id - ]); - if (in_array($reply["action"], self::getLoggedAPIs())) { - Console::msg($reply); - Cache::$out_count->add(1); - } - return true; - } else return false; - } -} \ No newline at end of file diff --git a/src/cqbot/connection/CustomWSConnection.php b/src/cqbot/connection/CustomWSConnection.php deleted file mode 100644 index 954fcb81..00000000 --- a/src/cqbot/connection/CustomWSConnection.php +++ /dev/null @@ -1,16 +0,0 @@ -server["remote_addr"]); - $this->create_success = true; - // Here to put your custom other websocket connection to manage. - } -} \ No newline at end of file diff --git a/src/cqbot/connection/NullConnection.php b/src/cqbot/connection/NullConnection.php deleted file mode 100644 index 59be835e..00000000 --- a/src/cqbot/connection/NullConnection.php +++ /dev/null @@ -1,37 +0,0 @@ -qq = $qq; - } - - public function push($data, $push_error_record = true) { - $data = unicodeDecode($data); - CQUtil::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", false); - if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true)); - return false; - } - - public function sendAPI($api, $params = [], $echo = []){ - $data["action"] = $api; - if($params != []) $data["params"] = $params; - $echo_result["self_id"] = $this->qq; - if($echo != []){ - if(count($echo) >= 1) $echo_result['type'] = array_shift($echo); - if(!empty($echo)) $echo_result["params"] = $echo; - } - $data["echo"] = $echo_result; - Console::debug("将要发送的API包:" . json_encode($data, 128 | 256)); - return $this->push(json_encode($data)); - } -} \ No newline at end of file diff --git a/src/cqbot/connection/RobotWSConnection.php b/src/cqbot/connection/RobotWSConnection.php deleted file mode 100644 index 36ed514c..00000000 --- a/src/cqbot/connection/RobotWSConnection.php +++ /dev/null @@ -1,133 +0,0 @@ - "小马哥", - "838714432" => "鲸鱼的test机器人" - ]; - - private $qq; - private $alias;//别名 - private $sub_type; - - public function __construct(swoole_websocket_server $server, $fd, $qq, swoole_http_request $request, $sub_type) { - parent::__construct($server, $fd, $request->server["remote_addr"]); - $this->qq = $qq; - $this->alias = self::ALIAS_LIST[$qq] ?? "机器人" . $qq; - $this->sub_type = $sub_type; - foreach (ConnectionManager::getAll("robot") as $k => $v) { - if ($v->getQQ() == $this->getQQ() && $k != $this->fd && $v->getSubType() == $this->getSubType()) { - $v->close(); - } - } - if ($sub_type != "event") { - $obj = $this; - $r = $this->sendAPI("get_version_info", [], function ($response) use ($obj) { - Cache::set("version_info", $response["data"]); - }); - if ($r) - $this->create_success = $this->sendAPI("send_group_msg", ["message" => "[CQBot] 机器人 " . $this->getAlias() . " 已连接,链接fd:" . $this->fd, "group_id" => Cache::get("admin_group")]); - } else $this->create_success = true; - } - - /** - * 返回连接的QQ - * @return mixed - */ - public function getQQ() { - return $this->qq; - } - - /** - * @return mixed|string - */ - public function getAlias() { - return $this->alias; - } - - public function sendAPI($api, $params = [], callable $callback = null) { - $data["action"] = $api; - if ($params != []) $data["params"] = $params; - - if ($this->sub_type == "event") { - $conns = ConnectionManager::getAll("robot"); - foreach ($conns as $k => $v) { - if ($v->getSubType() == "api" && $v->getQQ() == $this->getQQ()) { - $api_id = Cache::$api_id->get(); - $data["echo"] = $api_id; - Cache::$api_id->add(1); - Cache::appendKey("sent_api", $api_id, [ - "data" => $data, - "time" => microtime(true), - "func" => $callback, - "self_id" => $this->getQQ() - ]); - if ($v->push(json_encode($data))) { - if (in_array($data["action"], CQAPI::getLoggedAPIs())) { - Console::msg($data); - Cache::$out_count->add(1); - } - return true; - } else { - $response = [ - "status" => "failed", - "retcode" => 998, - "data" => null, - "self_id" => $this->getQQ() - ]; - $s = Cache::get("sent_api")[$data["echo"]]; - StatusParser::parse($response, $data); - if ($s["func"] !== null) - call_user_func($s["func"], $response, $data); - Cache::removeKey("sent_api", $data["echo"]); - return false; - } - } - } - return false; - } - $api_id = Cache::$api_id->get(); - $data["echo"] = $api_id; - Cache::$api_id->add(1); - Cache::appendKey("sent_api", $api_id, [ - "data" => $data, - "time" => microtime(true), - "func" => $callback, - "self_id" => $this->getQQ() - ]); - if ($this->push(json_encode($data))) { - if (in_array($data["action"], CQAPI::getLoggedAPIs())) { - Console::msg($data); - Cache::$out_count->add(1); - } - return true; - } else { - $response = [ - "status" => "failed", - "retcode" => 999, - "data" => null, - "self_id" => $this->getQQ() - ]; - $s = Cache::get("sent_api")[$data["echo"]]; - StatusParser::parse($response, $data); - if ($s["func"] !== null) - call_user_func($s["func"], $response, $data); - Cache::removeKey("sent_api", $data["echo"]); - return false; - } - } - - /** - * @return mixed - */ - public function getSubType() { - return $this->sub_type; - } -} diff --git a/src/cqbot/connection/WSConnection.php b/src/cqbot/connection/WSConnection.php deleted file mode 100644 index 95e76a25..00000000 --- a/src/cqbot/connection/WSConnection.php +++ /dev/null @@ -1,64 +0,0 @@ -server = $server; - $this->fd = $fd; - $this->remote_address = $remote; - } - - /** - * 返回swoole server - * @return swoole_websocket_server - */ - public function getServer() { - return $this->server; - } - - public function getType(){ - if($this instanceof RobotWSConnection) return "robot"; - elseif($this instanceof CustomWSConnection) return "custom"; - else return "unknown"; - } - - public function push($data, $push_error_record = true) { - if ($data === null || $data == "") { - Console::error("Empty data pushed."); - return false; - } - if ($this->server->push($this->fd, $data) === false) { - $data = unicodeDecode($data); - CQUtil::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", false); - if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true)); - - return false; - } - return true; - } - - public function close() { - $this->server->close($this->fd); - ConnectionManager::remove($this->fd); - } - - /** - * @return mixed - */ - public function getRemoteAddress() { - return $this->remote_address; - } -} \ No newline at end of file diff --git a/src/cqbot/event/Event.php b/src/cqbot/event/Event.php deleted file mode 100644 index 05063d22..00000000 --- a/src/cqbot/event/Event.php +++ /dev/null @@ -1,17 +0,0 @@ -get(); - Cache::$in_count->add(1); - if (Cache::$data["info_level"] >= 1) { - $num = CQUtil::getRobotAlias($req["self_id"]); - $type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG" . $num . ":" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" . $num : "DISCUSS_MSG" . $num . ":" . $req["discuss_id"])) : strtoupper($req["post_type"]); - Console::put(Console::setColor(date("H:i:s"), "green") . Console::setColor(" [$in_count]" . $type, "lightlightblue") . Console::setColor(" " . $req["user_id"], "yellow") . Console::setColor(" > ", "gray") . $req["message"]); - } - - CQUtil::updateMsg();//更新消息速度 - - //传入消息处理的逻辑 - $c = new CQBot($this->getFramework(), 0, $req); - $c->execute(); - $value = $c->endtime - $c->starttime; - Console::debug("Using time: " . $value); - - } -} \ No newline at end of file diff --git a/src/cqbot/event/post/MetaEvent.php b/src/cqbot/event/post/MetaEvent.php deleted file mode 100644 index 05e8fe69..00000000 --- a/src/cqbot/event/post/MetaEvent.php +++ /dev/null @@ -1,29 +0,0 @@ - $v){ - if(in_array("onNotice", get_class_methods($v))){ - /** @var ModBase $v */ - $v::onNotice($req); - } - } - } -} \ No newline at end of file diff --git a/src/cqbot/event/post/RequestEvent.php b/src/cqbot/event/post/RequestEvent.php deleted file mode 100644 index 6da270c1..00000000 --- a/src/cqbot/event/post/RequestEvent.php +++ /dev/null @@ -1,19 +0,0 @@ - $v){ - if(in_array("onRequest", get_class_methods($v))){ - /** @var ModBase $v */ - $v::onRequest($req); - } - } - } -} \ No newline at end of file diff --git a/src/cqbot/event/post/UnknownEvent.php b/src/cqbot/event/post/UnknownEvent.php deleted file mode 100644 index d7b29966..00000000 --- a/src/cqbot/event/post/UnknownEvent.php +++ /dev/null @@ -1,14 +0,0 @@ -end("Hello world!"); - //此为HTTP请求的回复,更多设置回复头、传送文件、POST、GET请求解析等内容请查阅文档https://wiki.swoole.com - } - - /** - * 此函数为一个示例的检测有效的函数,为此预留 - * 作用是判断传入的请求数据是合法的 - * @param $param - * @return bool - */ - public function isValidParam($param) { - if ($param === null) return false; - if (!isset($param["event"])) return false; - if (!isset($param["timestamp"])) return false; - if (!isset($param[$param["event"]])) return false; - if(($param["timestamp"] > (time() + 10)) || ($param["timestamp"] < (time() - 10))) return false; - return true; - } -} \ No newline at end of file diff --git a/src/cqbot/event/server/ServerEvent.php b/src/cqbot/event/server/ServerEvent.php deleted file mode 100644 index 4b7b4d63..00000000 --- a/src/cqbot/event/server/ServerEvent.php +++ /dev/null @@ -1,16 +0,0 @@ -server = $server; - } -} \ No newline at end of file diff --git a/src/cqbot/event/server/WSCloseEvent.php b/src/cqbot/event/server/WSCloseEvent.php deleted file mode 100644 index 744dd770..00000000 --- a/src/cqbot/event/server/WSCloseEvent.php +++ /dev/null @@ -1,21 +0,0 @@ -fd; - $req = json_decode($frame->data, true); - $conn = ConnectionManager::get($fd); - if ($conn === null) { - Console::info("收到一个未知链接发来的消息。" . $fd); - return; - } - switch ($conn->getType()) { - case "robot": - //处理酷Q HTTP事件接口消息(Event) - if (isset($req["post_type"])) { - switch ($req["post_type"]) { - case "message": - new MessageEvent($req); - break 2; - case "notice": - new NoticeEvent($req); - break 2; - case "request": - new RequestEvent($req); - break 2; - case "meta_event": - new MetaEvent($req); - break 2; - default: - new UnknownEvent($req); - break 2; - } - } else { - Console::debug("收到来自API[" . $fd . "]连接的回复:" . json_encode($req, 128 | 256)); - if (isset($req["echo"]) && Cache::array_key_exists("sent_api", $req["echo"])) { - $status = $req["status"]; - $retcode = $req["retcode"]; - $data = $req["data"]; - $origin = Cache::get("sent_api")[$req["echo"]]; - $self_id = $origin["self_id"]; - $response = [ - "status" => $status, - "retcode" => $retcode, - "data" => $data, - "self_id" => $self_id - ]; - StatusParser::parse($response, $origin); - if ($origin["func"] !== null) - call_user_func($origin["func"], $response, $origin, $conn); - Cache::removeKey("sent_api", $req["echo"]); - } - } - break; - default: - Console::info("收到未知链接的消息, 来自: " . $fd); - break; - } - } -} \ No newline at end of file diff --git a/src/cqbot/event/server/WSOpenEvent.php b/src/cqbot/event/server/WSOpenEvent.php deleted file mode 100644 index 2946673f..00000000 --- a/src/cqbot/event/server/WSOpenEvent.php +++ /dev/null @@ -1,56 +0,0 @@ -fd; - $get = $request->get; - $header = $request->header; - //echo json_encode($header, 128|256); - $connect_type = $get["type"] ?? (isset($header["x-client-role"]) ? strtolower($header["x-client-role"]) : ""); - $access_token = $get["token"] ?? (isset($header["authorization"]) ? explode(" ", $header["authorization"])[1] : ""); - //Console::info("链接类型:".$connect_type."\n链接token:".$access_token); - if ($connect_type == "") { - Console::info("未指定连接类型,关闭连接."); - $server->close($fd); - return; - } - //if (isset($request->header["authorization"])) { - //$tokens = explode(" ", $request->header["authorization"]); - //$tokens = trim($tokens[1]); - if ($access_token !== Cache::get("access_token") && Cache::get("access_token") != "") { - Console::info("监测到WS连接,但是token不对,无法匹配。"); - $server->close($fd); - return; - } - switch ($connect_type) { - case "event": - case "api": - case "universal": - $self_id = $get["qq"] ?? ($header["x-self-id"] ?? ""); - Console::info("收到 " . $connect_type . " 连接,来自机器人:" . $self_id . ",fd:" . $fd); - $conn = new RobotWSConnection($server, $fd, $self_id, $request, $connect_type); - if($conn->create_success) ConnectionManager::set($fd, $conn); - else { - Console::error("初始化WS连接失败!fd:".$fd.",QQ:".$self_id); - $server->close($fd); - return; - } - break; - case "custom": - $conn = new CustomWSConnection($server, $fd, $request); - if($conn->create_success) ConnectionManager::set($fd, $conn); - break; - default: - Console::info("Unknown WS Connection connected. I will close it."); - $server->close($fd); - } - } -} \ No newline at end of file diff --git a/src/cqbot/event/server/WorkerStartEvent.php b/src/cqbot/event/server/WorkerStartEvent.php deleted file mode 100644 index 0e17b3a2..00000000 --- a/src/cqbot/event/server/WorkerStartEvent.php +++ /dev/null @@ -1,30 +0,0 @@ -add(1); - CQUtil::loadAllFiles(); - $set = settings(); - foreach (get_included_files() as $file) - Console::debug("Loaded " . $file); - //计时器(ms) - if ($set["swoole_use_tick"] === true) { - Cache::$scheduler = new Scheduler($this->getFramework(), time()); - //$timer_id = $server->tick($set["swoole_tick_interval"], [Cache::$scheduler, "tick"]); - Console::info("已在worker #" . $worker_id . " 中成功启动了计时器!计时器间隔:" . $set["swoole_tick_interval"]); - } - Console::debug("master_pid = " . $server->master_pid); - Console::debug("worker_id = " . $worker_id); - Console::put("===== Worker " . Console::setColor("#" . $worker_id, "gold") . " 已启动 ====="); - } -} \ No newline at end of file diff --git a/src/cqbot/item/User.php b/src/cqbot/item/User.php deleted file mode 100755 index 5b359f6c..00000000 --- a/src/cqbot/item/User.php +++ /dev/null @@ -1,136 +0,0 @@ -id = $qid; - if (strlen($qid) >= 15) $this->user_type = 1; - $this->permission = DP::getJsonData("permissions.json")[$qid] ?? 0; - $this->friend = []; - } - - /** - * 获取用户QQ号 - * @return mixed - */ - public function getId() { return $this->id; } - - /** - * 获取用户权限值 - * @return int - */ - public function getPermission() { return $this->permission; } - - /** - * @param int $permission - * @return User - */ - public function setPermission(int $permission) { - $this->permission = $permission; - return $this; - } - - public function getBuffer() { - return $this->buffer; - } - - public function setBuffer($buffer) { - $this->buffer = $buffer; - return $this; - } - - /** - * @return string - */ - public function getNickname() { - return $this->nickname; - } - - /** - * @return array - */ - public function getFriend() { - return $this->friend; - } - - /** - * @param array $friend - */ - public function setFriend(array $friend) { - $this->friend = $friend; - } - - /** - * @param string $nickname - */ - public function setNickname(string $nickname) { - $this->nickname = $nickname; - } - - /** - * @return array - */ - public function getLexicon() { - return $this->lexicon; - } - - /** - * @param array $lexicon - */ - public function setLexicon(array $lexicon) { - $this->lexicon = $lexicon; - } - - /** - * @return array - */ - public function getFunction() { - return $this->function; - } - - /** - * @param array $function - */ - public function setFunction(array $function) { - $this->function = $function; - } - - public function closeFunction($name) { - unset($this->function[$name]); - } - - /** - * @return int - */ - public function getUserType() { - return $this->user_type; - } - - public function toJson() { - $ls = []; - foreach ($this as $k => $v) { - $ls[$k] = $v; - } - return json_encode($ls, 128 | 256); - } -} \ No newline at end of file diff --git a/src/cqbot/mods/Admin.php b/src/cqbot/mods/Admin.php deleted file mode 100755 index 007cdae2..00000000 --- a/src/cqbot/mods/Admin.php +++ /dev/null @@ -1,71 +0,0 @@ -split_execute = true; - } - - public static function initValues() { - Cache::set("msg_speed", []);//消息速度列表(存的是时间戳) - Cache::set("admin_active", ""); - Cache::set("admin", settings()["admin"]); - } - - public static function onTick($tick) { - if ($tick % 900 == 0) CQUtil::saveAllFiles();//900秒储存一次数据 - if (settings()["save_user_data"]) { - if ($tick % 21600 == 0) { //21600秒刷新一次好友列表 - GroupManager::updateGroupList(); - FriendManager::updateFriendList(); - } - } - } - - public static function onRequest($req) { - switch ($req["request_type"]) { - case "friend": - //好友添加请求处理 - $comment = $req["comment"];//验证信息 - $flag = $req["flag"];//添加好友用的flag,用于处理好友 - $user_id = $req["user_id"]; - - //如果不需要将好友添加信息发到群里,请注释下面这行 - CQAPI::debug("有用户请求添加好友啦!\n用户QQ:" . $user_id . "\n验证信息:" . $comment . "\n添加flag:" . $flag, ""); - - //如果不需要要自动同意,请注释下面这几行 - $params = ["flag" => $flag, "approve" => true]; - CQAPI::set_friend_add_request($req["self_id"], $params, function ($response) use ($user_id) { - CQAPI::debug("已自动同意 " . $user_id, "", $response["self_id"]); - CQAPI::send_private_msg($user_id, ["message" => "你好,第一次见面请多关照!"]); - }); - break; - } - } - - public function execute($it) { - if (!$this->main->isAdmin($this->getUserId())) return false; - switch ($it[0]) { - case "reload": //管理员重载代码 - $this->reply("正在重新启动..."); - CQUtil::reload(); - return true; - case "stop": //管理员停止server - $this->reply("正在停止服务器..."); - CQUtil::stop(); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/cqbot/mods/Example.php b/src/cqbot/mods/Example.php deleted file mode 100644 index 5f714bc4..00000000 --- a/src/cqbot/mods/Example.php +++ /dev/null @@ -1,52 +0,0 @@ -split_execute = true; - * 默认不会执行execute函数 - */ -class Example extends ModBase -{ - public function __construct(CQBot $main, $data) { - parent::__construct($main, $data); - //$data为CQHTTP插件上报的消息事件数组 - //这里编写你的内容 - $this->split_execute = true; - } - - /** - * 分词函数,如果开启分词模式的话将调用此数组。 - * 如果将一句话使用空格、换行和Tab进行分割,用来处理多项参数的功能指令 - * 例如:"随机数 1 100" 将被分割成数组$it ["随机数","1","100"] - * @param $it - * @return bool - */ - public function execute($it) { - switch ($it[0]) { - case "ping": - $this->reply("pong"); - return true; - case "你好": - $this->reply("你好,我是CQBot!"); - return true; - case "随机数": - if (!isset($it[1]) || !isset($it[2])) { - $this->reply("用法: 随机数 开始整数 结束整数"); - return true; - } - $c1 = intval($it[1]); - $c2 = intval($it[2]); - if ($c1 > $c2) { - $this->reply("随机数范围错误!应该从小的一方到大的一方!例如:\n随机数 1 99"); - return true; - } - $this->reply("生成的随机数是 " . mt_rand($c1, $c2)); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/cqbot/mods/Help.php b/src/cqbot/mods/Help.php deleted file mode 100644 index 7af0d85e..00000000 --- a/src/cqbot/mods/Help.php +++ /dev/null @@ -1,35 +0,0 @@ -split_execute = true; - } - - public function execute($it) { - switch ($it[0]) { - case "帮助": - $msg = "「机器人帮助」"; - $msg .= "\n随机数:生成一个随机数"; - $this->reply($msg); - return true; - case "如何增加机器人功能": - $msg = "机器人功能是在框架中src/cqbot/mods/xxx.php文件中编写的。"; - $msg .= "\nCQBot采用关键词系统,你可以直接像现有源码一样添加case在switch里面,"; - $msg .= "\n也可以自己新建一个任意名称的Mod名称,例如Entertain.php,你可以在里面编写娱乐功能。"; - $msg .= "\n你可以直接复制框架中Example.php文件的内容进行编辑。"; - $msg .= "\n预先封装好的机器人函数均在CQUtil类中,只需直接使用静态方法调用即可!"; - $msg .= "\n更多示例功能会逐渐添加到框架中,记得更新哦~"; - $this->reply($msg); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/cqbot/mods/ModBase.php b/src/cqbot/mods/ModBase.php deleted file mode 100755 index aca924cd..00000000 --- a/src/cqbot/mods/ModBase.php +++ /dev/null @@ -1,43 +0,0 @@ -main = $main; - $this->data = $data; - } - - public function execute($it) { } - - public function getUser($data = null) { return CQUtil::getUser($data === null ? $this->data["user_id"] : $data["user_id"]); } - - public function getUserId($data = null) { return $data === null ? strval($this->data["user_id"]) : strval($data["user_id"]); } - - public function reply($msg, callable $callback = null) { return $this->main->reply($msg, $callback); } - - public function getMessageType() { return $this->data["message_type"]; } - - public function getRobotId() { return $this->data["self_id"]; } -} \ No newline at end of file diff --git a/src/cqbot/utils/CQUtil.php b/src/cqbot/utils/CQUtil.php deleted file mode 100755 index ba23e752..00000000 --- a/src/cqbot/utils/CQUtil.php +++ /dev/null @@ -1,375 +0,0 @@ - $v) { - $serial = serialize($v); - file_put_contents(DP::getUserFolder() . $k . ".dat", $serial); - } - } - - Console::put("Saved files."); - } - - /** - * 生成报错日志 - * @param $log - * @param string $head - * @param int $send_debug_message - */ - public static function errorLog($log, $head = "ERROR", $send_debug_message = 1) { - Console::error($log, ($head === "ERROR") ? null : "[" . $head . "] "); - $time = date("Y-m-d H:i:s"); - $msg = "[$head @ $time]: $log\n"; - file_put_contents(DP::getDataFolder() . "log_error.txt", $msg, FILE_APPEND); - if ($send_debug_message) CQAPI::debug($msg); - } - - static function findRobot() { - foreach (ConnectionManager::getAll("robot") as $v) { - return $v->getQQ(); - } - return null; - } - - /** - * 获取运行时间 - * @param $time - * @return array - */ - static function getRunTime($time) { - $time_len = time() - $time; - $run_time = []; - if (intval($time_len / 86400) > 0) { - $run_time[0] = intval($time_len / 86400); - $time_len = $time_len % 86400; - } else { - $run_time[0] = 0; - } - if (intval($time_len / 3600) > 0) { - $run_time[1] = intval($time_len / 3600); - $time_len = $time_len % 3600; - } else { - $run_time[1] = 0; - } - if (intval($time_len / 60) > 0) { - $run_time[2] = intval($time_len / 60); - $time_len = $time_len % 60; - } else { - $run_time[2] = 0; - } - $run_time[3] = $time_len; - return $run_time; - } - - /** - * 获取格式化的运行时间 - * @param $time - * @return string - */ - static function getRunTimeFormat($time) { - $time_len = time() - $time; - $msg = ""; - if (intval($time_len / 86400) > 0) { - $msg .= intval($time_len / 86400) . "天"; - $time_len = $time_len % 86400; - } - if (intval($time_len / 3600) > 0) { - $msg .= intval($time_len / 3600) . "小时"; - $time_len = $time_len % 3600; - } - if (intval($time_len / 60) > 0) { - $msg .= intval($time_len / 60) . "分"; - $time_len = $time_len % 60; - } - $msg .= $time_len . "秒"; - return $msg; - } - - /** - * 获取所有已经加载到内存的用户。 - * read_all为true时,会加载所有User.dat到内存中,false时仅会读取已经加载到内存的用户 - * @param bool $real_all - * @return User[] - */ - static function getAllUsers($real_all = false): array { - if ($real_all === true) { - $dir = scandir(DP::getUserFolder()); - unset($dir[0], $dir[1]); - foreach ($dir as $d => $v) { - $vs = explode(".", $v); - if (array_pop($vs) == "dat") { - $class = unserialize(file_get_contents(DP::getUserFolder() . $v)); - if (!Cache::array_key_exists("user", $vs[0])) { - Cache::appendKey("user", $vs[0], $class); - } - } - } - } - return Cache::get("user"); - } - - /** - * 获取用户实例 - * @param $id - * @param bool $enable_init - * @return User - */ - static function getUser($id, $enable_init = true) { - $d = Cache::get("user"); - if (!isset($d[$id])) { - $r = self::initUser($id, $enable_init); - if (!$r) return null; - $d = Cache::get("user"); - } - /** @var User $class */ - $class = $d[$id]; - return $class; - } - - /** - * 初始化用户实例。如果没有此用户的实例数据,会创建 - * @param $id - * @param bool $enable_init - * @return bool - */ - static function initUser($id, $enable_init = true) { - if (file_exists(DP::getUserFolder() . $id . ".dat")) $class = unserialize(file_get_contents(DP::getUserFolder() . $id . ".dat")); - else { - if ($enable_init) { - Console::info("无法找到用户 " . $id . " 的数据,正在创建..."); - $class = new User($id); - } else return false; - } - Cache::appendKey("user", $id, $class); - return true; - } - - /** - * 获取模块列表的通用方法 - * @return array - */ - static function getMods() { - $dir = WORKING_DIR . "src/cqbot/mods/"; - $dirs = scandir($dir); - $ls = []; - unset($dirs[0], $dirs[1]); - foreach ($dirs as $v) { - if ($v != "ModBase.php" && (strstr($v, ".php") !== false)) { - $name = substr($v, 0, -4); - $ls[] = $name; - Console::debug("loading mod: " . $name); - } - } - /** @var ModBase[] $ls */ - for ($i = 0; $i < count($ls) - 1; $i++) { - for ($j = 0; $j < count($ls) - $i - 1; $j++) { - $s = defined($ls[$j] . "::mod_level") ? $ls[$j]::mod_level : 10; - $s1 = defined($ls[$j + 1] . "::mod_level") ? $ls[$j + 1]::mod_level : 10; - //Console::info("Comparing mod " . $ls[$j] . " with " . $ls[$j + 1] . ", level are " . $s . ", " . $s1); - if ($s < $s1) { - $t = $ls[$j + 1]; - $ls[$j + 1] = $ls[$j]; - $ls[$j] = $t; - } - } - } - for ($i = count($ls) - 1; $i >= 0; $i--) { - $s = defined($ls[$i] . "::mod_level") ? $ls[$i]::mod_level : 10; - if ($s === 0) unset($ls[$i]); - } - return $ls; - } - - /** - * 重启框架,此服务重启为全自动的 - */ - static function reload() { - Console::info("Reloading server"); - self::saveAllFiles(); - Cache::$data = []; - foreach (ConnectionManager::getAll() as $v) { - $v->close(); - } - Cache::$server->reload(); - } - - /** - * 停止运行框架,需要用shell再次开启才能启动 - */ - static function stop() { - Console::info("Stopping server..."); - self::saveAllFiles(); - Cache::$server->shutdown(); - } - - /** - * 此函数用于解析其他非消息类型事件,显示在log里 - * @param $req - * @return string - */ - static function executeType($req) { - switch ($req["post_type"]) { - case "message": - return "消息"; - case "event"://兼容3.x - switch ($req["event"]) { - case "group_upload": - return "群[" . $req["group_id"] . "] 文件上传:" . $req["file"]["name"] . "(" . intval($req["file"]["size"] / 1024) . "kb)"; - case "group_admin": - switch ($req["sub_type"]) { - case "set": - return "群[" . $req["group_id"] . "] 设置管理员:" . $req["user_id"]; - case "unset": - return "群[" . $req["group_id"] . "] 取消管理员:" . $req["user_id"]; - default: - return "unknown_group_admin_type"; - } - case "group_decrease": - switch ($req["sub_type"]) { - case "leave": - return "群[" . $req["group_id"] . "] 成员主动退群:" . $req["user_id"]; - case "kick": - return "群[" . $req["group_id"] . "] 管理员[" . $req["operator_id"] . "]踢出了:" . $req["user_id"]; - case "kick_me": - return "群[" . $req["group_id"] . "] 本账号被踢出"; - default: - return "unknown_group_decrease_type"; - } - case "group_increase": - return "群[" . $req["group_id"] . "] " . $req["operator_id"] . " 同意 " . $req["user_id"] . " 加入了群"; - default: - return "unknown_event"; - } - case "notice": - switch ($req["notice_type"]) { - case "group_upload": - return "群[" . $req["group_id"] . "] 文件上传:" . $req["file"]["name"] . "(" . intval($req["file"]["size"] / 1024) . "kb)"; - case "group_admin": - switch ($req["sub_type"]) { - case "set": - return "群[" . $req["group_id"] . "] 设置管理员:" . $req["user_id"]; - case "unset": - return "群[" . $req["group_id"] . "] 取消管理员:" . $req["user_id"]; - default: - return "unknown_group_admin_type"; - } - case "group_decrease": - switch ($req["sub_type"]) { - case "leave": - return "群[" . $req["group_id"] . "] 成员主动退群:" . $req["user_id"]; - case "kick": - return "群[" . $req["group_id"] . "] 管理员[" . $req["operator_id"] . "]踢出了:" . $req["user_id"]; - case "kick_me": - return "群[" . $req["group_id"] . "] 本账号被踢出"; - default: - return "unknown_group_decrease_type"; - } - case "group_increase": - return "群[" . $req["group_id"] . "] " . $req["operator_id"] . " 同意 " . $req["user_id"] . " 加入了群"; - default: - return "unknown_event"; - } - case "request": - switch ($req["request_type"]) { - case "friend": - return "加好友请求:" . $req["user_id"] . ",验证信息:" . ($req["message"] ?? $req["comment"]);//兼容3.x - case "group": - switch ($req["sub_type"]) { - case "add": - return "加群[" . $req["group_id"] . "] 请求:" . $req["user_id"] . ",请求信息:" . ($req["message"] ?? $req["comment"]);//兼容3.x - case "invite": - return "用户" . $req["user_id"] . "邀请机器人进入群:" . $req["group_id"]; - default: - return "unknown_group_type"; - } - default: - return "unknown_request_type"; - } - default: - return "unknown_post_type"; - } - } - - static function isRobot($user_id) { - $robots = []; - foreach (ConnectionManager::getAll("robot") as $v) { - if (!in_array($v->getQQ(), $robots)) - $robots[] = $v->getQQ(); - } - if ((Cache::get("bots") ?? []) != []) { - foreach (Cache::get("bots") as $v) { - $robots[] = $v; - } - } - return in_array($user_id, $robots); - } - - static function getRobotAlias($qq) { - return RobotWSConnection::ALIAS_LIST[$qq] ?? "机器人"; - } - - /** - * 刷新消息速率(每分钟) - * 当 $insert 为 true 时,表明运行此函数时收到了一条消息 - * @param bool $insert - */ - static function updateMsg($insert = true) { - $ls = Cache::get("msg_speed"); - if ($insert === true) - $ls [] = time(); - foreach ($ls as $k => $v) { - if ((time() - $v) > 60) { - array_splice($ls, $k, 1); - } - } - Cache::set("msg_speed", $ls); - } -} \ No newline at end of file diff --git a/src/cqbot/utils/ConnectionManager.php b/src/cqbot/utils/ConnectionManager.php deleted file mode 100644 index 2bf938ba..00000000 --- a/src/cqbot/utils/ConnectionManager.php +++ /dev/null @@ -1,45 +0,0 @@ - $v) { - switch ($type) { - case "robot": - /** @var RobotWSConnection[] $ls */ - if ($v instanceof RobotWSConnection) $ls[] = $v; - break; - default: - break; - } - } - return $ls; - } - - public static function get($fd) { return Cache::$connect[$fd] ?? null; } - - public static function set($fd, WSConnection $connection) { Cache::$connect[$fd] = $connection; } - - public static function remove($fd) { unset(Cache::$connect[$fd]); } - - public static function isConnectExists($fd) { return array_key_exists($fd, Cache::$connect); } - - /** - * @param $qq - * @return null|RobotWSConnection|NullConnection - */ - public static function getRobotConnection($qq) { - foreach (Cache::$connect as $v) { - if ($v instanceof RobotWSConnection && $v->getQQ() == $qq && $v->getSubType() != "event") return $v; - } - return new NullConnection(Cache::$server, -1, "0.0.0.0", $qq); - } -} \ No newline at end of file diff --git a/src/cqbot/utils/DataProvider.php b/src/cqbot/utils/DataProvider.php deleted file mode 100755 index bdd639c8..00000000 --- a/src/cqbot/utils/DataProvider.php +++ /dev/null @@ -1,64 +0,0 @@ - $v) { - $v->setFriend([]); - } - foreach (ConnectionManager::getAll("robot") as $k => $v) { - if ($v->getSubType() != "event") { - $robot_id = $v->getQQ(); - Console::put("正在获取机器人 " . $robot_id . " 的好友列表..."); - $v->sendAPI("_get_friend_list", ["flat" => "true"], function ($response) use ($robot_id) { - foreach ($response["data"]["friends"] as $k => $v) { - $user = CQUtil::getUser($v["user_id"]); - $ls = $user->getFriend(); - if (!in_array($robot_id, $ls)) { - $ls[] = $robot_id; - } - $user->setFriend($ls); - $user->setNickname($v["nickname"]); - } - foreach (CQUtil::getAllUsers() as $k => $v) { - $serial = serialize($v); - file_put_contents(DataProvider::getUserFolder() . $k . ".dat", $serial); - } - Cache::set("user", []); - }); - } - } - } -} \ No newline at end of file diff --git a/src/cqbot/utils/GroupManager.php b/src/cqbot/utils/GroupManager.php deleted file mode 100644 index 95bffcc4..00000000 --- a/src/cqbot/utils/GroupManager.php +++ /dev/null @@ -1,39 +0,0 @@ - $v) { - if ($v->getSubType() != "event") { - $robot_id = $v->getQQ(); - Console::put("正在获取机器人 " . $robot_id . " 的群组列表..."); - $v->sendAPI("get_group_list", [], function ($response) use ($robot_id) { - $list = Cache::get("group_list"); - foreach ($response["data"] as $k => $v) { - if (!isset($list[$v["group_id"]])) { - $list[$v["group_id"]] = [ - "group_id" => $v["group_id"], - "group_name" => $v["group_name"], - "fetch_members" => false, - "joiner" => [$robot_id] - ]; - } elseif (!in_array($robot_id, $list[$v["group_id"]]["joiner"])) { - $list[$v["group_id"]]["joiner"][] = $robot_id; - } - } - Cache::set("group_list", $list); - }); - } - } - } -} \ No newline at end of file diff --git a/src/cqbot/utils/Scheduler.php b/src/cqbot/utils/Scheduler.php deleted file mode 100644 index 5c3133aa..00000000 --- a/src/cqbot/utils/Scheduler.php +++ /dev/null @@ -1,47 +0,0 @@ -framework = $framework; - self::$obj = $this; - $this->start_time = $start_time; - } - - public static function getInstance() { - return self::$obj; - } - - public function tick($id) { - //Console::info("Timer ".Console::setColor("#".$id, "gold").":".Cache::$server->worker_id." ticking at ".time()); - /** @var array $ls */ - $ls = Cache::get("mods"); - foreach ($ls as $v) { - if (in_array("onTick", get_class_methods($v))) { - $v::onTick(time() - $this->start_time, $id); - } - } - } - - public static function after($ms, callable $callback){ - return swoole_timer_after($ms, $callback); - } -} \ No newline at end of file diff --git a/src/cqbot/utils/StatusParser.php b/src/cqbot/utils/StatusParser.php deleted file mode 100644 index 27ce2088..00000000 --- a/src/cqbot/utils/StatusParser.php +++ /dev/null @@ -1,30 +0,0 @@ -= 1) { - switch ($obj["action"]) { - case "send_private_msg": - Console::put(Console::setColor("[".date("H:i:s")." PRIVATE] ", "blue").Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray").($obj["params"]["message"] ?? "")); - break; - case "send_group_msg": - Console::put(Console::setColor("[".date("H:i:s")." GROUP:".$obj["params"]["group_id"]."] ", "blue").Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray").($obj["params"]["message"] ?? "")); - break; - case "send_discuss_msg": - Console::put(Console::setColor("[".date("H:i:s")." DISCUSS:".$obj["params"]["discuss_id"]."] ", "blue").Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray").($obj["params"]["message"] ?? "")); - break; - case "send_msg": - $obj["action"] = "send_".$obj["message_type"]."_msg"; - self::msg($obj); - break; - default: - break; - } - } - } -} \ No newline at end of file diff --git a/src/framework/ErrorStatus.php b/src/framework/ErrorStatus.php deleted file mode 100755 index 0e57eef1..00000000 --- a/src/framework/ErrorStatus.php +++ /dev/null @@ -1,47 +0,0 @@ - "POST 请求的正文格式不正确", - 1404 => "API 不存在", - 100 => "参数缺失或参数无效", - 102 => "酷q操作权限不足", - 103 => "用户权限不足或文件系统异常", - 201 => "工作线程池未正确初始化", - -1 => "请求发送失败", - -2 => "未收到服务器回复,可能未发送成功", - -3 => "消息过长或为空", - -4 => "消息解析过程异常", - -5 => "日志功能未启用", - -6 => "日志优先级错误", - -7 => "数据入库失败", - -8 => "不支持对系统帐号操作", - -9 => "帐号不在该群内,消息无法发送", - -10 => "该用户不存在/不在群内", - -11 => "数据错误,无法请求发送", - -12 => "不支持对匿名成员解除禁言", - -13 => "无法解析要禁言的匿名成员数据", - -14 => "由于未知原因,操作失败", - -15 => "群未开启匿名发言功能,或匿名帐号被禁言", - -16 => "帐号不在群内或网络错误,无法退出/解散该群", - -17 => "帐号为群主,无法退出该群", - -18 => "帐号非群主,无法解散该群", - -19 => "临时消息已失效或未建立", - -20 => "参数错误", - -21 => "临时消息已失效或未建立", - -22 => "获取QQ信息失败", - -23 => "找不到与目标QQ的关系,消息无法发送", - -26 => "消息过长" - ]; - - static function getMessage($retcode){ - return self::$error[$retcode] ?? "未知错误"; - } -} \ No newline at end of file diff --git a/src/framework/Framework.php b/src/framework/Framework.php deleted file mode 100755 index 00fdf9e7..00000000 --- a/src/framework/Framework.php +++ /dev/null @@ -1,92 +0,0 @@ -host = $config["swoole_host"]; - $this->port = $config["swoole_port"]; - //Buffer::set("access_token", $config["access_token"] ?? ""); - //Buffer::set("info_level", $config["info_level"]); - //Buffer::set("admin_group", $config["admin_group"]); - - $this->selfCheck(); - - Console::info("CQBot Framework starting on " . $this->host . ":" . $this->port); - $this->event = new swoole_websocket_server($this->host, $this->port); - - $this->event->set([ - "log_file" => $config["swoole_log_file"], - "worker_num" => $config["swoole_worker_num"], - "dispatch_mode" => $config["swoole_dispatch_mode"] - ]); - - //swoole服务器启动时运行的函数 - $this->event->on('WorkerStart', [$this, 'onWorkerStart']); - - //swoole服务端收到WebSocket信息时运行的函数 - $this->event->on('message', function ($server, $frame) { new WSMessageEvent($server, $frame); }); - - //收到ws连接和断开连接回调的函数 - $this->event->on('open', function (\swoole_websocket_server $server, \swoole_http_request $request) { new WSOpenEvent($server, $request); }); - $this->event->on('close', function (\swoole_server $server, int $fd) { new WSCloseEvent($server, $fd); }); - - //设置接收HTTP接口接收的内容,兼容微信公众号和其他服务用 - $this->event->on("request", function (\swoole_http_request $request, \swoole_http_response $response) { new HTTPEvent($request, $response); }); - - //设置原子计数器 - Cache::$in_count = new \swoole_atomic(0); - Cache::$out_count = new \swoole_atomic(0); - Cache::$reload_time = new \swoole_atomic(0); - Cache::$api_id = new \swoole_atomic(0); - } - - public function start() { $this->event->start(); } - - public static function getInstance() { return self::$obj; } - - /* Callback function down here */ - - /** - * This is async function in EventLoop - * When it reload, it will run this function again. - * @param \swoole_server $server - * @param $worker_id - */ - public function onWorkerStart(\swoole_server $server, $worker_id) { - self::$obj = $this; - new WorkerStartEvent($server, $worker_id); - } - - /** - * 开启时候的自检模块 - * 检测项目在下面列举 - */ - public function selfCheck() { - if (!extension_loaded("swoole")) die("无法找到swoole扩展,请先安装.\n"); - if (!function_exists("mb_substr")) die("无法找到mbstring扩展,请先安装.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); - if (!function_exists("curl_exec")) die("无法找到curl扩展,请先安装.\n"); - //if (!class_exists("ZipArchive")) die("无法找到zip扩展,请先安装.(如果不需要zip功能可以删除此条自检)\n"); - if (!is_file(settings()["swoole_log_file"])) file_put_contents(settings()["swoole_log_file"], ""); - return true; - } -} \ No newline at end of file diff --git a/src/framework/global_functions.php b/src/framework/global_functions.php deleted file mode 100644 index 8d490c4b..00000000 --- a/src/framework/global_functions.php +++ /dev/null @@ -1,157 +0,0 @@ - $t) { - if (!isset($t['file'])) { - $t['file'] = 'unknown'; - } - if (!isset($t['line'])) { - $t['line'] = 0; - } - if (!isset($t['function'])) { - $t['function'] = 'unknown'; - } - $log .= "#$i {$t['file']}({$t['line']}): "; - if (isset($t['object']) and is_object($t['object'])) { - $log .= get_class($t['object']) . '->'; - } - $log .= "{$t['function']}()\n"; - } - - file_put_contents(CRASH_DIR . "last_error.log", $log); - break; - default: - break; - } - } -}); - - -function CQMsg($msg, $type, $id) { - if ($type === "group") { - $reply = ["action" => "send_group_msg", "params" => ["group_id" => $id, "message" => $msg]]; - $reply["echo"] = $reply; - $reply["echo"]["time"] = time(); - $reply = json_encode($reply); - } else if ($type === "private") { - $reply = ["action" => "send_private_msg", "params" => ["user_id" => $id, "message" => $msg]]; - $reply["echo"] = $reply; - $reply["echo"]["time"] = time(); - $reply = json_encode($reply); - } else if ($type === "discuss") { - $reply = ["action" => "send_discuss_msg", "params" => ["discuss_id" => $id, "message" => $msg]]; - $reply["echo"] = $reply; - $reply["echo"]["time"] = time(); - $reply = json_encode($reply); - } else { - $reply = false; - } - return $reply; -} - -function getClassPath($dir, $class_name) { - $list = scandir($dir); - unset($list[0], $list[1]); - foreach ($list as $v) { - $taskFileName = explode(".", $v); - if (is_dir($dir . $v)) { - if (($find = getClassPath($dir . $v . "/", $class_name)) !== null) return $find; - else continue; - } else { - if (array_pop($taskFileName) == "php" && $taskFileName[0] == $class_name) return $dir . $v; - } - } - return null; -} - -function class_loader($p) { - $dir = WORKING_DIR . "src/"; - $filepath = getClassPath($dir, $p); - require_once $filepath; -} - -function load_extensions() { - $dir = WORKING_DIR . "src/extension/"; - $ls = scandir($dir); - unset($ls[0], $ls[1]); - foreach ($ls as $k => $v) { - if (mb_substr($v, -4) == "phar") { - require_once $dir . $v; - } - } -} - -function color($str, $end = "\n") { - $str = str_replace("{red}", "\e[38;5;203m", $str); - $str = str_replace("{green}", "\e[38;5;83m", $str); - $str = str_replace("{yellow}", "\e[38;5;227m", $str); - $str = str_replace("{lightpurple}", "\e[38;5;207m", $str); - $str = str_replace("{lightblue}", "\e[38;5;87m", $str); - $str = str_replace("{gold}", "\e[38;5;214m", $str); - $str = str_replace("{gray}", "\e[38;5;59m", $str); - $str = str_replace("{pink}", "\e[38;5;207m", $str); - $str = str_replace("{lightlightblue}", "\e[38;5;63m", $str); - $str = str_replace("{r}", "\e[m", $str); - $str .= "\e[m" . $end; - return $str; -} - -/** - * 使用自己定义的万(san)能分割函数 - * @param $msg - * @param bool $ban_comma - * @return array - */ -function explodeMsg($msg, $ban_comma = false) { - $msg = str_replace(" ", "\n", trim($msg)); - if (!$ban_comma) { - $msg = str_replace(",", "\n", $msg); - $msg = str_replace("\t", "\n", $msg); - } - $msgs = explode("\n", $msg); - $ls = []; - foreach ($msgs as $k => $v) { - if (trim($v) == "") continue; - $ls[] = trim($v); - } - return $ls; -} - -/** - * Unicode解析 - * @param $str - * @return null|string|string[] - */ -function unicodeDecode($str) { - return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { - return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); - }, $str); -} diff --git a/start-coolq.sh b/start-coolq.sh deleted file mode 100644 index 4471a072..00000000 --- a/start-coolq.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -if [[ ! -d "coolq-data" ]]; then - mkdir coolq-data -fi -sudo docker start coolq >/dev/null 2>&1 - -if [[ ! $? -eq 0 ]]; then - echo -n "请输入你的VNC登陆密码(无回显): " - read -s vnc_pwd - echo -n "请输入你的反向ws连接地址(默认ws://127.0.0.1:20000/): " - read reverse_url - if [[ ${reverse_url} = "" ]]; then - reverse_url="ws://127.0.0.1:20000/" - echo "使用默认ws地址。" - fi - echo -n "请输入连接CQBot-swoole框架的token(没有请回车,无回显):" - read access_token - while : - do - echo -n "请输入你的酷Q下载版本 [1(CQA,默认) / 2(CQP)] : " - read cqp_ver - if [[ ${cqp_ver} = "" ]]; then - cqp_ver="1" - fi - link="http://dlsec.cqp.me/cqa-tuling" - if [[ ${cqp_ver} = "2" ]]; then - link="-e COOLQ_URL=http://dlsec.cqp.me/cqp-tuling" - break - elif [[ ${cqp_ver} = "1" ]]; then - link="" - break - else - echo "你输入的数字有误!" - continue - fi - done - if [[ ${access_token} = "" ]]; then - access_token2=" " - else - access_token2="-e CQHTTP_ACCESS_TOKEN="${access_token} - fi - host_mode_line="--net=host" - sudo docker run --name coolq -d -v $(pwd)/coolq-data:/home/user/coolq \ - ${host_mode_line} \ - -e VNC_PASSWD=${vnc_pwd} \ - -e CQHTTP_USE_WS_REVERSE=true \ - ${link} \ - ${access_token2} \ - -e CQHTTP_WS_REVERSE_USE_UNIVERSAL_CLIENT=true \ - -e CQHTTP_WS_REVERSE_URL=${reverse_url} \ - -e FORCE_ENV=false \ - richardchien/cqhttp:latest - echo -n "成功启动docker!正在等待酷Q下载完成... " - while [[ ! -f "coolq-data/conf/CQP.cfg" ]] - do - sleep 1s - done - echo "" - echo "下载完成,请登陆VNC进行登陆QQ!" -else - sudo docker start coolq - echo "已启动酷Q docker!" -fi diff --git a/start-docker-screen.sh b/start-docker-screen.sh deleted file mode 100755 index 13275bff..00000000 --- a/start-docker-screen.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -cq=$(screen -list | grep "cq") -if [[ "$cq" = "" ]]; then - screen -dmS cq -fi -sleep 1s - -screen -x -S cq -p 0 -X stuff "sudo docker run -it --rm --net=host --name cqbot -v "$(pwd)"/cqbot/:/root/ jesse2061/cqbot-swoole" -screen -x -S cq -p 0 -X stuff "\n" -screen -x -S cq -p 0 -X stuff "php start.php" -screen -x -S cq -p 0 -X stuff "\n" diff --git a/start-docker.sh b/start-docker.sh deleted file mode 100755 index b5638972..00000000 --- a/start-docker.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole diff --git a/start-screen.sh b/start-screen.sh deleted file mode 100755 index 46ed6070..00000000 --- a/start-screen.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -cq=$(screen -list | grep "cq") -if [[ "$cq" = "" ]]; then - screen -dmS cq -fi -sleep 1s - -#ls="cd "$(pwd)"/CQBot-swoole/" -#screen -x -S cq -p 0 -X stuff ${ls} -#screen -x -S cq -p 0 -X stuff "\n" -screen -x -S cq -p 0 -X stuff "php start.php" -screen -x -S cq -p 0 -X stuff "\n" \ No newline at end of file diff --git a/start.php b/start.php deleted file mode 100755 index 9e994ed3..00000000 --- a/start.php +++ /dev/null @@ -1,30 +0,0 @@ -start(); - diff --git a/wizard.php b/wizard.php deleted file mode 100644 index a01d8827..00000000 --- a/wizard.php +++ /dev/null @@ -1,105 +0,0 @@ -