diff --git a/.gitignore b/.gitignore
new file mode 100755
index 00000000..fe7871cc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+.idea
+out
+gen
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/src/cqbot/CQBot.php b/src/cqbot/CQBot.php
new file mode 100755
index 00000000..850cf2f2
--- /dev/null
+++ b/src/cqbot/CQBot.php
@@ -0,0 +1,161 @@
+starttime = microtime(true);
+ $this->framework = $framework;
+ }
+
+ public function execute($it){
+ $this->data = $it;
+ if ($it["post_type"] == "message") {
+ try {
+ $this->callTask($it);
+ } catch (\Exception $e) {
+ CQUtil::errorLog("请求执行任务时异常\n" . $e->getMessage());
+ CQUtil::sendDebugMsg("引起异常的消息:\n" . $it["message"]);
+ }
+ $msg_arr = explode("&&", $this->replace($it["message"], $it));
+ foreach ($msg_arr as $item) {
+ $msg = trim($item);
+ $msg = explode(" ", $msg);
+ $this->checkFunction($msg, $it);
+ }
+ }
+ }
+
+ public function checkFunction($msgCut, $it){
+ $cmdList = Buffer::get("commands");
+ if (isset($cmdList[$msgCut[0]])) {
+ Console::debug("Call CQFunction:" . $msgCut[0]);
+ $temp = $cmdList[$msgCut[0]];
+ /** @var ModBase $class */
+ $class = new $temp($this, $it);
+ $class->execute($msgCut);
+ return true;
+ }
+ Console::debug("未找到指令:[" . $msgCut[0] . "]");
+ return false;
+ }
+
+ public function callTask($it){
+ if ($this->data["post_type"] == "message") {
+ foreach(Buffer::get("mod_task") as $v){
+ new $v($this, $this->data);
+ }
+ }
+ }
+
+ public function reply($msg){
+ $this->function_called = true;
+ switch ($this->data["message_type"]) {
+ case "group":
+ $this->sendGroupMsg($this->data["group_id"], $msg);
+ break;
+ case "private":
+ $this->sendPrivateMsg($this->data["user_id"], $msg);
+ break;
+ case "discuss":
+ $reply = json_encode(["action" => "send_discuss_msg", "params" => ["discuss_id" => $this->data["discuss_id"], "message" => $msg]]);
+ if (CQUtil::APIPush($reply)) {
+ $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;
+ case "wechat":
+ //TODO: add wechat account support in the future
+ break;
+ }
+ }
+
+ public function sendGroupMsg($groupId, $msg){
+ $this->function_called = true;
+ CQUtil::sendGroupMsg($groupId, $msg);
+ }
+
+ public function sendPrivateMsg($userId, $msg){
+ $this->function_called = true;
+ CQUtil::sendPrivateMsg($userId, $msg);
+ }
+
+ public function isAdmin($user){
+ if (in_array($user, Buffer::get("su"))) return true;
+ else return false;
+ }
+
+ public function replace($msg, $dat){
+ $msg = str_replace("{at}", '[CQ:at,qq=' . $dat["user_id"] . ']', $msg);
+ $msg = str_replace("{and}", '&', $msg);
+ while (strpos($msg, '{') !== false && strpos($msg, '}') !== false) {
+ if (strpos($msg, '{') > strpos($msg, '}')) return $msg;
+ $start = strpos($msg, '{');
+ $end = strpos($msg, '}');
+ $sub = explode("=", substr($msg, $start + 1, $end - $start - 1));
+ switch ($sub[0]) {
+ case "at":
+ $qq = $sub[1];
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:at,qq=' . $qq . ']', $msg);
+ break;
+ case "image":
+ case "record":
+ $pictFile = $sub[1];
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:' . $sub[0] . ',file=' . $pictFile . ']', $msg);
+ break;
+ case "dice":
+ $file = $sub[1];
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:dice,type=' . $file . ']', $msg);
+ break;
+ case "shake":
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:shake]', $msg);
+ break;
+ case "music":
+ $id = $sub[1];
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:music,type=163,id=' . $id . ']', $msg);
+ break;
+ case "internet":
+ array_shift($sub);
+ $id = implode("=", $sub);
+ if (substr($id, 0, 7) != "http://") $id = "http://" . $id;
+ $is = file_get_contents($id, false, NULL, 0, 1024);
+ if ($is == false) $is = "[请求时发生了错误] 如有疑问,请联系管理员";
+ $msg = str_replace(substr($msg, $start, $end - $start + 1), $is, $msg);
+ break 2;
+ default:
+ break 2;
+ }
+ }
+ return $msg;
+ }
+}
\ No newline at end of file
diff --git a/src/cqbot/Framework.php b/src/cqbot/Framework.php
new file mode 100755
index 00000000..218a91f0
--- /dev/null
+++ b/src/cqbot/Framework.php
@@ -0,0 +1,169 @@
+host = $host;
+ return $this;
+ }
+
+ public function setApiPort($port = 10000){
+ $this->api_port = $port;
+ return $this;
+ }
+
+ public function setEventPort($port = 20000){
+ $this->event_port = $port;
+ return $this;
+ }
+
+ public function setAdminGroup($group){
+ self::$admin_group = $group;
+ return $this;
+ }
+
+ public function setInfoLevel($level){
+ $this->info_level = $level;
+ return $this;
+ }
+
+ public function eventServerStart(){
+ $this->event->start();
+ }
+
+ public static function getInstance(){
+ return self::$obj;
+ }
+
+ public function init(){
+ self::$obj = $this;
+ Console::put("CQBot Framework starting...");
+ $this->event = new \swoole_websocket_server($this->host, $this->event_port);
+ Buffer::$log_file = CONFIG_DIR . "log/swoole.log";
+ Console::put("Current log file: " . Buffer::$log_file);
+ $worker_num = 1;
+ Console::put("Current worker count: " . $worker_num);
+ $dispatch_mode = 2;
+ Console::put("Current dispatch mode: " . $dispatch_mode);
+ $this->checkFiles();
+ $this->event->set([
+ "log_file" => Buffer::$log_file,
+ "worker_num" => 1,
+ "dispatch_mode" => 2
+ ]);
+ $this->event->on('WorkerStart', [$this, 'onWorkerStart']);
+ $this->event->on('message', [$this, 'onEventMessage']);
+ $this->event->on('open', [$this, 'onConnect']);
+ $this->event->on('close', function ($serv, $fd){
+ //put your connection close method here.
+ });
+ Buffer::$in_count = new \swoole_atomic(1);
+ Buffer::$out_count = new \swoole_atomic(1);
+ Buffer::$api_id = new \swoole_atomic(1);
+ return $this;
+ }
+
+ public function checkFiles(){
+ @mkdir(WORKING_DIR."log/", 0777, true);
+ if(!is_file(CONFIG_DIR."log/last_error.log"))
+ file_put_contents(CONFIG_DIR."log/last_error.log", "");
+ if(!is_file(CONFIG_DIR."log/error_flag"))
+ file_put_contents(CONFIG_DIR."log/error_flag", time());
+ }
+
+ /* Callback function down here */
+
+ /**
+ * This is async function in EventLoop
+ * When it reload, it will run this function again.
+ * @param \swoole_server $server
+ * @param $worker_id
+ */
+ public function onWorkerStart(\swoole_server $server, $worker_id){
+ $this->run_time = time();
+ Console::info("Starting worker " . $worker_id);
+ Console::info("Loading source code...");
+ require_once(WORKING_DIR . "src/cqbot/loader.php");
+ CQUtil::loadAllFiles();
+ Buffer::set("info_level", $this->info_level);//设置info等级
+ foreach (get_included_files() as $file)
+ Console::debug("Loaded " . $file);
+ echo("\n");
+
+ //计时器(ms)
+ $server->tick(1000, [$this, "processTick"]);
+
+ $this->api = new \swoole_http_client($this->host, $this->api_port);
+ $this->api->set(['websocket_mask' => true]);
+ $this->api->on('message', [$this, "onApiMessage"]);
+ $this->api->on("close", function ($cli){
+ Console::info(Console::setColor("API connection closed", "red"));
+ });
+ $this->api->upgrade('/api/', [$this, "onUpgrade"]);
+
+ Console::debug("master_pid = " . $server->master_pid);
+ Console::debug("worker_id = " . $worker_id);
+ Console::put("\n====================\n");
+ }
+
+ public function onUpgrade($cli){
+ Console::info("Upgraded API websocket");
+ Buffer::$api = $this->api;
+ Buffer::$event = $this->event;
+ if ($data = file(CONFIG_DIR . "last_error.log")) {
+ $last_time = file_get_contents(CONFIG_DIR . "error_flag");
+ if (time() - $last_time < 2) {
+ CQUtil::sendDebugMsg("检测到重复引起异常,停止服务器", 0);
+ file_put_contents(CONFIG_DIR."last_error.log", "");
+ $this->event->shutdown();
+ return;
+ }
+ CQUtil::sendDebugMsg("检测到异常", 0);
+ $msg = "";
+ foreach ($data as $e) {
+ $msg = $msg . $e . "\n";
+ }
+ CQUtil::sendDebugMsg($msg, 0);
+ CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0);
+ file_put_contents(CONFIG_DIR . "error_flag", time());
+ file_put_contents(CONFIG_DIR . "last_error.log", "");
+ }
+ else {
+ CQUtil::sendDebugMsg("[CQBot] 成功开启!", 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/cqbot/User.php b/src/cqbot/User.php
new file mode 100755
index 00000000..3e8d0d31
--- /dev/null
+++ b/src/cqbot/User.php
@@ -0,0 +1,81 @@
+id = $qid;
+ $this->permission = DP::getJsonData("permissions.json")[$qid] ?? 0;
+ $this->is_friend = isset(Buffer::get("friend_list")[$qid]) ? true : false;
+ }
+
+ /**
+ * 获取用户QQ号
+ * @return mixed
+ */
+ public function getId(){ return $this->id; }
+
+ /**
+ * 获取用户添加词库的状态
+ * @return array
+ */
+ public function getWordStatus() : array{ return $this->word_status; }
+
+ /**
+ * 获取用户权限值
+ * @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;
+ }
+
+ /**
+ * @param int $permission
+ * @return User
+ */
+ public function setPermission(int $permission): User{
+ $this->permission = $permission;
+ return $this;
+ }
+
+ /**
+ * 和用户是否是好友//TODO功能
+ * @return bool
+ */
+ public function isFriend(): bool{
+ return $this->is_friend;
+ }
+
+ public function getBuffer(){
+ return $this->buffer;
+ }
+
+ public function setBuffer($buffer){
+ $this->buffer = $buffer;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/src/cqbot/loader.php b/src/cqbot/loader.php
new file mode 100755
index 00000000..1fae19a3
--- /dev/null
+++ b/src/cqbot/loader.php
@@ -0,0 +1,37 @@
+main->isAdmin($this->getUserId())) return false;
+ switch ($it[0]) {
+ case "add-cmd":
+ if (count($it) < 3) {
+ $this->reply("用法:add-cmd 指令 模块名");
+ return true;
+ }
+ if(!CQUtil::isModExists($it[2])){
+ $this->reply("对不起,模块 ".$it[2]." 不存在!");
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/cqbot/mods/ModBase.php b/src/cqbot/mods/ModBase.php
new file mode 100755
index 00000000..8acd1d49
--- /dev/null
+++ b/src/cqbot/mods/ModBase.php
@@ -0,0 +1,44 @@
+main = $main;
+ $this->data = $data;
+ $this->cmds = $mod_cmd;
+ }
+
+ 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 abstract 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 getMessageType(){ return $this->data["message_type"]; }
+
+ public function getCommands(){ return $this->cmds; }
+}
\ No newline at end of file
diff --git a/src/cqbot/utils/Buffer.php b/src/cqbot/utils/Buffer.php
new file mode 100755
index 00000000..20a2e4f0
--- /dev/null
+++ b/src/cqbot/utils/Buffer.php
@@ -0,0 +1,49 @@
+isConnected() === false) {
+ //链接被断开
+ Buffer::$api->upgrade('/api/', function ($cli){
+ self::sendDebugMsg("API重新链接成功");
+ self::APIPushDelayMsg();
+ });
+ return 0;
+ }
+ return 1;
+ }
+
+ /**
+ * 推送API,给API端口
+ * @param $data
+ * @return bool
+ */
+ static function APIPush($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::$api->push($data) === false) {
+ $data = self::unicodeDecode($data);
+ self::errorlog("API推送失败,未发送的消息: \n" . $data, "API ERROR", 0);
+ self::sendErrorEmail("API推送失败", "未成功推送的消息:
$data
请检查酷q是否开启及网络链接情况
在此期间,机器人会中断所有消息处理
请及时处理");
+ return false;
+ }
+ return true;
+ }
+
+ 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 string $name
+ * @param int $send_debug_message
+ * @return bool|string
+ */
+ static function sendEmail($address, $title, $content, $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", $send_debug_message);
+ unset($mail);
+ return $info;
+ }
+ }
+
+ /**
+ * 获取运行时间
+ * @param $time
+ * @return array
+ */
+ static function getRunTime($time){
+ $time_len = time() - $time;
+ $run_time = [];
+ if (intval($time_len / 86400) > 0) {
+ $run_time[0] = intval($time_len / 86400);
+ $time_len = $time_len % 86400;
+ }
+ else {
+ $run_time[0] = 0;
+ }
+ if (intval($time_len / 3600) > 0) {
+ $run_time[1] = intval($time_len / 3600);
+ $time_len = $time_len % 3600;
+ }
+ else {
+ $run_time[1] = 0;
+ }
+ if (intval($time_len / 60) > 0) {
+ $run_time[2] = intval($time_len / 60);
+ $time_len = $time_len % 60;
+ }
+ else {
+ $run_time[2] = 0;
+ }
+ $run_time[3] = $time_len;
+ return $run_time;
+ }
+
+ /**
+ * 获取格式化的运行时间
+ * @param $time
+ * @return string
+ */
+ static function getRunTimeFormat($time){
+ $time_len = time() - $time;
+ $msg = "";
+ if (intval($time_len / 86400) > 0) {
+ $msg .= intval($time_len / 86400) . "天";
+ $time_len = $time_len % 86400;
+ }
+ if (intval($time_len / 3600) > 0) {
+ $msg .= intval($time_len / 3600) . "小时";
+ $time_len = $time_len % 3600;
+ }
+ if (intval($time_len / 60) > 0) {
+ $msg .= intval($time_len / 60) . "分";
+ $time_len = $time_len % 60;
+ }
+ $msg .= $time_len . "秒";
+ return $msg;
+ }
+
+ /**
+ * 检查是否为群组管理员或群主功能,此功能需要先获取群组列表,否则会产生一个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 string $name
+ */
+ static function sendErrorEmail($title, $content, $name = "机器人错误提示"){
+ self::sendEmail(["here your receive email address"], $title, $content, $name, 0);
+ }
+
+ /**
+ * 获取所有已经加载到内存的用户。
+ * read_all为true时,会加载所有User.dat到内存中,false时仅会读取已经加载到内存的用户
+ * @param bool $real_all
+ * @return array[User]
+ */
+ static function getAllUsers($real_all = false): array{
+ if ($real_all === true) {
+ $dir = scandir(DP::getUserFolder());
+ unset($dir[0], $dir[1]);
+ foreach ($dir as $d => $v) {
+ $vs = explode(".", $v);
+ if (array_pop($vs) == "dat") {
+ $class = unserialize(file_get_contents(DP::getUserFolder() . $v));
+ if (!Buffer::array_key_exists("user", $vs[0])) {
+ Buffer::appendKey("user", $vs[0], $class);
+ }
+ }
+ }
+ }
+ return Buffer::get("user");
+ }
+
+ /**
+ * 获取用户实例
+ * @param $id
+ * @return User
+ */
+ static function getUser($id){
+ $d = Buffer::get("user");
+ if (!isset($d[$id])) {
+ self::initUser($id);
+ $d = Buffer::get("user");
+ }
+ /** @var User $class */
+ $class = $d[$id];
+ return $class;
+ }
+
+ /**
+ * 初始化用户实例。如果没有此用户的实例数据,会创建
+ * @param $id
+ */
+ static function initUser($id){
+ if (file_exists(DP::getUserFolder() . $id . ".dat")) $class = unserialize(file_get_contents(DP::getUserFolder() . $id . ".dat"));
+ else {
+ Console::info("无法找到用户 " . $id . " 的数据,正在创建...");
+ $class = new User($id);
+ }
+ Buffer::appendKey("user", $id, $class);
+ }
+
+ /**
+ * 发送群组消息,含控制台推出
+ * @param $groupId
+ * @param $msg
+ * @return bool
+ */
+ static function sendGroupMsg($groupId, $msg){
+ $reply = ["action" => "send_group_msg", "params" => ["group_id" => $groupId, "message" => $msg]];
+ $reply["echo"] = $reply;
+ $reply["echo"]["time"] = time();
+ $reply = json_encode($reply);
+ if (self::APIPush($reply)) {
+ 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
+ * @return bool
+ */
+ static function sendPrivateMsg($userId, $msg){
+ $reply = ["action" => "send_private_msg", "params" => ["user_id" => $userId, "message" => $msg]];
+ $reply["echo"] = $reply;
+ $reply["echo"]["time"] = time();
+ $reply = json_encode($reply);
+ if (self::APIPush($reply)) {
+ 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 $data
+ * @param $echo
+ */
+ static function sendAPI($data, $echo){
+ if (!is_array($data)) {
+ $api = [];
+ $api["action"] = $data;
+ }
+ else {
+ $api = $data;
+ }
+ $api["echo"] = $echo;
+ self::APIPush(json_encode($api));
+ }
+
+ /**
+ * 删除一个和模块相关联的指令
+ * @param $name
+ * @return bool
+ */
+ static function removeCommand($name){
+ $list = Buffer::get("commands");
+ if (!isset($list[$name])) return false;
+ unset($list[$name]);
+ Buffer::set("commands", $list);
+ DP::setJsonData("commands.json", $list);
+ return true;
+ }
+
+ /**
+ * 添加一个指令给非callTask方式激活的模块。
+ * 注意:如果给callTask方式激活的模块添加指令,则在使用对应功能时会回复多次同样的内容
+ * @param $name
+ * @param $class
+ * @return bool
+ */
+ static function addCommand($name, $class){
+ if (!is_file(WORKING_DIR.'src/cqbot/mods/' . $class . '.php')) {
+ return false;
+ }
+ $list = Buffer::get("commands");
+ $list[$name] = $class;
+ DP::setJsonData("commands.json", $list);
+ Buffer::set("commands", $list);
+ return true;
+ }
+
+ /**
+ * 获取模块列表的通用方法
+ * @return array
+ */
+ static function getMods(){
+ $dir = WORKING_DIR."src/cqbot/mods/";
+ $dirs = scandir($dir);
+ $ls = [];
+ unset($dirs[0], $dirs[1]);
+ foreach($dirs as $v){
+ if($v != "ModBase.php" && (strstr($v,".php") !== false)){
+ $name = substr($v, 0, -4);
+ $ls[]=$name;
+ }
+ }
+ return $ls;
+ }
+
+ /**
+ * 判断模块是否存在
+ * @param $mod_name
+ * @return bool
+ */
+ static function isModExists($mod_name){
+ $ls = self::getMods();
+ return in_array($mod_name, $ls);
+ }
+}
\ No newline at end of file
diff --git a/src/cqbot/utils/Console.php b/src/cqbot/utils/Console.php
new file mode 100755
index 00000000..552f720c
--- /dev/null
+++ b/src/cqbot/utils/Console.php
@@ -0,0 +1,67 @@
+ "POST 请求的正文格式不正确",
+ 1404 => "API 不存在",
+ 100 => "参数缺失或参数无效",
+ 102 => "酷q操作权限不足",
+ 103 => "用户权限不足或文件系统异常",
+ 201 => "工作线程池未正确初始化",
+ -1 => "请求发送失败",
+ -2 => "未收到服务器回复,可能未发送成功",
+ -3 => "消息过长或为空",
+ -4 => "消息解析过程异常",
+ -5 => "日志功能未启用",
+ -6 => "日志优先级错误",
+ -7 => "数据入库失败",
+ -8 => "不支持对系统帐号操作",
+ -9 => "帐号不在该群内,消息无法发送",
+ -10 => "该用户不存在/不在群内",
+ -11 => "数据错误,无法请求发送",
+ -12 => "不支持对匿名成员解除禁言",
+ -13 => "无法解析要禁言的匿名成员数据",
+ -14 => "由于未知原因,操作失败",
+ -15 => "群未开启匿名发言功能,或匿名帐号被禁言",
+ -16 => "帐号不在群内或网络错误,无法退出/解散该群",
+ -17 => "帐号为群主,无法退出该群",
+ -18 => "帐号非群主,无法解散该群",
+ -19 => "临时消息已失效或未建立",
+ -20 => "参数错误",
+ -21 => "临时消息已失效或未建立",
+ -22 => "获取QQ信息失败",
+ -23 => "找不到与目标QQ的关系,消息无法发送",
+ -26 => "消息过长"
+ ];
+
+ static function getMessage($retcode){
+ return self::$error[$retcode] ?? "未知错误";
+ }
+}
\ No newline at end of file
diff --git a/src/extension/PHPMailer.phar b/src/extension/PHPMailer.phar
new file mode 100755
index 00000000..3367ed46
Binary files /dev/null and b/src/extension/PHPMailer.phar differ
diff --git a/start.php b/start.php
new file mode 100755
index 00000000..aac8efdc
--- /dev/null
+++ b/start.php
@@ -0,0 +1,100 @@
+ $t) {
+ if (!isset($t['file'])) {
+ $t['file'] = 'unknown';
+ }
+ if (!isset($t['line'])) {
+ $t['line'] = 0;
+ }
+ if (!isset($t['function'])) {
+ $t['function'] = 'unknown';
+ }
+ $log .= "#$i {$t['file']}({$t['line']}): ";
+ if (isset($t['object']) and is_object($t['object'])) {
+ $log .= get_class($t['object']) . '->';
+ }
+ $log .= "{$t['function']}()\n";
+ }
+
+ file_put_contents(CONFIG_DIR . "last_error.log", $log);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ function CQMsg($msg, $type, $id){
+ if ($type === "group") {
+ $reply = ["action" => "send_group_msg", "params" => ["group_id" => $id, "message" => $msg]];
+ $reply["echo"] = $reply;
+ $reply["echo"]["time"] = time();
+ $reply = json_encode($reply);
+ }
+ else if ($type === "private") {
+ $reply = ["action" => "send_private_msg", "params" => ["user_id" => $id, "message" => $msg]];
+ $reply["echo"] = $reply;
+ $reply["echo"]["time"] = time();
+ $reply = json_encode($reply);
+ }
+ else if ($type === "discuss") {
+ $reply = ["action" => "send_discuss_msg", "params" => ["discuss_id" => $id, "message" => $msg]];
+ $reply["echo"] = $reply;
+ $reply["echo"]["time"] = time();
+ $reply = json_encode($reply);
+ }
+ else {
+ $reply = false;
+ }
+ return $reply;
+ }
+
+ //loading projects
+ require(WORKING_DIR . "src/cqbot/Framework.php");
+ require(WORKING_DIR . "src/cqbot/utils/Buffer.php");
+ require(WORKING_DIR . "src/cqbot/utils/ErrorStatus.php");
+ require(WORKING_DIR . "src/cqbot/utils/Console.php");
+ $cqbot = new Framework();
+ $cqbot->setHost("127.0.0.1");
+ $cqbot->setApiPort("10000");
+ $cqbot->setEventPort("20000");
+ $cqbot->setAdminGroup("");
+ $cqbot->setInfoLevel(1);
+ $cqbot->init();
+ $cqbot->eventServerStart();
+}
\ No newline at end of file