Commit 429afca3 by hanccc

增加factory

1 parent b4bba072
* Groups 群组 (group_list & group_user)
* Group 群组 (group_list & group_user)
* Public 公众号
* Special 特殊账号
* Contact (公众号 & 联系人)
Server 登录、初始化
Http 处理HTTP请求
Qrcode 处理二维码
获取List数据
Handler 处理消息
Robot->server
Robot->public
Robot->special
Robot->contact
Robot->group
......@@ -13,7 +13,9 @@
"querypath/QueryPath": "^3.0",
"symfony/dom-crawler": "^3.2",
"endroid/qrcode": "^1.7",
"symfony/css-selector": "^3.2"
"symfony/css-selector": "^3.2",
"pimple/pimple": "^3.0",
"illuminate/support": "^5.3"
},
"autoload": {
"psr-4": {
......
......@@ -4,10 +4,77 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "b733c8fff81d3d74eef74147ee91b72a",
"content-hash": "43e84ce436c790f7670e8caa6fe63079",
"hash": "01d02b438cdd61b6f9c0202caaf088fb",
"content-hash": "524011ccf768eadbec9c686506e6057c",
"packages": [
{
"name": "doctrine/inflector",
"version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "90b2128806bfde671b6952ab8bea493942c1fdae"
},
"dist": {
"type": "zip",
"url": "https://packagist.phpcomposer.com/files/doctrine/inflector/90b2128806bfde671b6952ab8bea493942c1fdae.zip",
"reference": "90b2128806bfde671b6952ab8bea493942c1fdae",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\Inflector\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"inflection",
"pluralize",
"singularize",
"string"
],
"time": "2015-11-06 14:35:42"
},
{
"name": "endroid/qrcode",
"version": "1.7.4",
"source": {
......@@ -230,6 +297,105 @@
"time": "2016-06-24 23:00:38"
},
{
"name": "illuminate/contracts",
"version": "v5.3.23",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
"reference": "ce5d73c6015b2054d32f3f8530767847b358ae4e"
},
"dist": {
"type": "zip",
"url": "https://packagist.phpcomposer.com/files/illuminate/contracts/ce5d73c6015b2054d32f3f8530767847b358ae4e.zip",
"reference": "ce5d73c6015b2054d32f3f8530767847b358ae4e",
"shasum": ""
},
"require": {
"php": ">=5.6.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.3-dev"
}
},
"autoload": {
"psr-4": {
"Illuminate\\Contracts\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "The Illuminate Contracts package.",
"homepage": "https://laravel.com",
"time": "2016-09-26 20:36:27"
},
{
"name": "illuminate/support",
"version": "v5.3.23",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
"reference": "050d0ed3e1c0e1d129d73b2eaa14044e46a66f77"
},
"dist": {
"type": "zip",
"url": "https://packagist.phpcomposer.com/files/illuminate/support/050d0ed3e1c0e1d129d73b2eaa14044e46a66f77.zip",
"reference": "050d0ed3e1c0e1d129d73b2eaa14044e46a66f77",
"shasum": ""
},
"require": {
"doctrine/inflector": "~1.0",
"ext-mbstring": "*",
"illuminate/contracts": "5.3.*",
"paragonie/random_compat": "~1.4|~2.0",
"php": ">=5.6.4"
},
"replace": {
"tightenco/collect": "self.version"
},
"suggest": {
"illuminate/filesystem": "Required to use the composer class (5.2.*).",
"symfony/process": "Required to use the composer class (3.1.*).",
"symfony/var-dumper": "Required to use the dd function (3.1.*)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.3-dev"
}
},
"autoload": {
"psr-4": {
"Illuminate\\Support\\": ""
},
"files": [
"helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "The Illuminate Support package.",
"homepage": "https://laravel.com",
"time": "2016-11-03 15:25:28"
},
{
"name": "masterminds/html5",
"version": "2.2.2",
"source": {
......@@ -295,6 +461,100 @@
"time": "2016-09-22 11:01:11"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e"
},
"dist": {
"type": "zip",
"url": "https://packagist.phpcomposer.com/files/paragonie/random_compat/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e.zip",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
],
"time": "2016-11-07 23:38:38"
},
{
"name": "pimple/pimple",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
},
"dist": {
"type": "zip",
"url": "https://packagist.phpcomposer.com/files/silexphp/Pimple/a30f7d6e57565a2e1a316e1baf2a483f788b258a.zip",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Pimple": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"description": "Pimple, a simple Dependency Injection Container",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
"time": "2015-09-11 15:10:35"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
......
<?php
/**
* Created by PhpStorm.
* User: Hanson
* Date: 2016/12/9
* Time: 21:13
*/
namespace Hanson\Robot\Core;
use GuzzleHttp\Client as HttpClient;
class Http
{
protected $client;
public function get($url, array $options = [])
{
$query = $options ? ['query' => $options] : [];
return $this->request($url, 'GET', $query);
}
public function post($url, $options = [])
{
$key = is_array($options) ? 'form_params' : 'body';
return $this->request($url, 'POST', [$key => $options]);
}
public function json($url, $options = [])
{
return $this->request($url, 'POST', ['json' => $options]);
}
public function setClient(HttpClient $client)
{
$this->client = $client;
return $this;
}
/**
* Return GuzzleHttp\Client instance.
*
* @return \GuzzleHttp\Client
*/
public function getClient()
{
if (!($this->client instanceof HttpClient)) {
$this->client = new HttpClient(['cookies' => true]);
}
return $this->client;
}
public function request($url, $method = 'GET', $options = [])
{
$response = $this->getClient()->request($method, $url, $options);
return $response->getBody()->getContents();
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: Hanson
* Date: 2016/12/9
* Time: 21:10
*/
namespace Hanson\Robot\Core;
use Endroid\QrCode\QrCode;
use GuzzleHttp\Client;
use Hanson\Robot\Models\ContactFactory;
use Hanson\Robot\Support\Log;
use QueryPath\Exception;
use Symfony\Component\DomCrawler\Crawler;
class Server
{
protected $uuid;
protected $redirectUri;
public $skey;
protected $sid;
protected $uin;
public $passTicket;
protected $deviceId;
public $baseRequest;
protected $syncKey;
protected $myAccount;
protected $syncKeyStr;
protected $http;
protected $config;
protected $debug = false;
const BASE_URI = 'https://wx2.qq.com/cgi-bin/mmwebwx-bin';
const BASE_HOST = 'wx2.qq.com';
public function __construct($config = [])
{
$this->http = new Http();
$this->config = $config;
}
/**
* start a wechat trip
*/
public function run()
{
$this->prepare();
$this->init();
Log::echo('[INFO] init success!');
$this->statusNotify();
}
public function prepare()
{
$this->getUuid();
$this->generateQrCode();
Log::echo('[INFO] please scan qrcode to login');
$this->waitForLogin();
$this->login();
Log::echo('[INFO] login success!');
}
/**
* get uuid
*
* @throws \Exception
*/
protected function getUuid()
{
$content = $this->http->get('https://login.weixin.qq.com/jslogin', [
'appid' => 'wx782c26e4c19acffb',
'fun' => 'new',
'lang' => 'zh_CN',
'_' => time() * 1000 . random_int(1, 999)
]);
preg_match('/window.QRLogin.code = (\d+); window.QRLogin.uuid = \"(\S+?)\"/', $content, $matches);
if(!$matches){
throw new \Exception('fail to get uuid');
}
$this->uuid = $matches[2];
}
/**
* generate a login qrcode
*/
public function generateQrCode()
{
$url = 'https://login.weixin.qq.com/l/' . $this->uuid;
$qrCode = new QrCode($url);
$file = $this->config['tmp'] . 'login_qr_code.png';
$qrCode->save($file);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
system($file);
}
}
/**
* waiting user to login
*
* @return int
* @throws \Exception
*/
protected function waitForLogin(): int
{
$retryTime = 10;
$tip = 1;
while($retryTime > 0){
$url = sprintf('https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s', $tip, $this->uuid, time());
$content = $this->http->get($url);
preg_match('/window.code=(\d+);/', $content, $matches);
$code = $matches[1];
switch($code){
case '201':
Log::echo('[INFO] please confirm to login');
$tip = 0;
break;
case '200':
preg_match('/window.redirect_uri="(\S+?)";/', $content, $matches);
$this->redirectUri = $matches[1] . '&fun=new';
return;
case '408':
Log::echo('[ERROR] login timeout. please try 1 second later.');
$tip = 1;
$retryTime -= 1;
sleep(1);
break;
default:
Log::echo("[ERROR] login fail. exception code:$code . please try 1 second later.");
$tip = 1;
$retryTime -= 1;
sleep(1);
break;
}
}
throw new \Exception('[ERROR] login fail!');
}
/**
* login wechat
* @return bool
* @throws \Exception
*/
public function login()
{
$content = $this->http->get($this->redirectUri);
$crawler = new Crawler($content);
$this->skey = $crawler->filter('error skey')->text();
$this->sid = $crawler->filter('error wxsid')->text();
$this->uin = $crawler->filter('error wxuin')->text();
$this->passTicket = $crawler->filter('error pass_ticket')->text();
if(in_array('', [$this->skey, $this->sid, $this->uin, $this->passTicket])){
throw new \Exception('[ERROR] login fail!');
}
$this->deviceId = 'e' . strval(random_int(100000000000000, 999999999999999));
$this->baseRequest = [
'Uin' => $this->uin,
'Sid' => $this->sid,
'Skey' => $this->skey,
'DeviceID' => $this->deviceId
];
return true;
}
protected function init()
{
$url = sprintf(self::BASE_URI . '/webwxinit?r=%i&lang=en_US&pass_ticket=%s', time(), $this->passTicket);
$content = $this->http->json($url, [
'BaseRequest' => $this->baseRequest
]);
$result = json_decode($content, true);
$this->generateSyncKey($result);
$this->myAccount = $result['User'];
if($result['BaseResponse']['Ret'] != 0){
throw new Exception('[ERROR] init fail!');
}
$this->initContact();
}
protected function initContact()
{
new ContactFactory($this);
}
/**
* open wechat status notify
*/
protected function statusNotify()
{
$url = sprintf(self::BASE_URI . '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s', $this->passTicket);
$this->http->json($url, [
'BaseRequest' => $this->baseRequest,
'Code' => 3,
'FromUserName' => $this->myAccount['UserName'],
'ToUserName' => $this->myAccount['UserName'],
'ClientMsgId' => time()
]);
}
protected function generateSyncKey($result)
{
$this->syncKey = $result['SyncKey'];
$syncKey = [];
foreach ($this->syncKey['List'] as $item) {
$syncKey[] = $item['Key'] . '_' . $item['Val'];
}
$this->syncKeyStr = implode('|', $syncKey);
}
public function debug($debug = true)
{
$this->debug = $debug;
return $this;
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: Hanson
* Date: 2016/12/9
* Time: 21:22
*/
namespace Hanson\Robot\Foundation;
use Hanson\Robot\Core\Http;
use Illuminate\Support\Collection;
use Pimple\Container;
class Robot extends Container
{
/**
* Service Providers.
*
* @var array
*/
protected $providers = [
ServiceProviders\ServerServiceProvider::class,
];
public function __construct($config)
{
parent::__construct();
$this['config'] = function () use ($config) {
return new Collection($config);
};
$this->registerProviders();
}
/**
* Register providers.
*/
private function registerProviders()
{
foreach ($this->providers as $provider) {
$this->register(new $provider());
}
}
/**
* Magic get access.
*
* @param string $id
*
* @return mixed
*/
public function __get($id)
{
return $this->offsetGet($id);
}
/**
* Magic set access.
*
* @param string $id
* @param mixed $value
*/
public function __set($id, $value)
{
$this->offsetSet($id, $value);
}
}
\ No newline at end of file
<?php
namespace Hanson\Robot\Foundation\ServiceProviders;
use Hanson\Robot\Core\Server;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class ServerServiceProvider implements ServiceProviderInterface
{
public function register(Container $pimple)
{
$pimple['server'] = function ($pimple) {
$server = new Server($pimple['config']);
$server->debug($pimple['config']['debug']);
return $server;
};
}
}
<?php
/**
* Created by PhpStorm.
* User: Hanson
* Date: 2016/12/12
* Time: 20:41
*/
namespace Hanson\Robot\Models;
use Hanson\Robot\Core\Server;
class ContactFactory
{
protected $server;
public function __construct(Server $server)
{
$this->server = $server;
}
public function getContacts()
{
$url = sprintf(Server::BASE_URI . '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s', $this->server->passTicket, $this->server->skey, time());
$content = $this->http->json($url, [
'BaseRequest' => $this->baseRequest
]);
}
}
\ No newline at end of file
......@@ -181,7 +181,7 @@ class Robot
while($retryTime > 0){
$url = sprintf($urlTemplate, $tip, $this->uuid, time());
$content = $this->client->get($url)->getBody()->getContents();
$content = $this->client->get($url, ['query' => []])->getBody()->getContents();
preg_match('/window.code=(\d+);/', $content, $matches);
......
<?php
/**
* Created by PhpStorm.
* User: Hanson
* Date: 2016/12/9
* Time: 22:51
*/
namespace Hanson\Robot\Support;
class Log
{
public static function echo($str)
{
echo $str . PHP_EOL;
}
}
\ No newline at end of file
......@@ -15,19 +15,26 @@ $robot = new \Hanson\Robot\Robot([
'tuling_key' => ''
]);
$robot->setMessageHandler(function($message){
if($message->type === 'text'){
//$robot->setMessageHandler(function($message){
// if($message->type === 'text'){
//
// }elseif ($message->type === 'location'){
// return Message::make();
// }
//
// if($message->FromUserName === ''){
// # do something;
// }
//
//});
}elseif ($message->type === 'location'){
return Message::make();
}
//$robot->run();
if($message->FromUserName === ''){
# do something;
}
});
$robot = new \Hanson\Robot\Foundation\Robot([
'tmp' => realpath('./tmp') . '/',
'debug' => true,
'tuling' => true,
'tuling_key' => ''
]);
$robot->run();
echo 'finish';
//echo $robot->uuid;
\ No newline at end of file
//$robot->server->run();
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!