From d629cf9b96c5597b9f86062dc5d72ed67ade519e Mon Sep 17 00:00:00 2001 From: jerry Date: Wed, 28 Nov 2018 13:26:18 +0800 Subject: [PATCH] =?UTF-8?q?:kissing=5Fheart:=20=E9=87=8D=E6=9E=84=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=81=EF=BC=81=EF=BC=81=EF=BC=81=EF=BC=81=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 5 +- cqbot.json | 15 +- src/cqbot/CQBot.php | 66 +-- src/cqbot/{item => api}/CQ.php | 19 + src/cqbot/api/CQAPI.php | 210 ++++++++ src/cqbot/connection/CustomWSConnection.php | 16 + src/cqbot/connection/NullConnection.php | 37 ++ src/cqbot/connection/RobotWSConnection.php | 134 +++++ src/cqbot/connection/WSConnection.php | 64 +++ src/cqbot/event/post/MessageEvent.php | 8 +- src/cqbot/event/post/NoticeEvent.php | 7 +- src/cqbot/event/post/RequestEvent.php | 38 +- src/cqbot/event/server/APIConnectEvent.php | 20 - src/cqbot/event/server/HTTPEvent.php | 7 +- src/cqbot/event/server/ServerEvent.php | 16 + src/cqbot/event/server/WSCloseEvent.php | 14 +- src/cqbot/event/server/WSMessageEvent.php | 67 ++- src/cqbot/event/server/WSOpenEvent.php | 31 +- src/cqbot/event/server/WorkerStartEvent.php | 20 +- src/cqbot/handler/CQAPIHandler.php | 92 ---- src/cqbot/loader.php | 26 - src/cqbot/mods/Admin.php | 57 +- src/cqbot/mods/Example.php | 24 +- src/cqbot/mods/Help.php | 6 +- src/cqbot/mods/ModBase.php | 28 +- src/cqbot/utils/CQUtil.php | 494 +++--------------- src/cqbot/utils/ConnectionManager.php | 45 ++ src/cqbot/utils/DataProvider.php | 21 +- src/cqbot/utils/FriendManager.php | 30 +- src/cqbot/utils/GroupManager.php | 31 +- src/cqbot/{tasks => utils}/Scheduler.php | 13 +- src/cqbot/utils/StatusParser.php | 29 + src/cqbot/utils/WSConnection.php | 70 --- src/framework/{Buffer.php => Cache.php} | 55 +- src/framework/Console.php | 32 +- src/framework/Framework.php | 79 +-- .../framework/global_functions.php | 79 ++- src/framework/loader.php | 9 - start.php | 45 +- tmp.md | 1 - wizard.php | 40 +- 41 files changed, 1103 insertions(+), 997 deletions(-) rename src/cqbot/{item => api}/CQ.php (89%) create mode 100644 src/cqbot/api/CQAPI.php create mode 100644 src/cqbot/connection/CustomWSConnection.php create mode 100644 src/cqbot/connection/NullConnection.php create mode 100644 src/cqbot/connection/RobotWSConnection.php create mode 100644 src/cqbot/connection/WSConnection.php delete mode 100644 src/cqbot/event/server/APIConnectEvent.php create mode 100644 src/cqbot/event/server/ServerEvent.php delete mode 100644 src/cqbot/handler/CQAPIHandler.php delete mode 100755 src/cqbot/loader.php create mode 100644 src/cqbot/utils/ConnectionManager.php rename src/cqbot/{tasks => utils}/Scheduler.php (58%) create mode 100644 src/cqbot/utils/StatusParser.php delete mode 100644 src/cqbot/utils/WSConnection.php rename src/framework/{Buffer.php => Cache.php} (61%) rename tools.php => src/framework/global_functions.php (60%) delete mode 100644 src/framework/loader.php delete mode 100644 tmp.md diff --git a/composer.json b/composer.json index 3e4eb574..1cd67daa 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,9 @@ { + "description": "high-performance intelligent assistant", + "minimum-stability": "stable", + "license": "proprietary", "require": { - "php": ">=7.2.0", + "php": ">=7.0.0", "eaglewu/swoole-ide-helper": "dev-master", "ext-mbstring": "^7.1", "ext-json": "*", diff --git a/cqbot.json b/cqbot.json index 8396e41b..3b4a5f42 100644 --- a/cqbot.json +++ b/cqbot.json @@ -1,15 +1,20 @@ { "robot_name": "CQBot", - "framework_version": "1.0.0", "cqbot_version": "1.0.0", "config_dir": "cq_data\/config\/", "crash_dir": "cq_data\/crash\/", - "user_dir": "cq_data/users\/", + "user_dir": "cq_data\/users\/", "cq_data": "cq_data\/", "admin_group": "", - "host": "", - "port": "", "access_token": "", "info_level": 0, - "admin": [] + "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/src/cqbot/CQBot.php b/src/cqbot/CQBot.php index 9ccfb739..fa2eaa7c 100755 --- a/src/cqbot/CQBot.php +++ b/src/cqbot/CQBot.php @@ -19,7 +19,7 @@ class CQBot public $starttime; public $endtime; - public $current_id; + public $self_id; public $circle; public function __construct(Framework $framework, $circle, $package) { @@ -27,26 +27,26 @@ class CQBot $this->starttime = microtime(true); $this->framework = $framework; $this->data = $package; - $this->current_id = $this->data["self_id"]; + $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"] == Buffer::get("admin_group")) { - if ($this->getRobotId() != Buffer::get("admin_active")) { + 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 (Buffer::get("mods") as $v) { + foreach (Cache::get("mods") as $v) { /** @var ModBase $r */ $r = new $v($this, $this->data); - if ($r->function_call === false) { + if ($r->split_execute === true) { $msg = trim($this->data["message"]); - $msg = explode(" ", $msg); + $msg = explodeMsg($msg); $r->execute($msg); } } @@ -54,51 +54,41 @@ class CQBot return $this->function_called; } - public function reply($msg){ + /** + * 快速回复消息 + * @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->sendGroupMsg($this->data["group_id"], $msg); - break; + $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->sendPrivateMsg($this->data["user_id"], $msg); - break; + $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": - $reply = json_encode(["action" => "send_discuss_msg", "params" => ["discuss_id" => $this->data["discuss_id"], "message" => $msg]]); - $connect = CQUtil::getApiConnectionByQQ($this->current_id); - if (CQUtil::sendAPI($connect->fd, $reply, ["send_discuss_msg"])) { - $out_count = Buffer::$out_count->get(); - if (Buffer::$data["info_level"] == 2) { - Console::put("************API PUSHED***********"); - } - if (Buffer::$data["info_level"] >= 1) { - Console::put(Console::setColor(date("H:i:s "), "lightpurple") . Console::setColor("[$out_count]REPLY", "blue") . Console::setColor(" > ", "gray") . json_decode($reply, true)['params']["message"]); - } - Buffer::$out_count->add(1); - } - break; + $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 sendGroupMsg($groupId, $msg){ - $this->function_called = true; - CQUtil::sendGroupMsg($groupId, $msg, $this->current_id); - } - - public function sendPrivateMsg($userId, $msg){ - $this->function_called = true; - CQUtil::sendPrivateMsg($userId, $msg, $this->current_id); - } - - public function isAdmin($user){ - if (in_array($user, Buffer::get("admin"))) return true; + public function isAdmin($user) { + if (in_array($user, Cache::get("admin"))) return true; else return false; } - public function replace($msg, $dat){ + 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) { diff --git a/src/cqbot/item/CQ.php b/src/cqbot/api/CQ.php similarity index 89% rename from src/cqbot/item/CQ.php rename to src/cqbot/api/CQ.php index 0b47f69c..58b99f23 100644 --- a/src/cqbot/item/CQ.php +++ b/src/cqbot/api/CQ.php @@ -209,4 +209,23 @@ class CQ $str = str_replace("}}", "]", $str); return $str; } + + + 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/api/CQAPI.php b/src/cqbot/api/CQAPI.php new file mode 100644 index 00000000..7f1de52d --- /dev/null +++ b/src/cqbot/api/CQAPI.php @@ -0,0 +1,210 @@ + $msg, "group_id" => Cache::get("admin_group")]); + } + return false; + } + + 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]; + } + 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 new file mode 100644 index 00000000..954fcb81 --- /dev/null +++ b/src/cqbot/connection/CustomWSConnection.php @@ -0,0 +1,16 @@ +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 new file mode 100644 index 00000000..59be835e --- /dev/null +++ b/src/cqbot/connection/NullConnection.php @@ -0,0 +1,37 @@ +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 new file mode 100644 index 00000000..f337e9b1 --- /dev/null +++ b/src/cqbot/connection/RobotWSConnection.php @@ -0,0 +1,134 @@ + "小马哥", + "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()) { + $this->getServer()->close($k); + ConnectionManager::remove($k); + } + } + 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" => "[CrazyBot] 机器人 " . $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::unset("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::unset("sent_api", $data["echo"]); + return false; + } + } + + /** + * @return mixed + */ + public function getSubType() { + return $this->sub_type; + } +} \ No newline at end of file diff --git a/src/cqbot/connection/WSConnection.php b/src/cqbot/connection/WSConnection.php new file mode 100644 index 00000000..95e76a25 --- /dev/null +++ b/src/cqbot/connection/WSConnection.php @@ -0,0 +1,64 @@ +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/post/MessageEvent.php b/src/cqbot/event/post/MessageEvent.php index 72d135b1..1e2486e8 100644 --- a/src/cqbot/event/post/MessageEvent.php +++ b/src/cqbot/event/post/MessageEvent.php @@ -11,10 +11,10 @@ class MessageEvent extends Event public function __construct($req) { if (CQUtil::isRobot($req["user_id"])) return; - $in_count = Buffer::$in_count->get(); - Buffer::$in_count->add(1); - if (Buffer::$data["info_level"] >= 1) { - $num = CQUtil::getRobotNum($req["self_id"]); + $in_count = Cache::$in_count->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"]); } diff --git a/src/cqbot/event/post/NoticeEvent.php b/src/cqbot/event/post/NoticeEvent.php index 7bb8c2c0..64a3a323 100644 --- a/src/cqbot/event/post/NoticeEvent.php +++ b/src/cqbot/event/post/NoticeEvent.php @@ -9,6 +9,11 @@ class NoticeEvent extends Event { public function __construct($req) { - + foreach(Cache::get("mods") as $k => $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 index c4c332cb..6da270c1 100644 --- a/src/cqbot/event/post/RequestEvent.php +++ b/src/cqbot/event/post/RequestEvent.php @@ -9,39 +9,11 @@ class RequestEvent extends Event { public function __construct($req) { - switch ($req["request_type"]) { - case "friend": - //好友添加请求处理 - $comment = $req["comment"];//验证信息 - $flag = $req["flag"];//添加好友用的flag,用于处理好友 - $user_id = $req["user_id"]; - - //如果不需要将好友添加信息发到群里,请注释下面这行 - CQUtil::sendDebugMsg("有用户请求添加好友啦!\n用户QQ:" . $user_id . "\n验证信息:" . $comment . "\n添加flag:" . $flag, $req["self_id"], 0); - - //如果不需要要自动同意,请注释下面这几行 - $params = ["flag" => $flag, "approve" => true]; - CQUtil::sendAPI(CQUtil::getApiConnectionByQQ($req["self_id"])->fd, ["action" => "set_friend_add_request", "params" => $params], ["set_friend_add_request", $req["user_id"]]); - CQUtil::sendGroupMsg(Buffer::get("admin_group"), "已自动同意 " . $req["user_id"], $req["self_id"]); - break; - case "group": - switch ($req["sub_type"]) { - case "invite": - $comment = $req["comment"];//验证信息 - $user_id = $req["user_id"];//用户QQ - $group_id = $req["group_id"];//被邀请进群的群号 - $flag = $req["flag"];//同意加群用的flag,用于处理是否加群 - //TODO: 邀请登录号入群事件处理 - break; - case "add": - $comment = $req["comment"];//验证信息 - $user_id = $req["user_id"];//用户QQ - $group_id = $req["group_id"];//用户申请加群的群号 - $flag = $req["flag"];//同意加群用的flag,用于处理是否同意其入群 - //TODO: 机器人是管理员,处理入群请求事件 - break; - - } + foreach(Cache::get("mods") as $k => $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/server/APIConnectEvent.php b/src/cqbot/event/server/APIConnectEvent.php deleted file mode 100644 index 03891d07..00000000 --- a/src/cqbot/event/server/APIConnectEvent.php +++ /dev/null @@ -1,20 +0,0 @@ - '小马哥' - //这里放机器人连接的别名 - //"QQ" => "别名" - ]; - CQUtil::sendDebugMsg("[CQBot] 机器人 " . ($alias[$connection->getQQ()] ?? $connection->getQQ()) . " 已连接", strval($connection->getQQ()), false); - Buffer::set("admin_active", $connection->getQQ()); - } -} \ No newline at end of file diff --git a/src/cqbot/event/server/HTTPEvent.php b/src/cqbot/event/server/HTTPEvent.php index bdfab5a4..c7d65822 100644 --- a/src/cqbot/event/server/HTTPEvent.php +++ b/src/cqbot/event/server/HTTPEvent.php @@ -6,7 +6,7 @@ * Time: 下午2:05 */ -class HTTPEvent extends Event +class HTTPEvent extends ServerEvent { public $content; public $isValid = false; @@ -17,12 +17,13 @@ class HTTPEvent extends Event * @param swoole_http_response $response */ public function __construct(swoole_http_request $request, swoole_http_response $response) { + parent::__construct(Cache::$server); $response->end("Hello world!"); - //此为HTTP请求的回复,更多设置回复头、传送文件、POST、GET请求解析等内容请查阅文档https://www.swoole.com + //此为HTTP请求的回复,更多设置回复头、传送文件、POST、GET请求解析等内容请查阅文档https://wiki.swoole.com } /** - * 此函数为炸毛机器人中的函数,为此预留 + * 此函数为一个示例的检测有效的函数,为此预留 * 作用是判断传入的请求数据是合法的 * @param $param * @return bool diff --git a/src/cqbot/event/server/ServerEvent.php b/src/cqbot/event/server/ServerEvent.php new file mode 100644 index 00000000..4b7b4d63 --- /dev/null +++ b/src/cqbot/event/server/ServerEvent.php @@ -0,0 +1,16 @@ +server = $server; + } +} \ No newline at end of file diff --git a/src/cqbot/event/server/WSCloseEvent.php b/src/cqbot/event/server/WSCloseEvent.php index f8c3ce99..744dd770 100644 --- a/src/cqbot/event/server/WSCloseEvent.php +++ b/src/cqbot/event/server/WSCloseEvent.php @@ -6,12 +6,16 @@ * Time: 下午4:14 */ -class WSCloseEvent extends Event +class WSCloseEvent extends ServerEvent { public function __construct(swoole_server $server, int $fd) { - $connect = CQUtil::getConnection($fd); - if ($connect !== null) - unset(Buffer::$connect[$fd]); - Console::info("WebSocket Connection closed. fd: " . $fd); + parent::__construct($server); + $connect = ConnectionManager::get($fd); + if ($connect !== null) { + ConnectionManager::remove($fd); + Console::info("WebSocket Connection closed. fd: " . $fd); + } else { + Console::info("Unknown WS or HTTP Connection closed. fd: ".$fd); + } } } \ No newline at end of file diff --git a/src/cqbot/event/server/WSMessageEvent.php b/src/cqbot/event/server/WSMessageEvent.php index b2f03086..ab045adb 100644 --- a/src/cqbot/event/server/WSMessageEvent.php +++ b/src/cqbot/event/server/WSMessageEvent.php @@ -6,39 +6,58 @@ * Time: 下午4:04 */ -class WSMessageEvent extends Event +class WSMessageEvent extends ServerEvent { public function __construct(swoole_websocket_server $server, swoole_websocket_frame $frame) { + parent::__construct($server); $fd = $frame->fd; $req = json_decode($frame->data, true); - $connect = CQUtil::getConnection($fd); - if ($connect === null) { + $conn = ConnectionManager::get($fd); + if ($conn === null) { Console::info("收到一个未知链接发来的消息。" . $fd); return; } - switch ($connect->getType()) { - case 0: + switch ($conn->getType()) { + case "robot": //处理酷Q HTTP事件接口消息(Event) - 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; + 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); + Cache::unset("sent_api", $req["echo"]); + } } - case 1: - Console::debug("收到来自API[" . $fd . "]连接的回复:" . json_encode($req, 128 | 256)); - if (isset($req["echo"])) CQAPIHandler::execute($req["echo"], $req); break; default: Console::info("收到未知链接的消息, 来自: " . $fd); diff --git a/src/cqbot/event/server/WSOpenEvent.php b/src/cqbot/event/server/WSOpenEvent.php index f4d10cdf..2946673f 100644 --- a/src/cqbot/event/server/WSOpenEvent.php +++ b/src/cqbot/event/server/WSOpenEvent.php @@ -6,9 +6,10 @@ * Time: 下午4:10 */ -class WSOpenEvent extends Event +class WSOpenEvent extends ServerEvent { public function __construct(swoole_websocket_server $server, swoole_http_request $request) { + parent::__construct($server); $fd = $request->fd; $get = $request->get; $header = $request->header; @@ -21,15 +22,10 @@ class WSOpenEvent extends Event $server->close($fd); return; } - if ($access_token == "") { - Console::info("未指定连接token,关闭连接."); - $server->close($fd); - return; - } //if (isset($request->header["authorization"])) { //$tokens = explode(" ", $request->header["authorization"]); //$tokens = trim($tokens[1]); - if ($access_token !== Buffer::get("access_token")) { + if ($access_token !== Cache::get("access_token") && Cache::get("access_token") != "") { Console::info("监测到WS连接,但是token不对,无法匹配。"); $server->close($fd); return; @@ -37,17 +33,24 @@ class WSOpenEvent extends Event 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); - CQUtil::getConnection($fd, $connect_type, $self_id); - $robots = []; - foreach (Buffer::get("robots") as $v) { - $robots[] = $v["qq"]; - } - if (!in_array($self_id, $robots)) { - Buffer::append("robots", ["qq" => $self_id, "addr" => $request->server["remote_addr"]]); + $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 index f26eef7c..0e17b3a2 100644 --- a/src/cqbot/event/server/WorkerStartEvent.php +++ b/src/cqbot/event/server/WorkerStartEvent.php @@ -6,19 +6,25 @@ * Time: 下午3:15 */ -class WorkerStartEvent extends Event +class WorkerStartEvent extends ServerEvent { - public function __construct(swoole_server $server, $worker_id){ - Buffer::$reload_time->add(1); - Buffer::$connect = []; + public function __construct(swoole_server $server, $worker_id) { + parent::__construct($server); + Cache::$server = $server; + load_extensions(); + Cache::$reload_time->add(1); CQUtil::loadAllFiles(); + $set = settings(); foreach (get_included_files() as $file) Console::debug("Loaded " . $file); //计时器(ms) - $this->getFramework()->scheduler = new Scheduler($this->getFramework()); - $server->tick(1000, [$this->getFramework(), "processTick"]); + 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("\n==========STARTUP DONE==========\n"); + Console::put("===== Worker " . Console::setColor("#" . $worker_id, "gold") . " 已启动 ====="); } } \ No newline at end of file diff --git a/src/cqbot/handler/CQAPIHandler.php b/src/cqbot/handler/CQAPIHandler.php deleted file mode 100644 index c49c6192..00000000 --- a/src/cqbot/handler/CQAPIHandler.php +++ /dev/null @@ -1,92 +0,0 @@ - $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" => [$self_id] - ]; - } elseif (!in_array($self_id, $list[$v["group_id"]]["joiner"])) { - $list[$v["group_id"]]["joiner"][] = $self_id; - } - } - Buffer::set("group_list", $list); - break; - } - } - - /** - * 更新好友列表API - * @param $self_id - * @param $param - * @param $data - */ - static function getFriendList($self_id, $param, $data) { - switch ($param[0]) { - case "step1": - foreach ($data as $k => $v) { - $user = CQUtil::getUser($v["user_id"]); - $ls = $user->getFriend(); - if (!in_array($self_id, $ls)) { - $ls[] = $self_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); - } - Buffer::set("user", []); - break; - } - } -} \ No newline at end of file diff --git a/src/cqbot/loader.php b/src/cqbot/loader.php deleted file mode 100755 index f8f18daa..00000000 --- a/src/cqbot/loader.php +++ /dev/null @@ -1,26 +0,0 @@ -split_execute = true; } public static function initValues() { - Buffer::set("msg_speed", []);//消息速度列表(存的是时间戳) - Buffer::set("admin_active", ""); + 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 ($tick % 21600 == 0) {//21600秒刷新一次好友列表 - GroupManager::updateGroupList(); - FriendManager::updateFriendList(); + 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"://管理员重载代码 + case "reload": //管理员重载代码 $this->reply("正在重新启动..."); - if (file_get_contents("/home/ubuntu/CrazyBotFramework/src/Framework.php") != Buffer::get("res_code")) + if (file_get_contents("/home/ubuntu/CrazyBotFramework/src/Framework.php") != Cache::get("res_code")) $this->reply("检测到改变了Framework文件的内容!如需完全重载,请重启完整进程!"); CQUtil::reload(); return true; - case "stop"://管理员停止server + case "stop": //管理员停止server $this->reply("正在停止服务器..."); CQUtil::stop(); return true; - case "op"://添加管理员 - $user = $it[1]; - Buffer::append("admin", $user); - $this->reply("added operator $user"); - return true; - case "deop"://删除管理员 - $user = $it[1]; - if (Buffer::in_array("admin", $user)) Buffer::unsetByValue("admin", $user); - $this->reply("removed operator $user"); - return true; } return false; } diff --git a/src/cqbot/mods/Example.php b/src/cqbot/mods/Example.php index dab29c3d..5f714bc4 100644 --- a/src/cqbot/mods/Example.php +++ b/src/cqbot/mods/Example.php @@ -5,24 +5,26 @@ * 然后修改其class的名字(注意要和.php文件的文件名相同) * 例如,新建一个Mailer模块,则Mailer模块的文件名字为 * Mailer.php - * 然后更改class Entertain为class Mailer即可 - * 功能在execute中编写即可 - * 如果需要写判断所有文本的功能,则直接在__construct的parent::__construct下方编写自己的内容即可 + * 如果要开启框架的切割函数激活,请在__construct构造函数中 + * 添加一句:$this->split_execute = true; + * 默认不会执行execute函数 */ class Example extends ModBase { - protected $cmds; - public function __construct(CQBot $main, $data) { parent::__construct($main, $data); - $message = $data["message"]; - - //这里可以写一些匹配所有文本的功能,例如下面的一个简单的debug - if (mb_substr($message, 0, 3) == "dbg") { - $this->reply("你输入了:" . mb_substr($message, 3)); - } + //$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": diff --git a/src/cqbot/mods/Help.php b/src/cqbot/mods/Help.php index 7f08e79c..7af0d85e 100644 --- a/src/cqbot/mods/Help.php +++ b/src/cqbot/mods/Help.php @@ -8,7 +8,10 @@ class Help extends ModBase { - public function __construct(CQBot $main, $data) { parent::__construct($main, $data); } + public function __construct(CQBot $main, $data) { + parent::__construct($main, $data); + $this->split_execute = true; + } public function execute($it) { switch ($it[0]) { @@ -22,7 +25,6 @@ class Help extends ModBase $msg .= "\nCQBot采用关键词系统,你可以直接像现有源码一样添加case在switch里面,"; $msg .= "\n也可以自己新建一个任意名称的Mod名称,例如Entertain.php,你可以在里面编写娱乐功能。"; $msg .= "\n你可以直接复制框架中Example.php文件的内容进行编辑。"; - $msg .= "\n你也可以在tasks/Scheduler.php中tick函数里添加自己的定时执行的功能。"; $msg .= "\n预先封装好的机器人函数均在CQUtil类中,只需直接使用静态方法调用即可!"; $msg .= "\n更多示例功能会逐渐添加到框架中,记得更新哦~"; $this->reply($msg); diff --git a/src/cqbot/mods/ModBase.php b/src/cqbot/mods/ModBase.php index 6175e778..aca924cd 100755 --- a/src/cqbot/mods/ModBase.php +++ b/src/cqbot/mods/ModBase.php @@ -6,38 +6,36 @@ * Time: 10:39 */ +/** + * Class ModBase + * @method static onRequest($req) + * @method static onNotice($req) + */ abstract class ModBase { protected $main; protected $data; - public $call_task = false; /** - * 控制模块是否调用execute函数的变量 - * 当function_call 为 TRUE时,表明CQBot主实例不需要调用execute函数 - * 当为FALSE时,CQBot在实例化模块对象后会执行execute函数 + * 控制模块是否调用分割函数的变量 + * 当split 为FALSE时,表明CQBot主实例不需要调用execute函数 + * 当为TRUE时,CQBot在实例化模块对象后会执行execute函数 * @var bool */ - public $function_call = false; + public $split_execute = false; public function __construct(CQBot $main, $data) { $this->main = $main; $this->data = $data; } - public function getUser($data = null) { - return CQUtil::getUser($data === null ? $this->data["user_id"] : $data["user_id"]); - } + 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 execute($it) { } - - public function reply($msg) { $this->main->reply($msg); } - - public function sendPrivateMsg($user, $msg) { $this->main->sendPrivateMsg($user, $msg); } - - public function sendGroupMsg($user, $msg) { $this->main->sendGroupMsg($user, $msg); } + public function reply($msg, callable $callback = null) { return $this->main->reply($msg, $callback); } public function getMessageType() { return $this->data["message_type"]; } diff --git a/src/cqbot/utils/CQUtil.php b/src/cqbot/utils/CQUtil.php index a5f9ec79..35ee8877 100755 --- a/src/cqbot/utils/CQUtil.php +++ b/src/cqbot/utils/CQUtil.php @@ -10,17 +10,27 @@ use DataProvider as DP; class CQUtil { - public static function loadAllFiles() { - Console::debug("loading configs..."); - Buffer::set("mods", self::getMods());//加载的模块列表 - Buffer::set("user", []);//清空用户列表 - Buffer::set("time_send", false);//发送Timing数据到管理群 - Buffer::set("res_code", file_get_contents(WORKING_DIR . "src/framework/Framework.php")); - Buffer::set("group_list", DP::getJsonData("group_list.json"));//获取群组列表 + public static function initEmptyCaches() { + $ls = [ + "user", // 储存用户对象的数组 + "sent_api", // 储存每条API请求的会调函数Closure等原始内容 + "msg_speed", // 储存记录消息速度的数组 + "bug_msg_list" // 储存当前状态下所有未发出去的消息列表 + ]; + foreach ($ls as $v) { + Cache::set($v, []); + } + } + public static function loadAllFiles() { + Cache::set("info_level", settings()["info_level"]); + Console::debug("loading configs..."); + Cache::set("mods", self::getMods());//加载的模块列表 + Cache::set("group_list", DP::getJsonData("group_list.json"));//获取群组列表 + Cache::set("admin_group", settings()["admin_group"]); //加载全局屏蔽的机器人列表 - Buffer::set("bots", DP::getJsonData("bots.json")); + Cache::set("bots", DP::getJsonData("bots.json")); //调用各个模块单独的Buffer数据 foreach (self::getMods() as $v) { @@ -31,18 +41,20 @@ class CQUtil } public static function saveAllFiles() { - Console::info("Saving files..."); - - DP::setJsonData("bots.json", Buffer::get("bots")); - DP::setJsonData("group_list.json", Buffer::get("group_list")); + Console::info("Saving files... ", null, ""); + DP::setJsonDataAsync("bots.json", Cache::get("bots")); + DP::setJsonDataAsync("group_list.json", Cache::get("group_list")); //保存用户数据 - foreach (self::getAllUsers() as $k => $v) { - $serial = serialize($v); - file_put_contents(DP::getUserFolder() . $k . ".dat", $serial); + if (settings()["save_user_data"]) { + foreach (self::getAllUsers() as $k => $v) { + $serial = serialize($v); + file_put_contents(DP::getUserFolder() . $k . ".dat", $serial); + } } - Console::info("Saved files"); + + Console::put("Saved files."); } /** @@ -56,243 +68,16 @@ class CQUtil $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) - self::sendDebugMsg($msg); - } - - /** - * 发送调试信息到管理群(需先设置管理群号) - * @param $msg - * @param $self_id - * @param int $need_head - * @return null - */ - static function sendDebugMsg($msg, $self_id = null, $need_head = 1) { - if ($self_id === null) $self_id = self::findRobot(); - if ($self_id === null) return null; - - if ((Buffer::get("admin_group") ?? "") == "") return null; - if ($need_head) - $data = CQMsg("[DEBUG] " . date("H:i:s") . ": " . $msg, "group", Buffer::get("admin_group")); - else - $data = CQMsg($msg, "group", Buffer::get("admin_group")); - $connect = CQUtil::getApiConnectionByQQ($self_id); - return self::sendAPI($connect->fd, $data, ["send_debug_msg"]); + if ($send_debug_message) CQAPI::debug($msg); } static function findRobot() { - foreach (self::getConnections("api") as $v) { + foreach (ConnectionManager::getAll("robot") as $v) { return $v->getQQ(); } return null; } - /** - * 推送API,给API端口 - * @param $fd - * @param $data - * @return bool - */ - static function APIPush($fd, $data) { - if ($data == null || $data == "") { - Console::error("EMPTY DATA PUSH"); - return false; - } - /*if (self::checkAPIConnection() === -1) { - //忽略掉framework链接API之前的消息 - self::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", 0); - return false; - } - if (self::checkAPIConnection() === 0) { - self::APIPushAfterConnected($data); - return true; - }*/ - if (Buffer::$event->push($fd, $data) === false) { - $data = self::unicodeDecode($data); - self::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", 0); - return false; - } - return true; - } - - /** - * 延迟推送在API连接断开后收到的消息函数//待定 - */ - /*static function APIPushDelayMsg() { - $delay_push_list = Buffer::get("delay_push"); - $cur_time = time(); - foreach ($delay_push_list as $item) { - $data = $item["data"]; - $time = $item["time"]; - if ($cur_time - $time <= 10) { - self::APIPush($data); - } - } - Buffer::set("delay_push", []); - }*/ - - /** - * 推迟推送API,用于酷Q重启后的重新连接API//待定 - * @param $data - */ - static function APIPushAfterConnected($data) { - $delay_push_list = Buffer::get("delay_push"); - $delay_push_list[] = ["data" => $data, "time" => time()]; - Buffer::set("delay_push", $delay_push_list); - } - - /** - * 解码unicode中文编码 - * @param $str - * @return null|string|string[] - */ - static 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); - } - - /** - * 模拟发送一个HTML-get请求 - * @param $url - * @return mixed - */ - static function getHTML($url) { - $ch = curl_init(); - $timeout = 5; - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/39.0'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); - $r = curl_exec($ch); - curl_close($ch); - return $r; - } - - /** - * 获取字符串的反转结果 - * @param $str - * @param string $encoding - * @return string - */ - static public function getRev($str, $encoding = 'utf-8') { - $result = ''; - $len = mb_strlen($str); - for ($i = $len - 1; $i >= 0; $i--) { - $result .= mb_substr($str, $i, 1, $encoding); - } - return $result; - } - - /** - * 发送邮件功能,基于PHPMailer模块,需先安装phpmailer。默认此工程demo里已包含有phpmailer了。 - * 请根据实际自己的邮箱更新下面的用户名、密码、smtp服务器地址、端口等。 - * 此功能非基于本作者编写的代码,如有问题请在github上找PHPMailer项目进行反馈 - * @param $address - * @param $title - * @param $content - * @param $self_id - * @param string $name - * @param int $send_debug_message - * @return bool|string - */ - static function sendEmail($address, $title, $content, $self_id, $name = "CQ开发团队", $send_debug_message = 1) { - $mail = new \PHPMailer(true); - try { - $mail->isSMTP(); - $mail->Host = 'here your smtp host'; - $mail->SMTPAuth = true; - $mail->Username = 'here your mailbox address'; - $mail->Password = 'here your password'; - $mail->SMTPSecure = 'ssl'; - $mail->Port = 465; - $mail->setFrom('here your mailbox address', $name); - if (is_array($address)) { - foreach ($address as $item) - $mail->addAddress($item); - } else { - $mail->addAddress($address); - } - //Content - $mail->isHTML(true); - $mail->Subject = $title; - $mail->CharSet = "UTF-8"; - $mail->Body = $content; - $mail->send(); - if (is_array($address)) - $address = implode(",", $address); - Console::info("向 $address 发送的邮件完成"); - unset($mail); - return true; - } catch (\Exception $e) { - self::errorLog("发送邮件错误!错误信息:" . $info = $mail->ErrorInfo, "ERROR", 0); - unset($mail); - return $info; - } - } - - /** - * 返回所有api、event连接 - * @param string $type - * @return WSConnection[] - */ - static function getConnections($type = "all") { - switch ($type) { - case "all": - return Buffer::$connect; - case "event": - $ls = []; - foreach (Buffer::$connect as $fd => $connection) { - if ($connection->getType() === 0) { - $ls[$fd] = $connection; - } - } - return $ls; - case "api": - $ls = []; - foreach (Buffer::$connect as $fd => $connection) { - if ($connection->getType() === 1) { - $ls[$fd] = $connection; - } - } - return $ls; - default: - Console::error("寻找连接时链接类型传入错误!"); - return []; - } - } - - /** - * @param $fd - * @param null $type - * @param null $qq - * @return WSConnection - */ - static function getConnection($fd, $type = null, $qq = null) { - //var_dump(Buffer::$connect); - if (!isset(Buffer::$connect[$fd]) && $type !== null && $qq !== null) { - Console::info("创建连接 " . $fd . " 中..."); - $s = new WSConnection(Buffer::$event, $fd, $type, $qq); - if ($s->create_success) { - Buffer::$connect[$fd] = $s; - Console::debug("创建成功!"); - $s->initConnection(); - } else return null; - } - return Buffer::$connect[$fd] ?? null; - } - - static function getApiConnectionByQQ($qq) { - foreach (self::getConnections() as $fd => $c) { - if ($c->getType() === 1 && $c->getQQ() == $qq) { - return $c; - } - } - return null; - } - /** * 获取运行时间 * @param $time @@ -347,38 +132,6 @@ class CQUtil return $msg; } - /** - * 检查是否为群组管理员或群主功能,此功能需要先获取群组列表,否则会产生一个warning - * @param $group - * @param $user_id - * @return bool - */ - static function isGroupAdmin($group, $user_id) { - $ls = Buffer::get("group_list")[$group]["member"]; - $is_admin = false; - foreach ($ls as $k => $v) { - if ($v["user_id"] == $user_id) { - if ($v["role"] == "admin" || $v["role"] == "owner") { - $is_admin = true; - break; - } - } - } - return $is_admin; - } - - /** - * 用于发送错误日志邮件的功能,请根据实际情况填写邮箱。 - * 此功能基于sendMail,请看上方sendMail函数的介绍 - * @param $title - * @param $content - * @param $self_id - * @param string $name - */ - static function sendErrorEmail($title, $content, $self_id, $name = "机器人错误提示") { - self::sendEmail(["here your receive email address"], $title, $content, $self_id, $name, 0); - } - /** * 获取所有已经加载到内存的用户。 * read_all为true时,会加载所有User.dat到内存中,false时仅会读取已经加载到内存的用户 @@ -393,25 +146,27 @@ class CQUtil $vs = explode(".", $v); if (array_pop($vs) == "dat") { $class = unserialize(file_get_contents(DP::getUserFolder() . $v)); - if (!Buffer::array_key_exists("user", $vs[0])) { - Buffer::appendKey("user", $vs[0], $class); + if (!Cache::array_key_exists("user", $vs[0])) { + Cache::appendKey("user", $vs[0], $class); } } } } - return Buffer::get("user"); + return Cache::get("user"); } /** * 获取用户实例 * @param $id + * @param bool $enable_init * @return User */ - static function getUser($id) { - $d = Buffer::get("user"); + static function getUser($id, $enable_init = true) { + $d = Cache::get("user"); if (!isset($d[$id])) { - self::initUser($id); - $d = Buffer::get("user"); + $r = self::initUser($id, $enable_init); + if (!$r) return null; + $d = Cache::get("user"); } /** @var User $class */ $class = $d[$id]; @@ -421,103 +176,19 @@ class CQUtil /** * 初始化用户实例。如果没有此用户的实例数据,会创建 * @param $id + * @param bool $enable_init + * @return bool */ - static function initUser($id) { + static function initUser($id, $enable_init = true) { if (file_exists(DP::getUserFolder() . $id . ".dat")) $class = unserialize(file_get_contents(DP::getUserFolder() . $id . ".dat")); else { - Console::info("无法找到用户 " . $id . " 的数据,正在创建..."); - $class = new User($id); + if ($enable_init) { + Console::info("无法找到用户 " . $id . " 的数据,正在创建..."); + $class = new User($id); + } else return false; } - Buffer::appendKey("user", $id, $class); - } - - /** - * 发送群组消息,含控制台推出 - * @param $groupId - * @param $msg - * @param string $self_id - * @return bool - */ - static function sendGroupMsg($groupId, $msg, $self_id) { - $reply = ["action" => "send_group_msg", "params" => ["group_id" => $groupId, "message" => $msg]]; - $reply["echo"] = $reply; - $reply["echo"]["time"] = time(); - $connections = CQUtil::getApiConnectionByQQ($self_id); - if ($connections === null) { - Console::error("未找到qq号:" . $self_id . "的API连接"); - return false; - } else { - $api_fd = $connections->fd; - } - if (self::sendAPI($api_fd, $reply, ["send_group_msg"])) { - if (Buffer::$data["info_level"] == 1) { - $out_count = Buffer::$out_count->get(); - Console::put(Console::setColor(date("H:i:s "), "lightpurple") . Console::setColor("[{$out_count}]GROUP", "blue") . Console::setColor(" " . $groupId, "yellow") . Console::setColor(" > ", "gray") . $msg); - Buffer::$out_count->add(1); - } - return true; - } - return false; - } - - /** - * 发送私聊消息 - * @param $userId - * @param $msg - * @param $self_id - * @return bool - */ - static function sendPrivateMsg($userId, $msg, $self_id) { - $reply = ["action" => "send_private_msg", "params" => ["user_id" => $userId, "message" => $msg]]; - $reply["echo"] = $reply; - $reply["echo"]["time"] = time(); - $connections = CQUtil::getApiConnectionByQQ($self_id); - if ($connections === null) { - Console::error("未找到qq号:" . $self_id . "的API连接"); - return false; - } else { - $api_fd = $connections->fd; - } - if (self::sendAPI($api_fd, $reply, ["send_private_msg"])) { - if (Buffer::$data["info_level"] == 1) { - $out_count = Buffer::$out_count->get(); - Console::put(Console::setColor(date("H:i:s "), "lightpurple") . Console::setColor("[{$out_count}]PRIVATE", "blue") . Console::setColor(" " . $userId, "yellow") . Console::setColor(" > ", "gray") . $msg); - Buffer::$out_count->add(1); - } - return true; - } - return false; - } - - - static function getFriendName($qq) { return Buffer::get("friend_list")[$qq]["nickname"] ?? "unknown"; } - - static function getGroupName($group) { return Buffer::get("group_list")[$group]["group_name"] ?? "unknown"; } - - /** - * 发送其他API,HTTP插件支持的其他API都可以发送。 - * echo是返回内容,可以在APIHandler.php里面解析 - * @param $fd - * @param $data - * @param $echo - * @return bool - */ - static function sendAPI($fd, $data, $echo) { - if (!is_array($data)) { - $api = []; - $api["action"] = $data; - } else { - $api = $data; - } - $rw = $echo; - $echo = [ - "self_id" => self::getConnection($fd)->getQQ(), - "type" => array_shift($rw), - "params" => $rw - ]; - $api["echo"] = $echo; - Console::info("将要发送的API包:" . json_encode($api, 128 | 256)); - return self::APIPush($fd, json_encode($api)); + Cache::appendKey("user", $id, $class); + return true; } /** @@ -536,26 +207,33 @@ class CQUtil 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; } - /** - * 判断模块是否存在 - * @param $mod_name - * @return bool - */ - static function isModExists($mod_name) { - $ls = self::getMods(); - return in_array($mod_name, $ls); - } - /** * 重启框架,此服务重启为全自动的 */ static function reload() { Console::info("Reloading server"); self::saveAllFiles(); - Buffer::$event->reload(); + Cache::$server->reload(); } /** @@ -564,8 +242,7 @@ class CQUtil static function stop() { Console::info("Stopping server..."); self::saveAllFiles(); - Buffer::$api->close(); - Buffer::$event->shutdown(); + Cache::$server->shutdown(); } /** @@ -656,41 +333,20 @@ class CQUtil } } - 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]; - } - static function isRobot($user_id) { $robots = []; - foreach (Buffer::get("robots") as $v) { - $robots[] = $v["qq"]; + foreach (ConnectionManager::getAll("robot") as $v) { + if (!in_array($v->getQQ(), $robots)) + $robots[] = $v->getQQ(); } - foreach (Buffer::get("bots") as $v) { + foreach (Cache::get("bots") as $v) { $robots[] = $v; } return in_array($user_id, $robots); } - static function getRobotNum($qq) { - $ls = Buffer::get("robots"); - foreach ($ls as $k => $v) { - if ($v["qq"] == $qq) - return $k; - } - return null; + static function getRobotAlias($qq) { + return RobotWSConnection::ALIAS_LIST[$qq] ?? "机器人"; } /** @@ -699,7 +355,7 @@ class CQUtil * @param bool $insert */ static function updateMsg($insert = true) { - $ls = Buffer::get("msg_speed"); + $ls = Cache::get("msg_speed"); if ($insert === true) $ls [] = time(); foreach ($ls as $k => $v) { @@ -707,6 +363,6 @@ class CQUtil array_splice($ls, $k, 1); } } - Buffer::set("msg_speed", $ls); + 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 new file mode 100644 index 00000000..2bf938ba --- /dev/null +++ b/src/cqbot/utils/ConnectionManager.php @@ -0,0 +1,45 @@ + $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 index defdf409..de1d7959 100755 --- a/src/cqbot/utils/DataProvider.php +++ b/src/cqbot/utils/DataProvider.php @@ -6,6 +6,10 @@ * Time: 12:54 */ +/** + * 此类中使用的读取和写入文件等IO有区分同步IO和异步IO,请(尽量)不要在事件循环中使用过多阻塞IO的方法。 + * 如使用同步逻辑,推荐将数据写入内存缓存类Cache中后进行读写,使用定时器或关闭服务时储存。 + */ class DataProvider { /** @@ -35,11 +39,26 @@ class DataProvider } /** - * 储存PHP数组为json文件,文件不存在则会创建文件 + * 储存PHP数组为json文件,文件不存在则会创建文件。 + * 此方式为同步阻塞执行,可能会阻塞worker进程。 * @param $filename * @param array $args */ static function setJsonData($filename, array $args){ file_put_contents(self::getDataFolder() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING)); } + + /** + * 储存PHP数组为json文件,文件不存在会创建文件 + * 此方式为异步非阻塞执行,不会对worker造成阻塞。 + * @param $filename + * @param array $args + * @param callable|null $function + */ + static function setJsonDataAsync($filename, array $args, callable $function = null) { + $data = json_encode($args, 128 | 256); + $filename = self::getDataFolder() . $filename; + if ($function === null) swoole_async_writefile($filename, $data, function () { }); + else swoole_async_writefile($filename, $data, $function); + } } \ No newline at end of file diff --git a/src/cqbot/utils/FriendManager.php b/src/cqbot/utils/FriendManager.php index 70dafcf4..89bda055 100644 --- a/src/cqbot/utils/FriendManager.php +++ b/src/cqbot/utils/FriendManager.php @@ -8,17 +8,35 @@ class FriendManager { - + /** + * 更新所有机器人的好友列表 + */ public static function updateFriendList() { $list = CQUtil::getAllUsers(true); foreach ($list as $k => $v) { $v->setFriend([]); } - foreach (CQUtil::getConnections("api") as $k => $v) { - $fd = $v->fd; - $robot_id = $v->getQQ(); - Console::put("正在获取机器人 " . $robot_id . " 的好友列表..."); - CQUtil::sendAPI($fd, ["action" => "_get_friend_list", "params" => ["flat" => "true"]], ["get_friend_list", "step1"]); + 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 index b7b6b35f..95bffcc4 100644 --- a/src/cqbot/utils/GroupManager.php +++ b/src/cqbot/utils/GroupManager.php @@ -10,15 +10,30 @@ class GroupManager { /** * 更新群组列表 - * @param null $qq */ - public static function updateGroupList($qq = null) { - Buffer::set("group_list", []); - foreach (CQUtil::getConnections("api") as $k => $v) { - $fd = $v->fd; - $robot_id = $v->getQQ(); - Console::put("正在获取机器人 " . $robot_id . " 的群组列表..."); - CQUtil::sendAPI($fd, "get_group_list", ["get_group_list", "step1"]); + public static function updateGroupList() { + Cache::set("group_list", []); + foreach (ConnectionManager::getAll("robot") as $k => $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/tasks/Scheduler.php b/src/cqbot/utils/Scheduler.php similarity index 58% rename from src/cqbot/tasks/Scheduler.php rename to src/cqbot/utils/Scheduler.php index af96c880..01d2c7bb 100644 --- a/src/cqbot/tasks/Scheduler.php +++ b/src/cqbot/utils/Scheduler.php @@ -13,25 +13,30 @@ class Scheduler private $framework; + private $start_time; + /** * ScheduleTask constructor. * @param Framework $framework + * @param $start_time */ - public function __construct(Framework $framework) { + public function __construct(Framework $framework, $start_time) { $this->framework = $framework; self::$obj = $this; + $this->start_time = $start_time; } public static function getInstance() { return self::$obj; } - public function tick($id, $tick_time) { + public function tick($id) { + //Console::info("Timer ".Console::setColor("#".$id, "gold").":".Cache::$server->worker_id." ticking at ".time()); /** @var array $ls */ - $ls = Buffer::get("mods"); + $ls = Cache::get("mods"); foreach ($ls as $v) { if (in_array("onTick", get_class_methods($v))) { - $v::onTick($tick_time - $this->framework->run_time); + $v::onTick(time() - $this->start_time, $id); } } } diff --git a/src/cqbot/utils/StatusParser.php b/src/cqbot/utils/StatusParser.php new file mode 100644 index 00000000..b6320fdd --- /dev/null +++ b/src/cqbot/utils/StatusParser.php @@ -0,0 +1,29 @@ +server = $server; - $this->fd = $fd; - if ($type = null || $qq === null) return; - $this->type = ($type == "event" ? 0 : 1); - $this->qq = $qq; - $this->create_success = true; - } - - /** - * 返回swoole server - * @return swoole_websocket_server - */ - public function getServer() { - return $this->server; - } - - /** - * 返回本连接是什么类型的 - * @return int - */ - public function getType() { - return $this->type; - } - - public function initConnection() { - foreach (CQUtil::getConnections(($this->getType() == 0 ? "event" : "api")) as $k => $v) { - if ($v->getQQ() == $this->getQQ() && $k != $this->fd) { - $this->server->close($k); - unset(Buffer::$connect[$k]); - } - } - if ($this->type === 1) { - new APIConnectEvent($this, $this->server); - } - } - - /** - * @return string - */ - public function getQQ() { - return $this->qq; - } -} \ No newline at end of file diff --git a/src/framework/Buffer.php b/src/framework/Cache.php similarity index 61% rename from src/framework/Buffer.php rename to src/framework/Cache.php index fc83ad91..f976c7c7 100755 --- a/src/framework/Buffer.php +++ b/src/framework/Cache.php @@ -7,26 +7,31 @@ */ -class Buffer +class Cache { + /** @var int */ + static $worker_id = -1; + /** @var array Cache data */ static $data = []; - static $api_session = []; - /** @var \swoole_http_client $api */ - static $api; - /** @var \swoole_websocket_server $event */ - static $event; - /** @var string $log_file */ - static $log_file = ""; - /** @var \swoole_server $comm */ - static $comm = null; + /** @var \swoole_websocket_server $server */ + static $server; + /** @var swoole_http_client $http_client */ + static $http_client; + /** @var Scheduler $scheduler */ + static $scheduler; + + //共享内存的原子操作数据部分 /** @var \swoole_atomic $in_count */ static $in_count;//接收消息 /** @var \swoole_atomic $out_count */ static $out_count;//发送消息数量 - /** @var WSConnection[] */ - static $connect = []; /** @var swoole_atomic $reload_time */ static $reload_time; + /** @var \swoole_atomic $api_id */ + static $api_id; + + /** @var WSConnection[] $connect */ + static $connect = []; static function get($name){ return self::$data[$name] ?? null; } @@ -51,30 +56,4 @@ 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/framework/Console.php b/src/framework/Console.php index 3bb425e3..caeca2e4 100755 --- a/src/framework/Console.php +++ b/src/framework/Console.php @@ -38,7 +38,7 @@ class Console static function debug($obj, $head = null) { if ($head === null) $head = "[" . date("H:i:s") . " DEBUG]"; - if ((Buffer::get("info_level") ?? 0) < 2) return; + if ((Cache::get("info_level") ?? 0) < 2) return; if (!is_string($obj)) var_dump($obj); else echo(self::setColor($head . $obj, "green") . "\n"); } @@ -49,18 +49,36 @@ class Console else echo(self::setColor($head . $obj, "red") . "\n"); } - static function info($obj, $head = null) { + static function info($obj, $head = null, $tail = "\n") { if ($head === null) $head = "[" . date("H:i:s") . " INFO] "; if (!is_string($obj)) var_dump($obj); - else echo(self::setColor($head . $obj, "blue") . "\n"); - } - - static function chatLog($head, $msg) { - + else echo(self::setColor($head . $obj, "blue") . $tail); } static function put($obj, $color = "") { if (!is_string($obj)) var_dump($obj); else echo(self::setColor($obj, $color) . "\n"); } + + public static function msg($obj) { + if (Cache::get("info_level") >= 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/Framework.php b/src/framework/Framework.php index 984aff24..00fdf9e7 100755 --- a/src/framework/Framework.php +++ b/src/framework/Framework.php @@ -8,66 +8,55 @@ class Framework { - public static $super_user; - public $host = "127.0.0.1"; - public $event_port = 20000; + public $host; + public $port; + + const VERSION = "1.0.0"; /** @var \swoole_websocket_server $event */ public $event; public static $obj = null; - public $run_time; - public $info_level = 1; - - /** @var \swoole_http_client $api */ - public $api; - private $log_file; - public $tick_time; - /** @var Scheduler */ public $scheduler = null; public function __construct($config) { - $this->host = $config["host"]; - $this->event_port = $config["port"]; - Buffer::set("access_token", $config["access_token"] ?? ""); - Buffer::set("info_level", $config["info_level"]); - Buffer::set("admin_group", $config["admin_group"]); + $this->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..."); - $this->event = new swoole_websocket_server($this->host, $this->event_port); + Console::info("CQBot Framework starting on " . $this->host . ":" . $this->port); + $this->event = new swoole_websocket_server($this->host, $this->port); - Buffer::$log_file = CRASH_DIR . "swoole.log"; - - //设置swoole基本参数 - $worker_num = 1;//进程数调整,默认为1,如果调整worker为多个,则需要自行修改Buffer类的储存方式到redis、或其他数据库等数据结构 - $dispatch_mode = 2;//解析模式,详见wiki.swoole.com $this->event->set([ - "log_file" => Buffer::$log_file, - "worker_num" => $worker_num, - "dispatch_mode" => $dispatch_mode + "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', [$this, 'onEventMessage']); + $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", [$this, "onRequest"]); + $this->event->on("request", function (\swoole_http_request $request, \swoole_http_response $response) { new HTTPEvent($request, $response); }); //设置原子计数器 - Buffer::$in_count = new \swoole_atomic(1); - Buffer::$out_count = new \swoole_atomic(1); - Buffer::$reload_time = new \swoole_atomic(0); + 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(); } @@ -84,35 +73,9 @@ class Framework */ public function onWorkerStart(\swoole_server $server, $worker_id) { self::$obj = $this; - $this->run_time = time(); - Buffer::set("info_level", $this->info_level);//设置info等级 - Buffer::$event = $server; - require_once(WORKING_DIR . "src/cqbot/loader.php"); new WorkerStartEvent($server, $worker_id); } - /** - * 回调函数:当HTTP插件发来json包后激活此函数 - * @param swoole_websocket_server $server - * @param $frame - */ - public function onEventMessage($server, $frame) { new WSMessageEvent($server, $frame); } - - /** - * 回调函数:当IP:event端口收到相关HTTP请求时候调用 - * 你可在此编写HTTP请求回复的内容(比如做一个web界面?) - * 也可以在这里处理微信公众号的请求(可能需要端口转发) - * @param swoole_http_request $request - * @param swoole_http_response $response - */ - public function onRequest($request, $response) { new HTTPEvent($request, $response); } - - /** - * 回调函数:异步计时器,一秒执行一次。请勿在此使用阻塞IO方法 - * @param $id - */ - public function processTick($id) { $this->scheduler->tick($id, ($this->tick_time = time())); } - /** * 开启时候的自检模块 * 检测项目在下面列举 @@ -123,7 +86,7 @@ class Framework 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(CRASH_DIR . "swoole.log")) file_put_contents(CRASH_DIR . "swoole.log", ""); + 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/tools.php b/src/framework/global_functions.php similarity index 60% rename from tools.php rename to src/framework/global_functions.php index af310ecf..8d490c4b 100644 --- a/tools.php +++ b/src/framework/global_functions.php @@ -2,10 +2,19 @@ /** * Created by PhpStorm. * User: jerry - * Date: 2018/6/14 - * Time: 11:04 AM + * Date: 2018/11/22 + * Time: 2:42 PM */ +/** + * 获取全局配置 + * 此函数使用同步阻塞IO,请不要在功能逻辑代码中使用此函数。 + * @return mixed + */ +function settings() { + return json_decode(file_get_contents(WORKING_DIR . "/cqbot.json"), true); +} + register_shutdown_function(function () { $error = error_get_last(); if (isset($error['type'])) { @@ -68,6 +77,38 @@ function CQMsg($msg, $type, $id) { 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); @@ -81,4 +122,36 @@ function color($str, $end = "\n") { $str = str_replace("{r}", "\e[m", $str); $str .= "\e[m" . $end; return $str; -} \ No newline at end of file +} + +/** + * 使用自己定义的万(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/src/framework/loader.php b/src/framework/loader.php deleted file mode 100644 index 11f7d179..00000000 --- a/src/framework/loader.php +++ /dev/null @@ -1,9 +0,0 @@ -start(); \ No newline at end of file +(new Framework(settings()))->start(); + diff --git a/tmp.md b/tmp.md deleted file mode 100644 index 808ba806..00000000 --- a/tmp.md +++ /dev/null @@ -1 +0,0 @@ -#CQBot-swoole diff --git a/wizard.php b/wizard.php index 1b4f0e23..a01d8827 100644 --- a/wizard.php +++ b/wizard.php @@ -10,46 +10,45 @@ function printHelp() { echo color("{gold}=======CQBot-swoole======="); echo color("{gold}* 首次使用设置 *"); echo color("{red}红色{r}为未设置,{green}绿色{r}为已设置,其他颜色为可选设置"); - echo color("[{green}1{r}] {" . (settings()["host"] == "" ? "red" : "green") . "}设置监听地址"); - echo color("[{green}2{r}] {" . (settings()["port"] == "" ? "red" : "green") . "}设置监听端口"); + echo color("[{green}1{r}] {" . (settings()["swoole_host"] == "" ? "red" : "green") . "}设置监听地址"); + echo color("[{green}2{r}] {" . (settings()["swoole_port"] == "" ? "red" : "green") . "}设置监听端口"); echo color("[{green}3{r}] {" . (settings()["admin_group"] == "" ? "red" : "green") . "}设置管理群"); echo color("[{green}4{r}] {yellow}设置管理员"); echo color("[{green}5{r}] {yellow}设置连接token"); echo color("[{green}6{r}] {lightlightblue}开始运行"); } - +$id = "1"; while (true) { - printHelp(); - echo color("> ", ""); - $id = trim(fgets(STDIN)); switch ($id) { case "1": echo color("请输入监听地址(默认0.0.0.0):", ""); $host = trim(fgets(STDIN)); if ($host == "") { - $properties["host"] = "0.0.0.0"; + $properties["swoole_host"] = "0.0.0.0"; echo color("{gray}已设置地址:0.0.0.0(默认)"); } else { - $properties["host"] = $host; + $properties["swoole_host"] = $host; echo color("{gray}已设置地址:" . $host); } $ls = settings(); - $ls["host"] = $properties["host"]; + $ls["swoole_host"] = $properties["swoole_host"]; file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + $id = 2; break; case "2": echo color("请输入监听端口(默认20000):", ""); $host = trim(fgets(STDIN)); if ($host == "") { - $properties["port"] = 20000; + $properties["swoole_port"] = 20000; echo color("{gray}已设置端口:20000(默认)"); } else { - $properties["port"] = $host; + $properties["swoole_port"] = $host; echo color("{gray}已设置端口:" . $host); } $ls = settings(); - $ls["port"] = $properties["port"]; + $ls["swoole_port"] = $properties["swoole_port"]; file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + $id = 3; break; case "3": echo color("请输入机器人QQ的管理群(机器人必须已经在群内):", ""); @@ -63,22 +62,25 @@ while (true) { $ls = settings(); $ls["admin_group"] = $properties["admin_group"]; file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + $id = 4; break; case "4": - echo color("请输入机器人管理员QQ:", ""); + echo color("请输入机器人管理员QQ(多个用空格分割):", ""); $group = trim(fgets(STDIN)); if ($group == "") { echo color("{red}请勿输入空数据!"); break; } - $properties["admin"][] = $group; - echo color("{gray}已设置机器人的管理员:" . $group); + $s = explodeMsg($group, true); + $properties["admin"] = $s; + echo color("{gray}已设置机器人的管理员:" . implode(", ",$s)); $ls = settings(); $ls["admin"] = $properties["admin"]; file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + $id = 5; break; case "5": - echo color("请输入连接框架的access_token:", ""); + echo color("请输入连接框架的access_token(如果不用token则直接回车):", ""); $group = trim(fgets(STDIN)); $properties["access_token"] = $group; if ($group == "") echo color("{gray}token为空,将不检测验证token!"); @@ -86,7 +88,7 @@ while (true) { $ls = settings(); $ls["access_token"] = $properties["access_token"]; file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); - break; + break 2; case "6": break 2; case "?": @@ -99,5 +101,5 @@ while (true) { break; } } -if (settings()["host"] != "") return true; -else return false; +echo color("{green}设置就绪!3秒后启动服务器。"); +sleep(3);