Commit b3bfcfae by HanSon Committed by GitHub

Merge pull request #50 from HanSon/dev

🎲 优化群控功能
2 parents db06d623 cdd3479a
......@@ -31,13 +31,22 @@ PS:运行后二维码将保存于设置的缓存目录,命名为qr.png,控
**请在terminal运行!请在terminal运行!请在terminal运行!**
## 体验
<img src="https://ws2.sinaimg.cn/large/685b97a1gy1fdordpa0cgj20e80e811z.jpg" height="320">
扫码后,验证输入“上山打老虎”即可自动加为好友并且拉入vbot群。
vbot并非24小时执行,有时会因为开发调试等原因暂停功能。如果碰巧遇到关闭情况,可加Q群 492548647 了解开放时间。执行后发送“拉我”即可自动邀请进群。
vbot示例源码为 https://github.com/HanSon/vbot/blob/master/example/index.php
## 文档
详细文档在[wiki](https://github.com/HanSon/vbot/wiki)
### 例子
[所有类型例子](https://github.com/HanSon/vbot/blob/master/example/index.php)
### 小DEMO
[红包提醒](https://github.com/HanSon/vbot/blob/master/example/hongbao.php)
......
......@@ -26,7 +26,7 @@ class Contact extends Collection
*/
public static function getInstance()
{
if(static::$instance === null){
if (static::$instance === null) {
static::$instance = new Contact();
}
......@@ -41,8 +41,8 @@ class Contact extends Collection
*/
public function getContactById($id)
{
return $this->filter(function($item, $key) use ($id){
if($item['Alias'] === $id){
return $this->filter(function ($item, $key) use ($id) {
if ($item['Alias'] === $id) {
return true;
}
})->first();
......@@ -56,22 +56,23 @@ class Contact extends Collection
*/
public function getUsernameById($id)
{
return $this->search(function($item, $key) use ($id){
if($item['Alias'] === $id){
return $this->search(function ($item, $key) use ($id) {
if ($item['Alias'] === $id) {
return true;
}
});
}
/**
* 根据通讯录中的备注获取通讯对象
*
* @param $id
* @return mixed
*/
public function getUsernameByRemarkName( $id)
public function getUsernameByRemarkName($id)
{
return $this->search(function($item, $key) use ($id){
if($item['RemarkName'] === $id){
return $this->search(function ($item, $key) use ($id) {
if ($item['RemarkName'] === $id) {
return true;
}
});
......@@ -81,12 +82,15 @@ class Contact extends Collection
* 根据通讯录中的昵称获取通讯对象
*
* @param $nickname
* @param bool $blur
* @return mixed
*/
public function getUsernameByNickname($nickname)
public function getUsernameByNickname($nickname, $blur = false)
{
return $this->search(function($item, $key) use ($nickname){
if($item['NickName'] === $nickname){
return $this->search(function ($item, $key) use ($nickname, $blur) {
if ($blur && str_contains($item['NickName'], $nickname)) {
return true;
} elseif (!$blur && $item['NickName'] === $nickname) {
return true;
}
});
......@@ -113,7 +117,7 @@ class Contact extends Collection
$result = http()->json($url, [
'UserName' => $username,
'CmdId' => 3,
'OP' => (int) $isStick,
'OP' => (int)$isStick,
'BaseRequest' => server()->baseRequest
], true);
......
......@@ -34,11 +34,11 @@ class ContactFactory
$this->makeContactList();
$contact = contact()->get(myself()->username);
myself()->alias = isset($contact['Alias']) ? $contact['Alias'] : myself()->nickname ? : myself()->username;
myself()->alias = isset($contact['Alias']) ? $contact['Alias'] : myself()->nickname ?: myself()->username;
$this->getBatchGroupMembers();
if(server()->config['debug']){
if (server()->config['debug']) {
FileManager::download('contact.json', json_encode(contact()->all()));
FileManager::download('member.json', json_encode(member()->all()));
FileManager::download('group.json', json_encode(group()->all()));
......@@ -56,23 +56,23 @@ class ContactFactory
$url = sprintf(server()->baseUri . '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s&seq=%s', server()->passTicket, server()->skey, time(), $seq);
$result = http()->json($url, [], true);
$memberList = $result['MemberList'];
$seq = $result['Seq'];
foreach ($memberList as $contact) {
if(official()->isOfficial($contact['VerifyFlag'])){ #公众号
if (isset($result['MemberList']) && $result['MemberList']) {
foreach ($result['MemberList'] as $contact) {
if (official()->isOfficial($contact['VerifyFlag'])) { #公众号
Official::getInstance()->put($contact['UserName'], $contact);
}elseif (in_array($contact['UserName'], static::SPECIAL_USERS)){ # 特殊账户
} elseif (in_array($contact['UserName'], static::SPECIAL_USERS)) { # 特殊账户
Special::getInstance()->put($contact['UserName'], $contact);
}elseif (strstr($contact['UserName'], '@@') !== false){ # 群聊
} elseif (strstr($contact['UserName'], '@@') !== false) { # 群聊
group()->put($contact['UserName'], $contact);
}else{
} else {
contact()->put($contact['UserName'], $contact);
}
}
}
if($seq != 0){
$this->makeContactList($seq);
if (isset($result['Seq']) && $result['Seq'] != 0) {
$this->makeContactList($result['Seq']);
}
}
......@@ -84,7 +84,7 @@ class ContactFactory
$url = sprintf(server()->baseUri . '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s', time(), server()->passTicket);
$list = [];
group()->each(function($item, $key) use (&$list){
group()->each(function ($item, $key) use (&$list) {
$list[] = ['UserName' => $key, 'EncryChatRoomId' => ''];
});
......@@ -104,6 +104,7 @@ class ContactFactory
*/
private function initGroupMembers($array)
{
if (isset($array['ContactList']) && $array['ContactList']) {
foreach ($array['ContactList'] as $group) {
$groupAccount = group()->get($group['UserName']);
$groupAccount['MemberList'] = $group['MemberList'];
......@@ -113,6 +114,7 @@ class ContactFactory
member()->put($member['UserName'], $member);
}
}
}
}
......
......@@ -18,6 +18,13 @@ class Group extends Collection
static $instance = null;
/**
* username => id
*
* @var array
*/
public $map = [];
/**
* create a single instance
*
* @return Group
......@@ -62,6 +69,68 @@ class Group extends Collection
}
/**
* 根据通讯录中的昵称获取通讯对象
*
* @param $nickname
* @return mixed
*/
public function getUsernameByNickname($nickname)
{
return $this->search(function($item, $key) use ($nickname){
if($item['NickName'] === $nickname){
return true;
}
});
}
/**
* 根据昵称搜索群成员
*
* @param $groupUsername
* @param $memberNickname
* @param bool $blur
* @return array
*/
public function getMembersByNickname($groupUsername, $memberNickname, $blur = false)
{
$members = $this->get($groupUsername);
$result = [];
foreach ($members['MemberList'] as $member) {
if ($blur && str_contains($member['NickName'], $memberNickname)) {
$result[] = $member;
} elseif (!$blur && $member['NickName'] === $memberNickname) {
$result[] = $member;
}
}
return $result;
}
/**
* 根据ID获取群username
*
* @param $id
* @return mixed
*/
public function getUsernameById($id)
{
return array_search($id, $this->map);
}
/**
* 设置map
*
* @param $username
* @param $id
*/
public function setMap($username, $id)
{
$this->map[$username] = $id;
}
/**
* 创建群聊天
*
* @param array $contacts
......
......@@ -26,7 +26,7 @@ class Member extends Collection
*/
public static function getInstance()
{
if(static::$instance === null){
if (static::$instance === null) {
static::$instance = new Member();
}
......
......@@ -50,9 +50,11 @@ class Http
return $array ? json_decode($content, true) : $content;
}
public function json($url, $options = [], $array = false)
public function json($url, $params = [], $array = false, $extra = [])
{
$content = $this->request($url, 'POST', ['json' => $options]);
$params = array_merge(['json' => $params], $extra);
$content = $this->request($url, 'POST', $params);
return $array ? json_decode($content, true) : $content;
}
......@@ -86,15 +88,9 @@ class Http
*/
public function request($url, $method = 'GET', $options = [])
{
try{
$response = $this->getClient()->request($method, $url, $options);
return $response->getBody()->getContents();
}catch (\Exception $e){
Console::log('http链接失败:' . $e->getMessage(), Console::ERROR);
Console::log('错误URL:' . $url, Console::ERROR);
}
return null;
return $response->getBody()->getContents();
}
......
......@@ -68,7 +68,7 @@ class MessageFactory
else if(str_contains($msg['Content'], '添加') || str_contains($msg['Content'], 'have added') || str_contains($msg['Content'], '打招呼')){
# 添加好友
return new NewFriend($msg);
}else if(str_contains($msg['Content'], '加入了群聊') || str_contains($msg['Content'], '移出了群聊') || str_contains($msg['Content'], '改群名为')){
}else if(str_contains($msg['Content'], '加入了群聊') || str_contains($msg['Content'], '移出了群聊') || str_contains($msg['Content'], '改群名为') || str_contains($msg['Content'], '移出群聊') || str_contains($msg['Content'], '邀请你')){
return new GroupChange($msg);
}
break;
......
......@@ -32,6 +32,8 @@ class MessageHandler
private $exceptionHandler;
private $onceHandler;
private $sync;
private $messageFactory;
......@@ -49,7 +51,7 @@ class MessageHandler
*/
public static function getInstance()
{
if(static::$instance === null){
if (static::$instance === null) {
static::$instance = new MessageHandler();
}
......@@ -64,7 +66,7 @@ class MessageHandler
*/
public function setMessageHandler(Closure $closure)
{
if(!$closure instanceof Closure){
if (!$closure instanceof Closure) {
throw new \Exception('message handler must be a closure!');
}
......@@ -79,7 +81,7 @@ class MessageHandler
*/
public function setCustomHandler(Closure $closure)
{
if(!$closure instanceof Closure){
if (!$closure instanceof Closure) {
throw new \Exception('custom handler must be a closure!');
}
......@@ -94,7 +96,7 @@ class MessageHandler
*/
public function setExitHandler(Closure $closure)
{
if(!$closure instanceof Closure){
if (!$closure instanceof Closure) {
throw new \Exception('exit handler must be a closure!');
}
......@@ -109,7 +111,7 @@ class MessageHandler
*/
public function setExceptionHandler(Closure $closure)
{
if(!$closure instanceof Closure){
if (!$closure instanceof Closure) {
throw new \Exception('exit handler must be a closure!');
}
......@@ -117,29 +119,48 @@ class MessageHandler
}
/**
* 执行一次的处理器
*
* @param Closure $closure
* @throws \Exception
*/
public function setOnceHandler(Closure $closure)
{
if (!$closure instanceof Closure) {
throw new \Exception('exit handler must be a closure!');
}
$this->onceHandler = $closure;
}
/**
* 轮询消息API接口
*/
public function listen()
{
while (true){
if($this->customHandler instanceof Closure){
if ($this->onceHandler instanceof Closure) {
call_user_func_array($this->onceHandler, []);
}
while (true) {
if ($this->customHandler instanceof Closure) {
call_user_func_array($this->customHandler, []);
}
$time = time();
list($retCode, $selector) = $this->sync->checkSync();
if(in_array($retCode, ['1100', '1101'])){ # 微信客户端上登出或者其他设备登录
if (in_array($retCode, ['1100', '1101'])) { # 微信客户端上登出或者其他设备登录
Console::log('微信客户端正常退出');
if($this->exitHandler){
if ($this->exitHandler) {
call_user_func_array($this->exitHandler, []);
}
break;
}elseif ($retCode == 0){
} elseif ($retCode == 0) {
$this->handlerMessage($selector);
}else{
} else {
Console::log('微信客户端异常退出');
if($this->exceptionHandler){
if ($this->exceptionHandler) {
call_user_func_array($this->exitHandler, []);
}
break;
......@@ -157,27 +178,37 @@ class MessageHandler
*/
private function handlerMessage($selector)
{
if($selector === 0){
if ($selector === 0) {
return;
}
$message = $this->sync->sync();
if($message['AddMsgList']){
if (count($message['ModContactList']) > 0) {
foreach ($message['ModContactList'] as $contact) {
if (str_contains($contact['UserName'], '@@')) {
group()->put($contact['UserName'], $contact);
} else {
contact()->put($contact['UserName'], $contact);
}
}
}
if ($message['AddMsgList']) {
foreach ($message['AddMsgList'] as $msg) {
$content = $this->messageFactory->make($msg);
if($content){
if ($content) {
$this->addToMessageCollection($content);
if($this->handler){
if ($this->handler) {
$reply = call_user_func_array($this->handler, [$content]);
if($reply){
if($reply instanceof Image){
if ($reply) {
if ($reply instanceof Image) {
Image::sendByMsgId($content->from['UserName'], $reply->msg['MsgId']);
}elseif($reply instanceof Video){
} elseif ($reply instanceof Video) {
Video::sendByMsgId($content->from['UserName'], $reply->msg['MsgId']);
}elseif($reply instanceof Emoticon){
} elseif ($reply instanceof Emoticon) {
Emoticon::sendByMsgId($content->from['UserName'], $reply->msg['MsgId']);
}else{
} else {
Text::send($content->from['UserName'], $reply);
}
}
......@@ -194,7 +225,7 @@ class MessageHandler
{
message()->put($message->msg['MsgId'], $message);
if(server()->config['debug']) {
if (server()->config['debug']) {
$file = fopen(System::getPath() . 'message.json', 'a');
fwrite($file, json_encode($message) . PHP_EOL);
fclose($file);
......
......@@ -88,8 +88,10 @@ class Server
$this->statusNotify();
Console::log('开始初始化联系人');
$this->initContact();
Console::log(sprintf("初始化联系人成功\n群数量: %d\n联系人数量: %d\n公众号数量: %d\n特殊号数量: %d", group()->count(), contact()->count(), official()->count(), Special::getInstance()->count()));
Console::log('初始化联系人成功');
Console::log(sprintf("群数量: %d", group()->count()));
Console::log(sprintf("联系人数量: %d", contact()->count()));
Console::log(sprintf("公众号数量: %d", official()->count()));
MessageHandler::getInstance()->listen();
}
......@@ -337,4 +339,9 @@ class Server
{
MessageHandler::getInstance()->setExceptionHandler($closure);
}
public function setOnceHandler(\Closure $closure)
{
MessageHandler::getInstance()->setOnceHandler($closure);
}
}
\ No newline at end of file
......@@ -52,7 +52,7 @@ class Sync
'BaseRequest' => server()->baseRequest,
'SyncKey' => server()->syncKey,
'rr' => ~time()
], true);
], true, ['timeout' => 35]);
if($result['BaseResponse']['Ret'] == 0){
$this->generateSyncKey($result);
......@@ -60,7 +60,7 @@ class Sync
return $result;
}catch (\Exception $e){
return null;
$this->sync();
}
}
......@@ -107,7 +107,7 @@ class Sync
*/
public function debugMessage($retCode, $selector, $sleep = null)
{
Console::log('[DEBUG] retcode:' . $retCode . ' selector:' . $selector);
Console::log('retcode:' . $retCode . ' selector:' . $selector, Console::WARNING);
if($sleep){
sleep($sleep);
......
......@@ -35,7 +35,7 @@ class Emoticon extends Message implements MediaInterface, MessageInterface
$response = static::uploadMedia($username, $file);
if (!$response) {
Console::log("表情 {$file} 上传失败");
Console::log("表情 {$file} 上传失败", Console::WARNING);
return false;
}
......@@ -57,7 +57,7 @@ class Emoticon extends Message implements MediaInterface, MessageInterface
$result = http()->json($url, $data, true);
if ($result['BaseResponse']['Ret'] != 0) {
Console::log('发送表情失败');
Console::log('发送表情失败', Console::WARNING);
return false;
}
......@@ -79,6 +79,22 @@ class Emoticon extends Message implements MediaInterface, MessageInterface
}
/**
* 从当前账号的本地表情库随机发送一个
*
* @param $username
*/
public static function sendRandom($username)
{
$path = static::getPath(static::$folder);
$files = scandir($path);
unset($files[0], $files[1]);
$msgId = $files[array_rand($files)];
static::send($username, $path . '/' . $msgId);
}
/**
* 下载文件
*
* @return mixed
......
......@@ -18,8 +18,27 @@ class GroupChange extends Message implements MessageInterface
public $action;
/**
* 群名重命名的名称
*
* @var
*/
public $rename;
/**
* 被踢出群时的群信息
*
* @var
*/
public $group;
/**
* 新人进群的昵称(可能单个可能多个)
*
* @var
*/
public $nickname;
public function __construct($msg)
{
parent::__construct($msg);
......@@ -29,17 +48,25 @@ class GroupChange extends Message implements MessageInterface
public function make()
{
if(str_contains($this->msg['Content'], '加入了群聊')){
Console::debug($this->msg['Content']);
if (str_contains($this->msg['Content'], '邀请你')) {
$this->action = 'INVITE';
} elseif (str_contains($this->msg['Content'], '加入了群聊')) {
preg_match('/.+"(.+)"加入了群聊/', $this->msg['Content'], $match);
$this->action = 'ADD';
Console::log("检测到 {$this->from['NickName']} 有新成员,正在刷新群成员列表...");
$this->nickname = $match[1];
Console::debug("检测到 {$this->from['NickName']} 有新成员,正在刷新群成员列表...");
(new ContactFactory())->makeContactList();
Console::log('群成员更新成功!');
}elseif(str_contains($this->msg['Content'], '移出了群聊')){
Console::debug('群成员更新成功!');
} elseif (str_contains($this->msg['Content'], '移出了群聊')) {
$this->action = 'REMOVE';
}elseif(str_contains($this->msg['Content'], '改群名为')){
} elseif (str_contains($this->msg['Content'], '改群名为')) {
$this->action = 'RENAME';
preg_match('/改群名为“(.+)”/', $this->msg['Content'], $match);
$this->updateGroupName($match[1]);
} elseif (str_contains($this->msg['Content'], '移出群聊')) {
$this->action = 'BE_REMOVE';
$this->group = group()->pull($this->from['UserName']);
}
$this->content = $this->msg['Content'];
......
......@@ -42,7 +42,7 @@ class Image extends Message implements MessageInterface, MediaInterface
$response = static::uploadMedia($username, $file);
if (!$response) {
Console::log("文件 {$file} 上传失败");
Console::log("文件 {$file} 上传失败", Console::WARNING);
return false;
}
......@@ -63,7 +63,7 @@ class Image extends Message implements MessageInterface, MediaInterface
$result = http()->json($url, $data, true);
if ($result['BaseResponse']['Ret'] != 0) {
Console::log('发送图片失败');
Console::log('发送图片失败', Console::WARNING);
return false;
}
......
......@@ -24,8 +24,8 @@ class NewFriend extends Message implements MessageInterface
public function make()
{
Console::log('检测到新加好友,正在刷新好友列表...');
Console::debug('检测到新加好友,正在刷新好友列表...');
(new ContactFactory())->makeContactList();
Console::log('好友更新成功!');
Console::debug('好友更新成功!');
}
}
\ No newline at end of file
......@@ -58,7 +58,7 @@ class Text extends Message implements MessageInterface
);
if ($result['BaseResponse']['Ret'] != 0) {
Console::log('发送消息失败');
Console::log('发送消息失败', Console::WARNING);
return false;
}
......
......@@ -34,7 +34,7 @@ class Video extends Message implements MessageInterface, MediaInterface
$response = static::uploadMedia($username, $file);
if (!$response) {
Console::log("视频 {$file} 上传失败");
Console::log("视频 {$file} 上传失败", Console::WARNING);
return false;
}
......@@ -55,7 +55,7 @@ class Video extends Message implements MessageInterface, MediaInterface
$result = http()->json($url, $data, true);
if ($result['BaseResponse']['Ret'] != 0) {
Console::log('发送视频失败');
Console::log('发送视频失败', Console::WARNING);
return false;
}
......@@ -90,7 +90,8 @@ class Video extends Message implements MessageInterface, MediaInterface
]
]);
if(strlen($content) === 0){
Console::log('下载视频失败', Console::ERROR);
Console::log('下载视频失败', Console::WARNING);
Console::log('url:'. $url);
}else{
FileManager::download($this->msg['MsgId'] . '.mp4', $content, static::$folder);
}
......
......@@ -79,7 +79,7 @@ trait UploadAble
$response = static::uploadMedia($username, $file);
if(!$response){
Console::log("文件 {$file} 上传失败");
Console::log("文件 {$file} 上传失败", Console::WARNING);
return false;
}
......@@ -100,7 +100,7 @@ trait UploadAble
$result = http()->json($url, $data, true);
if($result['BaseResponse']['Ret'] != 0){
Console::log('发送文件失败');
Console::log('发送文件失败', Console::WARNING);
return false;
}
......
......@@ -34,6 +34,18 @@ class Console
}
/**
* debug 模式下调试输出
*
* @param $str
*/
public static function debug($str)
{
if (server()->config['debug']) {
static::log($str, 'DEBUG');
}
}
/**
* 初始化二维码style
*
* @param OutputInterface $output
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!