Commit 540f3f4e by HanSon

增加位置类,修改人员存储

1 parent 886c1954
......@@ -345,9 +345,9 @@ $robot->server->setCustomHandler(function(){
- [x] 语音
- [x] 位置
- [x] 撤回
- [x] 表情
- [ ] 好友验证
- [ ] 名片
- [ ] 表情
- [ ] 分享
- [ ] 视频
- [ ] 小程序
......
<?php
/**
* Created by PhpStorm.
* User: HanSon
* Date: 2016/12/7
* Time: 16:33
*/
require_once __DIR__ . './../vendor/autoload.php';
use Hanson\Robot\Foundation\Robot;
use Hanson\Robot\Message\Message;
use Hanson\Robot\Message\Image;
use Hanson\Robot\Message\Text;
use Hanson\Robot\Message\Emoticon;
use Hanson\Robot\Message\Location;
use Hanson\Robot\Message\Video;
use Hanson\Robot\Message\MessageInterface;
$path = __DIR__ . '/./../tmp/';
$robot = new Robot([
'tmp' => $path,
'debug' => true
]);
$robot->server->setMessageHandler(function($message) use ($path){
/** @var $message Message */
// 位置信息 返回位置文字
if($message instanceof Location){
return $message;
}
// 发送撤回消息 (排除自己)
// if($message->type === 'Recall' && $message->rawMsg['FromUserName'] !== myself()->username && $message->username === group()->getGroupsByNickname('stackoverflow', true)->first()['UserName']){
// $msg = message()->get($message->msgId);
// if($msg){
// $nickname = $msg['sender'] ? $msg['sender']['NickName'] : account()->getAccount($msg['username'])['NickName'];
// if($msg['type'] === 'Image'){
// Text::send($message->username, "{$nickname} 撤回了一张照片");
// Image::send($message->username, realpath($path . "jpg/{$message->msgId}.jpg"));
// }elseif($msg['type'] === 'Emoticon'){
// Text::send($message->username, "{$nickname} 撤回了一个表情");
// Emoticon::send($message->username, realpath($path . "gif/{$message->msgId}.gif"));
// }elseif($msg['type'] === 'Video' || $msg['type'] === 'VideoCall'){
// Text::send($message->username, "{$nickname} 撤回了一个视频");
// Video::send($message->username, realpath($path . "mp4/{$message->msgId}.mp4"));
// }elseif($msg['type'] === 'Voice'){
// Text::send($message->username, "{$nickname} 撤回了一条语音");
// }else{
// Text::send($message->username, "{$nickname} 撤回了一条信息 \"{$msg['content']}\"");
// }
// }
// }
});
$robot->server->run();
......@@ -26,22 +26,22 @@ $robot->server->setMessageHandler(function($message) use ($path){
// 发送撤回消息 (排除自己)
if($message->type === 'Recall' && $message->rawMsg['FromUserName'] !== myself()->username ){
$msg = message()->get($message->msgId);
print_r($msg);
print_r($message->rawMsg);
$nickname = $msg['sender'] ? $msg['sender']['NickName'] : account()->getAccount($msg['username'])['NickName'];
if($msg['type'] === 'Image'){
Text::send($message->username, "{$nickname} 撤回了一张照片");
Image::send($message->username, realpath($path . "jpg/{$message->msgId}.jpg"));
}elseif($msg['type'] === 'Emoticon'){
Text::send($message->username, "{$nickname} 撤回了一个表情");
Emoticon::send($message->username, realpath($path . "gif/{$message->msgId}.gif"));
}elseif($msg['type'] === 'Video' || $msg['type'] === 'VideoCall'){
Text::send($message->username, "{$nickname} 撤回了一个视频");
Video::send($message->username, realpath($path . "mp4/{$message->msgId}.mp4"));
}elseif($msg['type'] === 'Voice'){
Text::send($message->username, "{$nickname} 撤回了一条语音");
}else{
Text::send($message->username, "{$nickname} 撤回了一条信息 \"{$msg['content']}\"");
if($msg){
$nickname = $msg['sender'] ? $msg['sender']['NickName'] : account()->getAccount($msg['username'])['NickName'];
if($msg['type'] === 'Image'){
Text::send($message->username, "{$nickname} 撤回了一张照片");
Image::send($message->username, realpath($path . "jpg/{$message->msgId}.jpg"));
}elseif($msg['type'] === 'Emoticon'){
Text::send($message->username, "{$nickname} 撤回了一个表情");
Emoticon::send($message->username, realpath($path . "gif/{$message->msgId}.gif"));
}elseif($msg['type'] === 'Video' || $msg['type'] === 'VideoCall'){
Text::send($message->username, "{$nickname} 撤回了一个视频");
Video::send($message->username, realpath($path . "mp4/{$message->msgId}.mp4"));
}elseif($msg['type'] === 'Voice'){
Text::send($message->username, "{$nickname} 撤回了一条语音");
}else{
Text::send($message->username, "{$nickname} 撤回了一条信息 \"{$msg['content']}\"");
}
}
}
......
......@@ -53,7 +53,9 @@ class Account
*/
public function getAccount($username)
{
$account = static::$group->get($username, null);
$account = $username === myself()->username ? myself() : null;
$account = $account ? : static::$group->get($username, null);
$account = $account ? : static::$contact->get($username, null);
......
......@@ -55,7 +55,7 @@ class Contact extends Collection
public function getContactById($id)
{
$contact = $this->filter(function($item, $key) use ($id){
if($item['Alias'] === $id){
if($item->Alias === $id){
return true;
}
})->first();
......@@ -72,7 +72,7 @@ class Contact extends Collection
public function getUsernameById($id)
{
$contact = $this->search(function($item, $key) use ($id){
if($item['Alias'] === $id){
if($item->Alias === $id){
return true;
}
});
......
......@@ -11,9 +11,11 @@ namespace Hanson\Robot\Collections;
use Hanson\Robot\Core\Server;
use Hanson\Robot\Support\Console;
use Hanson\Robot\Support\ObjectAble;
class ContactFactory
{
use ObjectAble;
const SPECIAL_USERS = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail',
'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle',
......@@ -49,13 +51,13 @@ class ContactFactory
{
foreach ($memberList as $contact) {
if(($contact['VerifyFlag'] & 8) != 0){ #公众号
OfficialAccount::getInstance()->put($contact['UserName'], $contact);
OfficialAccount::getInstance()->put($contact['UserName'], $this->toObject($contact));
}elseif (in_array($contact['UserName'], static::SPECIAL_USERS)){ # 特殊账户
SpecialAccount::getInstance()->put($contact['UserName'], $contact);
SpecialAccount::getInstance()->put($contact['UserName'], $this->toObject($contact));
}elseif (strstr($contact['UserName'], '@@') !== false){ # 群聊
group()->put($contact['UserName'], $contact);
group()->put($contact['UserName'], $this->toObject($contact));
}else{
contact()->put($contact['UserName'], $contact);
contact()->put($contact['UserName'], $this->toObject($contact));
}
}
......@@ -101,9 +103,9 @@ class ContactFactory
$groupAccount = group()->get($group['UserName']);
$groupAccount['MemberList'] = $group['MemberList'];
$groupAccount['ChatRoomId'] = $group['EncryChatRoomId'];
group()->put($group['UserName'], $groupAccount);
group()->put($group['UserName'], $this->toObject($groupAccount));
foreach ($group['MemberList'] as $member) {
member()->put($member['UserName'], $member);
member()->put($member['UserName'], $this->toObject($member));
}
}
......
......@@ -53,9 +53,9 @@ class Group extends Collection
{
$groups = $this->filter(function($value, $key) use ($name, $blur){
if(!$blur){
return $value['NickName'] === $name;
return $value->NickName === $name;
}else{
return str_contains($value['NickName'], $name);
return str_contains($value->NickName, $name);
}
});
......
......@@ -15,6 +15,9 @@ use Illuminate\Support\Collection;
class Message extends Collection
{
/**
* @var Message
*/
static $instance = null;
/**
......
......@@ -77,9 +77,10 @@ class Http
public function getClient()
{
if (!($this->client instanceof HttpClient)) {
$this->cookieFile = realpath(server()->config['tmp']) . '/cookie.txt';
$this->cookieJar = new FileCookieJar($this->cookieFile);
$this->client = new HttpClient(['cookies' => $this->cookieJar]);
// $this->cookieFile = realpath(server()->config['tmp']) . '/cookie.txt';
// $this->cookieJar = new FileCookieJar($this->cookieFile);
// $this->client = new HttpClient(['cookies' => $this->cookieJar]);
$this->client = new HttpClient(['cookies' => true]);
}
return $this->client;
......@@ -95,7 +96,7 @@ class Http
{
try{
$response = $this->getClient()->request($method, $url, $options);
$this->cookieJar->save($this->cookieFile);
// $this->cookieJar->save($this->cookieFile);
return $response->getBody()->getContents();
}catch (\Exception $e){
Console::log('http链接失败:' . $e->getMessage());
......
<?php
/**
* Created by PhpStorm.
* User: HanSon
* Date: 2017/1/14
* Time: 11:54
*/
namespace Hanson\Robot\Core;
use Hanson\Robot\Message\Location;
use Hanson\Robot\Support\Console;
use Hanson\Robot\Support\FileManager;
class MessageFactory
{
public $msg;
public function make($selector, $msg)
{
$this->msg = $msg;
return $this->handleMessageByType();
}
/**
* 处理消息类型
*
*/
private function handleMessageByType()
{
switch($this->msg['MsgType']){
case 1: //文本消息
if(Location::isLocation($this->msg)){
return new Location($this->msg);
}else{
$this->type = 'Text';
$this->content = $this->msg['Content'];
}
break;
case 3: // 图片消息
$this->type = 'Image';
$this->content = Server::BASE_URI . sprintf('/webwxgetmsgimg?MsgID=%s&skey=%s', $this->msg['MsgId'], server()->skey);
$content = http()->get($this->content);
FileManager::download($this->msg['MsgId'].'.jpg', $content, 'jpg');
break;
case 34: // 语音消息
$this->type = 'Voice';
$this->content = Server::BASE_URI . sprintf('/webwxgetvoice?msgid=%s&skey=%s', $this->msg['MsgId'], server()->skey);
$content = http()->get($this->content);
FileManager::download($this->msg['MsgId'].'.mp3', $content, 'mp3');
break;
case 37: // 好友验证
$this->type = 'AddUser';
break;
case 42: //共享名片
$this->type = 'Recommend';
$this->content = (object)$this->msg['RecommendInfo'];
break;
case 43:
$this->type = 'VideoCall';
Console::log('video');
$url = Server::BASE_URI . sprintf('/webwxgetvideo?msgid=%s&skey=%s', $this->msg['MsgId'], server()->skey);
$content = http()->request($url, 'get', [
'headers' => [
'Range' => 'bytes=0-'
]
]);
FileManager::download($this->msg['MsgId'].'.mp4', $content, 'mp4');
break;
case 47: // 动画表情
$this->type = 'Emoticon';
$this->content = Server::BASE_URI . sprintf('/webwxgetmsgimg?MsgID=%s&skey=%s', $this->msg['MsgId'], server()->skey);
$content = http()->get($this->content);
FileManager::download($this->msg['MsgId'].'.gif', $content, 'gif');
break;
case 49:
$this->type = 'Share';
break;
case 62:
$this->type = 'Video';
Console::log('video');
$url = Server::BASE_URI . sprintf('/webwxgetvideo?msgid=%s&skey=%s', $this->msg['MsgId'], server()->skey);
$content = http()->request($url, 'get', [
'headers' => [
'Range' => 'bytes=0-'
]
]);
FileManager::download($this->msg['MsgId'].'.mp4', $content, 'mp4');
break;
case 51:
$this->type = 'Init';
break;
case 53:
$this->type = 'VideoCall';
break;
case 10000:
if($this->msg['Status'] == 4){
$this->type = 'RedPacket'; // 红包
}else{
$this->type = 'Unknown';
}
break;
case 10002:
$this->type = 'Recall'; // 撤回
$this->msgId = $msgId = $this->parseMsgId($this->msg['Content']);
break;
default:
$this->type = 'Unknown';
break;
}
}
}
\ No newline at end of file
......@@ -15,18 +15,27 @@ use Hanson\Robot\Support\Console;
class MessageHandler
{
protected $server;
private $syncHost;
/**
* @var MessageHandler
*/
static $instance = null;
private $handler;
private $customHandler;
static $instance = null;
private $sync;
private $messageFactory;
public function __construct()
{
$this->sync = new Sync();
$this->messageFactory = new MessageFactory();
}
/**
* get a message handler single instance
* 设置单例模式
*
* @return MessageHandler
*/
......@@ -70,12 +79,10 @@ class MessageHandler
}
/**
* listen the chat api
* 轮询消息API接口
*/
public function listen()
{
$this->preCheckSync();
while (true){
if($this->customHandler instanceof Closure){
......@@ -83,153 +90,45 @@ class MessageHandler
}
$time = time();
list($retCode, $selector) = $this->checkSync();
list($retCode, $selector) = $this->sync->checkSync();
if(in_array($retCode, ['1100', '1101'])){ # 微信客户端上登出或者其他设备登录
break;
}elseif ($retCode == 0){
$this->handlerMessage($selector);
}else{
$this->debugMessage($retCode, $selector, 10);
$this->sync->debugMessage($retCode, $selector, 10);
}
$this->checkTime($time);
$this->sync->checkTime($time);
}
}
/**
* 处理消息
*
* @param $selector
*/
private function handlerMessage($selector)
{
if($selector === 0){
return;
}
$message = $this->sync();
$message = $this->sync->sync();
if($message['AddMsgList']){
foreach ($message['AddMsgList'] as $msg) {
$content = (new Message)->make($selector, $msg);
// $content = (new Message)->make($selector, $msg);
$content = $this->messageFactory->make($selector, $msg);
if($this->handler instanceof Closure){
$reply = call_user_func_array($this->handler, [$content]);
Message::send($reply, $content->username);
if($reply){
Message::send($reply, $content->from->UserName);
}
}
}
}
}
/**
* get a message code
*
* @return array
*/
private function checkSync()
{
$url = 'https://' . $this->syncHost . '/cgi-bin/mmwebwx-bin/synccheck?' . http_build_query([
'r' => time(),
'sid' => server()->sid,
'uin' => server()->uin,
'skey' => server()->skey,
'deviceid' => server()->deviceId,
'synckey' => server()->syncKeyStr,
'_' => time()
]);
try{
$content = http()->get($url);
preg_match('/window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}/', $content, $matches);
return [$matches[1], $matches[2]];
}catch (\Exception $e){
return [-1, -1];
}
}
/**
* test a domain before sync
*
* @return bool
*/
private function preCheckSync()
{
foreach (['webpush.', 'webpush2.'] as $host) {
$this->syncHost = $host . Server::BASE_HOST;
list($retCode,) = $this->checkSync();
if($retCode == 0){
return true;
}
}
return false;
}
private function sync()
{
$url = sprintf(Server::BASE_URI . '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s', server()->sid, server()->skey, server()->passTicket);
try{
$result = http()->json($url, [
'BaseRequest' => server()->baseRequest,
'SyncKey' => server()->syncKey,
'rr' => ~time()
], true);
if($result['BaseResponse']['Ret'] == 0){
$this->generateSyncKey($result);
}
return $result;
}catch (\Exception $e){
return null;
}
}
/**
* generate a sync key
*
* @param $result
*/
private function generateSyncKey($result)
{
server()->syncKey = $result['SyncKey'];
$syncKey = [];
foreach (server()->syncKey['List'] as $item) {
$syncKey[] = $item['Key'] . '_' . $item['Val'];
}
server()->syncKeyStr = implode('|', $syncKey);
}
/**
* check message time
*
* @param $time
*/
private function checkTime($time)
{
$checkTime = time() - $time;
if($checkTime < 0.8){
sleep(1 - $checkTime);
}
}
/**
* debug while the sync
*
* @param $retCode
* @param $selector
* @param null $sleep
*/
private function debugMessage($retCode, $selector, $sleep = null)
{
Console::log('[DEBUG] retcode:' . $retCode . ' selector:' . $selector);
if($sleep){
sleep($sleep);
}
}
}
\ No newline at end of file
......@@ -15,12 +15,15 @@ use Hanson\Robot\Collections\Account;
use Hanson\Robot\Collections\ContactFactory;
use Hanson\Robot\Collections\Group;
use Hanson\Robot\Support\Console;
use Hanson\Robot\Support\ObjectAble;
use QueryPath\Exception;
use Symfony\Component\DomCrawler\Crawler;
class Server
{
use ObjectAble;
static $instance;
protected $uuid;
......@@ -49,9 +52,9 @@ class Server
protected $debug = false;
const BASE_URI = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin';
const BASE_URI = 'https://wx.qq.com/cgi-bin/mmwebwx-bin';
const BASE_HOST = 'wx2.qq.com';
const BASE_HOST = 'wx.qq.com';
public function __construct($config = [])
{
......@@ -249,7 +252,7 @@ class Server
if($contactList){
foreach ($contactList as $contact) {
if(Group::isGroup($contact['UserName'])){
group()->put($contact['UserName'], $contact);
group()->put($contact['UserName'], $this->toObject($contact));
}
}
}
......
<?php
/**
* Created by PhpStorm.
* User: HanSon
* Date: 2017/1/14
* Time: 11:21
*/
namespace Hanson\Robot\Core;
use Hanson\Robot\Support\Console;
class Sync
{
/**
* get a message code
*
* @return array
*/
public function checkSync()
{
$url = 'https://webpush.' . Server::BASE_HOST . '/cgi-bin/mmwebwx-bin/synccheck?' . http_build_query([
'r' => time(),
'sid' => server()->sid,
'uin' => server()->uin,
'skey' => server()->skey,
'deviceid' => server()->deviceId,
'synckey' => server()->syncKeyStr,
'_' => time()
]);
try{
$content = http()->get($url);
preg_match('/window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}/', $content, $matches);
return [$matches[1], $matches[2]];
}catch (\Exception $e){
return [-1, -1];
}
}
public function sync()
{
$url = sprintf(Server::BASE_URI . '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s', server()->sid, server()->skey, server()->passTicket);
try{
$result = http()->json($url, [
'BaseRequest' => server()->baseRequest,
'SyncKey' => server()->syncKey,
'rr' => ~time()
], true);
if($result['BaseResponse']['Ret'] == 0){
$this->generateSyncKey($result);
}
return $result;
}catch (\Exception $e){
return null;
}
}
/**
* generate a sync key
*
* @param $result
*/
public function generateSyncKey($result)
{
server()->syncKey = $result['SyncKey'];
$syncKey = [];
foreach (server()->syncKey['List'] as $item) {
$syncKey[] = $item['Key'] . '_' . $item['Val'];
}
server()->syncKeyStr = implode('|', $syncKey);
}
/**
* check message time
*
* @param $time
*/
public function checkTime($time)
{
$checkTime = time() - $time;
if($checkTime < 0.8){
sleep(1 - $checkTime);
}
}
/**
* debug while the sync
*
* @param $retCode
* @param $selector
* @param null $sleep
*/
public function debugMessage($retCode, $selector, $sleep = null)
{
Console::log('[DEBUG] retcode:' . $retCode . ' selector:' . $selector);
if($sleep){
sleep($sleep);
}
}
}
\ No newline at end of file
......@@ -9,18 +9,46 @@
namespace Hanson\Robot\Message;
class Location
class Location extends Message implements MessageInterface
{
/**
* @var string 位置链接
*/
public $url;
public function __construct($msg)
{
parent::__construct($msg);
$this->make();
}
/**
* 判断是否位置消息
*
* @param $content
* @return bool
*/
public static function isLocation($content)
{
return str_contains('webwxgetpubliclinkimg', $content['Content']) && $content['Url'];
return str_contains($content['Content'], 'webwxgetpubliclinkimg') && $content['Url'];
}
public static function getLocationText($content)
/**
* 设置位置文字信息
*/
private function setLocationText()
{
$result = explode('<br/>', $content);
$result = explode('<br/>', $this->msg['Content']);
return current(array_slice($result, -2, 1));
$this->content = substr(current($result), 0, -1);
$this->url = $this->msg['Url'];
}
public function make()
{
$this->setLocationText();
}
}
\ No newline at end of file
......@@ -16,10 +16,13 @@ use Hanson\Robot\Collections\OfficialAccount;
use Hanson\Robot\Collections\SpecialAccount;
use Hanson\Robot\Support\FileManager;
use Hanson\Robot\Support\Console;
use Hanson\Robot\Support\ObjectAble;
class Message
{
use ObjectAble;
/**
* @var array 消息来源
*/
......@@ -74,36 +77,46 @@ class Message
];
public $rawMsg;
public $msg;
static $mediaCount = -1;
public function make($selector, $msg)
public function __construct($msg)
{
$this->rawMsg = $msg;
$this->time = Carbon::now();
$this->msg = $msg;
$this->setFrom();
$this->setTo();
$this->setFromType();
$this->setType();
$this->rawMsg['selector'] = $selector;
$this->addMessageCollection();
return $this;
}
// public function make($selector, $msg)
// {
// $this->rawMsg = $msg;
// $this->time = Carbon::now();
//
// $this->setFrom();
// $this->setTo();
// $this->setFromType();
// $this->setType();
// $this->rawMsg['selector'] = $selector;
// $this->addMessageCollection();
// return $this;
// }
/**
* 设置消息发送者
*/
private function setFrom()
{
$this->from = $this->toObject(account()->getAccount($this->rawMsg['FromUserName']));
// $this->from = contact()->getContactByUsername($this->rawMsg['FromUserName']);
$this->username = $this->rawMsg['FromUserName'];
$from = account()->getAccount($this->msg['FromUserName']);
$this->from = $this->toObject($from);
}
private function setTo()
{
$this->to = $this->toObject(contact()->getContactByUsername($this->rawMsg['ToUserName']));
$to = account()->getAccount($this->msg['ToUserName']);
print_r($to);
$this->to = $this->toObject($to);
}
private function setFromType()
......@@ -298,12 +311,14 @@ class Message
* @param $username string 目标username
* @return bool
*/
public static function send($word, $username)
public static function send(string $word, $username)
{
if(!$word && !is_string($word)){
return false;
}
Console::log($word);
$random = strval(time() * 1000) . '0' . strval(rand(100, 999));
$data = [
......@@ -330,9 +345,9 @@ class Message
return true;
}
private function toObject(Array $array)
public function __toString()
{
return json_decode(json_encode($array));
return $this->content;
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: HanSon
* Date: 2017/1/14
* Time: 16:03
*/
namespace Hanson\Robot\Message;
interface MessageInterface
{
public function make();
}
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!