通过酷Q实现qq机器人,实现qq客服、定时推送qq消息、群管理等功能。
方式:
1.windows下载酷Q并安装http-api插件。
下载地址:https://pan.baidu.com/s/1qY55zp6
2.直接使用docker,通过wine运行在linux系统上。
$ docker pull richardchien/cqhttp:latest $ mkdir coolq # 用于存储酷 Q 的程序文件 $ docker run -ti --rm --name cqhttp-test \ -v $(pwd)/coolq:/home/user/coolq \ # 将宿主目录挂载到容器内用于持久化酷 Q 的程序文件 -p 9000:9000 \ # noVNC 端口,用于从浏览器控制酷 Q -p 5700:5700 \ # HTTP API 插件开放的端口 -e COOLQ_ACCOUNT=123456 \ # 要登录的 QQ 账号,可选但建议填 -e CQHTTP_POST_URL=http://example.com:8080 \ # 事件上报地址 -e CQHTTP_SERVE_DATA_FILES=yes \ # 允许通过 HTTP 接口访问酷 Q 数据文件 richardchien/cqhttp:latest
其中,CQHTTP_POST_URL、CQHTTP_SERVE_DATA_FILES 是用于配置插件运行的,格式为「CQHTTP_ + 插件配置项的大写」,具体的配置项,见 配置。
然后访问 http://
注意,默认情况下,容器启动时会检查是否已经存在 config 目录,如果不存在,则会将 CQHTTP_ 开头的环境变量写入到配置文件中;如果 config 目录存在,即不是首次启动,则不会做任何修改。因此,你可以在容器运行时手动修改配置文件,重启容器后仍然有效,这和旧版本的行为不一样!如果你要让容器每次启动时都使用环境变量重新创建配置文件,以保持插件行为和容器启动命令的一致性,可以设置环境变量 FORCE_ENV 的值为 true。
完成上述配置后即可通过调用api完成qq的发送消息及qq群管理等功能,剩下的具体的业务逻辑使用自己熟悉的语言去实现即可。
详细的接口文档:https://richardchien.gitee.io/coolq-http-api/
附点餐小呆萌代码:
/** * 接收事件上报 * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function receive(){ try{ $getData = input('post.') ? : file_get_contents('php://input'); if (!empty($getData['group_id']) && (int)$getData['group_id'] == $this->groupId) { if ($this->userId = $this->getGroupUserid()){ $content = $getData['raw_message'] ? : $getData['message']; //分离酷q的前置数据与内容文本 preg_match('/(.*[CQ:]{3}.*[]]{1})(.*)/u', $content, $arr); if (!empty($arr[1]) && !empty($arr[2])){ //提取前置数据中被@的qq号 preg_match('/(.*[CQ:at,qq=]{9})([0-9]{5,11})([]]{1})/u', $arr[1], $arr1); if (!empty($arr1[2]) && (int)$arr1[2] == 167114078){ //来到这里证明是被at了,取得纯文本内容进行下一步处理 $tasks = db("group_task") ->whereTime('create_time', 'today') ->select(); if (count($tasks)>=2){ preg_match_all("/([周]{1})([一二三四五六日]{1}).{0,3}([a-cA-C]{1}|[\u{4e0d}]{1}[\u{70b9}]{1})/u", $arr[2],$arr8); $dateArr = $this->getLastSevenDays(); if (!empty($arr8[2])) { //有选项 $data = []; //获取任务id与时间的对应数组,用于判断用户输入的时间有没有任务 $taskArr = array_column($tasks, 'id', 'time'); foreach ($arr8[2] as $key => $value) { $data[strtotime($dateArr[$this->changeDateType($value)])] = $arr8[3][$key]; if (!empty($taskArr[strtotime($dateArr[$this->changeDateType($value)])])){ $taskId = $taskArr[strtotime($dateArr[$this->changeDateType($value)])]; $this->order($taskId, $arr8[3][$key]); }else{ $this->sendGroup($this->groupId, "{$this->groupUserInfo['user_name']}周{$value},没有可点套餐。"); } } }else{ //无选项 preg_match('/([不]{1}[点]{1})/u', $arr[2],$arr2); preg_match('/([A-Ca-c])/u', $arr[2], $arr3); if (!empty($arr2[0]) && $arr2[0] == "不点" || !empty($arr3[0]) && in_array(ucfirst($arr3[0]), ['A', 'B', 'C'])){ $lists = []; $listsNote = []; foreach ($tasks as $v){ $lists[] = "周".$this->changeDateType((int)date("w", $v['time'])); $listsNote[] = "周".$this->changeDateType((int)date("w", $v['time'])) . "-A"; } $msg = "大佬,今天要点" . count($tasks) ."天的餐,你这样我不知道你想点哪一个呀。". PHP_EOL ."今日可点餐有" . implode('、', $lists) . ",点餐请按示例格式回复。". PHP_EOL ."如:" . implode(' ', $listsNote); $this->sendGroup($this->groupId, $msg); } } }else{ if (!empty($tasks[0]['id'])){ preg_match('/([不]{1}[点]{1})/u', $arr[2],$arr2); preg_match('/([A-Ca-c])/u', $arr[2], $arr3); if (!empty($arr2[0]) && $arr2[0] == "不点") { $this->order($tasks[0]['id'], $arr2[0]); exit; }else if (!empty($arr3[0]) && in_array(ucfirst($arr3[0]), ['A', 'B', 'C'])) { $this->order($tasks[0]['id'], ucfirst($arr3[0])); exit; }else { //$this->sendGroup($this->groupId, "不要@我,大佬不给我说话!"); exit; } } } } } } exit; } }catch (\Exception $e){ Log::write("Receive message:".$e->getMessage()); } } /** * 发送消息 * 多行内容部分记得urlencode * @param bool $toUserId * @param bool $content */ public function send($toUserId = false, $content = false){ if ($toUserId && $content){ $content = urlencode($content); $url = "http://127.0.0.1:5701/send_private_msg?user_id={$toUserId}&message={$content}"; file_get_contents($url); } } /** * 发送群消息 * @param bool $groupId * @param bool $message */ public function sendGroup($groupId = false, $message = false){ if ($groupId && $message){ $message = urlencode($message); $url = "http://127.0.0.1:5701/send_group_msg?group_id={$groupId}&message={$message}"; file_get_contents($url); } } /** * 获取群用户id * @return bool|mixed * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ private function getGroupUserid(){ $userModel = db('group_user'); $userInfo = $userModel->where(['qq'=>$this->params['user_id']])->find(); if (isset($userInfo['id'])){ if ((int)$userInfo['status']!=1) return false; $this->groupUserInfo = $userInfo; return $userInfo['id']; } $userParam = $this->params['sender']; $userInfo = [ 'qq' => $userParam['user_id'], 'user_name' => $userParam['nickname'], 'age' => $userParam['age'], 'sex' => $this->getSex($userParam['sex']), 'create_time' => time(), 'update_time' =>time() ]; $this->groupUserInfo = $userInfo; return $userModel->insertGetId($userInfo) ? : false; } private function order($taskId='', $type=''){ try { $type = ucfirst($type); $task = db("group_task") ->find($taskId); if ($task) { $dbModel = db('group_order'); $record = $dbModel ->where([ 'user_id' => $this->userId, 'task_id' => $task['id'] ]) ->find(); $msg = "{$this->groupUserInfo['user_name']},"; if ($record) { $result = $dbModel->update([ 'id' => $record['id'], 'task_id' => $task['id'], 'type' => $this->changeOrderType($type), 'update_time' => time() ]); $msg .= "更新点餐成功,"; } else { $result = $dbModel->insert([ 'user_id' => $this->userId, 'task_id' => $task['id'], 'type' => $this->changeOrderType($type), 'create_time' => time() ]); $msg .= "点餐成功,"; } if ((int)$this->changeOrderType($type) == 9) { $date = $this->changeDateType((int)date("w", $task['time'])); $msg = "{$this->groupUserInfo['user_name']},好的知道了,你周{$date}不点。"; }else if ($result) { $data = json_decode($task['data'], true); $msg .= "您点的是:{$type}餐,【{$data[$type]}】。"; }else{ $msg = "哦吼,点餐失败,系统出错了。"; } $this->sendGroup($this->groupId, $msg); }else{ $this->sendGroup($this->groupId, "{$this->groupUserInfo['user_name']},还没到点餐时间,到时间会通知你的,认真上班吧。"); } }catch (\Exception $e){ $this->sendGroup($this->groupId, "卡bug了,错误信息:".$e->getMessage().'line:'.$e->getLine()); } } /** * 添加点餐任务 * @return mixed */ public function add_task(){ if (request()->isPost()){ $content = input('post.content'); //提取文本中的日期 preg_match_all("'\d{1,2}月\d{1,2}'", $content,$arr1); $time = empty($arr1[0][0]) ? time() : strtotime(date('Y') . '/' . str_replace(['月'], '/', $arr1[0][0])); $dbModel = db("group_task"); $where = [ 'time' => $time ]; $check = $dbModel->where($where) ->find(); if ($check){ $date = date('Y年m月d日', $time); $this->error("{$date},该日期已存在订餐任务,不能重复添加!"); } preg_match_all('/([A-Za-z#].+)/u', $content,$arr); if (!empty($arr[1])) { $result = []; foreach ($arr[1] as $v){ $v = preg_replace("/(\s|\ \;| |\xc2\xa0)/","",$v); $data = explode('餐',$v); if (!empty($data[0]) && in_array($data[0], ['A', 'B', 'C']) && !empty($data[1])) { $result[$data[0]] = $data[1]; } } if (count($result)>=3) { $data = [ 'time' => $time, 'data' => json_encode($result, JSON_UNESCAPED_UNICODE), 'content' => $content, 'create_time' => time() ]; $dbModel->insert($data); $this->success('添加点餐任务成功!'); } } $this->error('添加任务失败!'); exit; } return $this->fetch(); } /** * 推送qq消息 */ public function push_message(){ if (request()->isPost()) { $content = input('post.content'); $this->sendGroup($this->groupId, $content); //$this->sendGroup($this->groupId, "来活了,老铁们,开始点餐啦,A、B、C随便选,不点的回复不点,点餐时需要先@小助手,否则不管用哦,点餐12点准时结束,过时不候。"); $this->success("推送成功!"); } return $this->fetch(); } /** * @return mixed * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function views(){ $userModel = db('group_user'); $day = input("param.day") ? : (date('d')); $startTime = strtotime(date("Y-m-{$day} 00:00:00")); $task = db("group_task") ->where(['time'=> $startTime]) ->find(); $data = empty($task['id'])? [] : $userModel->alias('u') ->field("u.id,u.user_name,o.type,o.create_time") ->join("group_order o", "o.task_id = {$task['id']} and o.user_id = u.id", 'LEFT') ->where(['u.status' => 1]) ->order("o.type asc") ->select(); $result = $this->array_group_by($data, 'type'); $notChoose = isset($result['']) ? implode(',', array_column($result[''], 'user_name')) : ''; $this->assign('not_choose', $notChoose); ksort($result); $record = []; $msg = []; foreach ($result as $k => $v){ if (!empty((int)$k) && in_array((int)$k, [1,2,3])){ $num = count($v); $msg[] = $this->changeOrderType($k) ." {$num}份"; $record[$k] = $num; } } $info = '云商项目开发部: '.implode(', ', $msg) . " 共计:".array_sum($record) . "份"; foreach ($data as $k => &$v){ if (!empty($v['type']) && in_array((int)$v['type'], [1, 2, 3])){ $v['status'] = 1; }else{ $v['status'] = ''; } $v['type'] = $this->changeOrderType($v['type']); if ($v['create_time']){ $v['create_time'] = date('Y-m-d H:i:s', $v['create_time']); } } krsort($msg); if (request()->isPost()){ $result = [ 'code' => 0, 'data' => $data ]; echo json_encode($result); exit; } if ($task){ $task = json_decode($task['data'], true); } $days = date('t', strtotime('Y-m')) + 1 ; $this->assign('days', $days); $this->assign('day', $day); $this->assign('task', $task); $this->assign('data', $data); $this->assign('info', $info); return $this->fetch(); } /** * @return mixed * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function view_month(){ $days = date('t', strtotime('2019-03')); $userModel = db('group_user'); $orderModel = db('group_order'); $month = input("param.month") ? : date('m'); if (request()->isPost()){ $startTime = strtotime(date("Y-{$month}-1")); $endTime = strtotime(date("Y-m-d", strtotime("last day of this month", strtotime(date("Y-{$month}-d"))))); $data = $userModel ->field("id,user_name") ->where(['status' => 1]) ->select(); foreach ($data as &$v){ $results = $orderModel->alias('o')->field("t.time") ->join('group_task t', "t.id = o.task_id", "LEFT") ->where([ 'o.user_id' => $v['id'], 't.time' => ['between', [$startTime, $endTime]], 'o.type' => ['IN', [1,2,3]] ])->select(); $v['type'] = "午餐"; $timeArr = array_column($results, 'time'); for ($i=1;$i 0, 'data' => $data ]; echo json_encode($result); exit; } $this->assign('months', [1,2,3,4,5,6,7,8,9,10,11,12]); $this->assign('month', $month); return $this->fetch(); } public static function array_group_by($arr, $key) { $grouped = []; foreach ($arr as $value) { $grouped[$value[$key]][] = $value; } if (func_num_args() > 2) { $args = func_get_args(); foreach ($grouped as $key => $value) { $parms = array_merge([$value], array_slice($args, 2, func_num_args())); $grouped[$key] = call_user_func_array('array_group_by', $parms); } } return $grouped; } /** * 点餐结束,推送点餐情况 * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function push_order_result(){ if (!empty($this->params['token']) && $this->params['token'] == $this->push_token) { $task = db("group_task") ->whereTime('create_time', 'today') ->select(); if ($task) { $userModel = db('group_user'); $msg = '今日点餐时间结束,点餐情况如下:'.PHP_EOL; foreach ($task as $v){ $orderDate = date('Y年m月d日', $v['time']); $msg .= "用餐时间:{$orderDate}".PHP_EOL; $data = $userModel->alias('u') ->field("u.id,u.user_name,o.type,o.create_time") ->join('group_order o', "o.user_id = u.id and task_id = {$v['id']}", 'LEFT') ->where(['u.status' => 1]) ->select(); $orderData = json_decode($v['data'], true); foreach ($data as $vo){ if (!empty($vo['type']) && in_array((int)$vo['type'], [1,2,3])){ $type = $this->changeOrderType($vo['type']); $msg .= "{$vo['user_name']} : {$type}餐 【{$orderData[$type]}】".PHP_EOL; }else{ $msg .= "{$vo['user_name']} : 不点".PHP_EOL; } } $result = $this->array_group_by($data, 'type'); $typeMsg = []; $record = []; foreach ($result as $k => $v){ if (!empty((int)$k) && in_array((int)$k, [1,2,3])){ $num = count($v); $typeMsg[$k] = $this->changeOrderType($k) ." {$num}份"; $record[$k] = $num; } } ksort($typeMsg); $msg .= PHP_EOL."{$orderDate} 云商项目开发部: ".implode(', ', $typeMsg) . " 共计:".array_sum($record) . "份".PHP_EOL; } $this->sendGroup($this->groupId, $msg); } } } /** * 开饭前推送今天的点餐情况 * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function push_today_order(){ if (!empty($this->params['token']) && $this->params['token'] == $this->push_token) { $task = db("group_task") ->whereTime('time', 'today') ->find(); if ($task){ $userModel = db('group_user'); $orderDate = date('m月d日', $task['time']); $msg = "下班开饭,{$orderDate}点餐情况如下,请对号入座:".PHP_EOL; $data = $userModel->alias('u') ->field("u.id,u.user_name,o.type,o.create_time") ->join('group_order o', "o.user_id = u.id and task_id = {$task['id']}", 'LEFT') ->where(['u.status' => 1]) ->select(); $orderData = json_decode($task['data'], true); foreach ($data as $vo){ if (!empty($vo['type']) && in_array((int)$vo['type'], [1,2,3])){ $type = $this->changeOrderType($vo['type']); $msg .= "{$vo['user_name']} : {$type}餐 【{$orderData[$type]}】".PHP_EOL; }else{ $msg .= "{$vo['user_name']} : 不点".PHP_EOL; } } //$msg .= PHP_EOL."你喜欢岁月静好,其实现实是大江奔流。"; $this->sendGroup($this->groupId, $msg); } } } /** * 获取未来7天分别是周几 * @param bool $time * @return array */ public function getLastSevenDays($time = false){ if (!$time) $time = time(); $data = []; for ($i=0;$i<7;$i++){ $data[date('w', $time)] = date('Y-m-d 00:00:00', $time); $time += 60*60*24; } return $data; } /** * 把中文的周几转换为数字并对应php date()方法 * @param string $date * @return bool|int */ public function changeDateType($date = ''){ $result = false; if (is_int($date)){ switch ($date){ case 1: $result = '一'; break; case 2: $result = '二'; break; case 3: $result = '三'; break; case 4: $result = '四'; break; case 5: $result = '五'; break; case 6: $result = '六'; break; case 0: $result = '日'; break; } }else{ if (empty($date)) return $result; switch ($date){ case '一': $result = 1; break; case '二': $result = 2; break; case '三': $result = 3; break; case '四': $result = 4; break; case '五': $result = 5; break; case '六': $result = 6; break; case '日': $result = 0; break; } } return $result; }