diff --git a/.gitignore b/.gitignore index fe7871cc..d646888b 100755 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ .idea out gen +config/ +cq_data/ +composer.lock \ No newline at end of file diff --git a/cqbot.json b/cqbot.json new file mode 100644 index 00000000..8396e41b --- /dev/null +++ b/cqbot.json @@ -0,0 +1,15 @@ +{ + "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\/", + "cq_data": "cq_data\/", + "admin_group": "", + "host": "", + "port": "", + "access_token": "", + "info_level": 0, + "admin": [] +} \ No newline at end of file diff --git a/src/cqbot/CQBot.php b/src/cqbot/CQBot.php index f99268be..9ccfb739 100755 --- a/src/cqbot/CQBot.php +++ b/src/cqbot/CQBot.php @@ -20,46 +20,38 @@ class CQBot public $starttime; public $endtime; public $current_id; + public $circle; - public function __construct(Framework $framework, $package) { + public function __construct(Framework $framework, $circle, $package) { + $this->circle = $circle; $this->starttime = microtime(true); $this->framework = $framework; - if ($package === null) return; $this->data = $package; $this->current_id = $this->data["self_id"]; - if ($package["post_type"] == "message") { - try { - $this->callTask($package); - } catch (\Exception $e) { - CQUtil::errorLog("请求执行任务时异常\n" . $e->getMessage(), $this->current_id); - CQUtil::sendDebugMsg("引起异常的消息:\n" . $package["message"], $this->current_id); - } - } } - public function execute($it) { - - } - - public function callTask($it){ - if ($this->data["post_type"] == "message") { - foreach(Buffer::get("mods") as $v){ - Console::info("Activating module ".$v); - /** @var ModBase $w */ - $w = new $v($this, $this->data); - if($w->call_task === false){ - $msg = trim($this->data["message"]); - $msg = explode(" ", $msg); - $prefix = Buffer::get("cmd_prefix"); - if($prefix != ""){ - if(mb_substr($msg[0],0,mb_strlen($prefix)) == $prefix){ - $msg[0] = mb_substr($msg[0], mb_strlen($prefix)); - } - } - $w->execute($msg); - } + 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")) { + return false; } } + if ($this->data["message"] == "") + return false; + foreach (Buffer::get("mods") as $v) { + /** @var ModBase $r */ + $r = new $v($this, $this->data); + if ($r->function_call === false) { + $msg = trim($this->data["message"]); + $msg = explode(" ", $msg); + $r->execute($msg); + } + } + $this->endtime = microtime(true); + return $this->function_called; } public function reply($msg){ @@ -102,7 +94,7 @@ class CQBot } public function isAdmin($user){ - if (in_array($user, Buffer::get("su"))) return true; + if (in_array($user, Buffer::get("admin"))) return true; else return false; } @@ -149,4 +141,12 @@ class CQBot } return $msg; } + + /** + * 返回当前机器人的id + * @return string|null + */ + public function getRobotId() { + return $this->data["self_id"] ?? null; + } } \ No newline at end of file diff --git a/src/cqbot/event/WSMessageEvent.php b/src/cqbot/event/WSMessageEvent.php deleted file mode 100644 index 865056d9..00000000 --- a/src/cqbot/event/WSMessageEvent.php +++ /dev/null @@ -1,79 +0,0 @@ -data, true); - if (isset($req["echo"])) if (APIHandler::execute($req["echo"], $req)) return; - //echo Console::setColor(json_encode($req, 128|256), "gold"); - if (isset($req["echo"]["type"]) && $req["echo"]["type"] == "handshake") { - $fd_id = $frame->fd; - $req["user_id"] = $req["data"]["user_id"]; - $connect = CQUtil::getConnection($fd_id); - $connect->setQQ($req["user_id"]); - $connect->setType(1); - $connect->findSub(); - 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("检测到重复引起异常,停止服务器", $req["user_id"], 0); - file_put_contents(CONFIG_DIR . "log/last_error.log", ""); - $this->getFramework()->event->shutdown(); - return; - } - CQUtil::sendDebugMsg("检测到异常", $req["user_id"], 0); - $msg = ""; - foreach ($data as $e) { - $msg = $msg . $e . "\n"; - } - CQUtil::sendDebugMsg($msg, strval($req["user_id"]), 0); - CQUtil::sendDebugMsg("[CQBot] 成功开启!", $req["user_id"], 0); - file_put_contents(CONFIG_DIR . "error_flag", time()); - file_put_contents(CONFIG_DIR . "last_error.log", ""); - } else { - Console::info("监测到API连接,已经完成初始化!"); - CQUtil::sendDebugMsg("[CQBot] 成功开启!", $req["user_id"], 0); - } - CQUtil::sendAPI($frame->fd, "_get_friend_list", ["get_friend_list"]); - CQUtil::sendAPI($frame->fd, "get_group_list", ["get_group_list"]); - CQUtil::sendAPI($frame->fd, "get_version_info", ["get_version_info"]); - return; - } - $connect = CQUtil::getConnection($frame->fd); - switch ($connect->getType()) { - case 0: - $connect->setQQ($req["self_id"]); - $connect->findSub(); - $in_count = Buffer::$in_count->get(); - Buffer::$in_count->add(1); - 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(), $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, $req["self_id"]); - } catch (Exception $e) { - CQUtil::errorlog("处理消息时异常,消息处理中断\n" . $e->getMessage() . "\n" . $e->getTraceAsString(), $req["self_id"]); - CQUtil::sendDebugMsg("引起异常的消息:\n" . var_export($req, true), $req['self_id']); - } - } - - } -} \ No newline at end of file diff --git a/src/cqbot/event/WSOpenEvent.php b/src/cqbot/event/WSOpenEvent.php deleted file mode 100644 index 48f208fc..00000000 --- a/src/cqbot/event/WSOpenEvent.php +++ /dev/null @@ -1,16 +0,0 @@ -fd; - //Console::info("收到连接:".$fd); - CQUtil::getConnection($fd); - } -} \ No newline at end of file diff --git a/src/cqbot/event/post/MessageEvent.php b/src/cqbot/event/post/MessageEvent.php new file mode 100644 index 00000000..72d135b1 --- /dev/null +++ b/src/cqbot/event/post/MessageEvent.php @@ -0,0 +1,31 @@ +get(); + Buffer::$in_count->add(1); + if (Buffer::$data["info_level"] >= 1) { + $num = CQUtil::getRobotNum($req["self_id"]); + $type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG" . $num . ":" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" . $num : "DISCUSS_MSG" . $num . ":" . $req["discuss_id"])) : strtoupper($req["post_type"]); + Console::put(Console::setColor(date("H:i:s"), "green") . Console::setColor(" [$in_count]" . $type, "lightlightblue") . Console::setColor(" " . $req["user_id"], "yellow") . Console::setColor(" > ", "gray") . $req["message"]); + } + + CQUtil::updateMsg();//更新消息速度 + + //传入消息处理的逻辑 + $c = new CQBot($this->getFramework(), 0, $req); + $c->execute(); + $value = $c->endtime - $c->starttime; + Console::debug("Using time: " . $value); + + } +} \ No newline at end of file diff --git a/src/cqbot/event/post/MetaEvent.php b/src/cqbot/event/post/MetaEvent.php new file mode 100644 index 00000000..05e8fe69 --- /dev/null +++ b/src/cqbot/event/post/MetaEvent.php @@ -0,0 +1,29 @@ + $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; + + } + } + } +} \ No newline at end of file diff --git a/src/cqbot/event/post/UnknownEvent.php b/src/cqbot/event/post/UnknownEvent.php new file mode 100644 index 00000000..d7b29966 --- /dev/null +++ b/src/cqbot/event/post/UnknownEvent.php @@ -0,0 +1,14 @@ + '小马哥' + //这里放机器人连接的别名 + //"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/HTTPEvent.php b/src/cqbot/event/server/HTTPEvent.php similarity index 100% rename from src/cqbot/event/HTTPEvent.php rename to src/cqbot/event/server/HTTPEvent.php diff --git a/src/cqbot/event/WSCloseEvent.php b/src/cqbot/event/server/WSCloseEvent.php similarity index 64% rename from src/cqbot/event/WSCloseEvent.php rename to src/cqbot/event/server/WSCloseEvent.php index 892e25f6..f8c3ce99 100644 --- a/src/cqbot/event/WSCloseEvent.php +++ b/src/cqbot/event/server/WSCloseEvent.php @@ -10,9 +10,8 @@ class WSCloseEvent extends Event { public function __construct(swoole_server $server, int $fd) { $connect = CQUtil::getConnection($fd); - if ($connect->getPair() !== null) { - $connect->setPair(null); - } - unset(Buffer::$connect[$fd]); + if ($connect !== null) + unset(Buffer::$connect[$fd]); + Console::info("WebSocket 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 new file mode 100644 index 00000000..b2f03086 --- /dev/null +++ b/src/cqbot/event/server/WSMessageEvent.php @@ -0,0 +1,48 @@ +fd; + $req = json_decode($frame->data, true); + $connect = CQUtil::getConnection($fd); + if ($connect === null) { + Console::info("收到一个未知链接发来的消息。" . $fd); + return; + } + switch ($connect->getType()) { + case 0: + //处理酷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; + } + 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); + break; + } + } +} \ No newline at end of file diff --git a/src/cqbot/event/server/WSOpenEvent.php b/src/cqbot/event/server/WSOpenEvent.php new file mode 100644 index 00000000..f4d10cdf --- /dev/null +++ b/src/cqbot/event/server/WSOpenEvent.php @@ -0,0 +1,53 @@ +fd; + $get = $request->get; + $header = $request->header; + //echo json_encode($header, 128|256); + $connect_type = $get["type"] ?? (isset($header["x-client-role"]) ? strtolower($header["x-client-role"]) : ""); + $access_token = $get["token"] ?? (isset($header["authorization"]) ? explode(" ", $header["authorization"])[1] : ""); + //Console::info("链接类型:".$connect_type."\n链接token:".$access_token); + if ($connect_type == "") { + Console::info("未指定连接类型,关闭连接."); + $server->close($fd); + return; + } + if ($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")) { + Console::info("监测到WS连接,但是token不对,无法匹配。"); + $server->close($fd); + return; + } + switch ($connect_type) { + case "event": + case "api": + $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"]]); + } + break; + } + } +} \ No newline at end of file diff --git a/src/cqbot/event/WorkerStartEvent.php b/src/cqbot/event/server/WorkerStartEvent.php similarity index 90% rename from src/cqbot/event/WorkerStartEvent.php rename to src/cqbot/event/server/WorkerStartEvent.php index 7e11f5bb..f26eef7c 100644 --- a/src/cqbot/event/WorkerStartEvent.php +++ b/src/cqbot/event/server/WorkerStartEvent.php @@ -9,17 +9,14 @@ class WorkerStartEvent extends Event { public function __construct(swoole_server $server, $worker_id){ - - Console::info("Starting worker: " . $worker_id); - + Buffer::$reload_time->add(1); + Buffer::$connect = []; CQUtil::loadAllFiles(); foreach (get_included_files() as $file) Console::debug("Loaded " . $file); - //计时器(ms) $this->getFramework()->scheduler = new Scheduler($this->getFramework()); $server->tick(1000, [$this->getFramework(), "processTick"]); - Console::debug("master_pid = " . $server->master_pid); Console::debug("worker_id = " . $worker_id); Console::put("\n==========STARTUP DONE==========\n"); diff --git a/src/cqbot/handler/CQAPIHandler.php b/src/cqbot/handler/CQAPIHandler.php new file mode 100644 index 00000000..c49c6192 --- /dev/null +++ b/src/cqbot/handler/CQAPIHandler.php @@ -0,0 +1,92 @@ + $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/item/CQ.php b/src/cqbot/item/CQ.php new file mode 100644 index 00000000..0b47f69c --- /dev/null +++ b/src/cqbot/item/CQ.php @@ -0,0 +1,212 @@ +时禁用CQ-HTTP-API插件的缓存 + * @param $file + * @param bool $cache + * @return string + */ + public static function image($file, $cache = true) { + if ($cache === false) + return "[CQ:image,file=" . $file . ",cache=0]"; + else + return "[CQ:image,file=" . $file . "]"; + } + + /** + * 发送语音 + * cache为时禁用CQ-HTTP-API插件的缓存 + * magic为时标记为变声 + * @param $file + * @param bool $magic + * @param bool $cache + * @return string + */ + public static function record($file, $magic = false, $cache = true) { + if ($cache === false) $c = ",cache=0"; + else $c = ""; + if ($magic === true) $m = ",magic=true"; + else $m = ""; + return "[CQ:record,file=" . $file . $c . $m . "]"; + } + + /** + * 发送投掷骰子(只能在单条回复中单独使用) + * @return string + */ + public static function rps() { + return "[CQ:rps]"; + } + + /** + * 发送掷骰子表情(只能在单条回复中单独使用) + * @return string + */ + public static function dice() { + return "[CQ:dice]"; + } + + /** + * 戳一戳(原窗口抖动,仅支持好友消息使用) + * @return string + */ + public static function shake() { + return "[CQ:shake]"; + } + + /** + * 发送音乐分享(只能在单条回复中单独使用) + * qq、163、xiami为内置分享,需要先通过搜索功能获取id后使用 + * custom为自定义分享 + * 当为自定义分享时: + * $id_or_url 为音乐卡片点进去打开的链接(一般是音乐介绍界面啦什么的) + * $audio 为音乐(如mp3文件)的HTTP链接地址(不可为空) + * $title 为音乐卡片的标题,建议12字以内(不可为空) + * $content 为音乐卡片的简介(可忽略) + * $image 为音乐卡片的图片链接地址(可忽略) + * @param $type + * @param $id_or_url + * @param string $audio + * @param string $title + * @param string $content + * @param string $image + * @return string + */ + public static function music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null) { + switch ($type) { + case "qq": + case "163": + case "xiami": + return "[CQ:music,id=" . $id_or_url . "]"; + case "custom": + if ($title === null || $audio === null) { + Console::error("传入CQ码实例的标题和音频链接不能为空!"); + return " "; + } + if ($content === null) $c = ""; + else $c = ",content=" . $content; + if ($image === null) $i = ""; + else $i = ",image=" . $image; + return "[CQ:music,type=custom,url=" . $id_or_url . ",audio=" . $audio . ",title=" . $title . $c . $i . "]"; + default: + Console::error("传入的music type($type)错误!"); + return " "; + } + } + + /** + * 发送链接分享(只能在单条回复中单独使用) + * @param $url + * @param $title + * @param null $content + * @param null $image + * @return string + */ + public static function share($url, $title, $content = null, $image = null) { + if ($content === null) $c = ""; + else $c = ",content=" . $content; + if ($image === null) $i = ""; + else $i = ",image=" . $image; + return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]"; + } + + /** + * 将字符串中的CQ码敏感符号进行转义 + * @param $str + * @return mixed + */ + public static function encode($str) { + $str = str_replace("&", "&", $str); + $str = str_replace("[", "[", $str); + $str = str_replace("]", "]", $str); + return $str; + } + + /** + * 反转义字符串中的CQ码敏感符号 + * @param $str + * @return mixed + */ + public static function decode($str) { + $str = str_replace("&", "&", $str); + $str = str_replace("[", "[", $str); + $str = str_replace("]", "]", $str); + return $str; + } + + public static function replace($str) { + $str = str_replace("{{", "[", $str); + $str = str_replace("}}", "]", $str); + return $str; + } +} \ No newline at end of file diff --git a/src/cqbot/item/Group.php b/src/cqbot/item/Group.php deleted file mode 100644 index 8464658a..00000000 --- a/src/cqbot/item/Group.php +++ /dev/null @@ -1,100 +0,0 @@ -self_id = $self_id; - $this->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 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 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) { - $connection = CQUtil::getApiConnectionByQQ($this->getSelfId()); - CQUtil::sendAPI($connection->fd, ["action" => "get_group_list"], ["update_group_info", $this->getGroupId()]); - if ($with_members) { - CQUtil::sendAPI($connection->fd, ["action" => "get_group_member_list", "params" => ["group_id" => $this->getGroupId()]], ["update_group_member_list", strval($this->getGroupId())]); - } - } - - /** - * @return mixed - */ - public function getSelfId() { - return $this->self_id; - } -} \ No newline at end of file diff --git a/src/cqbot/item/GroupMember.php b/src/cqbot/item/GroupMember.php deleted file mode 100644 index 5900f016..00000000 --- a/src/cqbot/item/GroupMember.php +++ /dev/null @@ -1,138 +0,0 @@ -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(CQUtil::getApiConnectionByQQ($this->getGroup()->getSelfId())->fd, $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(CQUtil::getApiConnectionByQQ($this->getGroup()->getSelfId())->fd, [ - "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/item/User.php b/src/cqbot/item/User.php index 2aab59be..5b359f6c 100755 --- a/src/cqbot/item/User.php +++ b/src/cqbot/item/User.php @@ -11,68 +11,126 @@ use DataProvider as DP; class User { private $id; - private $word_status = []; - private $permission; - private $is_friend = false; - private $buffer = null; + protected $nickname = ""; + protected $permission = 0; + protected $friend = []; + protected $lexicon = []; + protected $user_type = 0;//预留位置,0为QQ用户,1为为微信公众号用户 - public function __construct($qid){ + protected $function = []; + + public $buffer = null; + + public function __construct($qid) { $this->id = $qid; + if (strlen($qid) >= 15) $this->user_type = 1; $this->permission = DP::getJsonData("permissions.json")[$qid] ?? 0; - $this->is_friend = isset(Buffer::get("friend_list")[$qid]) ? true : false; + $this->friend = []; } /** * 获取用户QQ号 * @return mixed */ - public function getId(){ return $this->id; } - - /** - * 获取用户添加词库的状态 - * @return array - */ - public function getWordStatus() : array{ return $this->word_status; } + public function getId() { return $this->id; } /** * 获取用户权限值 * @return int */ - public function getPermission() : int{ return $this->permission; } - - /** - * @param array $word_status - * @return User - */ - public function setWordStatus(array $word_status): User{ - $this->word_status = $word_status; - return $this; - } + public function getPermission() { return $this->permission; } /** * @param int $permission * @return User */ - public function setPermission(int $permission): User{ + public function setPermission(int $permission) { $this->permission = $permission; return $this; } - /** - * 和用户是否是好友//TODO功能 - * @return bool - */ - public function isFriend(): bool{ - return $this->is_friend; - } - - public function getBuffer(){ + public function getBuffer() { return $this->buffer; } - public function setBuffer($buffer){ + public function setBuffer($buffer) { $this->buffer = $buffer; return $this; } + + /** + * @return string + */ + public function getNickname() { + return $this->nickname; + } + + /** + * @return array + */ + public function getFriend() { + return $this->friend; + } + + /** + * @param array $friend + */ + public function setFriend(array $friend) { + $this->friend = $friend; + } + + /** + * @param string $nickname + */ + public function setNickname(string $nickname) { + $this->nickname = $nickname; + } + + /** + * @return array + */ + public function getLexicon() { + return $this->lexicon; + } + + /** + * @param array $lexicon + */ + public function setLexicon(array $lexicon) { + $this->lexicon = $lexicon; + } + + /** + * @return array + */ + public function getFunction() { + return $this->function; + } + + /** + * @param array $function + */ + public function setFunction(array $function) { + $this->function = $function; + } + + public function closeFunction($name) { + unset($this->function[$name]); + } + + /** + * @return int + */ + public function getUserType() { + return $this->user_type; + } + + public function toJson() { + $ls = []; + foreach ($this as $k => $v) { + $ls[$k] = $v; + } + return json_encode($ls, 128 | 256); + } } \ No newline at end of file diff --git a/src/cqbot/item/WSConnection.php b/src/cqbot/item/WSConnection.php deleted file mode 100644 index 9037493c..00000000 --- a/src/cqbot/item/WSConnection.php +++ /dev/null @@ -1,148 +0,0 @@ -server = $server; - $this->fd = $fd; - $this->manageType(); - } - - /** - * 返回swoole server - * @return swoole_websocket_server - */ - public function getServer() { - return $this->server; - } - - /** - * 返回本连接是什么类型的 - * @return int - */ - public function getType() { - return $this->type; - } - - /** - * 用来确认此连接是API还是event - * 如果此fd连接是event,则不会返回任何信息,关于QQ的匹配,则会在接收入第一条消息后设置 - * 如果此连接是api,则此操作后,HTTP API会返回登录号的号码,如果返回了则标记此连接为api并记录这个api连接属于的QQ号 - */ - private function manageType() { - $this->server->push($this->fd, '{"action":"get_login_info","echo":{"type":"handshake"}}'); - } - - /** - * 返回此连接相关联的event连接,使用前需初始化完成 - * @return $this|null|WSConnection - */ - public function getEventConnection() { - switch ($this->type) { - case 0: - return $this; - case 1: - return $this->pair; - default: - return null; - } - } - - /** - * 返回此链接相关联的api连接,使用前需初始化完成 - * @return $this|null|WSConnection - */ - public function getApiConnection() { - switch ($this->type) { - case 0: - return $this->pair; - case 1: - return $this; - default: - return null; - } - } - - /** - * 检查此连接对应的QQ,此部分较为复杂,先留着 - * @param $qq - */ - public function manageQQ($qq) { - //TODO - } - - /** - * 返回此连接属于的QQ号 - * @return string - */ - public function getQQ() { - return $this->qq; - } - - /** - * 返回关联连接(experiment) - * @return WSConnection - */ - public function getPair() { - return $this->pair; - } - - /** - * 设置关联连接 - * @param WSConnection $pair - */ - public function setPair(WSConnection $pair) { - $this->pair = $pair; - } - - /** - * @param string $qq - */ - public function setQQ($qq) { - $this->qq = $qq; - } - - /** - * @param int $type - */ - public function setType(int $type) { - $this->type = $type; - } - - /** - * - */ - public function findSub() { - if ($this->qq != "") { - foreach (CQUtil::getConnections() as $fd => $cn) { - if ($cn->getQQ() == $this->qq && $cn->getType() != $this->getType()) { - $this->setPair($cn); - $cn->setPair($this); - } - } - } - } -} \ No newline at end of file diff --git a/src/cqbot/loader.php b/src/cqbot/loader.php index a2b6eea4..f8f18daa 100755 --- a/src/cqbot/loader.php +++ b/src/cqbot/loader.php @@ -6,20 +6,6 @@ * Time: 10:32 */ -function loadAllClass($dir){ - $dir_obj = scandir($dir); - unset($dir_obj[0], $dir_obj[1]); - foreach ($dir_obj as $m) { - $taskFileName = explode(".", $m); - if (is_dir($dir . $m . "/")) loadAllClass($dir . $m . "/"); - else { - if (count($taskFileName) < 2 || $taskFileName[1] != "php") continue; - require_once($dir . $m); - Console::debug("Loading PHP file ".$dir.$m); - } - } -} - //加载需要优先加载的文件 require_once(WORKING_DIR."src/cqbot/mods/ModBase.php"); require_once(WORKING_DIR."src/cqbot/item/User.php"); diff --git a/src/cqbot/mods/Admin.php b/src/cqbot/mods/Admin.php index 97b74a69..85ceba06 100755 --- a/src/cqbot/mods/Admin.php +++ b/src/cqbot/mods/Admin.php @@ -10,41 +10,44 @@ class Admin extends ModBase { protected $cmds; - public function __construct(CQBot $main, $data){ + public function __construct(CQBot $main, $data) { parent::__construct($main, $data); } - public function execute($it){ + public static function initValues() { + Buffer::set("msg_speed", []);//消息速度列表(存的是时间戳) + Buffer::set("admin_active", ""); + } + + public static function onTick($tick) { + if ($tick % 900 == 0) CQUtil::saveAllFiles();//900秒储存一次数据 + if ($tick % 21600 == 0) {//21600秒刷新一次好友列表 + GroupManager::updateGroupList(); + FriendManager::updateFriendList(); + } + } + + 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")) $this->reply("检测到改变了Framework文件的内容!如需完全重载,请重启完整进程!"); CQUtil::reload(); return true; - case "stop": + case "stop"://管理员停止server $this->reply("正在停止服务器..."); CQUtil::stop(); return true; - case "set-prefix": - if(count($it) < 2) return $this->sendDefaultHelp($it[0],["set-prefix","新前缀/空"],"设置新的前缀或设置不需要前缀(不需要前缀输入\"空\"即可)"); - $prefix = $it[1]; - if(mb_strlen($prefix) > 2){ - $this->reply("指令前缀最长为两个字符"); - return true; - } - Buffer::set("cmd_prefix", $prefix); - $this->reply("成功设置新前缀!\n下次输入指令时需前面带 ".$prefix); - return true; - case "op": + case "op"://添加管理员 $user = $it[1]; - Buffer::append("su", $user); + Buffer::append("admin", $user); $this->reply("added operator $user"); return true; - case "deop": + case "deop"://删除管理员 $user = $it[1]; - if(Buffer::in_array("su", $user)) Buffer::unsetByValue("su", $user); + if (Buffer::in_array("admin", $user)) Buffer::unsetByValue("admin", $user); $this->reply("removed operator $user"); return true; } diff --git a/src/cqbot/mods/Entertain.php b/src/cqbot/mods/Entertain.php deleted file mode 100644 index 234ce4ae..00000000 --- a/src/cqbot/mods/Entertain.php +++ /dev/null @@ -1,27 +0,0 @@ -reply("你好,我是CQBot!"); - return true; - case "robot": - $this->reply("机器人!\n机器人!"); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/cqbot/mods/Example.php b/src/cqbot/mods/Example.php index 4bf7e5d4..dab29c3d 100644 --- a/src/cqbot/mods/Example.php +++ b/src/cqbot/mods/Example.php @@ -1,71 +1,35 @@ reply("你输入了:" . mb_substr($message, 3)); + } } - public function execute($it){ + public function execute($it) { switch ($it[0]) { case "ping": $this->reply("pong"); return true; - case "王境泽动图": - $msg_help = "【王境泽动图帮助】"; - $msg_help .= "\n用法1:\n王境泽动图 第一句 第二句 第三句 第四句"; - $msg_help .= "\n[如果需要输入空格的话,用下面的方法]"; - $msg_help .= "\n王境泽动图 多行\n第一句\n第二句\n第三句\n第四句"; - $api = "https://sorry.xuty.tk/api/wangjingze/make"; - if (strstr($it[1], "\n") === false && count($it) < 5) { - $this->reply($msg_help); - return true; - } - array_shift($it); - if (mb_substr($it[0], 0, 2) == "多行") { - $ms = implode(" ", $it); - $ms = mb_substr($ms, 2); - $ms = trim($ms); - $ms = explode("\n", $ms); - if (count($ms) < 4) { - $this->reply($msg_help); - return true; - } - $content = [ - "3" => $ms[3], - "2" => $ms[2], - "1" => $ms[1], - "0" => $ms[0] - ]; - } elseif (count($it) >= 4) { - $content = [ - "3" => $it[3], - "0" => $it[0], - "1" => $it[1], - "2" => $it[2] - ]; - } else { - $this->reply($msg_help); - return true; - } - $opts = array('http' => array('method' => 'POST', 'header' => 'Content-Type: application/json; charset=utf-8', 'content' => json_encode($content, JSON_UNESCAPED_UNICODE))); - $context = stream_context_create($opts); - $this->reply("正在生成,请稍等"); - $result = file_get_contents($api, false, $context); - if ($result == false) { - $this->reply("抱歉,请求失败,请过一会儿再试吧~"); - return true; - } - $result = "https://sorry.xuty.tk" . $result; - $this->reply("[CQ:image,file=" . $result . "]"); + case "你好": + $this->reply("你好,我是CQBot!"); return true; case "随机数": if (!isset($it[1]) || !isset($it[2])) { @@ -80,9 +44,6 @@ class Example extends ModBase } $this->reply("生成的随机数是 " . mt_rand($c1, $c2)); return true; - case "test": - $this->reply("Hello world"); - return true; } return false; } diff --git a/src/cqbot/mods/Help.php b/src/cqbot/mods/Help.php index 64ff98b9..7f08e79c 100644 --- a/src/cqbot/mods/Help.php +++ b/src/cqbot/mods/Help.php @@ -8,25 +8,23 @@ class Help extends ModBase { - public function __construct(CQBot $main, $data, bool $mod_cmd = false) { parent::__construct($main, $data, $mod_cmd); } + public function __construct(CQBot $main, $data) { parent::__construct($main, $data); } public function execute($it) { switch ($it[0]) { case "帮助": $msg = "「机器人帮助」"; - $msg .= "\n王境泽动图:用来生成表情包"; - $msg .= "\ntest:测试机器人回复是否正常"; $msg .= "\n随机数:生成一个随机数"; $this->reply($msg); return true; case "如何增加机器人功能": $msg = "机器人功能是在框架中src/cqbot/mods/xxx.php文件中编写的。"; - $msg .= "CQBot采用关键词系统,你可以直接像现有源码一样添加case在switch里面,"; - $msg .= "也可以自己新建一个任意名称的Mod名称,例如Entertain.php,你可以在里面编写娱乐功能。"; - $msg .= "你可以直接复制框架中Entertain.php文件的内容进行编辑。"; - $msg .= "你也可以在tasks/Scheduler.php中tick函数里添加自己的定时执行的功能。"; - $msg .= "预先封装好的机器人函数均在CQUtil类中,只需直接使用静态方法调用即可!"; - $msg .= "更多示例功能会逐渐添加到框架中,记得更新哦~"; + $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); return true; } diff --git a/src/cqbot/mods/ModBase.php b/src/cqbot/mods/ModBase.php index d03225b5..6175e778 100755 --- a/src/cqbot/mods/ModBase.php +++ b/src/cqbot/mods/ModBase.php @@ -10,43 +10,36 @@ abstract class ModBase { protected $main; protected $data; - protected $cmds; public $call_task = false; - public function __construct(CQBot $main, $data, $mod_cmd = false){ + /** + * 控制模块是否调用execute函数的变量 + * 当function_call 为 TRUE时,表明CQBot主实例不需要调用execute函数 + * 当为FALSE时,CQBot在实例化模块对象后会执行execute函数 + * @var bool + */ + public $function_call = false; + + public function __construct(CQBot $main, $data) { $this->main = $main; $this->data = $data; - $this->cmds = $mod_cmd; } - public function getUser($data = null){ + 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 getUserId($data = null) { return $data === null ? strval($this->data["user_id"]) : strval($data["user_id"]); } - public abstract function execute($it); + public function execute($it) { } - public function reply($msg){ $this->main->reply($msg); } + public function reply($msg) { $this->main->reply($msg); } - public function sendPrivateMsg($user, $msg){ $this->main->sendPrivateMsg($user, $msg); } + public function sendPrivateMsg($user, $msg) { $this->main->sendPrivateMsg($user, $msg); } - public function sendGroupMsg($user, $msg){ $this->main->sendGroupMsg($user, $msg); } + public function sendGroupMsg($user, $msg) { $this->main->sendGroupMsg($user, $msg); } - public function getMessageType(){ return $this->data["message_type"]; } + public function getMessageType() { return $this->data["message_type"]; } - public function getCommands(){ return $this->cmds; } - - public function sendDefaultHelp($cmd, $args = [], $help = ""){ - $msg = "「".$cmd."使用帮助」"; - $msg .= "\n用法:".Buffer::get("cmd_prefix").$cmd; - if($args != []){ - $msg .= " ".implode(" ", $args); - } - if($help != ""){ - $msg .= ":".$help; - } - $this->reply($msg); - return true; - } + public function getRobotId() { return $this->data["self_id"]; } } \ No newline at end of file diff --git a/src/cqbot/tasks/Scheduler.php b/src/cqbot/tasks/Scheduler.php index 9e2af310..af96c880 100644 --- a/src/cqbot/tasks/Scheduler.php +++ b/src/cqbot/tasks/Scheduler.php @@ -27,9 +27,12 @@ class Scheduler } public function tick($id, $tick_time) { - //900秒执行一次文件保存功能 - if($tick_time - $this->framework->run_time % 900 == 0) CQUtil::saveAllFiles(); - - //这里添加计时器上处理的内容 + /** @var array $ls */ + $ls = Buffer::get("mods"); + foreach ($ls as $v) { + if (in_array("onTick", get_class_methods($v))) { + $v::onTick($tick_time - $this->framework->run_time); + } + } } } \ No newline at end of file diff --git a/src/cqbot/utils/APIHandler.php b/src/cqbot/utils/APIHandler.php deleted file mode 100644 index 2a9f0ac4..00000000 --- a/src/cqbot/utils/APIHandler.php +++ /dev/null @@ -1,91 +0,0 @@ - $v) { - $list[$v["user_id"]] = $friend[$k]; - } - Buffer::set("friend_list", $list); - Console::put(Console::setColor("已读取" . count(Buffer::get("friend_list")) . "个好友", "blue")); - return true; - case "update_group_member_list": - $group_id = $cmd["params"][0]; - $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(); - } - return true; - case "update_group_member_info": - $info_data = $res["data"]; - $group = $cmd["params"][0]; - $user = $cmd["params"][1]; - $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); - return true; - case "update_group_info": - $group = $res["data"]; - $current = $cmd["params"][0]; - $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); - return true; - } - $g = CQUtil::getGroup($current); - $g->setGroupName($list[$current]["group_name"]); - return true; - case "get_group_member_list": - $group_data = $res["data"]; - $ls = Buffer::get("group_list"); - $group_id = $cmd["params"][0]; - $ls[$group_id]["member"] = $group_data; - $group = new Group($group_id, $ls[$group_id], $cmd["self_id"]); - //TODO: 添加获取API时多账号对群的实例化的支持 - Buffer::appendKey("groups", $group_id, $group); - return true; - case "get_group_list": - $group = $res["data"]; - $list = []; - foreach ($group as $k => $v) { - $list[$v["group_id"]] = $group[$k]; - CQUtil::sendAPI(CQUtil::getApiConnectionByQQ($cmd["self_id"])->fd, ["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")); - return true; - case "get_version_info": - Buffer::set("version_info", $res["data"]); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/cqbot/utils/CQUtil.php b/src/cqbot/utils/CQUtil.php index 1bb2ec8a..a5f9ec79 100755 --- a/src/cqbot/utils/CQUtil.php +++ b/src/cqbot/utils/CQUtil.php @@ -12,13 +12,17 @@ class CQUtil { public static function loadAllFiles() { Console::debug("loading configs..."); - 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"] ?? "");//设置指令的前缀符号 - Buffer::set("res_code", file_get_contents(WORKING_DIR . "src/cqbot/Framework.php")); + Buffer::set("res_code", file_get_contents(WORKING_DIR . "src/framework/Framework.php")); + Buffer::set("group_list", DP::getJsonData("group_list.json"));//获取群组列表 + + + //加载全局屏蔽的机器人列表 + Buffer::set("bots", DP::getJsonData("bots.json")); + + //调用各个模块单独的Buffer数据 foreach (self::getMods() as $v) { if (in_array("initValues", get_class_methods($v))) { $v::initValues(); @@ -29,11 +33,9 @@ class CQUtil public static function saveAllFiles() { Console::info("Saving files..."); - //保存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); + DP::setJsonData("bots.json", Buffer::get("bots")); + DP::setJsonData("group_list.json", Buffer::get("group_list")); + //保存用户数据 foreach (self::getAllUsers() as $k => $v) { @@ -46,17 +48,16 @@ class CQUtil /** * 生成报错日志 * @param $log - * @param $self_id * @param string $head * @param int $send_debug_message */ - public static function errorLog($log, $self_id, $head = "ERROR", $send_debug_message = 1) { + public static function errorLog($log, $head = "ERROR", $send_debug_message = 1) { Console::error($log, ($head === "ERROR") ? null : "[" . $head . "] "); $time = date("Y-m-d H:i:s"); $msg = "[$head @ $time]: $log\n"; file_put_contents(DP::getDataFolder() . "log_error.txt", $msg, FILE_APPEND); if ($send_debug_message) - self::sendDebugMsg($msg, $self_id); + self::sendDebugMsg($msg); } /** @@ -66,16 +67,26 @@ class CQUtil * @param int $need_head * @return null */ - static function sendDebugMsg($msg, $self_id, $need_head = 1) { - if (Framework::$admin_group[strval($self_id)] == "") 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", Framework::$admin_group); + $data = CQMsg("[DEBUG] " . date("H:i:s") . ": " . $msg, "group", Buffer::get("admin_group")); else - $data = CQMsg($msg, "group", Framework::$admin_group); + $data = CQMsg($msg, "group", Buffer::get("admin_group")); $connect = CQUtil::getApiConnectionByQQ($self_id); return self::sendAPI($connect->fd, $data, ["send_debug_msg"]); } + static function findRobot() { + foreach (self::getConnections("api") as $v) { + return $v->getQQ(); + } + return null; + } + /** * 推送API,给API端口 * @param $fd @@ -98,9 +109,7 @@ class CQUtil }*/ if (Buffer::$event->push($fd, $data) === false) { $data = self::unicodeDecode($data); - $connect = self::getConnection($fd); - self::errorlog("API推送失败,未发送的消息: \n" . $data, $connect->getQQ(), "API ERROR", 0); - self::sendErrorEmail("API推送失败", "未成功推送的消息:
$data
请检查酷q是否开启及网络链接情况
在此期间,机器人会中断所有消息处理
请及时处理", $connect->getQQ()); + self::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", 0); return false; } return true; @@ -218,7 +227,7 @@ class CQUtil unset($mail); return true; } catch (\Exception $e) { - self::errorLog("发送邮件错误!错误信息:" . $info = $mail->ErrorInfo, $self_id, "ERROR", $send_debug_message); + self::errorLog("发送邮件错误!错误信息:" . $info = $mail->ErrorInfo, "ERROR", 0); unset($mail); return $info; } @@ -257,15 +266,22 @@ class CQUtil /** * @param $fd + * @param null $type + * @param null $qq * @return WSConnection */ - static function getConnection($fd) { + static function getConnection($fd, $type = null, $qq = null) { //var_dump(Buffer::$connect); - if (!isset(Buffer::$connect[$fd])) { - $s = new WSConnection(Buffer::$event, $fd); - Buffer::$connect[$fd] = $s; + 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]; + return Buffer::$connect[$fd] ?? null; } static function getApiConnectionByQQ($qq) { @@ -367,7 +383,7 @@ class CQUtil * 获取所有已经加载到内存的用户。 * read_all为true时,会加载所有User.dat到内存中,false时仅会读取已经加载到内存的用户 * @param bool $real_all - * @return array[User] + * @return User[] */ static function getAllUsers($real_all = false): array { if ($real_all === true) { @@ -640,16 +656,6 @@ class CQUtil } } - /** - * 返回群的类 - * @param $group_id - * @return Group|null - */ - static function getGroup($group_id) { - $d = Buffer::get("groups"); - return $d[$group_id] ?? null; - } - static function getCQ($msg) { if (($start = mb_strpos($msg, '[')) === false) return null; if (($end = mb_strpos($msg, ']')) === false) return null; @@ -666,4 +672,41 @@ class CQUtil } 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 (Buffer::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; + } + + /** + * 刷新消息速率(每分钟) + * 当 $insert 为 true 时,表明运行此函数时收到了一条消息 + * @param bool $insert + */ + static function updateMsg($insert = true) { + $ls = Buffer::get("msg_speed"); + if ($insert === true) + $ls [] = time(); + foreach ($ls as $k => $v) { + if ((time() - $v) > 60) { + array_splice($ls, $k, 1); + } + } + Buffer::set("msg_speed", $ls); + } } \ No newline at end of file diff --git a/src/cqbot/utils/FriendManager.php b/src/cqbot/utils/FriendManager.php new file mode 100644 index 00000000..70dafcf4 --- /dev/null +++ b/src/cqbot/utils/FriendManager.php @@ -0,0 +1,24 @@ + $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"]); + } + } +} \ No newline at end of file diff --git a/src/cqbot/utils/GroupManager.php b/src/cqbot/utils/GroupManager.php new file mode 100644 index 00000000..b7b6b35f --- /dev/null +++ b/src/cqbot/utils/GroupManager.php @@ -0,0 +1,24 @@ + $v) { + $fd = $v->fd; + $robot_id = $v->getQQ(); + Console::put("正在获取机器人 " . $robot_id . " 的群组列表..."); + CQUtil::sendAPI($fd, "get_group_list", ["get_group_list", "step1"]); + } + } +} \ No newline at end of file diff --git a/src/cqbot/utils/WSConnection.php b/src/cqbot/utils/WSConnection.php new file mode 100644 index 00000000..9b47fab6 --- /dev/null +++ b/src/cqbot/utils/WSConnection.php @@ -0,0 +1,70 @@ +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/cqbot/utils/Buffer.php b/src/framework/Buffer.php similarity index 96% rename from src/cqbot/utils/Buffer.php rename to src/framework/Buffer.php index f10120c9..fc83ad91 100755 --- a/src/cqbot/utils/Buffer.php +++ b/src/framework/Buffer.php @@ -23,10 +23,10 @@ class Buffer static $in_count;//接收消息 /** @var \swoole_atomic $out_count */ static $out_count;//发送消息数量 - /** @var \swoole_atomic $out_count */ - static $api_id;//API调用ID /** @var WSConnection[] */ static $connect = []; + /** @var swoole_atomic $reload_time */ + static $reload_time; static function get($name){ return self::$data[$name] ?? null; } diff --git a/src/cqbot/utils/Console.php b/src/framework/Console.php similarity index 73% rename from src/cqbot/utils/Console.php rename to src/framework/Console.php index 0d1b342e..3bb425e3 100755 --- a/src/cqbot/utils/Console.php +++ b/src/framework/Console.php @@ -8,7 +8,7 @@ class Console { - static function setColor($string, $color = ""){ + static function setColor($string, $color = "") { switch ($color) { case "red": return "\x1b[38;5;203m" . $string . "\x1b[m"; @@ -36,26 +36,30 @@ class Console } - static function debug($obj, $head = null){ - if ($head === null) $head = "[DEBUG] " . date("H:i:s") . " "; - if (Buffer::get("info_level") < 2) return; + static function debug($obj, $head = null) { + if ($head === null) $head = "[" . date("H:i:s") . " DEBUG]"; + if ((Buffer::get("info_level") ?? 0) < 2) return; if (!is_string($obj)) var_dump($obj); else echo(self::setColor($head . $obj, "green") . "\n"); } - static function error($obj, $head = null){ - if ($head === null) $head = "[ERROR] " . date("H:i:s") . " "; + static function error($obj, $head = null) { + if ($head === null) $head = "[" . date("H:i:s") . " ERROR]"; if (!is_string($obj)) var_dump($obj); else echo(self::setColor($head . $obj, "red") . "\n"); } - static function info($obj, $head = null){ - if ($head === null) $head = "[INFO] " . date("H:i:s") . " "; + static function info($obj, $head = null) { + 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 put($obj, $color = ""){ + static function chatLog($head, $msg) { + + } + + static function put($obj, $color = "") { if (!is_string($obj)) var_dump($obj); else echo(self::setColor($obj, $color) . "\n"); } diff --git a/src/cqbot/utils/ErrorStatus.php b/src/framework/ErrorStatus.php similarity index 100% rename from src/cqbot/utils/ErrorStatus.php rename to src/framework/ErrorStatus.php diff --git a/src/cqbot/Framework.php b/src/framework/Framework.php similarity index 54% rename from src/cqbot/Framework.php rename to src/framework/Framework.php index 3c3952be..984aff24 100755 --- a/src/cqbot/Framework.php +++ b/src/framework/Framework.php @@ -10,7 +10,6 @@ class Framework { public static $super_user; public $host = "127.0.0.1"; - public $api_port = 10000; public $event_port = 20000; /** @var \swoole_websocket_server $event */ @@ -19,7 +18,6 @@ class Framework public static $obj = null; public $run_time; - public static $admin_group; public $info_level = 1; /** @var \swoole_http_client $api */ @@ -30,39 +28,26 @@ class Framework /** @var Scheduler */ public $scheduler = null; - public function __construct(){ } + 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"]); - public function setHost($host = ""){ $this->host = $host; } - - public function setApiPort($port = 10000){ $this->api_port = $port; } - - public function setEventPort($port = 20000){ $this->event_port = $port; } - - public function setAdminGroup($group){ self::$admin_group = $group; } - - public function setInfoLevel($level){ $this->info_level = $level; } - - public function setSuperUser($option) { self::$super_user = $option; } - - public function eventServerStart(){ $this->event->start(); } - - public static function getInstance(){ return self::$obj; } - - public function init() { $this->selfCheck(); - $this->checkFiles(); - 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); + Console::info("CQBot Framework starting..."); + $this->event = new swoole_websocket_server($this->host, $this->event_port); + + Buffer::$log_file = CRASH_DIR . "swoole.log"; //设置swoole基本参数 - $worker_num = 1; - $dispatch_mode = 2; + $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, + "worker_num" => $worker_num, "dispatch_mode" => $dispatch_mode ]); @@ -73,8 +58,8 @@ class Framework $this->event->on('message', [$this, 'onEventMessage']); //收到ws连接和断开连接回调的函数 - $this->event->on('open', [$this, 'onEventOpen']); - $this->event->on('close', [$this, "onEventClose"]); + $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"]); @@ -82,9 +67,13 @@ class Framework //设置原子计数器 Buffer::$in_count = new \swoole_atomic(1); Buffer::$out_count = new \swoole_atomic(1); - Buffer::$api_id = new \swoole_atomic(1); + Buffer::$reload_time = new \swoole_atomic(0); } + public function start() { $this->event->start(); } + + public static function getInstance() { return self::$obj; } + /* Callback function down here */ /** @@ -102,26 +91,12 @@ class Framework new WorkerStartEvent($server, $worker_id); } - /** - * 回调函数:有客户端或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); } - - /** - * 回调函数:断开连接时候回调的函数 - * @param swoole_server $server - * @param int $fd - */ - 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){ new WSMessageEvent($server, $frame); } + public function onEventMessage($server, $frame) { new WSMessageEvent($server, $frame); } /** * 回调函数:当IP:event端口收到相关HTTP请求时候调用 @@ -130,13 +105,13 @@ class Framework * @param swoole_http_request $request * @param swoole_http_response $response */ - public function onRequest($request, $response){ new HTTPEvent($request, $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())); } + public function processTick($id) { $this->scheduler->tick($id, ($this->tick_time = time())); } /** * 开启时候的自检模块 @@ -147,18 +122,8 @@ class Framework if (!function_exists("mb_substr")) die("无法找到mbstring扩展,请先安装.\n"); if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); if (!function_exists("curl_exec")) die("无法找到curl扩展,请先安装.\n"); - if (!class_exists("ZipArchive")) die("无法找到zip扩展,请先安装.(如果不需要zip功能可以删除此条自检)\n"); + //if (!class_exists("ZipArchive")) die("无法找到zip扩展,请先安装.(如果不需要zip功能可以删除此条自检)\n"); + if (!is_file(CRASH_DIR . "swoole.log")) file_put_contents(CRASH_DIR . "swoole.log", ""); return true; } - - /** - * 检查必需的文件是否存在 - */ - 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/framework/loader.php b/src/framework/loader.php new file mode 100644 index 00000000..11f7d179 --- /dev/null +++ b/src/framework/loader.php @@ -0,0 +1,9 @@ +setHost($properties["host"]); -$cqbot->setEventPort($properties["port"]); -$cqbot->setAdminGroup($properties["admin_group"]); -$cqbot->setInfoLevel($properties["info_level"]); -$cqbot->setSuperUser($properties["super_user"]); -$cqbot->init(); -$cqbot->eventServerStart(); \ No newline at end of file +$cqbot = new Framework(settings()); +$cqbot->start(); \ No newline at end of file diff --git a/tools.php b/tools.php index 79560475..af310ecf 100644 --- a/tools.php +++ b/tools.php @@ -6,17 +6,7 @@ * Time: 11:04 AM */ -date_default_timezone_set("Asia/Shanghai"); - -//启动时间 -define("START_TIME", time()); -@mkdir(CONFIG_DIR, 0777, true); -@mkdir(USER_DIR, 0777, true); -register_shutdown_function('handleFatal'); - -if (!file_exists(CONFIG_DIR . "config.json")) file_put_contents(CONFIG_DIR . "config.json", json_encode([])); - -function handleFatal() { +register_shutdown_function(function () { $error = error_get_last(); if (isset($error['type'])) { switch ($error['type']) { @@ -47,13 +37,14 @@ function handleFatal() { $log .= "{$t['function']}()\n"; } - file_put_contents(CONFIG_DIR . "last_error.log", $log); + file_put_contents(CRASH_DIR . "last_error.log", $log); break; default: break; } } -} +}); + function CQMsg($msg, $type, $id) { if ($type === "group") { @@ -77,96 +68,6 @@ function CQMsg($msg, $type, $id) { return $reply; } -function printHelp() { - echo color("{gold}=====CQBot-swoole====="); - echo color("{gold}* 首次使用设置 *"); - echo color("[{green}?{r}] {lightlightblue}查看此列表"); - echo color("[{green}1{r}] {yellow}设置监听地址"); - echo color("[{green}2{r}] {yellow}设置监听端口"); - echo color("[{green}3{r}] {yellow}设置管理群"); - echo color("[{green}4{r}] {yellow}设置管理员"); - echo color("[{green}5{r}] {lightlightblue}开始运行"); -} - -function setupWizard(&$json, &$properties) { - printHelp(); - while (true) { - 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"; - echo color("{gray}已设置地址:0.0.0.0(默认)"); - } else { - $properties["host"] = $host; - echo color("{gray}已设置地址:" . $host); - } - break; - case "2": - echo color("请输入监听端口(默认20000):", ""); - $host = trim(fgets(STDIN)); - if ($host == "") { - $properties["port"] = 20000; - echo color("{gray}已设置端口:20000(默认)"); - } else { - $properties["port"] = $host; - echo color("{gray}已设置端口:" . $host); - } - break; - case "3": - echo color("请输入机器人QQ号:", ""); - $self_id = trim(fgets(STDIN)); - if ($self_id == "") { - echo color("{red}请勿输入空数据!"); - break; - } - echo color("请输入本机器人QQ的管理群(机器人必须已经在群内):", ""); - $group = trim(fgets(STDIN)); - if ($group == "") { - echo color("{red}请勿输入空数据!"); - break; - } - $properties["admin_group"][$self_id][] = $group; - echo color("{gray}已设置机器人" . $self_id . "的管理群:" . $group); - break; - case "4": - echo color("请输入机器人QQ号:", ""); - $self_id = trim(fgets(STDIN)); - if ($self_id == "") { - echo color("{red}请勿输入空数据!"); - break; - } - echo color("请输入本机器人QQ的管理员QQ:", ""); - $group = trim(fgets(STDIN)); - if ($group == "") { - echo color("{red}请勿输入空数据!"); - break; - } - $properties["super_user"][$self_id][] = $group; - echo color("{gray}已设置机器人" . $self_id . "的管理员:" . $group); - break; - case "5": - break 2; - case "?": - case "?": - printHelp(); - break; - default: - echo color("{red}请输入正确的编号进行操作!\n在设置监听端口和监听地址后可开始运行服务器"); - break; - } - } - $json["host"] = $properties["host"]; - $json["port"] = $properties["port"]; - $json["admin_group"] = $properties["admin_group"]; - $json["super_user"] = $properties["super_user"]; - $json["info_level"] = $properties["info_level"]; - file_put_contents(CONFIG_DIR . "config.json", json_encode($json, 128 | 256)); -} - function color($str, $end = "\n") { $str = str_replace("{red}", "\e[38;5;203m", $str); $str = str_replace("{green}", "\e[38;5;83m", $str); diff --git a/wizard.php b/wizard.php new file mode 100644 index 00000000..1b4f0e23 --- /dev/null +++ b/wizard.php @@ -0,0 +1,103 @@ + ", ""); + $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"; + echo color("{gray}已设置地址:0.0.0.0(默认)"); + } else { + $properties["host"] = $host; + echo color("{gray}已设置地址:" . $host); + } + $ls = settings(); + $ls["host"] = $properties["host"]; + file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + break; + case "2": + echo color("请输入监听端口(默认20000):", ""); + $host = trim(fgets(STDIN)); + if ($host == "") { + $properties["port"] = 20000; + echo color("{gray}已设置端口:20000(默认)"); + } else { + $properties["port"] = $host; + echo color("{gray}已设置端口:" . $host); + } + $ls = settings(); + $ls["port"] = $properties["port"]; + file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + break; + case "3": + echo color("请输入机器人QQ的管理群(机器人必须已经在群内):", ""); + $group = trim(fgets(STDIN)); + if ($group == "") { + echo color("{red}请勿输入空数据!"); + break; + } + $properties["admin_group"] = $group; + echo color("{gray}已设置机器人的管理群:" . $group); + $ls = settings(); + $ls["admin_group"] = $properties["admin_group"]; + file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + break; + case "4": + echo color("请输入机器人管理员QQ:", ""); + $group = trim(fgets(STDIN)); + if ($group == "") { + echo color("{red}请勿输入空数据!"); + break; + } + $properties["admin"][] = $group; + echo color("{gray}已设置机器人的管理员:" . $group); + $ls = settings(); + $ls["admin"] = $properties["admin"]; + file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + break; + case "5": + echo color("请输入连接框架的access_token:", ""); + $group = trim(fgets(STDIN)); + $properties["access_token"] = $group; + if ($group == "") echo color("{gray}token为空,将不检测验证token!"); + else echo color("{gray}已设置websocket连接token:" . $group); + $ls = settings(); + $ls["access_token"] = $properties["access_token"]; + file_put_contents("cqbot.json", json_encode($ls, 128 | 256)); + break; + case "6": + break 2; + case "?": + case "?": + case "0": + printHelp(); + break; + default: + echo color("{red}请输入正确的编号进行操作!\n在设置监听端口和监听地址后可开始运行服务器"); + break; + } +} +if (settings()["host"] != "") return true; +else return false;