From cfef2620ef5980981d3a63ff02ee2f58162de00e Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 3 Jun 2018 15:40:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=8B=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=A4=A7=E7=89=88=E6=9C=AC=EF=BC=8C=E5=9F=BA=E6=9C=AC=E4=B8=8A?= =?UTF-8?q?=E7=AE=97=E6=98=AF=E9=87=8D=E5=86=99=E4=BA=86=E4=B8=80=E9=81=8D?= =?UTF-8?q?=E5=90=A7=EF=BD=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cqbot/Framework.php | 281 ++++++++------------------- src/cqbot/event/ApiMessageEvent.php | 15 ++ src/cqbot/event/ApiUpgradeEvent.php | 55 ++++++ src/cqbot/event/Event.php | 17 ++ src/cqbot/event/HTTPEvent.php | 38 ++++ src/cqbot/event/WSCloseEvent.php | 14 ++ src/cqbot/event/WSMessageEvent.php | 38 ++++ src/cqbot/event/WSOpenEvent.php | 12 ++ src/cqbot/event/WorkerStartEvent.php | 36 ++++ src/cqbot/item/Group.php | 105 ++++++++++ src/cqbot/item/GroupMember.php | 138 +++++++++++++ src/cqbot/{ => item}/User.php | 0 src/cqbot/loader.php | 2 + src/cqbot/mods/Example.php | 25 +++ src/cqbot/tasks/Scheduler.php | 33 ++++ src/cqbot/tasks/TickTask.php | 17 -- src/cqbot/utils/APIHandler.php | 80 +++++++- src/cqbot/utils/Buffer.php | 26 +++ src/cqbot/utils/CQUtil.php | 80 +++++++- start.php | 154 +++++++++++---- 20 files changed, 906 insertions(+), 260 deletions(-) create mode 100644 src/cqbot/event/ApiMessageEvent.php create mode 100644 src/cqbot/event/ApiUpgradeEvent.php create mode 100644 src/cqbot/event/Event.php create mode 100644 src/cqbot/event/HTTPEvent.php create mode 100644 src/cqbot/event/WSCloseEvent.php create mode 100644 src/cqbot/event/WSMessageEvent.php create mode 100644 src/cqbot/event/WSOpenEvent.php create mode 100644 src/cqbot/event/WorkerStartEvent.php create mode 100644 src/cqbot/item/Group.php create mode 100644 src/cqbot/item/GroupMember.php rename src/cqbot/{ => item}/User.php (100%) create mode 100644 src/cqbot/mods/Example.php create mode 100644 src/cqbot/tasks/Scheduler.php delete mode 100644 src/cqbot/tasks/TickTask.php diff --git a/src/cqbot/Framework.php b/src/cqbot/Framework.php index 5dd63b11..d4c473bb 100755 --- a/src/cqbot/Framework.php +++ b/src/cqbot/Framework.php @@ -9,9 +9,9 @@ class Framework { public static $super_user; - private $host = "127.0.0.1"; - private $api_port = 10000; - private $event_port = 20000; + public $host = "127.0.0.1"; + public $api_port = 10000; + public $event_port = 20000; /** @var \swoole_websocket_server $event */ public $event; @@ -27,85 +27,61 @@ class Framework private $log_file; public $tick_time; + /** @var Scheduler */ + public $scheduler = null; + public function __construct(){ } - /** - * @param string $host - * @return $this - */ - public function setHost($host = ""){ - $this->host = $host; - return $this; - } + public function setHost($host = ""){ $this->host = $host; } - public function setApiPort($port = 10000){ - $this->api_port = $port; - return $this; - } + public function setApiPort($port = 10000){ $this->api_port = $port; } - public function setEventPort($port = 20000){ - $this->event_port = $port; - return $this; - } + public function setEventPort($port = 20000){ $this->event_port = $port; } - public function setAdminGroup($group){ - self::$admin_group = $group; - return $this; - } + public function setAdminGroup($group){ self::$admin_group = $group; } - public function setInfoLevel($level){ - $this->info_level = $level; - return $this; - } + public function setInfoLevel($level){ $this->info_level = $level; } - public function eventServerStart(){ - $this->event->start(); - } + public function eventServerStart(){ $this->event->start(); } - public static function getInstance(){ - return self::$obj; - } + public static function getInstance(){ return self::$obj; } - public function init($option = null){ - self::$super_user = ($option !== null ? $option : ""); - self::$obj = $this; + public function init($option = []){ + $this->selfCheck(); + $this->checkFiles(); + self::$super_user = $option; Console::info("CQBot Framework starting..."); $this->event = new \swoole_websocket_server($this->host, $this->event_port); + Buffer::$log_file = CONFIG_DIR . "log/swoole.log"; Console::info("Current log file: " . Buffer::$log_file); + + //设置swoole基本参数 $worker_num = 1; - Console::info("Current worker count: " . $worker_num); $dispatch_mode = 2; - Console::info("Current dispatch mode: " . $dispatch_mode); - $this->checkFiles(); $this->event->set([ "log_file" => Buffer::$log_file, - "worker_num" => 1, - "dispatch_mode" => 2 + "worker_num" =>$worker_num, + "dispatch_mode" => $dispatch_mode ]); + + //swoole服务器启动时运行的函数 $this->event->on('WorkerStart', [$this, 'onWorkerStart']); + + //swoole服务端收到WebSocket信息时运行的函数 $this->event->on('message', [$this, 'onEventMessage']); - //$this->event->on('open', [$this, 'onConnect']); - $this->event->on('open', function ($server, $request){ - Console::put("EVENT connection established", "lightblue"); - }); + + //收到ws连接和断开连接回调的函数 + $this->event->on('open', [$this, 'onEventOpen']); + $this->event->on('close', [$this, "onEventClose"]); + + //设置接收HTTP接口接收的内容,兼容微信公众号和其他服务用 $this->event->on("request", [$this, "onRequest"]); - $this->event->on('close', function ($serv, $fd){ - Console::info(Console::setColor("EVENT connection closed","red")); - //put your connection close method here. - }); + + //设置原子计数器 Buffer::$in_count = new \swoole_atomic(1); Buffer::$out_count = new \swoole_atomic(1); Buffer::$api_id = new \swoole_atomic(1); - return $this; - } - - public function checkFiles(){ - @mkdir(CONFIG_DIR."log/", 0777, true); - if(!is_file(CONFIG_DIR."log/last_error.log")) - file_put_contents(CONFIG_DIR."log/last_error.log", ""); - if(!is_file(CONFIG_DIR."log/error_flag")) - file_put_contents(CONFIG_DIR."log/error_flag", time()); } /* Callback function down here */ @@ -116,173 +92,78 @@ class Framework * @param \swoole_server $server * @param $worker_id */ - public function onWorkerStart(\swoole_server $server, $worker_id){ + public function onWorkerStart(\swoole_server $server, $worker_id) { + self::$obj = $this; $this->run_time = time(); Buffer::set("info_level", $this->info_level);//设置info等级 - Console::info("Starting worker: " . $worker_id); require_once(WORKING_DIR . "src/cqbot/loader.php"); - CQUtil::loadAllFiles(); - foreach (get_included_files() as $file) - Console::debug("Loaded " . $file); - - //计时器(ms) - $server->tick(1000, [$this, "processTick"]); - - //API连接部分 - $this->api = new \swoole_http_client($this->host, $this->api_port); - $this->api->set(['websocket_mask' => true]); - $this->api->on('message', [$this, "onApiMessage"]); - $this->api->on("close", function ($cli){ - Console::info(Console::setColor("API connection closed", "red")); - }); - $this->api->upgrade('/api/', [$this, "onUpgrade"]); - - Console::debug("master_pid = " . $server->master_pid); - Console::debug("worker_id = " . $worker_id); - Console::put("\n==========STARTUP DONE==========\n"); + new WorkerStartEvent($server, $worker_id); } - public function onUpgrade($cli){ - Console::info("Upgraded API websocket"); - Buffer::$api = $this->api; - Buffer::$event = $this->event; - if ($data = file(CONFIG_DIR . "log/last_error.log")) { - $last_time = file_get_contents(CONFIG_DIR . "log/error_flag"); - if (time() - $last_time < 2) { - CQUtil::sendDebugMsg("检测到重复引起异常,停止服务器", 0); - file_put_contents(CONFIG_DIR."log/last_error.log", ""); - $this->event->shutdown(); - return; - } - CQUtil::sendDebugMsg("检测到异常", 0); - $msg = ""; - foreach ($data as $e) { - $msg = $msg . $e . "\n"; - } - CQUtil::sendDebugMsg($msg, 0); - CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0); - file_put_contents(CONFIG_DIR . "error_flag", time()); - file_put_contents(CONFIG_DIR . "last_error.log", ""); - } - else { - CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0); - } - } + /** + * 回调函数:API连接升级为WebSocket时候调用,可用于成功和酷Qhttp建立连接的检测依据 + * @param $cli + */ + public function onUpgrade($cli){ new ApiUpgradeEvent($cli); } + /** + * 回调函数:有客户端或HTTP插件反向客户端连接时调用 + * @param swoole_websocket_server $server + * @param swoole_http_request $request + */ + public function onEventOpen(\swoole_websocket_server $server, \swoole_http_request $request){ new WSOpenEvent($server, $request); } + + public function onEventClose(\swoole_server $server, int $fd) { new WSCloseEvent($server, $fd); } /** * 回调函数:当HTTP插件发来json包后激活此函数 * @param swoole_websocket_server $server * @param $frame */ - public function onEventMessage($server, $frame){ - $in_count = Buffer::$in_count->get(); - Buffer::$in_count->add(1); - $req = json_decode($frame->data, true); - if (Buffer::$data["info_level"] == 2) { - Console::put("************EVENT RECEIVED***********"); - Console::put("msg_id = " . $in_count); - Console::put("worker_id = " . $server->worker_id); - } - if (Buffer::$data["info_level"] >= 1) { - $type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG:" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" : "DISCUSS_MSG:" . $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["post_type"] == "message" ? $req["message"] : Console::setColor($this->executeType($req), "gold"))); - } - //传入业务逻辑:CQBot - try { - $c = new CQBot($this); - $c->execute($req); - $c->endtime = microtime(true); - $value = $c->endtime - $c->starttime; - Console::debug("Using time: ".$value); - if(Buffer::get("time_send") === true) - CQUtil::sendDebugMsg("Using time: ".$value); - } catch (Exception $e) { - CQUtil::errorlog("处理消息时异常,消息处理中断\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); - CQUtil::sendDebugMsg("引起异常的消息:\n" . var_export($req, true)); - } - } + public function onEventMessage($server, $frame){ new WSMessageEvent($server, $frame); } - /******************* 微信HTTP 响应 ****************** + /** + * 回调函数:当IP:event端口收到相关HTTP请求时候调用 + * 你可在此编写HTTP请求回复的内容(比如做一个web界面?) + * 也可以在这里处理微信公众号的请求(可能需要端口转发) * @param swoole_http_request $request * @param swoole_http_response $response */ + public function onRequest($request, $response){ new HTTPEvent($request, $response); } - public function onRequest($request, $response){ - $response->end("Hello world"); - } - - /******************* API 响应 ****************** + /** + * 回调函数:API响应函数,用于发送api请求后返回的状态包的检查,比如rescode = 200 * @param swoole_http_client $client * @param $frame */ - public function onApiMessage($client, $frame){ + public function onApiMessage($client, $frame){ new ApiMessageEvent($client, $frame); } - } - - /***************** 计时器 ****************** + /** + * 回调函数:异步计时器,一秒执行一次。请勿在此使用过多的阻塞方法 * @param $id */ - public function processTick($id){ - $this->tick_time = time(); - new TickTask($this, $id); + public function processTick($id){ $this->scheduler->tick($id, ($this->tick_time = time())); } + + /** + * 开启时候的自检模块 + * 检测项目在下面列举 + */ + 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"); + return true; } /** - * 此函数用于解析其他非消息类型事件,显示在log里 - * @param $req - * @return string + * 检查必需的文件是否存在 */ - public function executeType($req){ - switch($req["post_type"]){ - case "message": - return "消息"; - case "event": - 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 "request": - switch($req["request_type"]){ - case "friend": - return "加好友请求:".$req["user_id"].",验证信息:".$req["message"]; - case "group": - switch($req["sub_type"]){ - case "add": - return "加群[".$req["group_id"]."] 请求:".$req["user_id"].",请求信息:".$req["message"]; - case "invite": - return "用户".$req["user_id"]."邀请机器人进入群:".$req["group_id"]; - default: - return "unknown_group_type"; - } - default: - return "unknown_request_type"; - } - default: - return "unknown_post_type"; - } + public function checkFiles(){ + @mkdir(CONFIG_DIR."log/", 0777, true); + if(!is_file(CONFIG_DIR."log/last_error.log")) + file_put_contents(CONFIG_DIR."log/last_error.log", ""); + if(!is_file(CONFIG_DIR."log/error_flag")) + file_put_contents(CONFIG_DIR."log/error_flag", time()); } } \ No newline at end of file diff --git a/src/cqbot/event/ApiMessageEvent.php b/src/cqbot/event/ApiMessageEvent.php new file mode 100644 index 00000000..abb78cf5 --- /dev/null +++ b/src/cqbot/event/ApiMessageEvent.php @@ -0,0 +1,15 @@ +data, true); + if (isset($res["echo"])) APIHandler::execute($res["echo"], $res); + } +} \ No newline at end of file diff --git a/src/cqbot/event/ApiUpgradeEvent.php b/src/cqbot/event/ApiUpgradeEvent.php new file mode 100644 index 00000000..efcc06bd --- /dev/null +++ b/src/cqbot/event/ApiUpgradeEvent.php @@ -0,0 +1,55 @@ +getFramework()->api->isConnected()) { + echo "API connection lost.\nI will try next time after 30 second.\n"; + + //这里本来该用异步计时器的,但是我太懒了,直接睡30秒先。 + //需要改用异步计时器的话,告诉我我会改的233333。 + sleep(30); + $this->getFramework()->api = new \swoole_http_client($this->getFramework()->host, $this->getFramework()->api_port); + $this->getFramework()->api->set(['websocket_mask' => true]); + $this->getFramework()->api->on('message', [$this->getFramework(), "onApiMessage"]); + $this->getFramework()->api->on("close", function ($cli){ + Console::info(Console::setColor("API connection closed", "red")); + }); + $this->getFramework()->api->upgrade('/api/', [$this->getFramework(), "onUpgrade"]); + return; + } + Buffer::$api = $this->getFramework()->api; + Buffer::$event = $this->getFramework()->event; + if ($data = file(CONFIG_DIR . "log/last_error.log")) { + $last_time = file_get_contents(CONFIG_DIR . "log/error_flag"); + if (time() - $last_time < 2) { + CQUtil::sendDebugMsg("检测到重复引起异常,停止服务器", 0); + file_put_contents(CONFIG_DIR."log/last_error.log", ""); + $this->getFramework()->event->shutdown(); + return; + } + CQUtil::sendDebugMsg("检测到异常", 0); + $msg = ""; + foreach ($data as $e) { + $msg = $msg . $e . "\n"; + } + CQUtil::sendDebugMsg($msg, 0); + CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0); + file_put_contents(CONFIG_DIR . "error_flag", time()); + file_put_contents(CONFIG_DIR . "last_error.log", ""); + } + else { + CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0); + } + CQUtil::sendAPI("_get_friend_list", ["get_friend_list"]); + CQUtil::sendAPI("get_group_list", ["get_group_list"]); + CQUtil::sendAPI("get_version_info", ["get_version_info"]); + } +} \ No newline at end of file diff --git a/src/cqbot/event/Event.php b/src/cqbot/event/Event.php new file mode 100644 index 00000000..05063d22 --- /dev/null +++ b/src/cqbot/event/Event.php @@ -0,0 +1,17 @@ +end("Hello world!"); + //此为HTTP请求的回复,更多设置回复头、传送文件、POST、GET请求解析等内容请查阅文档https://www.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/WSCloseEvent.php b/src/cqbot/event/WSCloseEvent.php new file mode 100644 index 00000000..ae43f54a --- /dev/null +++ b/src/cqbot/event/WSCloseEvent.php @@ -0,0 +1,14 @@ +get(); + Buffer::$in_count->add(1); + $req = json_decode($frame->data, true); + if (Buffer::$data["info_level"] == 2) { + Console::put("************EVENT RECEIVED***********"); + Console::put("msg_id = " . $in_count); + Console::put("worker_id = " . $server->worker_id); + } + if (Buffer::$data["info_level"] >= 1) { + $type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG:" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" : "DISCUSS_MSG:" . $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["post_type"] == "message" ? $req["message"] : Console::setColor(CQUtil::executeType($req), "gold"))); + } + //传入业务逻辑:CQBot + try { + $c = new CQBot($this->getFramework()); + $c->execute($req); + $c->endtime = microtime(true); + $value = $c->endtime - $c->starttime; + Console::debug("Using time: ".$value); + if(Buffer::get("time_send") === true) + CQUtil::sendDebugMsg("Using time: ".$value); + } catch (Exception $e) { + CQUtil::errorlog("处理消息时异常,消息处理中断\n" . $e->getMessage() . "\n" . $e->getTraceAsString()); + CQUtil::sendDebugMsg("引起异常的消息:\n" . var_export($req, true)); + } + } +} \ No newline at end of file diff --git a/src/cqbot/event/WSOpenEvent.php b/src/cqbot/event/WSOpenEvent.php new file mode 100644 index 00000000..c439155f --- /dev/null +++ b/src/cqbot/event/WSOpenEvent.php @@ -0,0 +1,12 @@ +getFramework()->scheduler = new Scheduler($this->getFramework()); + $server->tick(1000, [$this->getFramework(), "processTick"]); + + //API连接部分 + $this->getFramework()->api = new \swoole_http_client($this->getFramework()->host, $this->getFramework()->api_port); + $this->getFramework()->api->set(['websocket_mask' => true]); + $this->getFramework()->api->on('message', [$this->getFramework(), "onApiMessage"]); + $this->getFramework()->api->on("close", function ($cli){ + Console::info(Console::setColor("API connection closed", "red")); + }); + $this->getFramework()->api->upgrade('/api/', [$this->getFramework(), "onUpgrade"]); + + Console::debug("master_pid = " . $server->master_pid); + Console::debug("worker_id = " . $worker_id); + Console::put("\n==========STARTUP DONE==========\n"); + } +} \ No newline at end of file diff --git a/src/cqbot/item/Group.php b/src/cqbot/item/Group.php new file mode 100644 index 00000000..23ecd2fa --- /dev/null +++ b/src/cqbot/item/Group.php @@ -0,0 +1,105 @@ +group_id = $group_id; + $this->group_name = $info["group_name"]; + $this->prefix = $info["prefix"]; + $member_list = $info["member"]; + $this->members = []; + foreach ($member_list as $k => $v) { + $this->members[$v["user_id"]] = new GroupMember($v["user_id"], $this, $v); + } + } + + /** + * @return mixed + */ + public function getGroupId() { + return $this->group_id; + } + + /** + * @return mixed + */ + public function getGroupName() { + return $this->group_name; + } + + /** + * @return mixed + */ + public function getPrefix() { + return $this->prefix; + } + + /** + * @return array + */ + public function getMembers(): array { + return $this->members; + } + + /** + * @param $user_id + * @return GroupMember|null + */ + public function getMember($user_id) { + return isset($this->members[$user_id]) ? $this->members[$user_id] : null; + } + + /** + * @param mixed $group_name + */ + public function setGroupName($group_name) { + $this->group_name = $group_name; + } + + /** + * set自定义群称号的方法 + * @param mixed $prefix + */ + public function setPrefix($prefix) { + $this->prefix = $prefix; + } + + /** + * set群成员的类 + * @param array $members + */ + public function setMembers(array $members) { + $this->members = $members; + } + + /** + * set群成员的类 + * @param $user_id + * @param GroupMember $member + */ + public function setMember($user_id, GroupMember $member) { + $this->members[$user_id] = $member; + } + + /** + * 更新群信息 + * @param bool $with_members + */ + public function updateData($with_members = false) { + CQUtil::sendAPI(["action" => "get_group_list"], ["update_group_info", $this->getGroupId()]); + if ($with_members) { + CQUtil::sendAPI(["action" => "get_group_member_list", "params" => ["group_id" => $this->getGroupId()]], ["update_group_member_list", strval($this->getGroupId())]); + } + } +} \ No newline at end of file diff --git a/src/cqbot/item/GroupMember.php b/src/cqbot/item/GroupMember.php new file mode 100644 index 00000000..c107b6f6 --- /dev/null +++ b/src/cqbot/item/GroupMember.php @@ -0,0 +1,138 @@ +group = $group; + $this->card = $data["card"]; + $this->join_time = $data["join_time"]; + $this->last_sent_time = $data["last_sent_time"]; + $this->role = $data["role"]; + $this->attribute = $data; + } + + /** + * @return Group + */ + public function getGroup(): Group { + return $this->group; + } + + /** + * @return mixed + */ + public function getCard() { + return $this->card; + } + + /** + * @return mixed + */ + public function getJoinTime() { + return $this->join_time; + } + + /** + * @return mixed + */ + public function getLastSentTime() { + return $this->last_sent_time; + } + + /** + * 返回角色 + * @return mixed + */ + public function getRole() { + return $this->role; + } + + /** + * 返回用户是不是群管理员 + * @return bool + */ + public function isAdmin(){ + return in_array($this->getRole(), ["owner", "admin"]); + } + + /** + * @param string $card + */ + public function setCard(string $card) { + $this->card = $card; + $data = [ + "action" => "set_group_card", + "params" => [ + "group_id" => $this->getGroup()->getGroupId(), + "user_id" => $this->getId(), + "card" => $card + ] + ]; + CQUtil::sendAPI($data, []); + } + + /** + * @param int $join_time + */ + public function setJoinTime(int $join_time) { + $this->join_time = $join_time; + } + + /** + * @param int $last_sent_time + */ + public function setLastSentTime(int $last_sent_time) { + $this->last_sent_time = $last_sent_time; + } + + /** + * @param string $role + */ + public function setRole(string $role) { + $this->role = $role; + } + + /** + * @return array + */ + public function getAttribute(): array { + return $this->attribute; + } + + /** + * @param array $attribute + */ + public function setAttribute(array $attribute) { + $this->attribute = $attribute; + } + + /** + * 更新群组成员信息 + */ + public function updateData(){ + $user_id = $this->getId(); + CQUtil::sendAPI([ + "action" => "get_group_member_info", + "params" => [ + "group_id" => $this->getGroup()->getGroupId(), + "user_id" => $user_id, + "no_cache" => true + ] + ], ["update_group_member_info", $this->getGroup()->getGroupId(), $user_id]); + } + +} \ No newline at end of file diff --git a/src/cqbot/User.php b/src/cqbot/item/User.php similarity index 100% rename from src/cqbot/User.php rename to src/cqbot/item/User.php diff --git a/src/cqbot/loader.php b/src/cqbot/loader.php index 983a5670..a2b6eea4 100755 --- a/src/cqbot/loader.php +++ b/src/cqbot/loader.php @@ -22,6 +22,8 @@ function loadAllClass($dir){ //加载需要优先加载的文件 require_once(WORKING_DIR."src/cqbot/mods/ModBase.php"); +require_once(WORKING_DIR."src/cqbot/item/User.php"); +require_once(WORKING_DIR."src/cqbot/event/Event.php"); loadAllClass(WORKING_DIR."src/cqbot/"); diff --git a/src/cqbot/mods/Example.php b/src/cqbot/mods/Example.php new file mode 100644 index 00000000..18ef7dc4 --- /dev/null +++ b/src/cqbot/mods/Example.php @@ -0,0 +1,25 @@ +reply("pong"); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/cqbot/tasks/Scheduler.php b/src/cqbot/tasks/Scheduler.php new file mode 100644 index 00000000..f8436ddf --- /dev/null +++ b/src/cqbot/tasks/Scheduler.php @@ -0,0 +1,33 @@ +framework = $framework; + self::$obj = $this; + } + + public static function getInstance() { + return self::$obj; + } + + public function tick($id, $tick_time) { + if($tick_time - $this->framework->run_time % 900 == 0) CQUtil::saveAllFiles(); + //这里添加计时器上处理的内容 + } +} \ No newline at end of file diff --git a/src/cqbot/tasks/TickTask.php b/src/cqbot/tasks/TickTask.php deleted file mode 100644 index 49f65f96..00000000 --- a/src/cqbot/tasks/TickTask.php +++ /dev/null @@ -1,17 +0,0 @@ -tick_time - $framework->run_time); - if ($interval % 900 == 0) CQUtil::saveAllFiles();//15分钟存一次数据 - - //这里可以放置你的定时器内执行的功能,自由扩展 - } -} \ No newline at end of file diff --git a/src/cqbot/utils/APIHandler.php b/src/cqbot/utils/APIHandler.php index 95171c1b..90da5e08 100644 --- a/src/cqbot/utils/APIHandler.php +++ b/src/cqbot/utils/APIHandler.php @@ -8,5 +8,83 @@ class APIHandler { - + static function execute($cmd, $res = null) { + if (!isset($cmd[0])) return; + switch ($cmd[0]) { + case "set_friend_add_request": + $id = $cmd[1]; + $msg = "Hi~你好!"; + $msg .= "\n第一次见面请多关照!"; + CQUtil::sendPrivateMsg($id, $msg); + break; + case "get_friend_list": + $friend = $res["data"][0]["friends"]; + $list = []; + foreach ($friend as $k => $v) { + $list[$v["user_id"]] = $friend[$k]; + } + Buffer::set("friend_list", $list); + Console::put(Console::setColor("已读取" . count(Buffer::get("friend_list")) . "个好友", "blue")); + break; + case "update_group_member_list": + $group_id = $cmd[1]; + $info_data = $res["data"]; + Console::info(Console::setColor("Updating group $group_id members, it will take several minutes.", "yellow")); + foreach ($info_data as $k => $v) { + $s = new GroupMember($v["user_id"], CQUtil::getGroup($group_id), $v); + CQUtil::getGroup($group_id)->setMember($v["user_id"], $s); + $s->updateData(); + } + break; + case "update_group_member_info": + $info_data = $res["data"]; + $group = $cmd[1]; + $user = $cmd[2]; + $g = CQUtil::getGroup($group); + $member = $g->getMember($user); + $member->setAttribute($info_data); + $member->setCard($info_data["card"]); + $member->setJoinTime($info_data["join_time"]); + $member->setLastSentTime($info_data["last_sent_time"]); + $member->setRole($info_data["role"]); + Console::info("Updated group member information: " . $group . ":" . $user); + break; + case "update_group_info": + $group = $res["data"]; + $current = $cmd[1]; + $list = []; + foreach ($group as $k => $v) { + $list[$v["group_id"]] = $group[$k]; + } + if (!isset($list[$current]) && Buffer::array_key_exists("groups", $current)) { + Buffer::unset("groups", $current); + break; + } + $g = CQUtil::getGroup($current); + $g->setGroupName($list[$current]["group_name"]); + $g->setPrefix($list[$current]["prefix"]); + break; + case "get_group_member_list": + $group_data = $res["data"]; + $ls = Buffer::get("group_list"); + $group_id = $cmd[1]; + $ls[$group_id]["member"] = $group_data; + $group = new Group($group_id, $ls[$group_id]); + Buffer::appendKey("groups", $group_id, $group); + break; + case "get_group_list": + $group = $res["data"]; + $list = []; + foreach ($group as $k => $v) { + $list[$v["group_id"]] = $group[$k]; + CQUtil::sendAPI(["action" => "get_group_member_list", "params" => ["group_id" => $v["group_id"]]], ["get_group_member_list", $v["group_id"]]); + } + Buffer::set("group_list", $list); + Console::put(Console::setColor("已读取" . count(Buffer::get("group_list")) . "个群", "blue")); + break; + case "get_version_info": + Buffer::set("version_info", $res["data"]); + break; + } + } } \ No newline at end of file diff --git a/src/cqbot/utils/Buffer.php b/src/cqbot/utils/Buffer.php index 4924b701..830d0688 100755 --- a/src/cqbot/utils/Buffer.php +++ b/src/cqbot/utils/Buffer.php @@ -49,4 +49,30 @@ class Buffer if(!is_array((self::$data[$name] ?? 1))) return false; return in_array($value, self::$data[$name]); } + + ////////////预留部分,为redis更新作准备///////////// + + /** @var string[] 为未来支持redis数据库做准备 */ + static $vars = []; + static $ls = []; + + static function _get(string $name){ + + } + + static function _setString(string $key, string $value){ + + } + + static function _setList(string $key, array $value){ + + } + + static function _appendList(string $key, string $value){ + + } + + static function _ping(){ + + } } \ No newline at end of file diff --git a/src/cqbot/utils/CQUtil.php b/src/cqbot/utils/CQUtil.php index 26fc4abe..c629f6e9 100755 --- a/src/cqbot/utils/CQUtil.php +++ b/src/cqbot/utils/CQUtil.php @@ -12,12 +12,9 @@ class CQUtil { public static function loadAllFiles() { Console::debug("loading configs..."); - Buffer::set("su", DP::getJsonData("su.json"));//超级管理员用户列表 - if (count(Buffer::get("su")) < 1 && Framework::$super_user !== "") { - Console::info("Added super user"); - Buffer::set("su", [Framework::$super_user]); - } - Buffer::set("mods", self::getMods());//加载模块列表 + Buffer::set("su", Framework::$super_user);//超级管理员用户列表 + + Buffer::set("mods", self::getMods());//加载的模块列表 Buffer::set("user", []);//清空用户列表 Buffer::set("time_send", false);//发送Timing数据到管理群 Buffer::set("cmd_prefix", DP::getJsonData("config.json")["cmd_prefix"] ?? "");//设置指令的前缀符号 @@ -26,11 +23,11 @@ class CQUtil public static function saveAllFiles() { Console::info("Saving files..."); - DP::setJsonData("su.json", Buffer::get("su"));//保存超级管理员的QQ列表 //保存cmd_prefix(指令前缀) $config = DP::getJsonData("config.json"); $config["cmd_prefix"] = Buffer::get("cmd_prefix"); + $config["super_user"] = Buffer::get("su"); DP::setJsonData("config.json", $config); //保存用户数据 @@ -519,4 +516,73 @@ class CQUtil Buffer::$api->close(); Buffer::$event->shutdown(); } + + /** + * 此函数用于解析其他非消息类型事件,显示在log里 + * @param $req + * @return string + */ + public static function executeType($req){ + switch($req["post_type"]){ + case "message": + return "消息"; + case "event": + 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 "request": + switch($req["request_type"]){ + case "friend": + return "加好友请求:".$req["user_id"].",验证信息:".$req["message"]; + case "group": + switch($req["sub_type"]){ + case "add": + return "加群[".$req["group_id"]."] 请求:".$req["user_id"].",请求信息:".$req["message"]; + case "invite": + return "用户".$req["user_id"]."邀请机器人进入群:".$req["group_id"]; + default: + return "unknown_group_type"; + } + default: + return "unknown_request_type"; + } + default: + return "unknown_post_type"; + } + } + + /** + * 返回群的类 + * @param $group_id + * @return Group|null + */ + static function getGroup($group_id){ + $d = Buffer::get("groups"); + return $d[$group_id] ?? null; + } } \ No newline at end of file diff --git a/start.php b/start.php index 8f1398ed..57dedb51 100755 --- a/start.php +++ b/start.php @@ -7,9 +7,14 @@ */ date_default_timezone_set("Asia/Shanghai"); + +//工作目录设置 define("WORKING_DIR", __DIR__ . "/"); +echo "工作目录:".WORKING_DIR."\n"; define("CONFIG_DIR", WORKING_DIR . "config/"); define("USER_DIR", WORKING_DIR . "users"); + +//启动时间 define("START_TIME", time()); @mkdir(CONFIG_DIR, 0777, true); @mkdir(USER_DIR, 0777, true); @@ -74,47 +79,126 @@ function CQMsg($msg, $type, $id) { } return $reply; } + + + $host = "0.0.0.0"; +$api_host = "127.0.0.1"; $api_port = 10000; $event_port = 20000; $admin_group = ""; $info_level = 1; -$super_user = ""; -//check argv option. -if(count($argv) >= 3){ - echo "detected options.\n"; - //var_dump($argv); - array_shift($argv); - for($i = 0; $i < count($argv); $i++){ - if(substr($argv[$i],0,2) == '--'){ - $option = substr($argv[$i],2); - if(isset($argv[$i+1])){ - switch($option){ - case "host": - $host = $argv[$i+1]; - break; - case "api-port": - $api_port = $argv[$i+1]; - break; - case "event-port": - $event_port = $argv[$i+1]; - break; - case "admin-group": - $admin_group = $argv[$i+1]; - break; - case "info-level": - $info_level = $argv[$i+1]; - break; - case "super-user": - $super_user = $argv[$i+1]; - break; - default: - break; - } - } - } - } +$super_user = []; + +if (!file_exists(CONFIG_DIR . "config.json")) { + file_put_contents(CONFIG_DIR . "config.json", json_encode([])); } +$json = json_decode(file_get_contents(CONFIG_DIR . "config.json"), true); +if (!isset($json["host"])) { + echo "请输入你要监听的Event IP(默认0.0.0.0) :"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "监听地址:0.0.0.0(默认)\n"; + $json["host"] = $host; + } else { + $host = $r; + echo "监听地址:" . $r . "\n"; + $json["host"] = $host; + } +} else { + $host = $json["host"]; +} +if (!isset($json["event_port"])) { + a3: + echo "请输入你要监听的Event端口(默认20000) :"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "监听地址:20000(默认)\n"; + $json["event_port"] = $event_port; + } else { + if (!is_numeric($r)) { + echo "输入错误!请输入数字!(1-65535)\n"; + goto a3; + } + $event_port = $r; + echo "监听地址:" . $r . "\n"; + $json["event_port"] = $event_port; + } +} else { + $event_port = $json["event_port"]; +} +if (!isset($json["api_host"])) { + echo "请输入你要连接的api server IP(默认127.0.0.1) :"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "API地址:127.0.0.1(默认)\n"; + $json["api_host"] = $api_host; + } else { + $api_host = $r; + echo "监听地址:" . $r . "\n"; + $json["api_host"] = $api_host; + } +} else { + $api_host = $json["api_host"]; +} +if (!isset($json["api_port"])) { + a2: + echo "请输入你要监听的API端口(默认10000) :"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "监听地址:10000(默认)\n"; + $json["api_port"] = $api_port; + } else { + if (!is_numeric($r)) { + echo "输入错误!请输入数字!(1-65535)\n"; + goto a2; + } + $api_port = $r; + echo "监听地址:" . $r . "\n"; + $json["api_port"] = $api_port; + } +} else { + $api_port = $json["api_port"]; +} +if (!isset($json["admin_group"])) { + a4: + echo "请输入你要设置的管理员群:"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "检测到你没有设置管理员群,本次跳过\n"; + } else { + if (!is_numeric($r)) { + echo "输入错误!请输入数字群号!\n"; + goto a4; + } + $admin_group = $r; + echo "管理群:" . $r . "\n"; + $json["admin_group"] = $admin_group; + } +} else { + $admin_group = $json["admin_group"]; +} + +if (!isset($json["super_user"])) { + a5: + echo "请输入你要设置的高级管理员:"; + $r = strtolower(trim(fgets(STDIN))); + if ($r == "") { + echo "检测到你没有设置高级管理员,本次跳过\n"; + } else { + if (!is_numeric($r)) { + echo "输入错误!请输入数字QQ号!\n"; + goto a5; + } + $super_user[] = $r; + echo "管理员:" . $r . "\n"; + $json["super_user"][] = $r; + } +} else { + $super_user = $json["super_user"]; +} + +file_put_contents(CONFIG_DIR."config.json", json_encode($json, 128 | 256)); //loading projects require(WORKING_DIR . "src/cqbot/Framework.php");