├── .editorconfig ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── examples ├── bootstrap.php ├── get_discuss.php ├── get_friend_lnick.php ├── get_friend_qq.php ├── get_friends.php ├── get_groups.php ├── get_recent_list.php ├── get_self_user.php ├── login_for_credential.php ├── poll_message.php ├── poll_message_handler.php └── send_message.php ├── phpunit.xml.dist ├── src ├── Client.php ├── Credential.php ├── CredentialResolver.php ├── Entity │ ├── Birthday.php │ ├── Category.php │ ├── Discuss.php │ ├── DiscussDetail.php │ ├── DiscussMember.php │ ├── Entity.php │ ├── Friend.php │ ├── Group.php │ ├── GroupDetail.php │ ├── GroupMember.php │ ├── OnlineStatus.php │ ├── Profile.php │ ├── Recent.php │ └── User.php ├── EntityCollection.php ├── EntityFactory.php ├── Exception │ ├── Code103ResponseException.php │ ├── InvalidArgumentException.php │ ├── ResponseException.php │ └── RuntimeException.php ├── Message │ ├── Content.php │ ├── Font.php │ ├── Message.php │ ├── MessageInterface.php │ ├── Request │ │ ├── DiscussMessage.php │ │ ├── FriendMessage.php │ │ ├── GroupMessage.php │ │ └── Message.php │ └── Response │ │ ├── DiscussMessage.php │ │ ├── FriendMessage.php │ │ ├── GroupMessage.php │ │ └── Message.php ├── MessageHandler.php ├── Request │ ├── GetCurrentUserRequest.php │ ├── GetDiscussDetailRequest.php │ ├── GetDiscussesRequest.php │ ├── GetFriendDetailRequest.php │ ├── GetFriendsOnlineStatusRequest.php │ ├── GetFriendsRequest.php │ ├── GetGroupDetailRequest.php │ ├── GetGroupsRequest.php │ ├── GetLnickRequest.php │ ├── GetPtWebQQRequest.php │ ├── GetQQRequest.php │ ├── GetQrCodeRequest.php │ ├── GetRecentListRequest.php │ ├── GetUinAndPsessionidRequest.php │ ├── GetVfWebQQRequest.php │ ├── PollMessagesRequest.php │ ├── Request.php │ ├── RequestInterface.php │ ├── SendDiscusMessageRequest.php │ ├── SendFriendMessageRequest.php │ ├── SendGroupMessageRequest.php │ ├── SendMessageRequest.php │ └── VerifyQrCodeRequest.php └── Utils.php └── tests ├── ClientTest.php ├── CredentialResolverTest.php ├── CredentialTest.php ├── Entity ├── BirthdayTest.php ├── CategoryTest.php ├── DiscussDetailTest.php ├── DiscussMemberTest.php ├── DiscussTest.php ├── FriendTest.php ├── GroupDetailTest.php ├── GroupMemberTest.php ├── GroupTest.php ├── OnlineStatusTest.php ├── ProfileTest.php ├── RecentTest.php └── UserTestCase.php ├── EntityCollectionTest.php ├── EntityFactoryTest.php ├── Fixtures ├── 103_response.txt ├── credential.json ├── get_current_user.txt ├── get_discuss_detail.txt ├── get_discusses.txt ├── get_friend_detail.txt ├── get_friend_lnick.txt ├── get_friend_qq.txt ├── get_friends.txt ├── get_friends_online_status.txt ├── get_group_detail.txt ├── get_groups.txt ├── get_ptwebqq_headers.txt ├── get_recent_list.txt ├── get_uin_psession.txt ├── get_vfwebqq.txt ├── poll_messages.txt ├── qrcode.png ├── response_exception.txt ├── send_message.txt ├── verify_status_being_certified.txt ├── verify_status_certified.txt ├── verify_status_expired.txt └── verify_status_valid.txt ├── FooEntity.php ├── Message ├── Content.php ├── FontTest.php ├── MessageInterfaceTest.php ├── MessageTest.php ├── Request │ ├── DiscussMessageTest.php │ ├── FriendMessageTest.php │ ├── GroupMessageTest.php │ └── MessageTest.php └── Response │ ├── DiscussMessageTest.php │ ├── FriendMessageTest.php │ ├── GroupMessageTest.php │ └── MessageTest.php ├── MessageHandlerTest.php ├── TestCase.php └── UtilsTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; top-most EditorConfig file 2 | root = true 3 | 4 | ; Unix-style newlines 5 | [*] 6 | end_of_line = LF 7 | 8 | [*.php] 9 | indent_style = space 10 | indent_size = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .settings/ 3 | .project 4 | .buildpath 5 | composer.lock 6 | .idea 7 | examples/credential.json 8 | examples/result.log 9 | examples/smartqq-login-qr.png 10 | examples/test.php -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - "tests/*" 4 | - "vendor/*" 5 | 6 | tools: 7 | external_code_coverage: 8 | timeout: 600 9 | 10 | php_sim: true 11 | 12 | php_changetracking: true 13 | 14 | php_cs_fixer: 15 | enabled: true 16 | config: 17 | level: all 18 | filter: 19 | excluded_paths: 20 | - "tests/*" 21 | - "vendor/*" 22 | 23 | php_mess_detector: 24 | enabled: true 25 | filter: 26 | excluded_paths: 27 | - "tests/*" 28 | - "vendor/*" 29 | 30 | php_pdepend: 31 | enabled: true 32 | filter: 33 | excluded_paths: 34 | - "tests/*" 35 | - "vendor/*" 36 | 37 | php_analyzer: 38 | enabled: true 39 | filter: 40 | excluded_paths: 41 | - "tests/*" 42 | - "vendor/*" 43 | 44 | 45 | php_cpd: 46 | enabled: true 47 | excluded_dirs: 48 | - "tests/*" 49 | - "vendor/*" 50 | 51 | php_loc: 52 | enabled: true 53 | excluded_dirs: 54 | - "tests/*" 55 | - "vendor/*" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - 7.2 8 | 9 | before_script: 10 | - composer self-update 11 | - composer install 12 | 13 | script: ./vendor/bin/phpunit --coverage-clover=coverage.xml 14 | 15 | after_success: 16 | - bash <(curl -s https://codecov.io/bash) 17 | 18 | notifications: 19 | email: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.2.0 - 2018-09-27 4 | 5 | - 增加 `MessageHandler` 消息处理 6 | - 将登录流程逻辑从 `Client` 独立出去 7 | - 支持二维码字符串自行处理 8 | 9 | 10 | ## 2.1.2 - 2018-07-09 11 | 12 | - 增加废除API提示 13 | - 优化代码结构 14 | 15 | ## 2.1.1 - 2018-02-24 16 | 17 | ## Fixed 18 | - 尝试在登陆时避免 103 错误 19 | - 修复轮询消息时出现表情时的bug 20 | 21 | ## 2.1.0 - 2018-02-23 22 | 23 | ## Fixed 24 | 25 | - 修复发送消息时对 103 状态异常的判断 26 | 27 | ## Added 28 | 29 | - 增加发送消息时对表情的支持 30 | 31 | ## 2.0.5 - 2018-01-24 32 | 33 | ### Fixed 34 | 35 | - 修复发送讨论组/群/好友消息失败的问题 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2017 Slince https://www.phpdish.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartQQ协议 2 | 3 | [![Build Status](https://img.shields.io/travis/slince/smartqq/master.svg?style=flat-square)](https://travis-ci.org/slince/smartqq) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/slince/smartqq.svg?style=flat-square)](https://codecov.io/github/slince/smartqq) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/slince/smartqq.svg?style=flat-square&label=stable)](https://packagist.org/packages/slince/smartqq) 6 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/slince/smartqq.svg?style=flat-square)](https://scrutinizer-ci.com/g/slince/smartqq/?branch=master) 7 | 8 | SmartQQ(WebQQ) API的PHP实现,通过对原生web api的请求以及返回值的分析,重新进行了整理; 9 | 解决了原生接口杂乱的请求规则与混乱的数据返回;使得开发者可以更多关注自己的业务。 10 | 11 | 灵感来自于[Java SmartQQ](https://github.com/ScienJus/smartqq),感谢原作者对SmartQQ的详尽解释。 12 | 13 | ## 安装 14 | 15 | ```bash 16 | composer require slince/smartqq 17 | ``` 18 | 19 | ## 使用 20 | 21 | ### 登录 22 | 23 | 登录是获取授权的必备步骤,由于SmartQQ抛弃了用户名密码的登录方式,所以只能采用二维码登录 24 | 25 | ```php 26 | $smartQQ = new Slince\SmartQQ\Client(); 27 | 28 | $smartQQ->login(function($qrcode){ //$qrcode 是二维码字符串 29 | // 自定义逻辑 30 | @file_put_contents('/path/to/qrcode.png', $qrcode); 31 | }); 32 | ``` 33 | 如果成功的话你会在`/path/to/qrcode.png`下发现二维码,使用手机扫描即可登录; 34 | 注意:程序会阻塞直到确认成功;成功之后你可以通过下面方式持久化登录凭证,用于下次查询。 35 | 36 | ```php 37 | $credential = $smartQQ->getCredential(); 38 | 39 | //将凭证对象转换为标量方便存储,写入数据库或者缓存系统 40 | $credentialParameters = $credential->toArray(); 41 | ``` 42 | 43 | 通过下面方式还原一个凭证对象;需要注意的是此次凭证并不会长久有效, 44 | 如果该凭证长时间没有被用来发起查询,则很可能会失效。 45 | 46 | ```php 47 | //还原凭证对象 48 | $credential = Credential::fromArray($credentialParameters); 49 | $smartQQ = new Client($credential); 50 | ``` 51 | 52 | ### 查询好友、群以及讨论组 53 | 54 | #### 好友相关 55 | 56 | - 查询所有好友 57 | 58 | ```php 59 | $friends = $smartQQ->getFriends(); 60 | 61 | //找出昵称为张三的好友 62 | $zhangSan = $friends->firstByAttribute('nick', '张三'); 63 | 64 | //自定义筛选,如找出所有女性并且是vip会员的好友 65 | $girls = $friends->filter(function(Friend $friend){ 66 | return $friend->getGender() == 'female' && $friend->isVip(); 67 | })->toArray(); 68 | ``` 69 | 70 | - 查询好友详细资料 71 | 72 | ```php 73 | //上例,找出张三的资料 74 | $profile = $smartQQ->getFriendDetail($zhangSan); 75 | ``` 76 | 77 | #### 群相关 78 | 79 | - 查询所有群 80 | 81 | ```php 82 | $groups = $smartQQ->getGroups(); 83 | 84 | //找出名称为“少年”的群 85 | $shaoNianGroup = $groups->firstByAttribute('name', '少年'); 86 | ``` 87 | > 同样支持自定义筛选 88 | 89 | #### 讨论组相关 90 | 91 | - 查询所有讨论组 92 | 93 | ```php 94 | $discusses = $smartQQ->getDiscusses(); 95 | 96 | //找出名称为“少年”的讨论组 97 | $shaoNianDiscuss = $discusses->firstByAttribute('name', '少年'); 98 | ``` 99 | > 一样,也支持自定义筛选 100 | 101 | 102 | - 查询讨论组的详细资料,比如群成员信息等 103 | 104 | 接上例,查询讨论组“少年”的详细资料 105 | 106 | ```php 107 | $shaoNianDetail = $smartQQ->getDiscussDetail($shaoNianDiscuss); 108 | 109 | //所有群成员,支持自定义筛选 110 | $members = $shaoNianDetail->getMembers(); 111 | ``` 112 | 113 | ### 发送消息 114 | 115 | 支持表情,表情短语 116 | 117 | ```php 118 | [ 119 | "微笑" ,"撇嘴" ,"色" ,"发呆" ,"得意" ,"流泪" ,"害羞" ,"闭嘴" ,"睡" ,"大哭" ,"尴尬" ,"发怒" ,"调皮" ,"呲牙" ,"惊讶" ,"难过" ,"酷" ,"冷汗" ,"抓狂" ,"吐", 120 | "偷笑" ,"可爱" ,"白眼" ,"傲慢" ,"饥饿" ,"困" ,"惊恐" ,"流汗" ,"憨笑" ,"大兵" ,"奋斗" ,"咒骂" ,"疑问" ,"嘘" ,"晕" ,"折磨" ,"衰" ,"骷髅" ,"敲打" ,"再见", 121 | "擦汗" ,"抠鼻" ,"鼓掌" ,"糗大了" ,"坏笑" ,"左哼哼" ,"右哼哼" ,"哈欠" ,"鄙视" ,"委屈" ,"快哭了" ,"阴险" ,"亲亲" ,"吓" ,"可怜" ,"菜刀" ,"西瓜" ,"啤酒" ,"篮球" ,"乒乓", 122 | "咖啡" ,"饭" ,"猪头" ,"玫瑰" ,"凋谢" ,"示爱" ,"爱心" ,"心碎" ,"蛋糕" ,"闪电" ,"炸弹" ,"刀" ,"足球" ,"瓢虫" ,"便便" ,"月亮" ,"太阳" ,"礼物" ,"拥抱" ,"强", 123 | "弱" ,"握手" ,"胜利" ,"抱拳" ,"勾引" ,"拳头" ,"差劲" ,"爱你" ,"NO" ,"OK" ,"爱情" ,"飞吻" ,"跳跳" ,"发抖" ,"怄火" ,"转圈" ,"磕头" ,"回头" ,"跳绳" ,"挥手", 124 | "激动", "街舞", "献吻", "左太极", "右太极", "双喜", "鞭炮", "灯笼", "发财", "K歌", "购物", "邮件", "帅", "喝彩","祈祷","爆筋","棒棒糖","喝奶","下面","香蕉", 125 | "飞机","开车","左车头","车厢","右车头","多云","下雨","钞票","熊猫","灯泡","风车","闹钟","打伞","彩球","钻戒","沙发","纸巾","药","手枪","青蛙" 126 | ] 127 | ``` 128 | 129 | #### 给好友发送消息 130 | 131 | ```php 132 | //1、找到好友 133 | $friend = $friends->firstByAttribute('nick', '秋易'); 134 | 135 | //2、生成消息 136 | $message = new FriendMessage($friend, '[微笑] 你好'); 137 | $result = $smartQQ->sendMessage($message); 138 | var_dump($result); 139 | ``` 140 | 141 | #### 给群发送消息 142 | 143 | ```php 144 | //1、找到群 145 | $group = $groups->firstByAttribute('name', 'msu'); 146 | //2、生成消息 147 | $message = new GroupMessage($group, new Content('[微笑] 哈喽')); 148 | $result = $smartQQ->sendMessage($message); 149 | var_dump($result); 150 | ``` 151 | 152 | #### 发送讨论组消息 153 | 154 | ```php 155 | //1、找到讨论组 156 | $discuss = $discusses->firstByAttribute('name', '他是个少年'); 157 | //2、生成消息 158 | $message = new DiscussMessage($discuss, '[微笑] 讨论组消息'); 159 | $result = $smartQQ->sendMessage($message); 160 | var_dump($result); 161 | ``` 162 | 163 | #### 给讨论组成员发消息 164 | 165 | ```php 166 | $discussMember = $smartQQ->getDiscussDetail($discuss) 167 | ->getMembers() 168 | ->firstByAttribute('nick', '张三'); 169 | $message = new FriendMessage($discussMember, '[微笑] 你好'); 170 | $result = $smartQQ->sendMessage($message); 171 | var_dump($result); 172 | ``` 173 | 174 | ### 接收消息 175 | 176 | ```php 177 | $messages = $smartQQ->pollMessages(); 178 | ``` 179 | 关于消息的处理请参照 examples. 180 | 181 | 当然你也可以使用 `MessageHandler` 来帮忙维护客户端消息。 182 | 183 | ```php 184 | $handler = $smartQQ->getMessageHandler(); 185 | 186 | $handler->onMessage(function(Slince\SmartQQ\Message\Response\Message $message) use ($hander){ 187 | var_dump($message); 188 | $handler->stop(); //停止消息轮询 189 | }); 190 | ``` 191 | 192 | 详细使用案例以及更多其它案例请参考[examples](./examples) 193 | 194 | 195 | ## 其它 196 | 197 | - 关于103错误,多是由于webqq多点登录引起的,如果遇到错误,先到[http://w.qq.com/](http://w.qq.com/)确认能够收发消息,然后退出登录。 198 | 199 | - 关于登录凭证的时效性,smartqq是基于web接口的,对cookie有要求,登录成功之后如果长时间没有操作,cookie将会失效;此时需要重新登录。 200 | 201 | - 本组件只是对原生的请求与数据进行了整理并进行了合理的抽象,并没有过多的进行业务层级的封装。 202 | 203 | - SmartQQ接口一直在调整,如果api无法使用请直接提[issue](https://github.com/slince/smartqq/issues/new),或者给我发邮件;如果你找到了原因并且有了解决方案欢迎 PR。 204 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slince/smartqq", 3 | "description": "The wrapper for smartqq api using php", 4 | "keywords": ["smartqq", "webqq", "qq", "php-smartqq", "php-webqq", "php-qq"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "tao", 10 | "email": "taosikai@yeah.net" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Slince\\SmartQQ\\": "./src" 16 | } 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Slince\\SmartQQ\\Tests\\": "./tests" 21 | } 22 | }, 23 | "require": { 24 | "php": ">=5.6.0", 25 | "guzzlehttp/guzzle": "^6.0", 26 | "symfony/filesystem": "^2.7|^3.0|^4.0", 27 | "cakephp/collection": "^3.0", 28 | "slince/event-dispatcher": "^2.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^5.0|^6.0" 32 | }, 33 | "minimum-stability": "stable" 34 | } 35 | -------------------------------------------------------------------------------- /examples/bootstrap.php: -------------------------------------------------------------------------------- 1 | toArray())); 63 | } -------------------------------------------------------------------------------- /examples/get_discuss.php: -------------------------------------------------------------------------------- 1 | getDiscusses(); 16 | 17 | //2、获取指定讨论组的详细信息 18 | $firstDiscuss = $discusses->first(); 19 | $discussDetail = $smartQQ->getDiscussDetail($firstDiscuss); 20 | 21 | $discussMembers = $discussDetail->getMembers(); //讨论组下的所有成员 22 | 23 | printPrettyScreen($discusses->toArray()); 24 | printPrettyScreen($discussMembers->toArray()); 25 | printR($firstDiscuss); 26 | printR($discussDetail); -------------------------------------------------------------------------------- /examples/get_friend_lnick.php: -------------------------------------------------------------------------------- 1 | getFriends()->first(); 13 | 14 | //获取用户的个性签名 15 | $lnick = $smartQQ->getFriendLnick($friend); 16 | 17 | printR($lnick); -------------------------------------------------------------------------------- /examples/get_friend_qq.php: -------------------------------------------------------------------------------- 1 | getFriends()->first(); 13 | 14 | //获取用户的QQ号 15 | $qq = $smartQQ->getFriendQQ($friend); 16 | 17 | printR($qq); 18 | printR($friend->getQq()); -------------------------------------------------------------------------------- /examples/get_friends.php: -------------------------------------------------------------------------------- 1 | getFriends(); 16 | 17 | //2、从好友中筛选出指定好友, 18 | /** 19 | * 注意QQ号需要单独发起请求查询QQ号,故如果需要按照QQ号筛选,需要做好心里准备 20 | * smartqq会多次发起请求 21 | */ 22 | $friend = $friends->firstByAttribute('nick', '清延°'); 23 | 24 | //3、获取好友的详细信息 25 | $profile = $smartQQ->getFriendDetail($friend); 26 | 27 | //4、获取好友的真实QQ号(此接口目前已经被腾讯封禁) 28 | //$qq = $smartQQ->getFriendQQ($friend); 29 | 30 | //3、输出结果 31 | printPrettyScreen($friends->toArray()); 32 | printR($friend); 33 | printR($profile); 34 | //printR($qq); 35 | -------------------------------------------------------------------------------- /examples/get_groups.php: -------------------------------------------------------------------------------- 1 | getGroups(); 16 | 17 | //2、筛选出指定的群 18 | $group = $groups->firstByAttribute('name', 'Symfony.cn'); 19 | 20 | //3、获取群的详细信息 21 | $groupDetail = $smartQQ->getGroupDetail($group); 22 | // 输出 23 | printPrettyScreen($groups->toArray()); 24 | printR($group); 25 | printR($group); 26 | printR($groupDetail); 27 | -------------------------------------------------------------------------------- /examples/get_recent_list.php: -------------------------------------------------------------------------------- 1 | getRecentList(); 14 | 15 | printR($recentList->toArray()); -------------------------------------------------------------------------------- /examples/get_self_user.php: -------------------------------------------------------------------------------- 1 | getCurrentUserInfo(); 14 | 15 | printR($user); -------------------------------------------------------------------------------- /examples/login_for_credential.php: -------------------------------------------------------------------------------- 1 | login(LOGIN_QR_IMAGE); 12 | //执行之后你会在[LOGIN_QR_IMAGE]对应的位置发现二维码图片; 获取授权的时候程序会锁住直到二维码确认成功; 13 | 14 | //持久化凭证信息以方便其它接口调用 15 | saveCredential($smartQQ->getCredential()); -------------------------------------------------------------------------------- /examples/poll_message.php: -------------------------------------------------------------------------------- 1 | getFriends(); 17 | $groups = $smartQQ->getGroups(); 18 | $discusses = $smartQQ->getDiscusses(); 19 | 20 | /** 21 | * 获取讨论组详情. 22 | * 23 | * @param Discuss $discuss 24 | * 25 | * @return DiscussDetail 26 | */ 27 | function getDiscussDetails(Discuss $discuss) 28 | { 29 | global $smartQQ; 30 | static $discussDetails = []; 31 | if (isset($discussDetails[$discuss->getId()])) { 32 | return $discussDetails[$discuss->getId()]; 33 | } 34 | 35 | return $discussDetails[$discuss->getId()] = $smartQQ->getDiscussDetail($discuss); 36 | } 37 | 38 | while (true) { 39 | $messages = $smartQQ->pollMessages(); 40 | if ($messages) { 41 | printR('收到消息'.PHP_EOL); 42 | foreach ($messages as $message) { 43 | if ($message instanceof FriendMessage) { 44 | $friend = $friends->firstByAttribute('uin', $message->getFromUin()); 45 | $content = sprintf("好友[%s] -- %s: \r\n %s", 46 | $friend->getNick(), 47 | date('H:i:s'), 48 | $message 49 | ); 50 | } elseif ($message instanceof GroupMessage) { 51 | $group = $groups->firstByAttribute('id', $message->getFromUin()); 52 | $content = sprintf("群[%s] -- %s: \r\n %s", 53 | $group->getName(), 54 | date('H:i:s'), 55 | $message 56 | ); 57 | } elseif ($message instanceof DiscussMessage) { 58 | $discuss = $discusses->firstByAttribute('id', $message->getFromUin()); 59 | $sender = getDiscussDetails($discuss)->getMembers()->firstByAttribute('uin', $message->getSendUin()); 60 | $content = sprintf("讨论组[%s] [%s]-- %s: \r\n %s", 61 | $discuss->getName(), 62 | $sender->getNick(), 63 | date('H:i:s'), 64 | $message 65 | ); 66 | } else { 67 | continue; 68 | } 69 | printR($content); 70 | echo str_repeat(PHP_EOL, 2); 71 | } 72 | } else { 73 | //延缓2秒 74 | usleep(2000000); 75 | } 76 | } -------------------------------------------------------------------------------- /examples/poll_message_handler.php: -------------------------------------------------------------------------------- 1 | getFriends(); 17 | $groups = $smartQQ->getGroups(); 18 | $discusses = $smartQQ->getDiscusses(); 19 | 20 | /** 21 | * 获取讨论组详情. 22 | * 23 | * @param Discuss $discuss 24 | * 25 | * @return DiscussDetail 26 | */ 27 | function getDiscussDetails(Discuss $discuss) 28 | { 29 | global $smartQQ; 30 | static $discussDetails = []; 31 | if (isset($discussDetails[$discuss->getId()])) { 32 | return $discussDetails[$discuss->getId()]; 33 | } 34 | 35 | return $discussDetails[$discuss->getId()] = $smartQQ->getDiscussDetail($discuss); 36 | } 37 | 38 | $handler = $smartQQ->getMessageHandler(); 39 | 40 | $handler->onMessage(function($message) use ($friends, $groups, $discusses){ 41 | 42 | if ($message instanceof FriendMessage) { 43 | $friend = $friends->firstByAttribute('uin', $message->getFromUin()); 44 | $content = sprintf("好友[%s] -- %s: \r\n %s", 45 | $friend->getNick(), 46 | date('H:i:s'), 47 | $message 48 | ); 49 | } elseif ($message instanceof GroupMessage) { 50 | $group = $groups->firstByAttribute('id', $message->getFromUin()); 51 | $content = sprintf("群[%s] -- %s: \r\n %s", 52 | $group->getName(), 53 | date('H:i:s'), 54 | $message 55 | ); 56 | } elseif ($message instanceof DiscussMessage) { 57 | $discuss = $discusses->firstByAttribute('id', $message->getFromUin()); 58 | $sender = getDiscussDetails($discuss)->getMembers()->firstByAttribute('uin', $message->getSendUin()); 59 | $content = sprintf("讨论组[%s] [%s]-- %s: \r\n %s", 60 | $discuss->getName(), 61 | $sender->getNick(), 62 | date('H:i:s'), 63 | $message 64 | ); 65 | } else { 66 | exit('不支持的消息类型'); 67 | } 68 | 69 | printR('收到消息'.PHP_EOL); 70 | printR($content); 71 | echo str_repeat(PHP_EOL, 2); 72 | }); 73 | 74 | $handler->listen(); -------------------------------------------------------------------------------- /examples/send_message.php: -------------------------------------------------------------------------------- 1 | getFriends(); 17 | $groups = $smartQQ->getGroups(); 18 | $discusses = $smartQQ->getDiscusses(); 19 | //# 给好友发送消息 20 | 21 | //1、找到好友 22 | $friend = $friends->firstByAttribute('nick', '秋易'); 23 | //2、生成消息 24 | //支持表情,使用中括号括起来即可,表情短语 25 | /** 26 | * [. 27 | "微笑" ,"撇嘴" ,"色" ,"发呆" ,"得意" ,"流泪" ,"害羞" ,"闭嘴" ,"睡" ,"大哭" ,"尴尬" ,"发怒" ,"调皮" ,"呲牙" ,"惊讶" ,"难过" ,"酷" ,"冷汗" ,"抓狂" ,"吐", 28 | "偷笑" ,"可爱" ,"白眼" ,"傲慢" ,"饥饿" ,"困" ,"惊恐" ,"流汗" ,"憨笑" ,"大兵" ,"奋斗" ,"咒骂" ,"疑问" ,"嘘" ,"晕" ,"折磨" ,"衰" ,"骷髅" ,"敲打" ,"再见", 29 | "擦汗" ,"抠鼻" ,"鼓掌" ,"糗大了" ,"坏笑" ,"左哼哼" ,"右哼哼" ,"哈欠" ,"鄙视" ,"委屈" ,"快哭了" ,"阴险" ,"亲亲" ,"吓" ,"可怜" ,"菜刀" ,"西瓜" ,"啤酒" ,"篮球" ,"乒乓", 30 | "咖啡" ,"饭" ,"猪头" ,"玫瑰" ,"凋谢" ,"示爱" ,"爱心" ,"心碎" ,"蛋糕" ,"闪电" ,"炸弹" ,"刀" ,"足球" ,"瓢虫" ,"便便" ,"月亮" ,"太阳" ,"礼物" ,"拥抱" ,"强", 31 | "弱" ,"握手" ,"胜利" ,"抱拳" ,"勾引" ,"拳头" ,"差劲" ,"爱你" ,"NO" ,"OK" ,"爱情" ,"飞吻" ,"跳跳" ,"发抖" ,"怄火" ,"转圈" ,"磕头" ,"回头" ,"跳绳" ,"挥手", 32 | "激动", "街舞", "献吻", "左太极", "右太极", "双喜", "鞭炮", "灯笼", "发财", "K歌", "购物", "邮件", "帅", "喝彩","祈祷","爆筋","棒棒糖","喝奶","下面","香蕉", 33 | "飞机","开车","左车头","车厢","右车头","多云","下雨","钞票","熊猫","灯泡","风车","闹钟","打伞","彩球","钻戒","沙发","纸巾","药","手枪","青蛙" 34 | ] 35 | */ 36 | $message = new FriendMessage($friend, new Content('[大哭]你好[左哼哼]')); 37 | $result = $smartQQ->sendMessage($message); 38 | printR($result ? 'Send Success' : 'Send Error'); 39 | 40 | //# 给群发送消息 41 | //1、找到群 42 | $group = $groups->firstByAttribute('name', 'msu'); 43 | //2、生成消息 44 | $message = new GroupMessage($group, new Content('哈喽')); 45 | $result = $smartQQ->sendMessage($message); 46 | printR($result ? 'Send Success' : 'Send Error'); 47 | 48 | //# 发送讨论组消息 49 | //1、找到讨论组 50 | $discuss = $discusses->firstByAttribute('name', '他是个少年'); 51 | //2、生成消息 52 | $message = new DiscussMessage($discuss, '讨论组消息'); 53 | $result = $smartQQ->sendMessage($message); 54 | printR($result ? 'Send Success' : 'Send Error'); 55 | 56 | //# 给讨论组成员发消息 57 | $discussMember = $smartQQ->getDiscussDetail($discuss) 58 | ->getMembers() 59 | ->firstByAttribute('nick', '张三'); 60 | $message = new FriendMessage($discussMember, '你好'); 61 | $result = $smartQQ->sendMessage($message); 62 | printR($result ? 'Send Success' : 'Send Error'); 63 | 64 | //# 给群成员发送消息 65 | /* 66 | $groupMember = $smartQQ->getGroupDetail($group) 67 | ->getMembers() 68 | ->firstByAttribute('nick', '清延°'); 69 | $message = new FriendMessage($discussMember, '你好'); 70 | $result = $smartQQ->sendMessage($message); 71 | printR($result ? 'Send Success' : 'Send Error'); 72 | */ -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | ./tests/ 11 | 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | ./src/Request 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Credential.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Slince\SmartQQ; 13 | 14 | use GuzzleHttp\Cookie\CookieJar; 15 | use GuzzleHttp\Cookie\SetCookie; 16 | 17 | class Credential 18 | { 19 | /** 20 | * 鉴权参数ptwebqq,存储在cookie中. 21 | * 22 | * @var string 23 | */ 24 | protected $ptWebQQ; 25 | 26 | /** 27 | * 鉴权参数vfwebqq. 28 | * 29 | * @var string 30 | */ 31 | protected $vfWebQQ; 32 | 33 | /** 34 | * 鉴权参数pSessionId. 35 | * 36 | * @var string 37 | */ 38 | protected $pSessionId; 39 | 40 | /** 41 | * 客户端id. 42 | * 43 | * @var int 44 | */ 45 | protected $clientId; 46 | 47 | /** 48 | * 当前登录的用户编号(o+QQ号). 49 | * 50 | * @var string 51 | */ 52 | protected $uin; 53 | 54 | /** 55 | * cookie信息,由于client发起请求需要使用cookie信息故cookie也需要一同处理. 56 | * 57 | * @var CookieJar 58 | */ 59 | protected $cookies; 60 | 61 | public function __construct($ptWebQQ, $vfWebQQ, $pSessionId, $uin, $clientId, CookieJar $cookies) 62 | { 63 | $this->ptWebQQ = $ptWebQQ; 64 | $this->vfWebQQ = $vfWebQQ; 65 | $this->pSessionId = $pSessionId; 66 | $this->uin = $uin; 67 | $this->clientId = $clientId; 68 | $this->cookies = $cookies; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getPtWebQQ() 75 | { 76 | return $this->ptWebQQ; 77 | } 78 | 79 | /** 80 | * @param string $ptWebQQ 81 | */ 82 | public function setPtWebQQ($ptWebQQ) 83 | { 84 | $this->ptWebQQ = $ptWebQQ; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getVfWebQQ() 91 | { 92 | return $this->vfWebQQ; 93 | } 94 | 95 | /** 96 | * @param string $vfWebQQ 97 | */ 98 | public function setVfWebQQ($vfWebQQ) 99 | { 100 | $this->vfWebQQ = $vfWebQQ; 101 | } 102 | 103 | /** 104 | * @return string 105 | */ 106 | public function getPSessionId() 107 | { 108 | return $this->pSessionId; 109 | } 110 | 111 | /** 112 | * @return CookieJar 113 | */ 114 | public function getCookies() 115 | { 116 | return $this->cookies; 117 | } 118 | 119 | /** 120 | * @param CookieJar $cookies 121 | */ 122 | public function setCookies($cookies) 123 | { 124 | $this->cookies = $cookies; 125 | } 126 | 127 | /** 128 | * @param string $pSessionId 129 | */ 130 | public function setPSessionId($pSessionId) 131 | { 132 | $this->pSessionId = $pSessionId; 133 | } 134 | 135 | /** 136 | * @return int 137 | */ 138 | public function getClientId() 139 | { 140 | return $this->clientId; 141 | } 142 | 143 | /** 144 | * @param int $clientId 145 | */ 146 | public function setClientId($clientId) 147 | { 148 | $this->clientId = $clientId; 149 | } 150 | 151 | /** 152 | * @return string 153 | */ 154 | public function getUin() 155 | { 156 | return $this->uin; 157 | } 158 | 159 | /** 160 | * @param string $uin 161 | */ 162 | public function setUin($uin) 163 | { 164 | $this->uin = $uin; 165 | } 166 | 167 | /** 168 | * @return array 169 | */ 170 | public function toArray() 171 | { 172 | return [ 173 | 'ptWebQQ' => $this->ptWebQQ, 174 | 'vfWebQQ' => $this->vfWebQQ, 175 | 'pSessionId' => $this->pSessionId, 176 | 'uin' => $this->uin, 177 | 'clientId' => $this->clientId, 178 | 'cookies' => $this->cookies->toArray(), 179 | ]; 180 | } 181 | 182 | /** 183 | * Create from a array data. 184 | * 185 | * @param array $data 186 | * 187 | * @return static 188 | */ 189 | public static function fromArray(array $data) 190 | { 191 | $cookieJar = null; 192 | if (isset($data['cookies'])) { 193 | $cookieJar = new CookieJar(); 194 | foreach ($data['cookies'] as $cookie) { 195 | $cookieJar->setCookie(new SetCookie($cookie)); 196 | } 197 | } 198 | 199 | return new static($data['ptWebQQ'], $data['vfWebQQ'], 200 | $data['pSessionId'], $data['uin'], $data['clientId'], 201 | $cookieJar 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/CredentialResolver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Slince\SmartQQ; 13 | 14 | use GuzzleHttp\Cookie\CookieJar; 15 | use Slince\SmartQQ\Exception\RuntimeException; 16 | 17 | class CredentialResolver 18 | { 19 | /** 20 | * @var Client 21 | */ 22 | protected $client; 23 | 24 | /** 25 | * @var CookieJar 26 | */ 27 | protected $cookies; 28 | 29 | /** 30 | * 获取ptwebqq的地址 31 | * 32 | * @var string 33 | */ 34 | protected $certificationUrl; 35 | 36 | /** 37 | * @var callable 38 | */ 39 | protected $callback; 40 | 41 | public function __construct(Client $client) 42 | { 43 | $this->client = $client; 44 | } 45 | 46 | /** 47 | * 获取授权凭据. 48 | * 49 | * @param callable $callback 50 | * 51 | * @return $this 52 | */ 53 | public function resolve($callback) 54 | { 55 | // 重置cookie 56 | $this->cookies = new CookieJar(); 57 | $this->callback = $callback; 58 | $this->createQRSource(); 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * 等待授权验证. 65 | * 66 | * @return Credential 67 | */ 68 | public function wait() 69 | { 70 | //查找"qrsig"参数 71 | $qrSign = $this->findQRSign(); 72 | //计算ptqrtoken 73 | $ptQrToken = Utils::hash33($qrSign); 74 | 75 | //验证状态 76 | while (true) { 77 | //查看二维码状态 78 | $status = $this->getQrCodeStatus($ptQrToken); 79 | 80 | // 认证成功 81 | if (Request\VerifyQrCodeRequest::STATUS_CERTIFICATION === $status) { 82 | //授权成功跳出状态检查 83 | break; 84 | } elseif (Request\VerifyQrCodeRequest::STATUS_EXPIRED == $status) { 85 | $this->createQRSource(); 86 | //查找"qrsig"参数 87 | $qrSign = $this->findQRSign(); 88 | //计算ptqrtoken 89 | $ptQrToken = Utils::hash33($qrSign); 90 | } 91 | //暂停1秒 92 | usleep(1000000); 93 | } 94 | 95 | $ptWebQQ = $this->getPtWebQQ(); 96 | $vfWebQQ = $this->getVfWebQQ($ptWebQQ); 97 | list($uin, $pSessionId) = $this->getUinAndPSessionId($ptWebQQ); 98 | 99 | $credential = new Credential( 100 | $ptWebQQ, 101 | $vfWebQQ, 102 | $pSessionId, 103 | $uin, 104 | Client::$clientId, //smartqq保留字段,固定值 105 | $this->cookies 106 | ); 107 | 108 | return $credential; 109 | } 110 | 111 | protected function createQRSource() 112 | { 113 | //获取二维码资源 114 | $response = $this->sendRequest(new Request\GetQrCodeRequest()); 115 | call_user_func($this->callback, (string) $response->getBody()); 116 | } 117 | 118 | /** 119 | * 从cookie中查找 "qrsig" 参数. 120 | * 121 | * @return string 122 | */ 123 | protected function findQRSign() 124 | { 125 | foreach ($this->getCookies() as $cookie) { 126 | if (0 === strcasecmp($cookie->getName(), 'qrsig')) { 127 | return $cookie->getValue(); 128 | } 129 | } 130 | throw new RuntimeException('Can not find parameter [qrsig]'); 131 | } 132 | 133 | /** 134 | * 验证二维码状态 135 | * 136 | * @param int $ptQrToken qr token 137 | * 138 | * @return int 139 | */ 140 | protected function getQrCodeStatus($ptQrToken) 141 | { 142 | $request = new Request\VerifyQrCodeRequest($ptQrToken); 143 | $response = $this->sendRequest($request); 144 | if (false !== strpos($response->getBody(), '未失效')) { 145 | $status = Request\VerifyQrCodeRequest::STATUS_UNEXPIRED; 146 | } elseif (false !== strpos($response->getBody(), '已失效')) { 147 | $status = Request\VerifyQrCodeRequest::STATUS_EXPIRED; 148 | } elseif (false !== strpos($response->getBody(), '认证中')) { 149 | $status = Request\VerifyQrCodeRequest::STATUS_ACCREDITATION; 150 | } else { 151 | $status = Request\VerifyQrCodeRequest::STATUS_CERTIFICATION; 152 | //找出认证url 153 | if (preg_match("#'(http.+)'#U", strval($response->getBody()), $matches)) { 154 | $this->certificationUrl = trim($matches[1]); 155 | } else { 156 | throw new RuntimeException('Can not find certification url'); 157 | } 158 | } 159 | 160 | return $status; 161 | } 162 | 163 | /** 164 | * 获取ptwebqq的参数值 165 | * 166 | * @return string 167 | */ 168 | protected function getPtWebQQ() 169 | { 170 | $request = new Request\GetPtWebQQRequest(); 171 | $request->setUri($this->certificationUrl); 172 | $this->sendRequest($request); 173 | foreach ($this->getCookies() as $cookie) { 174 | if (0 === strcasecmp($cookie->getName(), 'ptwebqq')) { 175 | return $cookie->getValue(); 176 | } 177 | } 178 | throw new RuntimeException('Can not find parameter [ptwebqq]'); 179 | } 180 | 181 | /** 182 | * @param string $ptWebQQ 183 | * 184 | * @return string 185 | */ 186 | protected function getVfWebQQ($ptWebQQ) 187 | { 188 | $request = new Request\GetVfWebQQRequest($ptWebQQ); 189 | $response = $this->sendRequest($request); 190 | 191 | return Request\GetVfWebQQRequest::parseResponse($response); 192 | } 193 | 194 | /** 195 | * 获取pessionid和uin. 196 | * 197 | * @param string $ptWebQQ 198 | * 199 | * @return array 200 | */ 201 | protected function getUinAndPSessionId($ptWebQQ) 202 | { 203 | $request = new Request\GetUinAndPsessionidRequest([ 204 | 'ptwebqq' => $ptWebQQ, 205 | 'clientid' => Client::$clientId, 206 | 'psessionid' => '', 207 | 'status' => 'online', 208 | ]); 209 | $response = $this->sendRequest($request); 210 | 211 | return Request\GetUinAndPsessionidRequest::parseResponse($response); 212 | } 213 | 214 | protected function sendRequest(Request\RequestInterface $request) 215 | { 216 | return $this->client->sendRequest($request, [ 217 | 'cookies' => $this->cookies, //使用当前cookies 218 | ]); 219 | } 220 | 221 | protected function getCookies() 222 | { 223 | return $this->cookies; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Entity/Birthday.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class Birthday 14 | { 15 | /** 16 | * @var int 17 | */ 18 | protected $year; 19 | 20 | /** 21 | * @var int 22 | */ 23 | protected $month; 24 | 25 | /** 26 | * @var int 27 | */ 28 | protected $day; 29 | 30 | public function __construct($year, $month, $day) 31 | { 32 | $this->year = $year; 33 | $this->month = $month; 34 | $this->day = $day; 35 | } 36 | 37 | /** 38 | * @param int $year 39 | */ 40 | public function setYear($year) 41 | { 42 | $this->year = $year; 43 | } 44 | 45 | /** 46 | * @param int $month 47 | */ 48 | public function setMonth($month) 49 | { 50 | $this->month = $month; 51 | } 52 | 53 | /** 54 | * @param int $day 55 | */ 56 | public function setDay($day) 57 | { 58 | $this->day = $day; 59 | } 60 | 61 | /** 62 | * @return int 63 | */ 64 | public function getYear() 65 | { 66 | return $this->year; 67 | } 68 | 69 | /** 70 | * @return int 71 | */ 72 | public function getMonth() 73 | { 74 | return $this->month; 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getDay() 81 | { 82 | return $this->day; 83 | } 84 | 85 | /** 86 | * 创建生日. 87 | * 88 | * @param array $data 89 | * 90 | * @return Birthday 91 | */ 92 | public static function createFromArray(array $data) 93 | { 94 | return new static($data['year'], $data['month'], $data['day']); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Entity/Category.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | /** 14 | * QQ好友分组. 15 | */ 16 | class Category 17 | { 18 | /** 19 | * 分组名称. 20 | * 21 | * @var string 22 | */ 23 | protected $name; 24 | 25 | /** 26 | * 编号,作用不明. 27 | * 28 | * @var int 29 | */ 30 | protected $index; 31 | 32 | /** 33 | * 顺序. 34 | * 35 | * @var int 36 | */ 37 | protected $sort; 38 | 39 | public function __construct($name, $index = 0, $sort = 0) 40 | { 41 | $this->name = $name; 42 | $this->index = $index; 43 | $this->sort = $sort; 44 | } 45 | 46 | /** 47 | * @param string $name 48 | */ 49 | public function setName($name) 50 | { 51 | $this->name = $name; 52 | } 53 | 54 | /** 55 | * @param int $index 56 | */ 57 | public function setIndex($index) 58 | { 59 | $this->index = $index; 60 | } 61 | 62 | /** 63 | * @param mixed $sort 64 | */ 65 | public function setSort($sort) 66 | { 67 | $this->sort = $sort; 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function getName() 74 | { 75 | return $this->name; 76 | } 77 | 78 | /** 79 | * @return int 80 | */ 81 | public function getIndex() 82 | { 83 | return $this->index; 84 | } 85 | 86 | /** 87 | * @return mixed 88 | */ 89 | public function getSort() 90 | { 91 | return $this->sort; 92 | } 93 | 94 | /** 95 | * 创建我的好友默认分类. 96 | * 97 | * @return Category 98 | */ 99 | public static function createMyFriendCategory() 100 | { 101 | return new static('我的好友', 0, 0); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Entity/Discuss.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class Discuss 14 | { 15 | /** 16 | * @var int 17 | */ 18 | protected $id; 19 | 20 | /** 21 | * @var string 22 | */ 23 | protected $name; 24 | 25 | /** 26 | * @return int 27 | */ 28 | public function getId() 29 | { 30 | return $this->id; 31 | } 32 | 33 | /** 34 | * @param int $id 35 | */ 36 | public function setId($id) 37 | { 38 | $this->id = $id; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getName() 45 | { 46 | return $this->name; 47 | } 48 | 49 | /** 50 | * @param string $name 51 | */ 52 | public function setName($name) 53 | { 54 | $this->name = $name; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Entity/DiscussDetail.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | use Slince\SmartQQ\EntityCollection; 14 | 15 | class DiscussDetail 16 | { 17 | /** 18 | * @var int 19 | */ 20 | protected $did; 21 | 22 | /** 23 | * @var string 24 | */ 25 | protected $name; 26 | 27 | /** 28 | * @var EntityCollection|DiscussMember[] 29 | */ 30 | protected $members; 31 | 32 | /** 33 | * @return int 34 | */ 35 | public function getDid() 36 | { 37 | return $this->did; 38 | } 39 | 40 | /** 41 | * @param int $did 42 | */ 43 | public function setDid($did) 44 | { 45 | $this->did = $did; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getName() 52 | { 53 | return $this->name; 54 | } 55 | 56 | /** 57 | * @param string $name 58 | */ 59 | public function setName($name) 60 | { 61 | $this->name = $name; 62 | } 63 | 64 | /** 65 | * 获取讨论组成员. 66 | * 67 | * @return EntityCollection|DiscussMember[] 68 | */ 69 | public function getMembers() 70 | { 71 | return $this->members; 72 | } 73 | 74 | /** 75 | * @param EntityCollection $members 76 | */ 77 | public function setMembers($members) 78 | { 79 | $this->members = $members; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Entity/DiscussMember.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class DiscussMember extends User 14 | { 15 | /** 16 | * @var string 17 | */ 18 | protected $nick; 19 | 20 | /** 21 | * @var int 22 | */ 23 | protected $clientType; 24 | 25 | /** 26 | * @var string 27 | */ 28 | protected $status; 29 | 30 | /** 31 | * 作用不明. 32 | * 33 | * @var int 34 | */ 35 | protected $ruin; 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getNick() 41 | { 42 | return $this->nick; 43 | } 44 | 45 | /** 46 | * @param string $nick 47 | */ 48 | public function setNick($nick) 49 | { 50 | $this->nick = $nick; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function getClientType() 57 | { 58 | return $this->clientType; 59 | } 60 | 61 | /** 62 | * @param int $clientType 63 | */ 64 | public function setClientType($clientType) 65 | { 66 | $this->clientType = $clientType; 67 | } 68 | 69 | /** 70 | * @return string 71 | */ 72 | public function getStatus() 73 | { 74 | return $this->status; 75 | } 76 | 77 | /** 78 | * @param string $status 79 | */ 80 | public function setStatus($status) 81 | { 82 | $this->status = $status; 83 | } 84 | 85 | /** 86 | * @return int 87 | */ 88 | public function getRuin() 89 | { 90 | return $this->ruin; 91 | } 92 | 93 | /** 94 | * @param int $ruin 95 | */ 96 | public function setRuin($ruin) 97 | { 98 | $this->ruin = $ruin; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Entity/Entity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | use Slince\SmartQQ\Client; 14 | 15 | class Entity 16 | { 17 | /** 18 | * @var Client 19 | */ 20 | protected $client; 21 | 22 | /** 23 | * @return Client 24 | */ 25 | public function getClient() 26 | { 27 | return $this->client; 28 | } 29 | 30 | /** 31 | * @param Client $client 32 | */ 33 | public function setClient($client) 34 | { 35 | $this->client = $client; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Entity/Friend.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class Friend extends User 14 | { 15 | /** 16 | * flag,作用不明. 17 | * 18 | * @var int 19 | */ 20 | protected $flag; 21 | 22 | /** 23 | * face,作用不明. 24 | * 25 | * @var int 26 | */ 27 | protected $face; 28 | 29 | /** 30 | * 昵称. 31 | * 32 | * @var string 33 | */ 34 | protected $nick; 35 | 36 | /** 37 | * 用户QQ号. 38 | * 39 | * @var int 40 | */ 41 | protected $qq; 42 | 43 | /** 44 | * 是否是VIP. 45 | * 46 | * @var bool 47 | */ 48 | protected $isVip; 49 | 50 | /** 51 | * VIP等级. 52 | * 53 | * @var int 54 | */ 55 | protected $vipLevel; 56 | 57 | /** 58 | * @var Category 59 | */ 60 | protected $category; 61 | 62 | /** 63 | * @var string 64 | */ 65 | protected $markName; 66 | 67 | /** 68 | * @param string $markName 69 | */ 70 | public function setMarkName($markName) 71 | { 72 | $this->markName = $markName; 73 | } 74 | 75 | /** 76 | * @return string 77 | */ 78 | public function getMarkName() 79 | { 80 | return $this->markName; 81 | } 82 | 83 | /** 84 | * @return int 85 | */ 86 | public function getQq() 87 | { 88 | return $this->qq; 89 | } 90 | 91 | /** 92 | * @return bool 93 | */ 94 | public function isVip() 95 | { 96 | return $this->isVip; 97 | } 98 | 99 | /** 100 | * @return int 101 | */ 102 | public function getVipLevel() 103 | { 104 | return $this->vipLevel; 105 | } 106 | 107 | /** 108 | * @return Category 109 | */ 110 | public function getCategory() 111 | { 112 | return $this->category; 113 | } 114 | 115 | /** 116 | * @return int 117 | */ 118 | public function getFlag() 119 | { 120 | return $this->flag; 121 | } 122 | 123 | /** 124 | * @return int 125 | */ 126 | public function getFace() 127 | { 128 | return $this->face; 129 | } 130 | 131 | /** 132 | * @param int $face 133 | */ 134 | public function setFace($face) 135 | { 136 | $this->face = $face; 137 | } 138 | 139 | /** 140 | * @return string 141 | */ 142 | public function getNick() 143 | { 144 | return $this->nick; 145 | } 146 | 147 | /** 148 | * @param string $nick 149 | */ 150 | public function setNick($nick) 151 | { 152 | $this->nick = $nick; 153 | } 154 | 155 | /** 156 | * @param int $qq 157 | */ 158 | public function setQq($qq) 159 | { 160 | $this->qq = $qq; 161 | } 162 | 163 | /** 164 | * @param int $flag 165 | */ 166 | public function setFlag($flag) 167 | { 168 | $this->flag = $flag; 169 | } 170 | 171 | /** 172 | * @param bool $isVip 173 | */ 174 | public function setIsVip($isVip) 175 | { 176 | $this->isVip = $isVip; 177 | } 178 | 179 | /** 180 | * @param int $vipLevel 181 | */ 182 | public function setVipLevel($vipLevel) 183 | { 184 | $this->vipLevel = $vipLevel; 185 | } 186 | 187 | /** 188 | * @param Category $category 189 | */ 190 | public function setCategory($category) 191 | { 192 | $this->category = $category; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/Entity/Group.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class Group 14 | { 15 | /** 16 | * gid. 17 | * 18 | * @var int 19 | */ 20 | protected $id; 21 | 22 | /** 23 | * @var string 24 | */ 25 | protected $name; 26 | 27 | /** 28 | * @var string 29 | */ 30 | protected $code; 31 | 32 | /** 33 | * @var int 34 | */ 35 | protected $flag; 36 | 37 | /** 38 | * @var string 39 | */ 40 | protected $markName; 41 | 42 | /** 43 | * @param int $id 44 | */ 45 | public function setId($id) 46 | { 47 | $this->id = $id; 48 | } 49 | 50 | /** 51 | * @return int 52 | */ 53 | public function getId() 54 | { 55 | return $this->id; 56 | } 57 | 58 | /** 59 | * @param string $name 60 | */ 61 | public function setName($name) 62 | { 63 | $this->name = $name; 64 | } 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getName() 70 | { 71 | return $this->name; 72 | } 73 | 74 | /** 75 | * @param string $code 76 | */ 77 | public function setCode($code) 78 | { 79 | $this->code = $code; 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getCode() 86 | { 87 | return $this->code; 88 | } 89 | 90 | /** 91 | * @param int $flag 92 | */ 93 | public function setFlag($flag) 94 | { 95 | $this->flag = $flag; 96 | } 97 | 98 | /** 99 | * @return int 100 | */ 101 | public function getFlag() 102 | { 103 | return $this->flag; 104 | } 105 | 106 | /** 107 | * @param string $markName 108 | */ 109 | public function setMarkName($markName) 110 | { 111 | $this->markName = $markName; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getMarkName() 118 | { 119 | return $this->markName; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Entity/GroupDetail.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | use Slince\SmartQQ\EntityCollection; 14 | 15 | class GroupDetail 16 | { 17 | /** 18 | * 组id. 19 | * 20 | * @var int 21 | */ 22 | protected $gid; 23 | 24 | /** 25 | * 群名称. 26 | * 27 | * @var string 28 | */ 29 | protected $name; 30 | 31 | /** 32 | * code. 33 | * 34 | * @var string 35 | */ 36 | protected $code; 37 | 38 | /** 39 | * 创建者的编号. 40 | * 41 | * @var int 42 | */ 43 | protected $owner; 44 | 45 | /** 46 | * 群等级. 47 | * 48 | * @var int 49 | */ 50 | protected $level; 51 | 52 | /** 53 | * 创建时间. 54 | * 55 | * @var int 56 | */ 57 | protected $createTime; 58 | 59 | /** 60 | * flag. 61 | * 62 | * @var int 63 | */ 64 | protected $flag; 65 | 66 | /** 67 | * 群公告. 68 | * 69 | * @var string 70 | */ 71 | protected $memo; 72 | 73 | /** 74 | * @var EntityCollection 75 | */ 76 | protected $members; 77 | 78 | /** 79 | * @return int 80 | */ 81 | public function getGid() 82 | { 83 | return $this->gid; 84 | } 85 | 86 | /** 87 | * @param int $gid 88 | */ 89 | public function setGid($gid) 90 | { 91 | $this->gid = $gid; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getName() 98 | { 99 | return $this->name; 100 | } 101 | 102 | /** 103 | * @param string $name 104 | */ 105 | public function setName($name) 106 | { 107 | $this->name = $name; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getCode() 114 | { 115 | return $this->code; 116 | } 117 | 118 | /** 119 | * @param string $code 120 | */ 121 | public function setCode($code) 122 | { 123 | $this->code = $code; 124 | } 125 | 126 | /** 127 | * @return int 128 | */ 129 | public function getOwner() 130 | { 131 | return $this->owner; 132 | } 133 | 134 | /** 135 | * @param int $owner 136 | */ 137 | public function setOwner($owner) 138 | { 139 | $this->owner = $owner; 140 | } 141 | 142 | /** 143 | * @return int 144 | */ 145 | public function getLevel() 146 | { 147 | return $this->level; 148 | } 149 | 150 | /** 151 | * @param int $level 152 | */ 153 | public function setLevel($level) 154 | { 155 | $this->level = $level; 156 | } 157 | 158 | /** 159 | * @return int 160 | */ 161 | public function getCreateTime() 162 | { 163 | return $this->createTime; 164 | } 165 | 166 | /** 167 | * @param int $createTime 168 | */ 169 | public function setCreateTime($createTime) 170 | { 171 | $this->createTime = $createTime; 172 | } 173 | 174 | /** 175 | * @return int 176 | */ 177 | public function getFlag() 178 | { 179 | return $this->flag; 180 | } 181 | 182 | /** 183 | * @param int $flag 184 | */ 185 | public function setFlag($flag) 186 | { 187 | $this->flag = $flag; 188 | } 189 | 190 | /** 191 | * @return string 192 | */ 193 | public function getMemo() 194 | { 195 | return $this->memo; 196 | } 197 | 198 | /** 199 | * @param string $memo 200 | */ 201 | public function setMemo($memo) 202 | { 203 | $this->memo = $memo; 204 | } 205 | 206 | /** 207 | * @return EntityCollection|GroupMember[] 208 | */ 209 | public function getMembers() 210 | { 211 | return $this->members; 212 | } 213 | 214 | /** 215 | * @param EntityCollection|GroupMember[] $members 216 | */ 217 | public function setMembers($members) 218 | { 219 | $this->members = $members; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Entity/GroupMember.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class GroupMember extends User 14 | { 15 | /** 16 | * flag,作用不明. 17 | * 18 | * @var int 19 | */ 20 | protected $flag; 21 | 22 | /** 23 | * 昵称. 24 | * 25 | * @var string 26 | */ 27 | protected $nick; 28 | 29 | /** 30 | * 省 31 | * 32 | * @var string 33 | */ 34 | protected $province; 35 | 36 | /** 37 | * 性别. 38 | * 39 | * @var string 40 | */ 41 | protected $gender; 42 | 43 | /** 44 | * 国家. 45 | * 46 | * @var string 47 | */ 48 | protected $country; 49 | 50 | /** 51 | * 城市 52 | * 53 | * @var string 54 | */ 55 | protected $city; 56 | 57 | /** 58 | * 群名片. 59 | * 60 | * @var string 61 | */ 62 | protected $card; 63 | 64 | /** 65 | * 是否是vip. 66 | * 67 | * @var string 68 | */ 69 | protected $isVip; 70 | 71 | /** 72 | * vip等级. 73 | * 74 | * @var int 75 | */ 76 | protected $vipLevel; 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getNick() 82 | { 83 | return $this->nick; 84 | } 85 | 86 | /** 87 | * @param string $nick 88 | */ 89 | public function setNick($nick) 90 | { 91 | $this->nick = $nick; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getProvince() 98 | { 99 | return $this->province; 100 | } 101 | 102 | /** 103 | * @param string $province 104 | */ 105 | public function setProvince($province) 106 | { 107 | $this->province = $province; 108 | } 109 | 110 | /** 111 | * @return string 112 | */ 113 | public function getGender() 114 | { 115 | return $this->gender; 116 | } 117 | 118 | /** 119 | * @param string $gender 120 | */ 121 | public function setGender($gender) 122 | { 123 | $this->gender = $gender; 124 | } 125 | 126 | /** 127 | * @return string 128 | */ 129 | public function getCountry() 130 | { 131 | return $this->country; 132 | } 133 | 134 | /** 135 | * @param string $country 136 | */ 137 | public function setCountry($country) 138 | { 139 | $this->country = $country; 140 | } 141 | 142 | /** 143 | * @return string 144 | */ 145 | public function getCity() 146 | { 147 | return $this->city; 148 | } 149 | 150 | /** 151 | * @param string $city 152 | */ 153 | public function setCity($city) 154 | { 155 | $this->city = $city; 156 | } 157 | 158 | /** 159 | * @return string 160 | */ 161 | public function getCard() 162 | { 163 | return $this->card; 164 | } 165 | 166 | /** 167 | * @param string $card 168 | */ 169 | public function setCard($card) 170 | { 171 | $this->card = $card; 172 | } 173 | 174 | /** 175 | * @return string 176 | */ 177 | public function isVip() 178 | { 179 | return $this->isVip; 180 | } 181 | 182 | /** 183 | * @param string $isVip 184 | */ 185 | public function setIsVip($isVip) 186 | { 187 | $this->isVip = $isVip; 188 | } 189 | 190 | /** 191 | * @return int 192 | */ 193 | public function getVipLevel() 194 | { 195 | return $this->vipLevel; 196 | } 197 | 198 | /** 199 | * @param int $vipLevel 200 | */ 201 | public function setVipLevel($vipLevel) 202 | { 203 | $this->vipLevel = $vipLevel; 204 | } 205 | 206 | /** 207 | * @param int $flag 208 | */ 209 | public function setFlag($flag) 210 | { 211 | $this->flag = $flag; 212 | } 213 | 214 | /** 215 | * @return int 216 | */ 217 | public function getFlag() 218 | { 219 | return $this->flag; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Entity/OnlineStatus.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class OnlineStatus 14 | { 15 | /** 16 | * @var string 17 | */ 18 | const ONLINE = 'online'; 19 | 20 | /** 21 | * @var string 22 | */ 23 | const OFFLINE = 'offline'; 24 | 25 | /** 26 | * 在线类型. 27 | * 28 | * @var int 29 | */ 30 | protected $clientType; 31 | 32 | /** 33 | * 用户编号. 34 | * 35 | * @var int 36 | */ 37 | protected $uin; 38 | 39 | /** 40 | * busy,away,online. 41 | * 42 | * @var string 43 | */ 44 | protected $status; 45 | 46 | /** 47 | * @return int 48 | */ 49 | public function getUin() 50 | { 51 | return $this->uin; 52 | } 53 | 54 | /** 55 | * @param int $uin 56 | */ 57 | public function setUin($uin) 58 | { 59 | $this->uin = $uin; 60 | } 61 | 62 | /** 63 | * 转换为字符串. 64 | */ 65 | public function __toString() 66 | { 67 | return $this->status; 68 | } 69 | 70 | /** 71 | * @param string $status 72 | */ 73 | public function setStatus($status) 74 | { 75 | $this->status = $status; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getStatus() 82 | { 83 | return $this->status; 84 | } 85 | 86 | /** 87 | * @return int 88 | */ 89 | public function getClientType() 90 | { 91 | return $this->clientType; 92 | } 93 | 94 | /** 95 | * @param int $clientType 96 | */ 97 | public function setClientType($clientType) 98 | { 99 | $this->clientType = $clientType; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Entity/Recent.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class Recent 14 | { 15 | /** 16 | * 好友会话. 17 | * 18 | * @var int 19 | */ 20 | const TYPE_FRIEND = 0; 21 | 22 | /** 23 | * 群会话. 24 | * 25 | * @var int 26 | */ 27 | const TYPE_GROUP = 1; 28 | 29 | /** 30 | * 讨论组会话. 31 | * 32 | * @var int 33 | */ 34 | const TYPE_DISCUSS = 2; 35 | 36 | /** 37 | * 会话类型. 38 | * 39 | * @var int 40 | */ 41 | protected $type; 42 | 43 | /** 44 | * 对方编号. 45 | * 46 | * @var int 47 | */ 48 | protected $uin; 49 | 50 | /** 51 | * @return int 52 | */ 53 | public function getType() 54 | { 55 | return $this->type; 56 | } 57 | 58 | /** 59 | * @param int $type 60 | */ 61 | public function setType($type) 62 | { 63 | $this->type = $type; 64 | } 65 | 66 | /** 67 | * @return int 68 | */ 69 | public function getUin() 70 | { 71 | return $this->uin; 72 | } 73 | 74 | /** 75 | * @param int $uin 76 | */ 77 | public function setUin($uin) 78 | { 79 | $this->uin = $uin; 80 | } 81 | 82 | /** 83 | * 是否是好友会话. 84 | * 85 | * @return bool 86 | */ 87 | public function isFriendType() 88 | { 89 | return $this->type == static::TYPE_FRIEND; 90 | } 91 | 92 | /** 93 | * 是否是群会话. 94 | * 95 | * @return bool 96 | */ 97 | public function isGroupType() 98 | { 99 | return $this->type == static::TYPE_GROUP; 100 | } 101 | 102 | /** 103 | * 是否是讨论组会话. 104 | * 105 | * @return bool 106 | */ 107 | public function isDiscussType() 108 | { 109 | return $this->type == static::TYPE_DISCUSS; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Entity/User.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Entity; 12 | 13 | class User 14 | { 15 | /** 16 | * 用户编号. 17 | * 18 | * @var int 19 | */ 20 | protected $uin; 21 | 22 | /** 23 | * @return int 24 | */ 25 | public function getUin() 26 | { 27 | return $this->uin; 28 | } 29 | 30 | /** 31 | * @param int $uin 32 | */ 33 | public function setUin($uin) 34 | { 35 | $this->uin = $uin; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/EntityCollection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ; 12 | 13 | use Cake\Collection\Collection; 14 | 15 | class EntityCollection extends Collection 16 | { 17 | /** 18 | * 根据实体属性筛选指定一个实体. 19 | * 20 | * @param string $attributeName 21 | * @param mixed $attributeValue 22 | * 23 | * @return object|null 24 | */ 25 | public function firstByAttribute($attributeName, $attributeValue) 26 | { 27 | $callback = function ($entity) use ($attributeName, $attributeValue) { 28 | $method = 'get'.ucfirst($attributeName); 29 | 30 | return $entity->$method() == $attributeValue; 31 | }; 32 | 33 | return $this->filter($callback)->first(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/EntityFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ; 12 | 13 | class EntityFactory 14 | { 15 | /** 16 | * 创建多个实体对象 17 | * 18 | * @param string $entityClass 19 | * @param array $dataArray 20 | * 21 | * @return array 22 | */ 23 | public static function createEntities($entityClass, $dataArray) 24 | { 25 | return array_map(function ($data) use ($entityClass) { 26 | return static::createEntity($entityClass, $data); 27 | }, $dataArray); 28 | } 29 | 30 | /** 31 | * 创建实体对象 32 | * 33 | * @param string $entityClass 34 | * @param array $data 35 | * 36 | * @return object 37 | */ 38 | public static function createEntity($entityClass, $data) 39 | { 40 | $entity = new $entityClass(); 41 | static::applyProperties($entity, $data); 42 | 43 | return $entity; 44 | } 45 | 46 | /** 47 | * 设置属性参数. 48 | * 49 | * @param object $entityInstance 50 | * @param array $data 51 | * 52 | * @return object 53 | */ 54 | protected static function applyProperties($entityInstance, $data) 55 | { 56 | foreach ($data as $property => $value) { 57 | $funcName = 'set'.ucfirst($property); 58 | if (method_exists($entityInstance, $funcName)) { 59 | $entityInstance->$funcName($value); 60 | } 61 | } 62 | 63 | return $entityInstance; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Exception/Code103ResponseException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Exception; 12 | 13 | class Code103ResponseException extends ResponseException 14 | { 15 | public function __construct($response = null) 16 | { 17 | $message = 'Please visit http://w.qq.com, confirm that you can send and receive messages and then exit'; 18 | parent::__construct(103, $response, $message); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Exception; 12 | 13 | class InvalidArgumentException extends \InvalidArgumentException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Exception/ResponseException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Exception; 12 | 13 | class ResponseException extends RuntimeException 14 | { 15 | protected $response; 16 | 17 | public function __construct($code, $response = null, $message = null) 18 | { 19 | if (!is_null($response)) { 20 | $this->response = $response; 21 | } 22 | $message = $message ?: 'The response is incorrect'; 23 | parent::__construct($message, $code); 24 | } 25 | 26 | /** 27 | * @param null $response 28 | */ 29 | public function setResponse($response) 30 | { 31 | $this->response = $response; 32 | } 33 | 34 | public function getResponse() 35 | { 36 | return $this->response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Exception; 12 | 13 | class RuntimeException extends \RuntimeException 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/Message/Content.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message; 12 | 13 | class Content 14 | { 15 | /** 16 | * 消息内容. 17 | * 18 | * @var string 19 | */ 20 | protected $content; 21 | 22 | /** 23 | * 消息内容字体. 24 | * 25 | * @var Font 26 | */ 27 | protected $font; 28 | 29 | /** 30 | * 表情. 31 | * 32 | * @var array 33 | */ 34 | protected static $faceTexts = [ 35 | '微笑', '撇嘴', '色', '发呆', '得意', '流泪', '害羞', '闭嘴', '睡', '大哭', '尴尬', '发怒', '调皮', '呲牙', '惊讶', '难过', '酷', '冷汗', '抓狂', '吐', 36 | '偷笑', '可爱', '白眼', '傲慢', '饥饿', '困', '惊恐', '流汗', '憨笑', '大兵', '奋斗', '咒骂', '疑问', '嘘', '晕', '折磨', '衰', '骷髅', '敲打', '再见', 37 | '擦汗', '抠鼻', '鼓掌', '糗大了', '坏笑', '左哼哼', '右哼哼', '哈欠', '鄙视', '委屈', '快哭了', '阴险', '亲亲', '吓', '可怜', '菜刀', '西瓜', '啤酒', '篮球', '乒乓', 38 | '咖啡', '饭', '猪头', '玫瑰', '凋谢', '示爱', '爱心', '心碎', '蛋糕', '闪电', '炸弹', '刀', '足球', '瓢虫', '便便', '月亮', '太阳', '礼物', '拥抱', '强', 39 | '弱', '握手', '胜利', '抱拳', '勾引', '拳头', '差劲', '爱你', 'NO', 'OK', '爱情', '飞吻', '跳跳', '发抖', '怄火', '转圈', '磕头', '回头', '跳绳', '挥手', 40 | '激动', '街舞', '献吻', '左太极', '右太极', '双喜', '鞭炮', '灯笼', '发财', 'K歌', '购物', '邮件', '帅', '喝彩', '祈祷', '爆筋', '棒棒糖', '喝奶', '下面', '香蕉', 41 | '飞机', '开车', '左车头', '车厢', '右车头', '多云', '下雨', '钞票', '熊猫', '灯泡', '风车', '闹钟', '打伞', '彩球', '钻戒', '沙发', '纸巾', '药', '手枪', '青蛙', 42 | ]; 43 | 44 | /** 45 | * 表情索引. 46 | * 47 | * @var array 48 | */ 49 | protected static $faceIndexs = [ 50 | 14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 51 | 0, 50, 51, 96, 53, 54, 73, 74, 75, 76, 77, 78, 55, 56, 52 | 57, 58, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 97, 98, 53 | 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 54 | 32, 113, 114, 115, 63, 64, 59, 33, 34, 116, 36, 37, 38, 91, 55 | 92, 93, 29, 117, 72, 45, 42, 39, 62, 46, 47, 71, 95, 118, 56 | 119, 120, 121, 122, 123, 124, 27, 21, 23, 25, 26, 125, 126, 127, 57 | 128, 129, 130, 131, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 58 | 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 59 | 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 60 | ]; 61 | 62 | /** 63 | * Content constructor. 64 | * 65 | * @param string $content 消息内容正文 66 | * @param Font $font 消息内容字体 67 | */ 68 | public function __construct($content, Font $font = null) 69 | { 70 | $this->content = $content; 71 | $this->font = $font; 72 | } 73 | 74 | /** 75 | * 魔术方法. 76 | * 77 | * @return string 78 | */ 79 | public function __toString() 80 | { 81 | return \GuzzleHttp\json_encode($this->parseFaceContents()); 82 | } 83 | 84 | protected function parseFaceContents() 85 | { 86 | $content = preg_replace('#\[([A-Z\x{4e00}-\x{9fa5}]{1,20}?)\]#ui', '@#[$1]@#', $this->getContent()); 87 | $chunks = explode('@#', $content); 88 | $contents = []; 89 | foreach ($chunks as $chunk) { 90 | if (!$chunk) { 91 | continue; 92 | } 93 | $faceText = trim($chunk, '[]'); 94 | if ($faceId = static::searchFaceId($faceText)) { 95 | $contents[] = ['face', $faceId]; 96 | } else { 97 | $contents[] = $chunk; 98 | } 99 | } 100 | $font = $this->getFont() ?: Font::createDefault(); 101 | $contents[] = [ 102 | 'font', 103 | $font->toArray(), 104 | ]; 105 | 106 | return $contents; 107 | } 108 | 109 | /** 110 | * 查找表情对应的ID. 111 | * 112 | * @param string $faceText 113 | * 114 | * @return int|null 115 | */ 116 | public static function searchFaceId($faceText) 117 | { 118 | if (false !== ($key = array_search($faceText, static::$faceTexts))) { 119 | return isset(static::$faceIndexs[$key]) ? static::$faceIndexs[$key] : null; 120 | } 121 | 122 | return null; 123 | } 124 | 125 | /** 126 | * 查找表情描述. 127 | * 128 | * @param int $faceId 129 | * 130 | * @return string|null 131 | */ 132 | public static function searchFaceText($faceId) 133 | { 134 | if (false !== ($key = array_search($faceId, static::$faceIndexs))) { 135 | return isset(static::$faceTexts[$key]) ? static::$faceTexts[$key] : null; 136 | } 137 | 138 | return null; 139 | } 140 | 141 | /** 142 | * @param string $content 143 | */ 144 | public function setContent($content) 145 | { 146 | $this->content = $content; 147 | } 148 | 149 | /** 150 | * @param Font $font 151 | */ 152 | public function setFont($font) 153 | { 154 | $this->font = $font; 155 | } 156 | 157 | /** 158 | * @return string 159 | */ 160 | public function getContent() 161 | { 162 | return $this->content; 163 | } 164 | 165 | /** 166 | * @return Font 167 | */ 168 | public function getFont() 169 | { 170 | return $this->font; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Message/Font.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message; 12 | 13 | class Font 14 | { 15 | /** 16 | * 名称. 17 | * 18 | * @var string 19 | */ 20 | protected $name; 21 | 22 | /** 23 | * 颜色. 24 | * 25 | * @var string 26 | */ 27 | protected $color; 28 | 29 | /** 30 | * 字号. 31 | * 32 | * @var string 33 | */ 34 | protected $size; 35 | 36 | /** 37 | * 风格,具体不知效果. 38 | * 39 | * @var string 40 | */ 41 | protected $style; 42 | 43 | public function __construct($name, $color, $size, array $style = []) 44 | { 45 | $this->name = $name; 46 | $this->color = $color; 47 | $this->size = $size; 48 | $this->style = $style; 49 | } 50 | 51 | /** 52 | * @param string $name 53 | */ 54 | public function setName($name) 55 | { 56 | $this->name = $name; 57 | } 58 | 59 | /** 60 | * @param string $color 61 | */ 62 | public function setColor($color) 63 | { 64 | $this->color = $color; 65 | } 66 | 67 | /** 68 | * @param string $size 69 | */ 70 | public function setSize($size) 71 | { 72 | $this->size = $size; 73 | } 74 | 75 | /** 76 | * @param string $style 77 | */ 78 | public function setStyle($style) 79 | { 80 | $this->style = $style; 81 | } 82 | 83 | /** 84 | * @return string 85 | */ 86 | public function getName() 87 | { 88 | return $this->name; 89 | } 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getColor() 95 | { 96 | return $this->color; 97 | } 98 | 99 | /** 100 | * @return string 101 | */ 102 | public function getSize() 103 | { 104 | return $this->size; 105 | } 106 | 107 | /** 108 | * @return string 109 | */ 110 | public function getStyle() 111 | { 112 | return $this->style; 113 | } 114 | 115 | /** 116 | * 转换成数组. 117 | * 118 | * @return array 119 | */ 120 | public function toArray() 121 | { 122 | return [ 123 | 'name' => $this->name, 124 | 'size' => $this->size, 125 | 'style' => $this->style, 126 | 'color' => $this->color, 127 | ]; 128 | } 129 | 130 | /** 131 | * 创建一个默认字体. 132 | * 133 | * @return static 134 | */ 135 | public static function createDefault() 136 | { 137 | return new static('微软雅黑', '000000', 10, [0, 0, 0]); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Message/Message.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message; 12 | 13 | class Message implements MessageInterface 14 | { 15 | /** 16 | * @var Content 17 | */ 18 | protected $content; 19 | 20 | /** 21 | * 消息id. 22 | * 23 | * @var int 24 | */ 25 | protected $msgId; 26 | 27 | /** 28 | * AbstractMessage constructor. 29 | * 30 | * @param Content $content 消息内容 31 | * @param int $msgId 消息id 32 | */ 33 | public function __construct(Content $content, $msgId) 34 | { 35 | $this->content = $content; 36 | $this->msgId = $msgId; 37 | } 38 | 39 | /** 40 | * @param Content $content 41 | */ 42 | public function setContent($content) 43 | { 44 | $this->content = $content; 45 | } 46 | 47 | /** 48 | * @param int $msgId 49 | */ 50 | public function setMsgId($msgId) 51 | { 52 | $this->msgId = $msgId; 53 | } 54 | 55 | /** 56 | * @return Content 57 | */ 58 | public function getContent() 59 | { 60 | return $this->content; 61 | } 62 | 63 | /** 64 | * @return int 65 | */ 66 | public function getMsgId() 67 | { 68 | return $this->msgId; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Message/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message; 12 | 13 | interface MessageInterface 14 | { 15 | /** 16 | * 消息类型,好友消息. 17 | * 18 | * @var string 19 | */ 20 | const TYPE_FRIEND = 'message'; 21 | 22 | /** 23 | * 消息类型,群消息. 24 | * 25 | * @var string 26 | */ 27 | const TYPE_GROUP = 'group_message'; 28 | 29 | /** 30 | * 消息类型,讨论组消息, 31 | * PS: discu接口返回该值,非故意拼错. 32 | * 33 | * @var string 34 | */ 35 | const TYPE_DISCUSS = 'discu_message'; 36 | 37 | /** 38 | * 获取消息内容. 39 | * 40 | * @return Content 41 | */ 42 | public function getContent(); 43 | } 44 | -------------------------------------------------------------------------------- /src/Message/Request/DiscussMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Request; 12 | 13 | use Slince\SmartQQ\Entity\Discuss; 14 | 15 | class DiscussMessage extends Message 16 | { 17 | /** 18 | * @var Discuss 19 | */ 20 | protected $discuss; 21 | 22 | public function __construct(Discuss $discuss, $content) 23 | { 24 | $this->discuss = $discuss; 25 | parent::__construct($content); 26 | } 27 | 28 | /** 29 | * @param Discuss $discuss 30 | */ 31 | public function setDiscuss($discuss) 32 | { 33 | $this->discuss = $discuss; 34 | } 35 | 36 | /** 37 | * @return Discuss 38 | */ 39 | public function getDiscuss() 40 | { 41 | return $this->discuss; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Message/Request/FriendMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Request; 12 | 13 | use Slince\SmartQQ\Entity\User; 14 | 15 | class FriendMessage extends Message 16 | { 17 | /** 18 | * @var User 19 | */ 20 | protected $user; 21 | 22 | public function __construct(User $user, $content) 23 | { 24 | $this->user = $user; 25 | parent::__construct($content); 26 | } 27 | 28 | /** 29 | * @return User 30 | */ 31 | public function getUser() 32 | { 33 | return $this->user; 34 | } 35 | 36 | /** 37 | * @param User $user 38 | */ 39 | public function setUser($user) 40 | { 41 | $this->user = $user; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Message/Request/GroupMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Request; 12 | 13 | use Slince\SmartQQ\Entity\Group; 14 | 15 | class GroupMessage extends Message 16 | { 17 | /** 18 | * @var Group 19 | */ 20 | protected $group; 21 | 22 | public function __construct(Group $group, $content) 23 | { 24 | $this->group = $group; 25 | parent::__construct($content); 26 | } 27 | 28 | /** 29 | * @return Group 30 | */ 31 | public function getGroup() 32 | { 33 | return $this->group; 34 | } 35 | 36 | /** 37 | * @param Group $group 38 | */ 39 | public function setGroup($group) 40 | { 41 | $this->group = $group; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Message/Request/Message.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Request; 12 | 13 | use Slince\SmartQQ\Message\Message as BaseMessage; 14 | use Slince\SmartQQ\Message\Content; 15 | use Slince\SmartQQ\Utils; 16 | 17 | class Message extends BaseMessage 18 | { 19 | /** 20 | * 具体不明. 21 | * 22 | * @var int 23 | */ 24 | protected $face = 522; 25 | 26 | public function __construct($content) 27 | { 28 | if (is_string($content)) { 29 | $content = new Content($content); 30 | } 31 | parent::__construct($content, Utils::makeMsgId()); 32 | } 33 | 34 | /** 35 | * @param int $face 36 | */ 37 | public function setFace($face) 38 | { 39 | $this->face = $face; 40 | } 41 | 42 | /** 43 | * @return int 44 | */ 45 | public function getFace() 46 | { 47 | return $this->face; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Message/Response/DiscussMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Response; 12 | 13 | use Slince\SmartQQ\Message\Content; 14 | 15 | class DiscussMessage extends Message 16 | { 17 | /** 18 | * 接收用户编号,也是QQ号. 19 | * 20 | * @var int 21 | */ 22 | protected $toUin; 23 | 24 | /** 25 | * 讨论组编号. 26 | * 27 | * @var int 28 | */ 29 | protected $fromUin; 30 | 31 | /** 32 | * 讨论组编号,同fromUin 33 | * PS: 对应影响中的did. 34 | * 35 | * @var int 36 | */ 37 | protected $discussId; 38 | 39 | /** 40 | * 发信用户编号,非QQ号. 41 | * 42 | * @var int 43 | */ 44 | protected $sendUin; 45 | 46 | public function __construct($toUin, $fromUin, $discussId, $sendUin, Content $content, $time, $msgId = 0, $msgType = 0) 47 | { 48 | $this->toUin = $toUin; 49 | $this->fromUin = $fromUin; 50 | $this->discussId = $discussId; 51 | $this->sendUin = $sendUin; 52 | parent::__construct($content, $time, $msgId, $msgType); 53 | } 54 | 55 | /** 56 | * @param int $toUin 57 | */ 58 | public function setToUin($toUin) 59 | { 60 | $this->toUin = $toUin; 61 | } 62 | 63 | /** 64 | * @param int $fromUin 65 | */ 66 | public function setFromUin($fromUin) 67 | { 68 | $this->fromUin = $fromUin; 69 | } 70 | 71 | /** 72 | * @param int $discussId 73 | */ 74 | public function setDiscussId($discussId) 75 | { 76 | $this->discussId = $discussId; 77 | } 78 | 79 | /** 80 | * @param int $sendUin 81 | */ 82 | public function setSendUin($sendUin) 83 | { 84 | $this->sendUin = $sendUin; 85 | } 86 | 87 | /** 88 | * @return int 89 | */ 90 | public function getToUin() 91 | { 92 | return $this->toUin; 93 | } 94 | 95 | /** 96 | * @return int 97 | */ 98 | public function getFromUin() 99 | { 100 | return $this->fromUin; 101 | } 102 | 103 | /** 104 | * @return int 105 | */ 106 | public function getDiscussId() 107 | { 108 | return $this->discussId; 109 | } 110 | 111 | /** 112 | * @return int 113 | */ 114 | public function getSendUin() 115 | { 116 | return $this->sendUin; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Message/Response/FriendMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Response; 12 | 13 | use Slince\SmartQQ\Message\Content; 14 | 15 | class FriendMessage extends Message 16 | { 17 | /** 18 | * 接收用户编号,也是QQ号. 19 | * 20 | * @var int 21 | */ 22 | protected $toUin; 23 | 24 | /** 25 | * 发信用户编号,非QQ号. 26 | * 27 | * @var int 28 | */ 29 | protected $fromUin; 30 | 31 | public function __construct($toUin, $fromUin, Content $content, $time, $msgId = 0, $msgType = 0) 32 | { 33 | $this->toUin = $toUin; 34 | $this->fromUin = $fromUin; 35 | parent::__construct($content, $time, $msgId, $msgType); 36 | } 37 | 38 | /** 39 | * @param int $toUin 40 | */ 41 | public function setToUin($toUin) 42 | { 43 | $this->toUin = $toUin; 44 | } 45 | 46 | /** 47 | * @param int $fromUin 48 | */ 49 | public function setFromUin($fromUin) 50 | { 51 | $this->fromUin = $fromUin; 52 | } 53 | 54 | /** 55 | * @return int 56 | */ 57 | public function getToUin() 58 | { 59 | return $this->toUin; 60 | } 61 | 62 | /** 63 | * @return int 64 | */ 65 | public function getFromUin() 66 | { 67 | return $this->fromUin; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Message/Response/GroupMessage.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Response; 12 | 13 | use Slince\SmartQQ\Message\Content; 14 | 15 | class GroupMessage extends Message 16 | { 17 | /** 18 | * 接收用户编号,也是QQ号. 19 | * 20 | * @var int 21 | */ 22 | protected $toUin; 23 | 24 | /** 25 | * 群编号,非群号. 26 | * 27 | * @var int 28 | */ 29 | protected $fromUin; 30 | 31 | /** 32 | * 群编号,非群号,同fromUin. 33 | * 34 | * @var int 35 | */ 36 | protected $groupCode; 37 | 38 | /** 39 | * 发信用户编号,非QQ号. 40 | * 41 | * @var int 42 | */ 43 | protected $sendUin; 44 | 45 | public function __construct($toUin, $fromUin, $groupCode, $sendUin, Content $content, $time, $msgId = 0, $msgType = 0) 46 | { 47 | $this->toUin = $toUin; 48 | $this->fromUin = $fromUin; 49 | $this->groupCode = $groupCode; 50 | $this->sendUin = $sendUin; 51 | parent::__construct($content, $time, $msgId, $msgType); 52 | } 53 | 54 | /** 55 | * @param int $toUin 56 | */ 57 | public function setToUin($toUin) 58 | { 59 | $this->toUin = $toUin; 60 | } 61 | 62 | /** 63 | * @param int $fromUin 64 | */ 65 | public function setFromUin($fromUin) 66 | { 67 | $this->fromUin = $fromUin; 68 | } 69 | 70 | /** 71 | * @param int $groupCode 72 | */ 73 | public function setGroupCode($groupCode) 74 | { 75 | $this->groupCode = $groupCode; 76 | } 77 | 78 | /** 79 | * @param int $sendUin 80 | */ 81 | public function setSendUin($sendUin) 82 | { 83 | $this->sendUin = $sendUin; 84 | } 85 | 86 | /** 87 | * @return int 88 | */ 89 | public function getToUin() 90 | { 91 | return $this->toUin; 92 | } 93 | 94 | /** 95 | * @return int 96 | */ 97 | public function getFromUin() 98 | { 99 | return $this->fromUin; 100 | } 101 | 102 | /** 103 | * @return int 104 | */ 105 | public function getGroupCode() 106 | { 107 | return $this->groupCode; 108 | } 109 | 110 | /** 111 | * @return int 112 | */ 113 | public function getSendUin() 114 | { 115 | return $this->sendUin; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Message/Response/Message.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Message\Response; 12 | 13 | use Slince\SmartQQ\Message\Message as BaseMessage; 14 | use Slince\SmartQQ\Message\Content; 15 | 16 | class Message extends BaseMessage 17 | { 18 | /** 19 | * 发送时间,时间戳. 20 | * 21 | * @var int 22 | */ 23 | protected $time; 24 | 25 | /** 26 | * 消息类型,作用不明. 27 | * 28 | * @var int 29 | */ 30 | protected $msgType = 0; 31 | 32 | /** 33 | * Message constructor. 34 | * 35 | * @param Content $content 消息内容 36 | * @param int $time 发信时间 37 | * @param int $msgId 消息id 38 | * @param int $msgType 消息类型 39 | */ 40 | public function __construct(Content $content, $time, $msgId, $msgType) 41 | { 42 | $this->time = $time; 43 | $this->msgType = $msgType; 44 | parent::__construct($content, $msgId); 45 | } 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function __toString() 51 | { 52 | return (string) $this->content->getContent(); 53 | } 54 | 55 | /** 56 | * @param int $msgType 57 | */ 58 | public function setMsgType($msgType) 59 | { 60 | $this->msgType = $msgType; 61 | } 62 | 63 | /** 64 | * @param int $time 65 | */ 66 | public function setTime($time) 67 | { 68 | $this->time = $time; 69 | } 70 | 71 | /** 72 | * @return int 73 | */ 74 | public function getMsgType() 75 | { 76 | return $this->msgType; 77 | } 78 | 79 | /** 80 | * @return int 81 | */ 82 | public function getTime() 83 | { 84 | return $this->time; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/MessageHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Slince\SmartQQ; 13 | 14 | use GuzzleHttp\Exception\ConnectException; 15 | use Slince\EventDispatcher\DispatcherInterface; 16 | use Slince\EventDispatcher\Event; 17 | use Slince\SmartQQ\Exception\InvalidArgumentException; 18 | use Slince\SmartQQ\Exception\ResponseException; 19 | use Slince\SmartQQ\Exception\RuntimeException; 20 | 21 | class MessageHandler 22 | { 23 | /** 24 | * 事件名,当收到消息时触发. 25 | * 26 | * @var string 27 | */ 28 | const EVENT_MESSAGE = 'message'; 29 | 30 | /** 31 | * @var Client 32 | */ 33 | protected $client; 34 | 35 | /** 36 | * @var DispatcherInterface 37 | */ 38 | protected $eventDispatcher; 39 | 40 | /** 41 | * @var boolean 42 | */ 43 | protected $stop = false; 44 | 45 | /** 46 | * 可以被忽略的状态码. 47 | * 48 | * @var array 49 | */ 50 | protected static $ignoredCodes = [ 51 | 0, 100003, 100100, 100012, 52 | ]; 53 | 54 | public function __construct(Client $client) 55 | { 56 | $this->client = $client; 57 | $this->eventDispatcher = $client->getEventDispatcher(); 58 | } 59 | 60 | /** 61 | * 绑定消息处理回调. 62 | * 63 | * ```php 64 | * $handler->onMessage(function(Slince\SmartQQ\Message\Response\Message $message){ 65 | * //... 66 | * }); 67 | * ``` 68 | * 69 | * @param callable $handler 70 | */ 71 | public function onMessage($handler) 72 | { 73 | if (!is_callable($handler)) { 74 | throw new InvalidArgumentException('Message handler should be callable.'); 75 | } 76 | $this->eventDispatcher->addListener(static::EVENT_MESSAGE, function (Event $event) use ($handler) { 77 | $handler($event->getArgument('message')); 78 | }); 79 | } 80 | 81 | /** 82 | * 开始监听客户端消息. 83 | */ 84 | public function listen() 85 | { 86 | while (!$this->stop) { 87 | try { 88 | $messages = $this->client->pollMessages(); 89 | foreach ($messages as $message) { 90 | // 收到消息时触发事件 91 | $event = new Event(static::EVENT_MESSAGE, null, [ 92 | 'message' => $message, 93 | ]); 94 | $this->eventDispatcher->dispatch($event); 95 | } 96 | } catch (ResponseException $exception) { 97 | if (in_array($exception->getCode(), static::$ignoredCodes)) { 98 | $this->testLogin(); 99 | usleep(2000000); 100 | } else { 101 | throw $exception; // 其它状态码接着抛出异常 102 | } 103 | } catch (ConnectException $exception) { 104 | // 超时请求忽略 105 | usleep(2000000); 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * 终止下一次消息监听. 112 | * 113 | * ```php 114 | * $handler->onMessage(function($message) use ($handler){ 115 | * var_dump($message); 116 | * $handler->stop(); //停止 117 | * }); 118 | * ``` 119 | */ 120 | public function stop() 121 | { 122 | $this->stop = true; 123 | } 124 | 125 | /** 126 | * 测试登录. 127 | */ 128 | protected function testLogin() 129 | { 130 | try { 131 | $this->client->getFriendsOnlineStatus(); 132 | } catch (\Exception $exception) { 133 | //登录凭证可能失效 134 | throw new RuntimeException('The credential may be expired, please login again.', $exception->getCode()); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Request/GetCurrentUserRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Entity\Profile; 15 | 16 | class GetCurrentUserRequest extends Request 17 | { 18 | protected $uri = 'http://s.web2.qq.com/api/get_self_info2?t=0.1'; 19 | 20 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 21 | 22 | /** 23 | * 解析响应数据. 24 | * 25 | * @param Response $response 26 | * 27 | * @return Profile 28 | */ 29 | public static function parseResponse(Response $response) 30 | { 31 | return GetFriendDetailRequest::parseResponse($response); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Request/GetDiscussDetailRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Cake\Collection\Collection; 14 | use GuzzleHttp\Psr7\Response; 15 | use Slince\SmartQQ\Credential; 16 | use Slince\SmartQQ\Entity\Discuss; 17 | use Slince\SmartQQ\Entity\DiscussDetail; 18 | use Slince\SmartQQ\Entity\DiscussMember; 19 | use Slince\SmartQQ\EntityCollection; 20 | use Slince\SmartQQ\EntityFactory; 21 | use Slince\SmartQQ\Exception\ResponseException; 22 | 23 | class GetDiscussDetailRequest extends Request 24 | { 25 | protected $uri = 'http://d1.web2.qq.com/channel/get_discu_info?did={discussId}&psessionid={psessionid}&vfwebqq={vfwebqq}&clientid=53999199&t=0.1'; 26 | 27 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 28 | 29 | public function __construct(Discuss $discuss, Credential $credential) 30 | { 31 | $this->setTokens([ 32 | 'discussId' => $discuss->getId(), 33 | 'psessionid' => $credential->getPSessionId(), 34 | 'vfwebqq' => $credential->getVfWebQQ(), 35 | ]); 36 | } 37 | 38 | /** 39 | * 解析响应数据. 40 | * 41 | * @param Response $response 42 | * 43 | * @return DiscussDetail 44 | */ 45 | public static function parseResponse(Response $response) 46 | { 47 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 48 | if ($jsonData && 0 == $jsonData['retcode']) { 49 | //成员在线状态 50 | $statuses = (new Collection($jsonData['result']['mem_status']))->combine('uin', function ($entity) { 51 | return $entity; 52 | })->toArray(); 53 | //成员基本信息 54 | $ruins = (new Collection($jsonData['result']['info']['mem_list']))->combine('mem_uin', 'ruin') 55 | ->toArray(); 56 | //讨论组基本详情 57 | $discussData = $jsonData['result']['info']; 58 | $discussDetailData = [ 59 | 'did' => $discussData['did'], 60 | 'name' => $discussData['discu_name'], 61 | ]; 62 | $members = []; 63 | foreach ($jsonData['result']['mem_info'] as $memberData) { 64 | $uin = $memberData['uin']; 65 | $members[] = EntityFactory::createEntity(DiscussMember::class, [ 66 | 'uin' => $uin, 67 | 'nick' => $memberData['nick'], 68 | 'clientType' => isset($statuses[$uin]) ? $statuses[$uin]['client_type'] : null, 69 | 'status' => isset($statuses[$uin]) ? $statuses[$uin]['status'] : null, 70 | 'ruin' => isset($ruins[$uin]) ? $ruins[$uin] : null, 71 | ]); 72 | } 73 | $discussDetailData['members'] = new EntityCollection($members); 74 | 75 | return EntityFactory::createEntity(DiscussDetail::class, $discussDetailData); 76 | } 77 | throw new ResponseException($jsonData['retcode'], $response); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Request/GetDiscussesRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Entity\Discuss; 16 | use Slince\SmartQQ\EntityCollection; 17 | use Slince\SmartQQ\EntityFactory; 18 | use Slince\SmartQQ\Exception\ResponseException; 19 | 20 | class GetDiscussesRequest extends Request 21 | { 22 | protected $uri = 'http://s.web2.qq.com/api/get_discus_list?clientid=53999199&psessionid={psessionid}&vfwebqq={vfwebqq}&t=0.1'; 23 | 24 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 25 | 26 | public function __construct(Credential $credential) 27 | { 28 | $this->setTokens([ 29 | 'psessionid' => $credential->getPSessionId(), 30 | 'vfwebqq' => $credential->getVfWebQQ(), 31 | ]); 32 | } 33 | 34 | /** 35 | * 解析响应数据. 36 | * 37 | * @param Response $response 38 | * 39 | * @return EntityCollection 40 | */ 41 | public static function parseResponse(Response $response) 42 | { 43 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 44 | if ($jsonData && 0 == $jsonData['retcode']) { 45 | $discusses = []; 46 | foreach ($jsonData['result']['dnamelist'] as $discussData) { 47 | $discusses[] = EntityFactory::createEntity(Discuss::class, [ 48 | 'id' => $discussData['did'], 49 | 'name' => $discussData['name'], 50 | ]); 51 | } 52 | 53 | return new EntityCollection($discusses); 54 | } 55 | throw new ResponseException($jsonData['retcode'], $response); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/GetFriendDetailRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Entity\Birthday; 16 | use Slince\SmartQQ\Entity\Friend; 17 | use Slince\SmartQQ\Entity\Profile; 18 | use Slince\SmartQQ\EntityFactory; 19 | use Slince\SmartQQ\Exception\ResponseException; 20 | 21 | class GetFriendDetailRequest extends Request 22 | { 23 | protected $uri = 'http://s.web2.qq.com/api/get_friend_info2?tuin={uin}&vfwebqq={vfwebqq}&clientid=53999199&psessionid={psessionid}&t=0.1'; 24 | 25 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 26 | 27 | public function __construct(Friend $friend, Credential $credential) 28 | { 29 | $this->setTokens([ 30 | 'uin' => $friend->getUin(), 31 | 'vfwebqq' => $credential->getVfWebQQ(), 32 | 'psessionid' => $credential->getPSessionId(), 33 | ]); 34 | } 35 | 36 | /** 37 | * 解析响应数据. 38 | * 39 | * @param Response $response 40 | * 41 | * @return Profile 42 | */ 43 | public static function parseResponse(Response $response) 44 | { 45 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 46 | if ($jsonData && 0 == $jsonData['retcode']) { 47 | $profileData = $jsonData['result']; 48 | //注意获取好友详情接口并不返回account和lnick 49 | return EntityFactory::createEntity(Profile::class, [ 50 | 'account' => isset($profileData['account']) ? $profileData['account'] : null, 51 | 'lnick' => isset($profileData['lnick']) ? $profileData['lnick'] : null, 52 | 'face' => $profileData['face'], 53 | 'birthday' => Birthday::createFromArray($profileData['birthday']), 54 | 'occupation' => $profileData['occupation'], 55 | 'phone' => $profileData['phone'], 56 | 'allow' => $profileData['allow'], 57 | 'college' => $profileData['college'], 58 | 'uin' => $profileData['uin'], 59 | 'constel' => $profileData['constel'], 60 | 'blood' => $profileData['blood'], 61 | 'homepage' => $profileData['homepage'], 62 | 'stat' => isset($profileData['stat']) ? $profileData['stat'] : null, 63 | 'vipInfo' => $profileData['vip_info'], 64 | 'country' => $profileData['country'], 65 | 'city' => $profileData['city'], 66 | 'personal' => $profileData['personal'], 67 | 'nick' => $profileData['nick'], 68 | 'shengXiao' => $profileData['shengxiao'], 69 | 'email' => $profileData['email'], 70 | 'province' => $profileData['province'], 71 | 'gender' => $profileData['gender'], 72 | 'mobile' => $profileData['mobile'], 73 | ]); 74 | } 75 | throw new ResponseException($jsonData['retcode'], $response); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Request/GetFriendsOnlineStatusRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Slince\SmartQQ\Credential; 14 | use Slince\SmartQQ\Entity\OnlineStatus; 15 | use Slince\SmartQQ\EntityCollection; 16 | use Slince\SmartQQ\EntityFactory; 17 | use GuzzleHttp\Psr7\Response; 18 | use Slince\SmartQQ\Exception\ResponseException; 19 | 20 | class GetFriendsOnlineStatusRequest extends Request 21 | { 22 | protected $uri = 'http://d1.web2.qq.com/channel/get_online_buddies2?vfwebqq={vfwebqq}&clientid=53999199&psessionid={psessionid}&t=0.1'; 23 | 24 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 25 | 26 | public function __construct(Credential $credential) 27 | { 28 | $this->setTokens([ 29 | 'vfwebqq' => $credential->getVfWebQQ(), 30 | 'psessionid' => $credential->getPSessionId(), 31 | ]); 32 | } 33 | 34 | /** 35 | * 解析响应数据. 36 | * 37 | * @param Response $response 38 | * 39 | * @return EntityCollection 40 | */ 41 | public static function parseResponse(Response $response) 42 | { 43 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 44 | if ($jsonData && 0 == $jsonData['retcode']) { 45 | $onlineStatuses = []; 46 | foreach ($jsonData['result'] as $status) { 47 | $onlineStatuses[] = EntityFactory::createEntity(OnlineStatus::class, [ 48 | 'clientType' => $status['client_type'], 49 | 'uin' => $status['uin'], 50 | 'status' => $status['status'], 51 | ]); 52 | } 53 | 54 | return new EntityCollection($onlineStatuses); 55 | } 56 | throw new ResponseException($jsonData['retcode'], $response); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Request/GetFriendsRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Cake\Collection\Collection; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Entity\Category; 16 | use Slince\SmartQQ\Entity\Friend; 17 | use Slince\SmartQQ\EntityCollection; 18 | use Slince\SmartQQ\EntityFactory; 19 | use Slince\SmartQQ\Exception\ResponseException; 20 | use GuzzleHttp\Psr7\Response; 21 | use Slince\SmartQQ\Utils; 22 | 23 | class GetFriendsRequest extends Request 24 | { 25 | protected $uri = 'http://s.web2.qq.com/api/get_user_friends2'; 26 | 27 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 28 | 29 | protected $method = RequestInterface::REQUEST_METHOD_POST; 30 | 31 | public function __construct(Credential $credential) 32 | { 33 | $this->setParameter('r', \GuzzleHttp\json_encode([ 34 | 'vfwebqq' => $credential->getVfWebQQ(), 35 | 'hash' => Utils::hash($credential->getUin(), $credential->getPtWebQQ()), 36 | ])); 37 | } 38 | 39 | /** 40 | * 解析响应数据. 41 | * 42 | * @param Response $response 43 | * 44 | * @return EntityCollection 45 | */ 46 | public static function parseResponse(Response $response) 47 | { 48 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 49 | //有时候获取好友接口retcode=100003时也可以获取数据,但数据不完整故当做无效返回 50 | if ($jsonData && 0 == $jsonData['retcode']) { 51 | //好友基本信息 52 | $friendDatas = (new Collection($jsonData['result']['friends']))->combine('uin', function ($entity) { 53 | return $entity; 54 | })->toArray(); 55 | //markNames 56 | $markNames = (new Collection($jsonData['result']['marknames']))->combine('uin', function ($entity) { 57 | return $entity; 58 | })->toArray(); 59 | //分类 60 | $categories = (new Collection($jsonData['result']['categories']))->combine('index', function ($entity) { 61 | return $entity; 62 | })->toArray(); 63 | //vip信息 64 | $vipInfos = (new Collection($jsonData['result']['vipinfo']))->combine('u', function ($entity) { 65 | return $entity; 66 | })->toArray(); 67 | $friends = []; 68 | foreach ($jsonData['result']['info'] as $friendData) { 69 | $uin = $friendData['uin']; 70 | $friend = [ 71 | 'uin' => $friendData['uin'], 72 | 'flag' => $friendData['flag'], 73 | 'face' => $friendData['face'], 74 | 'nick' => $friendData['nick'], 75 | 'markName' => isset($markNames[$uin]) ? $markNames[$uin]['markname'] : null, 76 | 'isVip' => isset($vipInfos[$uin]) ? 1 == $vipInfos[$uin]['is_vip'] : false, 77 | 'vipLevel' => isset($vipInfos[$uin]) ? $vipInfos[$uin]['vip_level'] : 0, 78 | ]; 79 | $category = null; 80 | if (isset($friendDatas[$uin])) { 81 | $categoryIndex = $friendDatas[$uin]['categories']; 82 | if (0 == $categoryIndex) { 83 | $category = Category::createMyFriendCategory(); 84 | } else { 85 | $category = new Category( 86 | $categories[$categoryIndex]['name'], 87 | $categories[$categoryIndex]['index'], 88 | $categories[$categoryIndex]['sort'] 89 | ); 90 | } 91 | } 92 | $friend['category'] = $category; 93 | $friends[] = EntityFactory::createEntity(Friend::class, $friend); 94 | } 95 | 96 | return new EntityCollection($friends); 97 | } 98 | throw new ResponseException($jsonData['retcode'], $response); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Request/GetGroupDetailRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Cake\Collection\Collection; 14 | use GuzzleHttp\Psr7\Response; 15 | use Slince\SmartQQ\Credential; 16 | use Slince\SmartQQ\Entity\GroupDetail; 17 | use Slince\SmartQQ\Entity\GroupMember; 18 | use Slince\SmartQQ\EntityCollection; 19 | use Slince\SmartQQ\EntityFactory; 20 | use Slince\SmartQQ\Exception\ResponseException; 21 | use Slince\SmartQQ\Entity\Group; 22 | 23 | class GetGroupDetailRequest extends Request 24 | { 25 | protected $uri = 'http://s.web2.qq.com/api/get_group_info_ext2?gcode={groupcode}&vfwebqq={vfwebqq}&t=0.1'; 26 | 27 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 28 | 29 | public function __construct(Group $group, Credential $credential) 30 | { 31 | $this->setTokens([ 32 | 'groupcode' => $group->getCode(), 33 | 'vfwebqq' => $credential->getVfWebQQ(), 34 | ]); 35 | } 36 | 37 | /** 38 | * 解析响应数据. 39 | * 40 | * @param Response $response 41 | * 42 | * @return GroupDetail 43 | */ 44 | public static function parseResponse(Response $response) 45 | { 46 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 47 | if ($jsonData && 0 == $jsonData['retcode']) { 48 | //群成员的vip信息 49 | $vipInfos = (new Collection($jsonData['result']['vipinfo']))->combine('u', function ($entity) { 50 | return $entity; 51 | })->toArray(); 52 | //群成员的名片信息, 如果全部成员都没有设置群名片则会不存在这个字段 53 | $cards = isset($jsonData['result']['cards']) ? (new Collection($jsonData['result']['cards'])) 54 | ->combine('muin', 'card') 55 | ->toArray() : []; 56 | //群成员的简要信息 57 | $flags = (new Collection($jsonData['result']['ginfo']['members']))->combine('muin', 'mflag') 58 | ->toArray(); 59 | //群基本详细信息 60 | $groupData = $jsonData['result']['ginfo']; 61 | $groupDetailData = [ 62 | 'gid' => $groupData['gid'], 63 | 'name' => $groupData['name'], 64 | 'code' => $groupData['code'], 65 | 'owner' => $groupData['owner'], 66 | 'level' => $groupData['level'], 67 | 'createTime' => $groupData['createtime'], 68 | 'flag' => $groupData['flag'], 69 | 'memo' => $groupData['memo'], 70 | 'members' => null, 71 | ]; 72 | $members = []; 73 | foreach ($jsonData['result']['minfo'] as $memberData) { 74 | $uin = $memberData['uin']; 75 | $member = EntityFactory::createEntity(GroupMember::class, [ 76 | 'flag' => isset($flags[$uin]) ? $flags[$uin] : null, 77 | 'nick' => $memberData['nick'], 78 | 'province' => $memberData['province'], 79 | 'gender' => $memberData['gender'], 80 | 'uin' => $uin, 81 | 'country' => $memberData['country'], 82 | 'city' => $memberData['city'], 83 | 'card' => isset($cards[$uin]) ? $cards[$uin] : null, 84 | 'isVip' => isset($vipInfos[$uin]) ? 1 == $vipInfos[$uin]['is_vip'] : false, 85 | 'vipLevel' => isset($vipInfos[$uin]) ? $vipInfos[$uin]['vip_level'] : 0, 86 | ]); 87 | $members[] = $member; 88 | } 89 | $groupDetailData['members'] = new EntityCollection($members); 90 | 91 | return EntityFactory::createEntity(GroupDetail::class, $groupDetailData); 92 | } 93 | throw new ResponseException($jsonData['retcode'], $response); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Request/GetGroupsRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Cake\Collection\Collection; 14 | use GuzzleHttp\Psr7\Response; 15 | use Slince\SmartQQ\Entity\Group; 16 | use Slince\SmartQQ\EntityCollection; 17 | use Slince\SmartQQ\Credential; 18 | use Slince\SmartQQ\EntityFactory; 19 | use Slince\SmartQQ\Exception\ResponseException; 20 | use Slince\SmartQQ\Utils; 21 | 22 | class GetGroupsRequest extends Request 23 | { 24 | protected $uri = 'http://s.web2.qq.com/api/get_group_name_list_mask2'; 25 | 26 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 27 | 28 | protected $method = RequestInterface::REQUEST_METHOD_POST; 29 | 30 | public function __construct(Credential $credential) 31 | { 32 | $this->setParameter('r', \GuzzleHttp\json_encode([ 33 | 'vfwebqq' => $credential->getVfWebQQ(), 34 | 'hash' => Utils::hash($credential->getUin(), $credential->getPtWebQQ()), 35 | ])); 36 | } 37 | 38 | /** 39 | * 解析响应数据. 40 | * 41 | * @param Response $response 42 | * 43 | * @return EntityCollection 44 | */ 45 | public static function parseResponse(Response $response) 46 | { 47 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 48 | if ($jsonData && 0 == $jsonData['retcode']) { 49 | $markNames = (new Collection($jsonData['result']['gmarklist']))->combine('uin', 'markname') 50 | ->toArray(); 51 | $groups = []; 52 | foreach ($jsonData['result']['gnamelist'] as $groupData) { 53 | $groupId = $groupData['gid']; 54 | $groupData['id'] = $groupData['gid']; 55 | $groupData['markName'] = isset($markNames[$groupId]) ? $markNames[$groupId] : ''; 56 | $group = EntityFactory::createEntity(Group::class, $groupData); 57 | $groups[] = $group; 58 | } 59 | 60 | return new EntityCollection($groups); 61 | } 62 | throw new ResponseException($jsonData['retcode'], $response); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Request/GetLnickRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Cake\Collection\Collection; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Entity\Friend; 16 | use GuzzleHttp\Psr7\Response; 17 | use Slince\SmartQQ\Exception\ResponseException; 18 | 19 | class GetLnickRequest extends Request 20 | { 21 | protected $uri = 'http://s.web2.qq.com/api/get_single_long_nick2?tuin={uin}&vfwebqq={vfwebqq}&t=0.1'; 22 | 23 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 24 | 25 | public function __construct(Friend $friend, Credential $credential) 26 | { 27 | $this->setTokens([ 28 | 'uin' => $friend->getUin(), 29 | 'vfwebqq' => $credential->getVfWebQQ(), 30 | ]); 31 | } 32 | 33 | /** 34 | * 解析响应数据. 35 | * 36 | * @param Response $response 37 | * @param Friend $friend 38 | * 39 | * @return int 40 | */ 41 | public static function parseResponse(Response $response, Friend $friend) 42 | { 43 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 44 | if ($jsonData && 0 == $jsonData['retcode']) { 45 | $info = (new Collection($jsonData['result']))->filter(function ($info) use ($friend) { 46 | return $info['uin'] == $friend->getUin(); 47 | })->first(); 48 | 49 | return $info ? $info['lnick'] : null; 50 | } 51 | throw new ResponseException($jsonData['retcode'], $response); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Request/GetPtWebQQRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | class GetPtWebQQRequest extends Request 14 | { 15 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 16 | } 17 | -------------------------------------------------------------------------------- /src/Request/GetQQRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Slince\SmartQQ\Credential; 14 | use Slince\SmartQQ\Entity\Friend; 15 | use GuzzleHttp\Psr7\Response; 16 | use Slince\SmartQQ\Exception\ResponseException; 17 | 18 | class GetQQRequest extends Request 19 | { 20 | protected $uri = 'http://s.web2.qq.com/api/get_friend_uin2?tuin={uin}&type=1&vfwebqq={vfwebqq}&t=0.1'; 21 | 22 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 23 | 24 | public function __construct(Friend $friend, Credential $credential) 25 | { 26 | $this->setTokens([ 27 | 'uin' => $friend->getUin(), 28 | 'vfwebqq' => $credential->getVfWebQQ(), 29 | ]); 30 | } 31 | 32 | /** 33 | * 解析响应数据. 34 | * 35 | * @param Response $response 36 | * 37 | * @return int 38 | */ 39 | public static function parseResponse(Response $response) 40 | { 41 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 42 | if ($jsonData && 0 == $jsonData['retcode']) { 43 | return $jsonData['result']['account']; 44 | } 45 | throw new ResponseException($jsonData['retcode'], $response); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Request/GetQrCodeRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | class GetQrCodeRequest extends Request 14 | { 15 | protected $uri = 'https://ssl.ptlogin2.qq.com/ptqrshow?appid=501004106&e=0&l=M&s=5&d=72&v=4&t=0.1'; 16 | 17 | protected $referer = null; 18 | } 19 | -------------------------------------------------------------------------------- /src/Request/GetRecentListRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Entity\Recent; 16 | use Slince\SmartQQ\EntityCollection; 17 | use Slince\SmartQQ\EntityFactory; 18 | use Slince\SmartQQ\Exception\ResponseException; 19 | 20 | class GetRecentListRequest extends Request 21 | { 22 | protected $uri = 'http://d1.web2.qq.com/channel/get_recent_list2'; 23 | 24 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 25 | 26 | protected $method = RequestInterface::REQUEST_METHOD_POST; 27 | 28 | public function __construct(Credential $credential) 29 | { 30 | $this->setParameter('r', \GuzzleHttp\json_encode([ 31 | 'vfwebqq' => $credential->getVfWebQQ(), 32 | 'clientid' => $credential->getClientId(), 33 | 'psessionid' => $credential->getPSessionId(), 34 | ])); 35 | } 36 | 37 | /** 38 | * 解析响应数据. 39 | * 40 | * @param Response $response 41 | * 42 | * @return EntityCollection 43 | */ 44 | public static function parseResponse(Response $response) 45 | { 46 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 47 | if ($jsonData && 0 == $jsonData['retcode']) { 48 | $recentList = []; 49 | foreach ($jsonData['result'] as $recent) { 50 | $recentList[] = EntityFactory::createEntity(Recent::class, [ 51 | 'type' => $recent['type'], 52 | 'uin' => $recent['uin'], 53 | ]); 54 | } 55 | 56 | return new EntityCollection($recentList); 57 | } 58 | throw new ResponseException($jsonData['retcode'], $response); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Request/GetUinAndPsessionidRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Exception\RuntimeException; 15 | 16 | class GetUinAndPsessionidRequest extends Request 17 | { 18 | protected $uri = 'http://d1.web2.qq.com/channel/login2'; 19 | 20 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 21 | 22 | protected $method = RequestInterface::REQUEST_METHOD_POST; 23 | 24 | public function __construct(array $data) 25 | { 26 | $this->setParameter('r', json_encode($data)); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public static function parseResponse(Response $response) 33 | { 34 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 35 | if (!isset($jsonData['result']['uin'])) { 36 | throw new RuntimeException('Can not find argument [uin] and [psessionid]'); 37 | } 38 | 39 | return [$jsonData['result']['uin'], $jsonData['result']['psessionid']]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Request/GetVfWebQQRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Exception\RuntimeException; 15 | 16 | class GetVfWebQQRequest extends Request 17 | { 18 | protected $uri = 'http://s.web2.qq.com/api/getvfwebqq?ptwebqq={ptwebqq}&clientid=53999199&psessionid=&t=0.1'; 19 | 20 | protected $referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'; 21 | 22 | public function __construct($ptWebQQ) 23 | { 24 | $this->setToken('ptwebqq', $ptWebQQ); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public static function parseResponse(Response $response) 31 | { 32 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 33 | if (!isset($jsonData['result']['vfwebqq'])) { 34 | throw new RuntimeException('Can not find argument [vfwebqq]'); 35 | } 36 | 37 | return $jsonData['result']['vfwebqq']; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Request/PollMessagesRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Exception\Code103ResponseException; 16 | use Slince\SmartQQ\Exception\ResponseException; 17 | use Slince\SmartQQ\Exception\RuntimeException; 18 | use Slince\SmartQQ\Message\Content; 19 | use Slince\SmartQQ\Message\Font; 20 | use Slince\SmartQQ\Message\Response\DiscussMessage; 21 | use Slince\SmartQQ\Message\Response\FriendMessage; 22 | use Slince\SmartQQ\Message\Response\GroupMessage; 23 | use Slince\SmartQQ\Message\Response\Message; 24 | 25 | class PollMessagesRequest extends Request 26 | { 27 | protected $uri = 'http://d1.web2.qq.com/channel/poll2'; 28 | 29 | protected $referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'; 30 | 31 | protected $method = RequestInterface::REQUEST_METHOD_POST; 32 | 33 | public function __construct(Credential $credential) 34 | { 35 | $this->setParameter('r', \GuzzleHttp\json_encode([ 36 | 'ptwebqq' => '', 37 | 'clientid' => $credential->getClientId(), 38 | 'psessionid' => $credential->getPSessionId(), 39 | 'key' => '', 40 | ])); 41 | } 42 | 43 | /** 44 | * 解析响应数据. 45 | * 46 | * @param Response $response 47 | * 48 | * @return Message[] 49 | */ 50 | public static function parseResponse(Response $response) 51 | { 52 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 53 | if ($jsonData && 0 == $jsonData['retcode'] && !empty($jsonData['result'])) { 54 | $messages = []; 55 | foreach ($jsonData['result'] as $messageData) { 56 | $messages[] = static::makeResponseMessage($messageData['poll_type'], $messageData); 57 | } 58 | 59 | return $messages; 60 | } elseif (103 == $jsonData['retcode']) { 61 | throw new Code103ResponseException($response); 62 | } 63 | throw new ResponseException($jsonData['retcode'], $response); 64 | } 65 | 66 | protected static function makeResponseMessage($type, $messageData) 67 | { 68 | $contents = $messageData['value']['content']; 69 | //消息正文 70 | $content = static::parseContents($contents); 71 | switch ($messageData['poll_type']) { 72 | case Message::TYPE_FRIEND: 73 | $message = new FriendMessage( 74 | $messageData['value']['to_uin'], 75 | $messageData['value']['from_uin'], 76 | $content, 77 | $messageData['value']['time'], 78 | $messageData['value']['msg_id'], 79 | $messageData['value']['msg_type'] 80 | ); 81 | break; 82 | case Message::TYPE_GROUP: 83 | $message = new GroupMessage( 84 | $messageData['value']['to_uin'], 85 | $messageData['value']['from_uin'], 86 | $messageData['value']['group_code'], 87 | $messageData['value']['send_uin'], 88 | $content, 89 | $messageData['value']['time'], 90 | $messageData['value']['msg_id'], 91 | $messageData['value']['msg_type'] 92 | ); 93 | break; 94 | case Message::TYPE_DISCUSS: 95 | $message = new DiscussMessage( 96 | $messageData['value']['to_uin'], 97 | $messageData['value']['from_uin'], 98 | $messageData['value']['did'], 99 | $messageData['value']['send_uin'], 100 | $content, 101 | $messageData['value']['time'], 102 | $messageData['value']['msg_id'], 103 | $messageData['value']['msg_type'] 104 | ); 105 | break; 106 | default: 107 | throw new RuntimeException(sprintf('Unknown message type [%s]', $type)); 108 | break; 109 | } 110 | 111 | return $message; 112 | } 113 | 114 | /** 115 | * Parse Contents. 116 | * 117 | * @param array $contents 118 | * 119 | * @return Content 120 | */ 121 | protected static function parseContents($contents) 122 | { 123 | //正文字体 124 | $fontParameters = $contents[0][1]; 125 | $font = new Font( 126 | $fontParameters['name'], 127 | $fontParameters['color'], 128 | $fontParameters['size'], 129 | $fontParameters['style'] 130 | ); 131 | unset($contents[0]); 132 | $contentString = implode('', array_map(function ($content) { 133 | if ($content && is_array($content) && 'face' === $content[0]) { //处理表情 134 | $faceText = Content::searchFaceText($content[1]); 135 | 136 | return $faceText ? '['.$faceText.']' : ''; 137 | } else { 138 | return (string) $content; 139 | } 140 | }, $contents)); 141 | 142 | return new Content($contentString, $font); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Request/Request.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | class Request implements RequestInterface 14 | { 15 | /** 16 | * 请求地址 17 | * 18 | * @var string 19 | */ 20 | protected $uri; 21 | 22 | /** 23 | * referer. 24 | * 25 | * @var string 26 | */ 27 | protected $referer; 28 | 29 | /** 30 | * 请求方式. 31 | * 32 | * @var string 33 | */ 34 | protected $method = RequestInterface::REQUEST_METHOD_GET; 35 | 36 | /** 37 | * 请求参数. 38 | * 39 | * @var array 40 | */ 41 | protected $parameters = []; 42 | 43 | /** 44 | * 处理占位符. 45 | * 46 | * @var array 47 | */ 48 | protected $tokens = []; 49 | 50 | /** 51 | * 获取请求地址 52 | * 53 | * @return string 54 | */ 55 | public function getUri() 56 | { 57 | return $this->processUri($this->uri); 58 | } 59 | 60 | /** 61 | * 获取referer. 62 | * 63 | * @return string 64 | */ 65 | public function getReferer() 66 | { 67 | return $this->processUri($this->referer); 68 | } 69 | 70 | /** 71 | * 设置uri. 72 | * 73 | * @param string $uri 74 | */ 75 | public function setUri($uri) 76 | { 77 | $this->uri = $uri; 78 | } 79 | 80 | /** 81 | * 设置referer. 82 | * 83 | * @param string $referer 84 | */ 85 | public function setReferer($referer) 86 | { 87 | $this->referer = $referer; 88 | } 89 | 90 | /** 91 | * 获取请求方式. 92 | * 93 | * @return string 94 | */ 95 | public function getMethod() 96 | { 97 | return $this->method; 98 | } 99 | 100 | /** 101 | * 获取请求参数. 102 | * 103 | * @return array 104 | */ 105 | public function getParameters() 106 | { 107 | return $this->parameters; 108 | } 109 | 110 | /** 111 | * 设置参数. 112 | * 113 | * @param array $parameters 114 | */ 115 | public function setParameters(array $parameters) 116 | { 117 | $this->parameters = $parameters; 118 | } 119 | 120 | /** 121 | * 设置请求参数. 122 | * 123 | * @param string $name 124 | * @param string $parameter 125 | */ 126 | public function setParameter($name, $parameter) 127 | { 128 | $this->parameters[$name] = $parameter; 129 | } 130 | 131 | /** 132 | * 设置链接中的占位符. 133 | * 134 | * @param array $tokens 135 | */ 136 | public function setTokens(array $tokens) 137 | { 138 | $this->tokens = $tokens; 139 | } 140 | 141 | /** 142 | * 获取所有的token. 143 | * 144 | * @return array 145 | */ 146 | public function getTokens() 147 | { 148 | return $this->tokens; 149 | } 150 | 151 | /** 152 | * 设置链接中的指定占位符. 153 | * 154 | * @param string $name 155 | * @param string $token 156 | */ 157 | public function setToken($name, $token) 158 | { 159 | $this->tokens[$name] = $token; 160 | } 161 | 162 | /** 163 | * 处理链接中的占位符. 164 | * 165 | * @param string $uri 166 | * 167 | * @return string 168 | */ 169 | protected function processUri($uri) 170 | { 171 | return preg_replace_callback('#\{([a-zA-Z0-9_,]*)\}#i', function ($matches) { 172 | return isset($this->tokens[$matches[1]]) ? $this->tokens[$matches[1]] : ''; 173 | }, $uri); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Request/RequestInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | interface RequestInterface 14 | { 15 | /** 16 | * 请求方式,Get. 17 | * 18 | * @var string 19 | */ 20 | const REQUEST_METHOD_GET = 'GET'; 21 | 22 | /** 23 | * 请求方式,Post. 24 | * 25 | * @var string 26 | */ 27 | const REQUEST_METHOD_POST = 'POST'; 28 | 29 | /** 30 | * 获取请求地址 31 | * 32 | * @return string 33 | */ 34 | public function getUri(); 35 | 36 | /** 37 | * 获取referer. 38 | * 39 | * @return string 40 | */ 41 | public function getReferer(); 42 | 43 | /** 44 | * 获取请求方式. 45 | * 46 | * @return string 47 | */ 48 | public function getMethod(); 49 | 50 | /** 51 | * 获取请求参数. 52 | * 53 | * @return string 54 | */ 55 | public function getParameters(); 56 | 57 | /** 58 | * 设置请求参数. 59 | * 60 | * @param array $parameters 61 | */ 62 | public function setParameters(array $parameters); 63 | 64 | /** 65 | * 设置请求参数. 66 | * 67 | * @param $name 68 | * @param $parameter 69 | */ 70 | public function setParameter($name, $parameter); 71 | 72 | /** 73 | * 设置链接中的占位符. 74 | * 75 | * @param array $tokens 76 | */ 77 | public function setTokens(array $tokens); 78 | 79 | /** 80 | * 设置链接中的指定占位符. 81 | * 82 | * @param string $name 83 | * @param string $token 84 | */ 85 | public function setToken($name, $token); 86 | } 87 | -------------------------------------------------------------------------------- /src/Request/SendDiscusMessageRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Slince\SmartQQ\Credential; 14 | use Slince\SmartQQ\Message\Request\DiscussMessage; 15 | 16 | class SendDiscusMessageRequest extends SendMessageRequest 17 | { 18 | protected $uri = 'http://d1.web2.qq.com/channel/send_discu_msg2'; 19 | 20 | public function __construct(DiscussMessage $message, Credential $credential) 21 | { 22 | $parameters = array_merge([ 23 | 'did' => $message->getDiscuss()->getId(), 24 | ], $this->makeMessageParameter($message, $credential)); 25 | $this->setParameter('r', \GuzzleHttp\json_encode($parameters)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Request/SendFriendMessageRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Slince\SmartQQ\Credential; 14 | use Slince\SmartQQ\Message\Request\FriendMessage; 15 | 16 | class SendFriendMessageRequest extends SendMessageRequest 17 | { 18 | protected $uri = 'http://d1.web2.qq.com/channel/send_buddy_msg2'; 19 | 20 | public function __construct(FriendMessage $message, Credential $credential) 21 | { 22 | $parameters = array_merge([ 23 | 'to' => $message->getUser()->getUin(), 24 | ], $this->makeMessageParameter($message, $credential)); 25 | $this->setParameter('r', \GuzzleHttp\json_encode($parameters)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Request/SendGroupMessageRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use Slince\SmartQQ\Credential; 14 | use Slince\SmartQQ\Message\Request\GroupMessage; 15 | 16 | class SendGroupMessageRequest extends SendMessageRequest 17 | { 18 | protected $uri = 'http://d1.web2.qq.com/channel/send_qun_msg2'; 19 | 20 | public function __construct(GroupMessage $message, Credential $credential) 21 | { 22 | $parameters = array_merge([ 23 | 'group_uin' => $message->getGroup()->getId(), 24 | ], $this->makeMessageParameter($message, $credential)); 25 | $this->setParameter('r', \GuzzleHttp\json_encode($parameters)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Request/SendMessageRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | use GuzzleHttp\Psr7\Response; 14 | use Slince\SmartQQ\Credential; 15 | use Slince\SmartQQ\Exception\Code103ResponseException; 16 | use Slince\SmartQQ\Message\Request\Message; 17 | 18 | class SendMessageRequest extends Request 19 | { 20 | protected $method = RequestInterface::REQUEST_METHOD_POST; 21 | 22 | protected $referer = 'http://d1.web2.qq.com/cfproxy.html?v=20151105001&callback=1'; 23 | 24 | public function makeMessageParameter(Message $message, Credential $credential) 25 | { 26 | return [ 27 | 'content' => (string) $message->getContent(), 28 | 'face' => $message->getFace(), 29 | 'clientid' => $credential->getClientId(), 30 | 'msg_id' => $message->getMsgId(), 31 | 'psessionid' => $credential->getPSessionId(), 32 | ]; 33 | } 34 | 35 | /** 36 | * @param Response $response 37 | * 38 | * @throws Code103ResponseException 39 | * 40 | * @return bool 41 | */ 42 | public static function parseResponse(Response $response) 43 | { 44 | $jsonData = \GuzzleHttp\json_decode($response->getBody(), true); 45 | if ( 46 | (isset($jsonData['errCode']) && 0 === $jsonData['errCode']) 47 | || (isset($jsonData['retcode']) && 0 === $jsonData['retcode']) 48 | ) { 49 | return true; 50 | } 51 | if (isset($jsonData['retcode']) && 103 === $jsonData['retcode']) { 52 | throw new Code103ResponseException($response); 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/VerifyQrCodeRequest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ\Request; 12 | 13 | class VerifyQrCodeRequest extends Request 14 | { 15 | /** 16 | * 二维码状态,未失效. 17 | * 18 | * @var int 19 | */ 20 | const STATUS_UNEXPIRED = 1; 21 | 22 | /** 23 | * 二维码状态,已失效. 24 | * 25 | * @var int 26 | */ 27 | const STATUS_EXPIRED = 2; 28 | 29 | /** 30 | * 二维码状态,认证中. 31 | * 32 | * @var int 33 | */ 34 | const STATUS_ACCREDITATION = 3; 35 | 36 | /** 37 | * 二维码状态,认证成功 38 | * 39 | * @var int 40 | */ 41 | const STATUS_CERTIFICATION = 4; 42 | 43 | protected $uri = 'https://ssl.ptlogin2.qq.com/ptqrlogin?ptqrtoken={ptqrtoken}&webqq_type=10&remember_uin=1&login2qq=1&aid=501004106&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-0-4303&mibao_css=m_webqq&t=undefined&g=1&js_type=0&js_ver=10203&login_sig=&pt_randsalt=0'; 44 | 45 | protected $referer = 'https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&no_verifyimg=1 &s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&f_url=loginerroralert &strong_login=1&login_state=10&t=20131024001'; 46 | 47 | public function __construct($ptQrToken) 48 | { 49 | $this->setToken('ptqrtoken', $ptQrToken); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace Slince\SmartQQ; 12 | 13 | use Symfony\Component\Filesystem\Filesystem; 14 | 15 | class Utils 16 | { 17 | /** 18 | * @var Filesystem 19 | */ 20 | protected static $filesystem; 21 | 22 | /** 23 | * Get filesystem. 24 | * 25 | * @return Filesystem 26 | */ 27 | public static function getFilesystem() 28 | { 29 | if (is_null(static::$filesystem)) { 30 | static::$filesystem = new Filesystem(); 31 | } 32 | 33 | return static::$filesystem; 34 | } 35 | 36 | /** 37 | * hash. 38 | * 39 | * @param int $uin 40 | * @param string $ptWebQQ 41 | * 42 | * @return string 43 | */ 44 | public static function hash($uin, $ptWebQQ) 45 | { 46 | $x = array( 47 | 0, $uin >> 24 & 0xff ^ 0x45, 48 | 0, $uin >> 16 & 0xff ^ 0x43, 49 | 0, $uin >> 8 & 0xff ^ 0x4f, 50 | 0, $uin & 0xff ^ 0x4b, 51 | ); 52 | for ($i = 0; $i < 64; ++$i) { 53 | $x[($i & 3) << 1] ^= ord(substr($ptWebQQ, $i, 1)); 54 | } 55 | $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'); 56 | $hash = ''; 57 | for ($i = 0; $i < 8; ++$i) { 58 | $hash .= $hex[$x[$i] >> 4 & 0xf].$hex[$x[$i] & 0xf]; 59 | } 60 | 61 | return $hash; 62 | } 63 | 64 | /** 65 | * 生成ptqrtoken的哈希函数. 66 | * 67 | * @param string $string 68 | * 69 | * @return int 70 | */ 71 | public static function hash33($string) 72 | { 73 | $e = 0; 74 | $n = strlen($string); 75 | for ($i = 0; $n > $i; ++$i) { 76 | //64位php才进行32位转换 77 | if (PHP_INT_MAX > 2147483647) { 78 | $e = static::toUint32val($e); 79 | } 80 | $e += ($e << 5) + static::charCodeAt($string, $i); 81 | } 82 | 83 | return 2147483647 & $e; 84 | } 85 | 86 | /** 87 | * 入转换为32位无符号整数,若溢出,则只保留低32位. 88 | * 89 | * @see http://outofmemory.cn/code-snippet/18291/out-switch-32-place-sign-integer-spill-maintain-32-place 90 | * 91 | * @param mixed $var 92 | * 93 | * @return float|int|string 94 | */ 95 | public static function toUint32val($var) 96 | { 97 | if (is_string($var)) { 98 | if (PHP_INT_MAX > 2147483647) { 99 | $var = intval($var); 100 | } else { 101 | $var = floatval($var); 102 | } 103 | } 104 | if (!is_int($var)) { 105 | $var = intval($var); 106 | } 107 | if ((0 > $var) || ($var > 4294967295)) { 108 | $var &= 4294967295; 109 | if (0 > $var) { 110 | $var = sprintf('%u', $var); 111 | } 112 | } 113 | 114 | return $var; 115 | } 116 | 117 | /** 118 | * 计算字符的unicode,类似js中charCodeAt 119 | * [Link](http://www.phpjiayuan.com/90/225.html). 120 | * 121 | * @param string $str 122 | * @param int $index 123 | * 124 | * @return null|number 125 | */ 126 | public static function charCodeAt($str, $index) 127 | { 128 | $char = mb_substr($str, $index, 1, 'UTF-8'); 129 | if (mb_check_encoding($char, 'UTF-8')) { 130 | $ret = mb_convert_encoding($char, 'UTF-32BE', 'UTF-8'); 131 | 132 | return hexdec(bin2hex($ret)); 133 | } else { 134 | return null; 135 | } 136 | } 137 | 138 | /** 139 | * 获取当前时间的毫秒数. 140 | * 141 | * @return float 142 | */ 143 | public static function getMillisecond() 144 | { 145 | list($s1, $s2) = explode(' ', microtime()); 146 | 147 | return (float) sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000); 148 | } 149 | 150 | /** 151 | * 用于发送消息时生成msg id. 152 | * 153 | * @return int 154 | */ 155 | public static function makeMsgId() 156 | { 157 | static $sequence = 0; 158 | static $t = 0; 159 | if (!$t) { 160 | $t = static::getMillisecond(); 161 | $t = ($t - $t % 1000) / 1000; 162 | $t = $t % 10000 * 10000; 163 | } 164 | //获取msgId 165 | ++$sequence; 166 | 167 | return $t + $sequence; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/CredentialResolverTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(CredentialResolver::class) 14 | ->disableOriginalConstructor() 15 | ->setMethods(['sendRequest', 'getCookies']) 16 | ->getMock(); 17 | 18 | $forQrImageResponse = $this->createResponseFromFixture('qrcode.png'); 19 | $verifyStatusResponse = $this->createResponseFromFixture('verify_status_certified.txt'); 20 | $forPtWebQQResponse = new Response(200); 21 | $forVfWebQQResponse = $this->createResponseFromFixture('get_vfwebqq.txt'); 22 | $forUinAndPSessionResponse = $this->createResponseFromFixture('get_uin_psession.txt'); 23 | $getOnlineStatusResponse = $this->createResponseFromFixture('get_friends_online_status.txt'); 24 | $cookies = Credential::fromArray($this->readFixtureFileJson('credential.json'))->getCookies(); 25 | 26 | $resolver->expects($this->any()) 27 | ->method('sendRequest') 28 | ->will($this->onConsecutiveCalls( 29 | $forQrImageResponse, 30 | $verifyStatusResponse, 31 | $forPtWebQQResponse, 32 | $forVfWebQQResponse, 33 | $forUinAndPSessionResponse, 34 | $getOnlineStatusResponse 35 | )); 36 | $resolver->expects($this->any()) 37 | ->method('getCookies') 38 | ->willReturn($cookies); 39 | 40 | $resolver->resolve(function($qrcode){ 41 | $this->assertNotEmpty($qrcode); 42 | }); 43 | 44 | $credential = $resolver->wait(); 45 | $this->assertNotEmpty($credential->getUin()); 46 | $this->assertNotEmpty($credential->getPtWebQQ()); 47 | $this->assertNotEmpty($credential->getVfWebQQ()); 48 | $this->assertNotEmpty($credential->getPSessionId()); 49 | } 50 | } -------------------------------------------------------------------------------- /tests/CredentialTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $credential->getPtWebQQ()); 15 | $this->assertEquals('bar', $credential->getVfWebQQ()); 16 | $this->assertEquals('baz', $credential->getPSessionId()); 17 | $this->assertEquals(1234, $credential->getUin()); 18 | $this->assertEquals(604800, $credential->getClientId()); 19 | $this->assertInstanceOf(CookieJar::class, $credential->getCookies()); 20 | } 21 | 22 | public function testSetter() 23 | { 24 | $credential = new Credential('foo', 'bar', 'baz', 1234, 604800, new CookieJar()); 25 | 26 | $credential->setPtWebQQ('foo1'); 27 | $credential->setVfWebQQ('bar1'); 28 | $credential->setPSessionId('baz1'); 29 | $credential->setUin(12345); 30 | $credential->setClientId(6048001); 31 | 32 | $this->assertEquals('foo1', $credential->getPtWebQQ()); 33 | $this->assertEquals('bar1', $credential->getVfWebQQ()); 34 | $this->assertEquals('baz1', $credential->getPSessionId()); 35 | $this->assertEquals(12345, $credential->getUin()); 36 | $this->assertEquals(6048001, $credential->getClientId()); 37 | } 38 | 39 | public function testToArray() 40 | { 41 | $credential = new Credential('foo', 'bar', 'baz', 1234, 604800, new CookieJar()); 42 | $this->assertEquals([ 43 | 'ptWebQQ' => 'foo', 44 | 'vfWebQQ' => 'bar', 45 | 'pSessionId' => 'baz', 46 | 'uin' => 1234, 47 | 'clientId' => 604800, 48 | 'cookies' => [], 49 | ], $credential->toArray()); 50 | } 51 | 52 | public function testFromArray() 53 | { 54 | $parameters = [ 55 | 'ptWebQQ' => 'foo', 56 | 'vfWebQQ' => 'bar', 57 | 'pSessionId' => 'baz', 58 | 'uin' => 1234, 59 | 'clientId' => 604800, 60 | 'cookies' => [], 61 | ]; 62 | $credential = Credential::fromArray($parameters); 63 | $this->assertEquals('foo', $credential->getPtWebQQ()); 64 | $this->assertEquals('bar', $credential->getVfWebQQ()); 65 | $this->assertEquals('baz', $credential->getPSessionId()); 66 | $this->assertEquals(1234, $credential->getUin()); 67 | $this->assertEquals(604800, $credential->getClientId()); 68 | } 69 | } -------------------------------------------------------------------------------- /tests/Entity/BirthdayTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(2017, $birthday->getYear()); 14 | $this->assertEquals(03, $birthday->getMonth()); 15 | $this->assertEquals(28, $birthday->getDay()); 16 | } 17 | 18 | public function testSetter() 19 | { 20 | $birthday = new Birthday(2017, 03, 28); 21 | $birthday->setYear(2018); 22 | $birthday->setMonth(04); 23 | $birthday->setDay(29); 24 | $this->assertEquals(2018, $birthday->getYear()); 25 | $this->assertEquals(04, $birthday->getMonth()); 26 | $this->assertEquals(29, $birthday->getDay()); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Entity/CategoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $category->getName()); 14 | $this->assertEquals(1, $category->getIndex()); 15 | $this->assertEquals(2, $category->getSort()); 16 | } 17 | 18 | public function testSetter() 19 | { 20 | $category = new Category('foo', 1, 2); 21 | $category->setName('bar'); 22 | $category->setIndex(2); 23 | $category->setSort(3); 24 | $this->assertEquals('bar', $category->getName()); 25 | $this->assertEquals(2, $category->getIndex()); 26 | $this->assertEquals(3, $category->getSort()); 27 | } 28 | 29 | public function testCreateMyFriendCategory() 30 | { 31 | $category = Category::createMyFriendCategory(); 32 | $this->assertEquals('我的好友', $category->getName()); 33 | $this->assertEquals(0, $category->getIndex()); 34 | $this->assertEquals(0, $category->getSort()); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Entity/DiscussDetailTest.php: -------------------------------------------------------------------------------- 1 | assertNull($detail->getDid()); 15 | $this->assertNull($detail->getName()); 16 | $this->assertNull($detail->getMembers()); 17 | $detail->setDid(1); 18 | $detail->setName('foo'); 19 | $detail->setMembers(new EntityCollection([])); 20 | $this->assertEquals(1, $detail->getDid()); 21 | $this->assertEquals('foo', $detail->getName()); 22 | $this->assertNotNull($detail->getMembers()); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Entity/DiscussMemberTest.php: -------------------------------------------------------------------------------- 1 | assertNull($member->getNick()); 13 | $this->assertNull($member->getRuin()); 14 | $this->assertNull($member->getStatus()); 15 | $this->assertNull($member->getClientType()); 16 | $member->setNick('foo'); 17 | $member->setStatus('online'); 18 | $member->setClientType(1); 19 | $member->setRuin(123); 20 | $this->assertEquals('foo', $member->getNick()); 21 | $this->assertEquals('online', $member->getStatus()); 22 | $this->assertEquals(1, $member->getClientType()); 23 | $this->assertEquals(123, $member->getRuin()); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Entity/DiscussTest.php: -------------------------------------------------------------------------------- 1 | assertNull($discuss->getId()); 14 | $this->assertNull($discuss->getName()); 15 | $discuss->setName('foo'); 16 | $discuss->setId(1); 17 | $this->assertEquals('foo', $discuss->getName()); 18 | $this->assertEquals(1, $discuss->getId()); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/Entity/FriendTest.php: -------------------------------------------------------------------------------- 1 | assertNull($friend->getFlag()); 14 | $this->assertNull($friend->getFace()); 15 | $this->assertNull($friend->getNick()); 16 | $this->assertNull($friend->getQq()); 17 | $this->assertNull($friend->isVip()); 18 | $this->assertNull($friend->getVipLevel()); 19 | $this->assertNull($friend->getCategory()); 20 | $this->assertNull($friend->getMarkName()); 21 | 22 | $friend->setFlag(123); 23 | $friend->setFace(123); 24 | $friend->setNick('foo'); 25 | $friend->setQq(111222); 26 | $friend->setIsVip(true); 27 | $friend->setVipLevel(6); 28 | $friend->setCategory(Category::createMyFriendCategory()); 29 | $friend->setMarkName('bar'); 30 | 31 | $this->assertEquals('123', $friend->getFlag()); 32 | $this->assertEquals('123', $friend->getFace()); 33 | $this->assertEquals('foo', $friend->getNick()); 34 | $this->assertEquals('111222', $friend->getQq()); 35 | $this->assertTrue($friend->isVip()); 36 | $this->assertInstanceOf(Category::class, $friend->getCategory()); 37 | $this->assertEquals('bar', $friend->getMarkName()); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Entity/GroupDetailTest.php: -------------------------------------------------------------------------------- 1 | assertNull($detail->getGid()); 15 | $this->assertNull($detail->getName()); 16 | $this->assertNull($detail->getCode()); 17 | $this->assertNull($detail->getOwner()); 18 | $this->assertNull($detail->getLevel()); 19 | $this->assertNull($detail->getCreateTime()); 20 | $this->assertNull($detail->getFlag()); 21 | $this->assertNull($detail->getMemo()); 22 | $this->assertNull($detail->getMembers()); 23 | 24 | $detail->setGId(1); 25 | $detail->setName('foo'); 26 | $detail->setCode(111222); 27 | $detail->setOwner(111222); 28 | $detail->setLevel(5); 29 | $detail->setCreateTime(111222); 30 | $detail->setFlag(2); 31 | $detail->setMemo('foo memo'); 32 | $detail->setMembers(new EntityCollection([])); 33 | 34 | $this->assertEquals(1, $detail->getGid()); 35 | $this->assertEquals('foo', $detail->getName()); 36 | $this->assertEquals(111222, $detail->getCode()); 37 | $this->assertEquals(111222, $detail->getOwner()); 38 | $this->assertEquals(5, $detail->getLevel()); 39 | $this->assertEquals(111222, $detail->getCreateTime()); 40 | $this->assertEquals(2, $detail->getFlag()); 41 | $this->assertEquals('foo memo', $detail->getMemo()); 42 | $this->assertNotNull($detail->getMembers()); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/Entity/GroupMemberTest.php: -------------------------------------------------------------------------------- 1 | assertNull($member->getFlag()); 13 | $this->assertNull($member->getNick()); 14 | $this->assertNull($member->getProvince()); 15 | $this->assertNull($member->getGender()); 16 | $this->assertNull($member->getCountry()); 17 | $this->assertNull($member->getCity()); 18 | $this->assertNull($member->getCard()); 19 | $this->assertNull($member->isVip()); 20 | $this->assertNull($member->getVipLevel()); 21 | 22 | $member->setFlag(20); 23 | $member->setNick('foo'); 24 | $member->setProvince('foo'); 25 | $member->setGender('male'); 26 | $member->setCountry('zh'); 27 | $member->setCity('foo'); 28 | $member->setCard('bar'); 29 | $member->setIsVip(true); 30 | $member->setVipLevel(6); 31 | 32 | $this->assertEquals(20, $member->getFlag()); 33 | $this->assertEquals('foo', $member->getNick()); 34 | $this->assertEquals('foo', $member->getProvince()); 35 | $this->assertEquals('male', $member->getGender()); 36 | $this->assertEquals('zh', $member->getCountry()); 37 | $this->assertEquals('foo', $member->getCity()); 38 | $this->assertEquals('bar', $member->getCard()); 39 | $this->assertTrue($member->isVip()); 40 | $this->assertEquals(6, $member->getVipLevel()); 41 | } 42 | } -------------------------------------------------------------------------------- /tests/Entity/GroupTest.php: -------------------------------------------------------------------------------- 1 | assertNull($group->getId()); 14 | $this->assertNull($group->getName()); 15 | $this->assertNull($group->getCode()); 16 | $this->assertNull($group->getFlag()); 17 | $this->assertNull($group->getMarkName()); 18 | 19 | $group->setId(1); 20 | $group->setName('foo'); 21 | $group->setCode(111222); 22 | $group->setFlag(2); 23 | $group->setMarkName('bar'); 24 | 25 | $this->assertEquals(1, $group->getId()); 26 | $this->assertEquals('foo', $group->getName()); 27 | $this->assertEquals(111222, $group->getCode()); 28 | $this->assertEquals(2, $group->getFlag()); 29 | $this->assertEquals('bar', $group->getMarkName()); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/Entity/OnlineStatusTest.php: -------------------------------------------------------------------------------- 1 | assertNull($onlineStatus->getStatus()); 14 | $this->assertNull($onlineStatus->getUin()); 15 | $this->assertNull($onlineStatus->getClientType()); 16 | 17 | $onlineStatus->setStatus(OnlineStatus::ONLINE); 18 | $onlineStatus->setUin(123); 19 | $onlineStatus->setClientType(1); 20 | 21 | $this->assertEquals(123, $onlineStatus->getUin()); 22 | $this->assertEquals(OnlineStatus::ONLINE, $onlineStatus->getStatus()); 23 | $this->assertEquals(1, $onlineStatus->getClientType()); 24 | } 25 | 26 | public function testToString() 27 | { 28 | $onlineStatus = new OnlineStatus(); 29 | $onlineStatus->setStatus(OnlineStatus::ONLINE); 30 | $this->assertEquals(OnlineStatus::ONLINE, strval($onlineStatus)); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Entity/ProfileTest.php: -------------------------------------------------------------------------------- 1 | assertNull($profile->getUin()); 16 | $this->assertNull($profile->getAllow()); 17 | $this->assertNull($profile->getAccount()); 18 | $this->assertNull($profile->getLnick()); 19 | $this->assertNull($profile->getEmail()); 20 | $this->assertNull($profile->getBirthday()); 21 | $this->assertNull($profile->getOccupation()); 22 | $this->assertNull($profile->getPhone()); 23 | $this->assertNull($profile->getCollege()); 24 | $this->assertNull($profile->getConstel()); 25 | $this->assertNull($profile->getBlood()); 26 | $this->assertNull($profile->getHomepage()); 27 | $this->assertNull($profile->getStat()); 28 | $this->assertNull($profile->getVipInfo()); 29 | $this->assertNull($profile->getCountry()); 30 | $this->assertNull($profile->getProvince()); 31 | $this->assertNull($profile->getCity()); 32 | $this->assertNull($profile->getPersonal()); 33 | $this->assertNull($profile->getNick()); 34 | $this->assertNull($profile->getShengXiao()); 35 | $this->assertNull($profile->getGender()); 36 | $this->assertNull($profile->getMobile()); 37 | 38 | $profile->setUin(1234567); 39 | $profile->setAllow(1); 40 | $profile->setAccount(123456); 41 | $profile->setEmail('foo@foo.com'); 42 | $profile->setLnick('foo lnick'); 43 | $profile->setBirthday(new Birthday(2017, 03, 28)); 44 | $profile->setOccupation('foo'); 45 | $profile->setPhone('123-12345'); 46 | $profile->setCollege('Foo College'); 47 | $profile->setConstel('foo'); 48 | $profile->setBlood(2); 49 | $profile->setHomepage('http://foo.com'); 50 | $profile->setStat(20); 51 | $profile->setVipInfo(6); 52 | $profile->setCountry('CN'); 53 | $profile->setProvince('Bar'); 54 | $profile->setCity('Baz'); 55 | $profile->setPersonal('foo personal'); 56 | $profile->setNick('foo'); 57 | $profile->setShengXiao(5); 58 | $profile->setGender('male'); 59 | $profile->setMobile('12345678901'); 60 | 61 | $this->assertEquals(1234567, $profile->getUin()); 62 | $this->assertEquals(1, $profile->getAllow()); 63 | $this->assertEquals(123456, $profile->getAccount()); 64 | $this->assertEquals('foo@foo.com', $profile->getEmail()); 65 | $this->assertEquals('foo lnick', $profile->getLnick()); 66 | $this->assertInstanceOf(Birthday::class, $profile->getBirthday()); 67 | $this->assertEquals('foo', $profile->getOccupation()); 68 | $this->assertEquals('123-12345', $profile->getPhone()); 69 | $this->assertEquals('Foo College', $profile->getCollege()); 70 | $this->assertEquals('foo', $profile->getConstel()); 71 | $this->assertEquals(2, $profile->getBlood()); 72 | $this->assertEquals('http://foo.com', $profile->getHomepage()); 73 | $this->assertEquals(20, $profile->getStat()); 74 | $this->assertEquals(6, $profile->getVipInfo()); 75 | $this->assertEquals('CN', $profile->getCountry()); 76 | $this->assertEquals('Bar', $profile->getProvince()); 77 | $this->assertEquals('Baz', $profile->getCity()); 78 | $this->assertEquals('foo personal', $profile->getPersonal()); 79 | $this->assertEquals('foo', $profile->getNick()); 80 | $this->assertEquals(5, $profile->getShengXiao()); 81 | $this->assertEquals('male', $profile->getGender()); 82 | $this->assertEquals('12345678901', $profile->getMobile()); 83 | } 84 | } -------------------------------------------------------------------------------- /tests/Entity/RecentTest.php: -------------------------------------------------------------------------------- 1 | assertNull($recent->getType()); 14 | $this->assertNull($recent->getUin()); 15 | $recent->setType(Recent::TYPE_GROUP); 16 | $recent->setUin(123); 17 | $this->assertEquals(Recent::TYPE_GROUP, $recent->getType()); 18 | $this->assertEquals(123, $recent->getUin()); 19 | } 20 | 21 | public function testIsType() 22 | { 23 | $recent = new Recent(); 24 | $recent->setType(Recent::TYPE_GROUP); 25 | $this->assertTrue($recent->isGroupType()); 26 | $recent->setType(Recent::TYPE_DISCUSS); 27 | $this->assertTrue($recent->isDiscussType()); 28 | $recent->setType(Recent::TYPE_FRIEND); 29 | $this->assertTrue($recent->isFriendType()); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/Entity/UserTestCase.php: -------------------------------------------------------------------------------- 1 | assertNull($user->getUin()); 17 | $user->setUin(123); 18 | $this->assertEquals(123, $user->getUin()); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/EntityCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'foo', 15 | ], 16 | [ 17 | 'name' => 'bar', 18 | ], 19 | [ 20 | 'name' => 'baz', 21 | ], 22 | ]; 23 | $collection = new EntityCollection(EntityFactory::createEntities(FooEntity::class, $dataArray)); 24 | $this->assertEquals('foo', $collection->firstByAttribute('name', 'foo')->getName()); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/EntityFactoryTest.php: -------------------------------------------------------------------------------- 1 | 'bar', 13 | ]); 14 | $this->assertInstanceOf(FooEntity::class, $foo); 15 | $this->assertEquals('bar', $foo->getName()); 16 | } 17 | 18 | public function testCreateEntities() 19 | { 20 | $dataArray = [ 21 | [ 22 | 'name' => 'foo', 23 | ], 24 | [ 25 | 'name' => 'bar', 26 | ], 27 | [ 28 | 'name' => 'baz', 29 | ], 30 | ]; 31 | $entities = EntityFactory::createEntities(FooEntity::class, $dataArray); 32 | $this->assertEquals(3, count($entities)); 33 | $this->assertEquals('foo', $entities[0]->getName()); 34 | $this->assertEquals('bar', $entities[1]->getName()); 35 | $this->assertEquals('baz', $entities[2]->getName()); 36 | } 37 | } -------------------------------------------------------------------------------- /tests/Fixtures/103_response.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | { 4 | "poll_type": "message", 5 | "value": { 6 | "content": [ 7 | [ 8 | "font", 9 | { 10 | "color": "000000", 11 | "name": "微软雅黑", 12 | "size": 10, 13 | "style": [ 14 | 0, 15 | 0, 16 | 0 17 | ] 18 | } 19 | ], 20 | "好啊" 21 | ], 22 | "from_uin": 3785096088, 23 | "msg_id": 25477, 24 | "msg_type": 0, 25 | "time": 1450686775, 26 | "to_uin": 931996776 27 | } 28 | }, 29 | { 30 | "poll_type": "group_message", 31 | "value": { 32 | "content": [ 33 | [ 34 | "font", 35 | { 36 | "color": "000000", 37 | "name": "微软雅黑", 38 | "size": 10, 39 | "style": [ 40 | 0, 41 | 0, 42 | 0 43 | ] 44 | } 45 | ], 46 | "好啊" 47 | ], 48 | "from_uin": 2323421101, 49 | "group_code": 2323421101, 50 | "msg_id": 50873, 51 | "msg_type": 0, 52 | "send_uin": 3680220215, 53 | "time": 1450687625, 54 | "to_uin": 931996776 55 | } 56 | }, 57 | { 58 | "poll_type": "discu_message", 59 | "value": { 60 | "content": [ 61 | [ 62 | "font", 63 | { 64 | "color": "000000", 65 | "name": "微软雅黑", 66 | "size": 10, 67 | "style": [ 68 | 0, 69 | 0, 70 | 0 71 | ] 72 | } 73 | ], 74 | "好啊" 75 | ], 76 | "from_uin": 2322423201, 77 | "did": 2322423201, 78 | "msg_id": 50873, 79 | "msg_type": 0, 80 | "send_uin": 3680220215, 81 | "time": 1450687625, 82 | "to_uin": 931996776 83 | } 84 | } 85 | ], 86 | "retcode": 103 87 | } -------------------------------------------------------------------------------- /tests/Fixtures/credential.json: -------------------------------------------------------------------------------- 1 | {"ptWebQQ":"551a0ec0f70f347e2ffc7931bcf7a13e4c774a2cb9f9085b8ac65a9505e412c9","vfWebQQ":"6eb778d9980a0997824001f36ada3251d47dcd7266dfbf2318a53e61181c65a200031adb1ac8cb6a","pSessionId":"8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e383400001ad00000066b026e040015808a206d0000000a406172314338344a69526d0000002859185d94e66218548d1ecb1a12513c86126b3afb97a3c2955b1070324790733ddb059ab166de6857","uin":1045541707,"clientId":53999199,"cookies":[{"Name":"qrsig","Value":"g3x2rsxBl5XERjRyBw1UMc2vgoXpjLyVXUa0qBUX5BEqHg2UWuF8rJtk-KB*jL1A","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"pt2gguin","Value":"o1045541707","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":1578009600,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"uin","Value":"o1045541707","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"skey","Value":"@beMESLu1X","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"superuin","Value":"o1045541707","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"superkey","Value":"z2Mwq-zNFoAW1PrGzJryCnxjJsJN3-AmyOCt9arHRhs_","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":true},{"Name":"supertoken","Value":"397467713","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"pt_recent_uins","Value":"e547f38135c43ba8504cb27ba467fa757c757dd5654a9319ac7445e6bc241f7390d139815f2c5bac14b614e057d9f47d199a1dae52baed55","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":1493742901,"Secure":false,"Discard":false,"HttpOnly":true},{"Name":"pt_guid_sig","Value":"7294837e6eed666d474c671e7a051c49c2626949633338e3561f88142c3f7af8","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":1493742901,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"ptisp","Value":"ctc","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"RK","Value":"IQ0DNPxfXz","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":1806510901,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"ptnick_1045541707","Value":"e7a78be980b8","Domain":"ptlogin2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"ptwebqq","Value":"551a0ec0f70f347e2ffc7931bcf7a13e4c774a2cb9f9085b8ac65a9505e412c9","Domain":"qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"p_uin","Value":"o1045541707","Domain":"web2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"p_skey","Value":"arVfuljghcoTHLU*sGzgs2TF0BsuqfWoO7TAnhSbqdw_","Domain":"web2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false},{"Name":"pt4_token","Value":"C1mGhW8oFgjmDcOBBkNAsZy3mgRd0QJaeXQ9c*3oVus_","Domain":"web2.qq.com","Path":"\/","Max-Age":null,"Expires":null,"Secure":false,"Discard":false,"HttpOnly":false}]} -------------------------------------------------------------------------------- /tests/Fixtures/get_current_user.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "account": 1045538901, 5 | "lnick": "风起时", 6 | "face": 603, 7 | "birthday": { 8 | "month": 8, 9 | "year": 1895, 10 | "day": 15 11 | }, 12 | "occupation": "其他", 13 | "phone": "110", 14 | "allow": 1, 15 | "college": "aaa", 16 | "uin": 1382902354, 17 | "constel": 7, 18 | "blood": 5, 19 | "homepage": "木有", 20 | "stat": 20, 21 | "vip_info": 6, 22 | "country": "乍得", 23 | "city": "南京", 24 | "personal": "这是简介", 25 | "nick": "ABCD", 26 | "shengxiao": 11, 27 | "email": "352323245@qq.com", 28 | "province": "JiangSu", 29 | "gender": "female", 30 | "mobile": "139********" 31 | } 32 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_discuss_detail.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": { 3 | "info": { 4 | "did": 236426547, 5 | "discu_name": "啊啊啊啊", 6 | "mem_list": [ 7 | { 8 | "mem_uin": 3466696377, 9 | "ruin": 3466696377 10 | } 11 | ] 12 | }, 13 | "mem_info": [ 14 | { 15 | "nick": "Hey", 16 | "uin": 3466696377 17 | } 18 | ], 19 | "mem_status": [ 20 | { 21 | "client_type": 7, 22 | "status": "online", 23 | "uin": 3466696377 24 | } 25 | ] 26 | }, 27 | "retcode": 0 28 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_discusses.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "dnamelist": [ 5 | { 6 | "did": 167864331, 7 | "name": "讨论组名" 8 | }, 9 | { 10 | "did": 1678643312, 11 | "name": "讨论组名2" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_friend_detail.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "face": 603, 5 | "birthday": { 6 | "month": 8, 7 | "year": 1895, 8 | "day": 15 9 | }, 10 | "occupation": "其他", 11 | "phone": "110", 12 | "allow": 1, 13 | "college": "aaa", 14 | "uin": 1382902354, 15 | "constel": 7, 16 | "blood": 5, 17 | "homepage": "木有", 18 | "stat": 20, 19 | "vip_info": 6, 20 | "country": "乍得", 21 | "city": "南京", 22 | "personal": "这是简介", 23 | "nick": "ABCD", 24 | "shengxiao": 11, 25 | "email": "352323245@qq.com", 26 | "province": "JiangSu", 27 | "gender": "female", 28 | "mobile": "139********" 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_friend_lnick.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode":0, 3 | "result":[ 4 | { 5 | "uin":41837138855, 6 | "lnick":"风云起 笑红尘" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_friend_qq.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "uiuin": "", 5 | "account": 3524125, 6 | "uin": 41837138855 7 | } 8 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_friends.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "friends": [ 5 | { 6 | "flag": 4, 7 | "uin": 41837138855, 8 | "categories": 1 9 | } 10 | ], 11 | "marknames": [ 12 | { 13 | "uin": 41837138855, 14 | "markname": "备注", 15 | "type": 0 16 | } 17 | ], 18 | "categories": [ 19 | { 20 | "index": 1, 21 | "sort": 2, 22 | "name": "同学" 23 | } 24 | ], 25 | "vipinfo": [ 26 | { 27 | "vip_level": 6, 28 | "u": 41837138855, 29 | "is_vip": 1 30 | } 31 | ], 32 | "info": [ 33 | { 34 | "face": 603, 35 | "flag": 4751942, 36 | "nick": "昵称", 37 | "uin": 41837138855 38 | } 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_friends_online_status.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | { 4 | "client_type": 1, 5 | "status": "online", 6 | "uin": 3017767504 7 | } 8 | ], 9 | "retcode": 0 10 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_group_detail.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "stats": [ 5 | { 6 | "client_type": 1, 7 | "uin": 3623536468, 8 | "stat": 50 9 | } 10 | ], 11 | "minfo": [ 12 | { 13 | "nick": "昵称", 14 | "province": "北京", 15 | "gender": "male", 16 | "uin": 3623536468, 17 | "country": "中国", 18 | "city": "南京" 19 | } 20 | ], 21 | "ginfo": { 22 | "face": 0, 23 | "memo": "群公告!", 24 | "class": 25, 25 | "fingermemo": "", 26 | "code": 591539174, 27 | "createtime": 1231435199, 28 | "flag": 721421329, 29 | "level": 4, 30 | "name": "群名称", 31 | "gid": 2419762790, 32 | "owner": 3509557797, 33 | "members": [ 34 | { 35 | "muin": 3623536468, 36 | "mflag": 192 37 | } 38 | ], 39 | "option": 2 40 | }, 41 | "cards": [ 42 | { 43 | "muin": 3623536468, 44 | "card": "群名片" 45 | } 46 | ], 47 | "vipinfo": [ 48 | { 49 | "vip_level": 6, 50 | "u": 3623536468, 51 | "is_vip": 1 52 | } 53 | ] 54 | } 55 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_groups.txt: -------------------------------------------------------------------------------- 1 | { 2 | "retcode": 0, 3 | "result": { 4 | "gmasklist": [], 5 | "gnamelist": [ 6 | { 7 | "flag": 167864331, 8 | "name": "测试群", 9 | "gid": 14611812014, 10 | "code": 3157131718 11 | }, 12 | { 13 | "flag": 1678643312, 14 | "name": "测试群2", 15 | "gid": 146118120142, 16 | "code": 31571317182 17 | } 18 | ], 19 | "gmarklist": [ 20 | { 21 | "uin": 14611812014, 22 | "markname": "测试群备注" 23 | } 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_ptwebqq_headers.txt: -------------------------------------------------------------------------------- 1 | Connection:Close 2 | Content-Type:text/html 3 | Date:Mon, 03 Apr 2017 12:33:15 GMT 4 | Location:http://w.qq.com/proxy.html?login2qq=1&webqq_type=10 5 | P3P:CP="CAO PSA OUR" 6 | Server:Tencent Login Server/2.0.0 7 | Set-Cookie:uin=o123456; PATH=/; DOMAIN=qq.com; 8 | Set-Cookie:skey=@Z86pBPzDt; PATH=/; DOMAIN=qq.com; 9 | Set-Cookie:pt2gguin=o123456; EXPIRES=Fri, 02-Jan-2020 00:00:00 GMT; PATH=/; DOMAIN=qq.com; 10 | Set-Cookie:p_skey=bwQ3WTKqguiZIWBqwu376YwaQN9uO3vneCGtHnvdS62C4_; PATH=/; DOMAIN=web2.qq.com; ; 11 | Set-Cookie:pt4_token=3ckZcOQ-PpZOqjd2OFE9c9rwEx110tOYF7E6UAfN6VA_; PATH=/; DOMAIN=web2.qq.com; 12 | Set-Cookie:p_uin=; EXPIRES=Fri, 02-Jan-1970 00:00:00 GMT; PATH=/; DOMAIN=qq.com; 13 | Set-Cookie:p_skey=; EXPIRES=Fri, 02-Jan-1970 00:00:00 GMT; PATH=/; DOMAIN=qq.com; 14 | Set-Cookie:pt4_token=; EXPIRES=Fri, 02-Jan-1970 00:00:00 GMT; PATH=/; DOMAIN=qq.com; 15 | Set-Cookie:p_uin=o123456; PATH=/; DOMAIN=web2.qq.com; -------------------------------------------------------------------------------- /tests/Fixtures/get_recent_list.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | { 4 | "type": 1, 5 | "uin": 2856977416 6 | }, 7 | { 8 | "type": 2, 9 | "uin": 285697741656 10 | }, 11 | { 12 | "type": 3, 13 | "uin": 285697741667 14 | } 15 | ], 16 | "retcode": 0 17 | } -------------------------------------------------------------------------------- /tests/Fixtures/get_uin_psession.txt: -------------------------------------------------------------------------------- 1 | {"result":{"cip":23600812,"f":0,"index":1075,"port":47450,"psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e383400001ad00000066b026e040015808a206d0000000a406172314338344a69526d0000002859185d94e66218548d1ecb1a12513c86126b3afb97a3c2955b1070324790733ddb059ab166de6857","status":"online","uin":1234567,"user_state":0,"vfwebqq":"59185d94e66218548d1ecb1a12513c86126b3afb97a3c2955b1070324790733ddb059ab166de6857"},"retcode":0} -------------------------------------------------------------------------------- /tests/Fixtures/get_vfwebqq.txt: -------------------------------------------------------------------------------- 1 | {"retcode":0,"result":{"vfwebqq":"ac3000e2378f8cc6ed71542dfbf5516ed18264d52dcba3d0aa1e052d2b0d29922bf56a7f9d62846c"}} -------------------------------------------------------------------------------- /tests/Fixtures/poll_messages.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | { 4 | "poll_type": "message", 5 | "value": { 6 | "content": [ 7 | [ 8 | "font", 9 | { 10 | "color": "000000", 11 | "name": "微软雅黑", 12 | "size": 10, 13 | "style": [ 14 | 0, 15 | 0, 16 | 0 17 | ] 18 | } 19 | ], 20 | "好啊" 21 | ], 22 | "from_uin": 3785096088, 23 | "msg_id": 25477, 24 | "msg_type": 0, 25 | "time": 1450686775, 26 | "to_uin": 931996776 27 | } 28 | }, 29 | { 30 | "poll_type": "group_message", 31 | "value": { 32 | "content": [ 33 | [ 34 | "font", 35 | { 36 | "color": "000000", 37 | "name": "微软雅黑", 38 | "size": 10, 39 | "style": [ 40 | 0, 41 | 0, 42 | 0 43 | ] 44 | } 45 | ], 46 | "好啊" 47 | ], 48 | "from_uin": 2323421101, 49 | "group_code": 2323421101, 50 | "msg_id": 50873, 51 | "msg_type": 0, 52 | "send_uin": 3680220215, 53 | "time": 1450687625, 54 | "to_uin": 931996776 55 | } 56 | }, 57 | { 58 | "poll_type": "discu_message", 59 | "value": { 60 | "content": [ 61 | [ 62 | "font", 63 | { 64 | "color": "000000", 65 | "name": "微软雅黑", 66 | "size": 10, 67 | "style": [ 68 | 0, 69 | 0, 70 | 0 71 | ] 72 | } 73 | ], 74 | "好啊" 75 | ], 76 | "from_uin": 2322423201, 77 | "did": 2322423201, 78 | "msg_id": 50873, 79 | "msg_type": 0, 80 | "send_uin": 3680220215, 81 | "time": 1450687625, 82 | "to_uin": 931996776 83 | } 84 | } 85 | ], 86 | "retcode": 0 87 | } -------------------------------------------------------------------------------- /tests/Fixtures/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slince/smartqq/06ad056d47f2324f81e7118b47cc492529325252/tests/Fixtures/qrcode.png -------------------------------------------------------------------------------- /tests/Fixtures/response_exception.txt: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | { 4 | "poll_type": "message", 5 | "value": { 6 | "content": [ 7 | [ 8 | "font", 9 | { 10 | "color": "000000", 11 | "name": "微软雅黑", 12 | "size": 10, 13 | "style": [ 14 | 0, 15 | 0, 16 | 0 17 | ] 18 | } 19 | ], 20 | "好啊" 21 | ], 22 | "from_uin": 3785096088, 23 | "msg_id": 25477, 24 | "msg_type": 0, 25 | "time": 1450686775, 26 | "to_uin": 931996776 27 | } 28 | }, 29 | { 30 | "poll_type": "group_message", 31 | "value": { 32 | "content": [ 33 | [ 34 | "font", 35 | { 36 | "color": "000000", 37 | "name": "微软雅黑", 38 | "size": 10, 39 | "style": [ 40 | 0, 41 | 0, 42 | 0 43 | ] 44 | } 45 | ], 46 | "好啊" 47 | ], 48 | "from_uin": 2323421101, 49 | "group_code": 2323421101, 50 | "msg_id": 50873, 51 | "msg_type": 0, 52 | "send_uin": 3680220215, 53 | "time": 1450687625, 54 | "to_uin": 931996776 55 | } 56 | }, 57 | { 58 | "poll_type": "discu_message", 59 | "value": { 60 | "content": [ 61 | [ 62 | "font", 63 | { 64 | "color": "000000", 65 | "name": "微软雅黑", 66 | "size": 10, 67 | "style": [ 68 | 0, 69 | 0, 70 | 0 71 | ] 72 | } 73 | ], 74 | "好啊" 75 | ], 76 | "from_uin": 2322423201, 77 | "did": 2322423201, 78 | "msg_id": 50873, 79 | "msg_type": 0, 80 | "send_uin": 3680220215, 81 | "time": 1450687625, 82 | "to_uin": 931996776 83 | } 84 | } 85 | ], 86 | "retcode": 103 87 | } -------------------------------------------------------------------------------- /tests/Fixtures/send_message.txt: -------------------------------------------------------------------------------- 1 | { 2 | "errCode": 0, 3 | "msg": "send ok" 4 | } -------------------------------------------------------------------------------- /tests/Fixtures/verify_status_being_certified.txt: -------------------------------------------------------------------------------- 1 | ptuiCB('66','0','','0','二维码认证中。(3203423232)',''); -------------------------------------------------------------------------------- /tests/Fixtures/verify_status_certified.txt: -------------------------------------------------------------------------------- 1 | ptuiCB('0','0','http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=123456789&service=ptqrlogin&nodirect=0&ptsigx=6d859e18070bf9af1c4919dd5af4ec67a373f2dfa88e925c97d0718496b1fd9ea5ddfbc2a55ae74931f8722bcd778af6aa086b861c136d7ebce2cb331911c490&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=http%3A%2F%2Fid.b.qq.com%2Fhrtx%2Fredirect%2Ferror%3Fuin%3D2355501554%26ckey%3D8993D02AEA7C9D8D6EFB64D66C7C836CC55DC6466868C04C1D137444C7C0B72F%26jump%3D24%26kfuin%3D2355002534&ptlang=2052&ptredirect=100&aid=501004106&daid=164&j_later=0&low_login_hour=0®master=0&pt_login_type=3&pt_aid=0&pt_aaid=16&pt_light=0&pt_3rd_aid=0','0','登录成功!', '张三'); -------------------------------------------------------------------------------- /tests/Fixtures/verify_status_expired.txt: -------------------------------------------------------------------------------- 1 | ptuiCB('65','0','','0','二维码已失效。(4012918406)', ''); -------------------------------------------------------------------------------- /tests/Fixtures/verify_status_valid.txt: -------------------------------------------------------------------------------- 1 | ptuiCB('66','0','','0','二维码未失效。(473959432)', ''); -------------------------------------------------------------------------------- /tests/FooEntity.php: -------------------------------------------------------------------------------- 1 | name = $name; 16 | } 17 | 18 | /** 19 | * @return mixed 20 | */ 21 | public function getName() 22 | { 23 | return $this->name; 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Message/Content.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $content->getContent()); 15 | $this->assertInstanceOf(Font::class, $content->getFont()); 16 | } 17 | 18 | public function testSetter() 19 | { 20 | $content = new Content('foo', Font::createDefault()); 21 | $content->setContent('bar'); 22 | $content->setFont(new Font('simsun', 'ffffff', 20, [20, 30, 40])); 23 | $this->assertEquals('bar', $content->getContent()); 24 | $this->assertEquals('simsun', $content->getFont()->getName()); 25 | } 26 | 27 | public function testToString() 28 | { 29 | $content = new Content('foo', Font::createDefault()); 30 | $json = '["foo", ["font", {"name":"微软雅黑","color":"000000","size":"10","style":[10, 10, 10]}]]'; 31 | $this->assertEquals(\GuzzleHttp\json_decode($json), \GuzzleHttp\json_decode(strval($content))); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Message/FontTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('foo', $font->getName()); 15 | $this->assertEquals('000000', $font->getColor()); 16 | $this->assertEquals(18, $font->getSize()); 17 | $this->assertEquals([30, 40, 50], $font->getStyle()); 18 | } 19 | 20 | public function testSetter() 21 | { 22 | $font = new Font('foo', '000000', 18, [30, 40, 50]); 23 | 24 | $font->setName('bar'); 25 | $font->setColor('000001'); 26 | $font->setSize(20); 27 | $font->setStyle([10, 10, 10]); 28 | 29 | $this->assertEquals('bar', $font->getName()); 30 | $this->assertEquals('000001', $font->getColor()); 31 | $this->assertEquals(20, $font->getSize()); 32 | $this->assertEquals([10, 10, 10], $font->getStyle()); 33 | } 34 | 35 | public function testToArray() 36 | { 37 | $font = new Font('foo', '000000', 18, [30, 40, 50]); 38 | $this->assertEquals([ 39 | 'name' => 'foo', 40 | 'color' => '000000', 41 | 'size' => 18, 42 | 'style' => [30, 40, 50], 43 | ], $font->toArray()); 44 | } 45 | 46 | public function testCreateDefault() 47 | { 48 | $font = Font::createDefault(); 49 | $this->assertInstanceOf(Font::class, $font); 50 | } 51 | } -------------------------------------------------------------------------------- /tests/Message/MessageInterfaceTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(MessageInterface::class)->getMock(); 13 | $this->assertTrue(method_exists($message, 'getContent')); 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Message/MessageTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Content::class, $message->getContent()); 16 | $this->assertEquals('foo', $message->getContent()->getContent()); 17 | $this->assertEquals(10, $message->getMsgId()); 18 | } 19 | 20 | public function testSetter() 21 | { 22 | $message = new Message(new Content('foo'), 10); 23 | $message->setContent(new Content('bar')); 24 | $message->setMsgId(12); 25 | $this->assertEquals('bar', $message->getContent()->getContent()); 26 | $this->assertEquals(12, $message->getMsgId()); 27 | } 28 | 29 | public function testImplement() 30 | { 31 | $message = new Message(new Content('foo'), 10); 32 | $this->assertInstanceOf(MessageInterface::class, $message); 33 | $this->assertTrue(is_a($message, MessageInterface::class)); 34 | $this->assertTrue(is_subclass_of($message, MessageInterface::class)); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Message/Request/DiscussMessageTest.php: -------------------------------------------------------------------------------- 1 | setId('123'); 15 | $message = new DiscussMessage($foo, 'hello'); 16 | $this->assertInstanceOf(Discuss::class, $message->getDiscuss()); 17 | $this->assertEquals(123, $message->getDiscuss()->getId()); 18 | 19 | $bar = new Discuss(); 20 | $bar->setId('456'); 21 | $message->setDiscuss($bar); 22 | $this->assertEquals(456, $message->getDiscuss()->getId()); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Message/Request/FriendMessageTest.php: -------------------------------------------------------------------------------- 1 | setUin('123'); 15 | $message = new FriendMessage($foo, 'hello'); 16 | $this->assertInstanceOf(Friend::class, $message->getUser()); 17 | $this->assertEquals(123, $message->getUser()->getUin()); 18 | 19 | $bar = new Friend(); 20 | $bar->setUin('456'); 21 | $message->setUser($bar); 22 | $this->assertEquals(456, $message->getUser()->getUin()); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Message/Request/GroupMessageTest.php: -------------------------------------------------------------------------------- 1 | setId('123'); 15 | $message = new GroupMessage($foo, 'hello'); 16 | $this->assertInstanceOf(Group::class, $message->getGroup()); 17 | $this->assertEquals(123, $message->getGroup()->getId()); 18 | 19 | $bar = new Group(); 20 | $bar->setId('456'); 21 | $message->setGroup($bar); 22 | $this->assertEquals(456, $message->getGroup()->getId()); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Message/Request/MessageTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(522, $message->getFace()); 15 | $message->setFace(523); 16 | $this->assertEquals(523, $message->getFace()); 17 | } 18 | 19 | public function testConstruct() 20 | { 21 | $message = new Message('foo'); 22 | $this->assertInstanceOf(Content::class, $message->getContent()); 23 | $this->assertGreaterThan(0, $message->getMsgId()); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Message/Response/DiscussMessageTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(123, $message->getToUin()); 16 | $this->assertEquals(456, $message->getFromUin()); 17 | $this->assertEquals(456, $message->getDiscussId()); 18 | $this->assertEquals(789, $message->getSendUin()); 19 | } 20 | 21 | public function testSetter() 22 | { 23 | $message = new DiscussMessage(123, 456, 456, 789, new Content('foo'), time()); 24 | 25 | $message->setToUin(1234); 26 | $message->setFromUin(4567); 27 | $message->setDiscussId(4567); 28 | $message->setSendUin(78910); 29 | 30 | $this->assertEquals(1234, $message->getToUin()); 31 | $this->assertEquals(4567, $message->getFromUin()); 32 | $this->assertEquals(4567, $message->getDiscussId()); 33 | $this->assertEquals(78910, $message->getSendUin()); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/Message/Response/FriendMessageTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(123, $message->getToUin()); 16 | $message->setToUin(1234); 17 | $this->assertEquals(1234, $message->getToUin()); 18 | } 19 | 20 | public function testFromUin() 21 | { 22 | $message = new FriendMessage(123, 456, new Content('foo'), time()); 23 | 24 | $this->assertEquals(456, $message->getFromUin()); 25 | $message->setFromUin(4567); 26 | $this->assertEquals(4567, $message->getFromUin()); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Message/Response/GroupMessageTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(123, $message->getToUin()); 16 | $this->assertEquals(456, $message->getFromUin()); 17 | $this->assertEquals(456, $message->getGroupCode()); 18 | $this->assertEquals(789, $message->getSendUin()); 19 | } 20 | 21 | public function testSetter() 22 | { 23 | $message = new GroupMessage(123, 456, 456, 789, new Content('foo'), time()); 24 | 25 | $message->setToUin(1234); 26 | $message->setFromUin(4567); 27 | $message->setGroupCode(4567); 28 | $message->setSendUin(78910); 29 | 30 | $this->assertEquals(1234, $message->getToUin()); 31 | $this->assertEquals(4567, $message->getFromUin()); 32 | $this->assertEquals(4567, $message->getGroupCode()); 33 | $this->assertEquals(78910, $message->getSendUin()); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/Message/Response/MessageTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Content::class, $message->getContent()); 16 | $this->assertEquals('foo', $message->getContent()->getContent()); 17 | $this->assertEquals($now, $message->getTime()); 18 | $this->assertEquals(100, $message->getMsgId()); 19 | $this->assertEquals(123, $message->getMsgType()); 20 | } 21 | 22 | public function testSetter() 23 | { 24 | $now = time(); 25 | $message = new Message(new Content('foo'), $now, 100, 123); 26 | 27 | $yesterday = strtotime('-1 day'); 28 | $message->setContent(new Content('bar')); 29 | $message->setTime($yesterday); 30 | $message->setMsgId(200); 31 | $message->setMsgType(456); 32 | 33 | $this->assertEquals('bar', $message->getContent()->getContent()); 34 | $this->assertEquals($yesterday, $message->getTime()); 35 | $this->assertEquals(200, $message->getMsgId()); 36 | $this->assertEquals(456, $message->getMsgType()); 37 | } 38 | 39 | public function testToString() 40 | { 41 | $now = time(); 42 | $message = new Message(new Content('foo'), $now, 100, 123); 43 | $this->assertEquals('foo', (string) $message); 44 | } 45 | 46 | public function testSearchFace() 47 | { 48 | $this->assertEquals(14, Content::searchFaceId('微笑')); 49 | $this->assertEquals('微笑', Content::searchFaceText(14)); 50 | } 51 | } -------------------------------------------------------------------------------- /tests/MessageHandlerTest.php: -------------------------------------------------------------------------------- 1 | createClientMock('poll_messages.txt'); 13 | 14 | $handler = new MessageHandler($client); 15 | $startTime = time(); 16 | $handler->onMessage(function($message) use (&$index, $startTime, $handler){ 17 | $this->assertInstanceOf(Message::class, $message); 18 | if (time() - $startTime > 1) { 19 | $handler->stop(); 20 | } 21 | }); 22 | $handler->listen(); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | readFixtureFile($filename)); 14 | } 15 | 16 | public function readFixtureFileJson($filename) 17 | { 18 | $rawContent = $this->readFixtureFile($filename); 19 | 20 | return \GuzzleHttp\json_decode($rawContent, true); 21 | } 22 | 23 | public function readFixtureFile($filename) 24 | { 25 | $content = file_get_contents(__DIR__."/Fixtures/{$filename}"); 26 | if (false === $content) { 27 | throw new \Exception(sprintf('Fixture [%s] does not exists', $filename)); 28 | } 29 | return $content; 30 | } 31 | 32 | /** 33 | * @param string $fixtureFilename 34 | * 35 | * @return Client 36 | */ 37 | protected function createClientMock($fixtureFilename) 38 | { 39 | $credential = Credential::fromArray($this->readFixtureFileJson('credential.json')); 40 | $response = $this->createResponseFromFixture($fixtureFilename); 41 | 42 | $client = $this->getMockBuilder(Client::class) 43 | ->setMethods(['sendRequest', 'getCredential']) 44 | ->getMock(); 45 | 46 | $client->expects($this->any()) 47 | ->method('sendRequest') 48 | ->willReturn($response); 49 | 50 | $client->expects($this->any()) 51 | ->method('getCredential') 52 | ->willReturn($credential); 53 | 54 | return $client; 55 | } 56 | } -------------------------------------------------------------------------------- /tests/UtilsTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Filesystem::class, Utils::getFilesystem()); 13 | } 14 | 15 | public function testHash() 16 | { 17 | $uin = 1234; 18 | $ptWebQQ = 'bar'; 19 | $this->assertEquals('62456143724B0099', Utils::hash($uin, $ptWebQQ)); 20 | } 21 | 22 | public function testHash33() 23 | { 24 | $string = 'Qjy*mWVaseiG-qeonlAXEtSvIMfHFWHB*0QN*Axsf6AZ*xWL6uDJ*WEGSl0YJhJM'; 25 | $this->assertEquals(1264130581, Utils::hash33($string)); 26 | } 27 | 28 | public function testCharCodeAt() 29 | { 30 | $string = 'foo'; 31 | $this->assertEquals(102, Utils::charCodeAt($string, 0)); 32 | } 33 | 34 | public function testGetCurrentMillisecond() 35 | { 36 | $this->assertNotEmpty(Utils::getMillisecond()); 37 | } 38 | 39 | public function testMakeMsgId() 40 | { 41 | $this->assertNotEmpty(Utils::makeMsgId()); 42 | } 43 | } --------------------------------------------------------------------------------