├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config └── .gitignore ├── index.php ├── multiple.php ├── src ├── libs │ ├── Config.php │ ├── Curl.php │ ├── Helper.php │ └── Log.php └── plugins │ ├── Auth.php │ ├── Base.php │ ├── Capsule.php │ ├── DailyBag.php │ ├── Danmaku.php │ ├── GiftSend.php │ ├── Group.php │ ├── Heart.php │ ├── Silver.php │ ├── SmallTV.php │ ├── SocketClient.php │ └── Task.php └── tools ├── configGenerator.php └── configs.example /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /tmp/ 3 | config 4 | *.log 5 | .idea 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | fast_finish: true 5 | 6 | include: 7 | - php: '5.6' 8 | - php: '7.0' 9 | - php: '7.1' 10 | - php: '7.2' 11 | - php: 'nightly' 12 | 13 | allow_failures: 14 | - php: 'nightly' 15 | 16 | sudo: false 17 | 18 | script: 19 | - find -L . -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## v0.9.1 (2018-05-09) 4 | 5 | ### Added 6 | - 添加应援团签到功能 7 | - 添加扭蛋机功能(暂时支持普通扭蛋币) 8 | 9 | ### Changed 10 | - [dev] 当配置文件为空时跳过写入 11 | - 当回调地址为空时跳过 12 | 13 | ### Fixed 14 | - 兼容 PHP 5.6 版本测试 ([#37](https://github.com/metowolf/BilibiliHelper/issues/37)) 15 | - 修复部分接口逻辑问题 16 | 17 | 18 | ## v0.9.0-pre (2018-05-07) 19 | 20 | **该版本为非兼容更新,从 0.8.x 升级需要重新覆盖配置文件** 21 | 22 | ### Added 23 | - 项目重构,为多帐号准备 24 | - 弹幕监听采用 websocket 接口 (https://github.com/varspool/Wrench) 25 | - 更换 guzzle 库,支持 CookieJar (https://github.com/guzzle/guzzle) 26 | - 添加网络代理支持 27 | 28 | ### Changed 29 | - 修改插件逻辑 30 | 31 | ### Fixed 32 | - 修复配置文件检测问题 33 | - 修复 debug 信息递归黑洞问题 34 | 35 | 36 | ## v0.8.0 (2018-05-04) 37 | 38 | **该版本为非兼容更新,从 0.7.x 升级需要重新覆盖配置文件** 39 | 40 | ### Added 41 | - 新增小电视抽奖功能 ([#18](https://github.com/metowolf/BilibiliHelper/issues/18)) 42 | - 新增配置项检测 43 | 44 | ### Changed 45 | - 修改部分时间锁逻辑 46 | - 修改部分日志提示 47 | 48 | ## v0.7.3 (2018-04-28) 49 | 50 | ### Added 51 | - 新增帐号别名参数 52 | 53 | ### Changed 54 | - 更新模拟客户端版本 (iOS 6670) 55 | - 修正环境变量刷新逻辑 56 | 57 | ### Fixed 58 | - 修复部分语法错误 59 | - 修复错误信息回调函数逻辑错误 60 | 61 | 62 | ## v0.7.2 (2018-04-22) 63 | 64 | ### Added 65 | - 新增令牌刷新机制 66 | - 新增日志通知级别设置 67 | 68 | ### Changed 69 | - 调整部分日志文案 70 | - 修正 README 的错误 ([#29](https://github.com/metowolf/BilibiliHelper/pull/29)) 71 | 72 | ### Fixed 73 | - 修复每日任务无法领取的问题 74 | - 修复部分逻辑错误 75 | 76 | 77 | ## v0.7.1 (2018-04-21) 78 | 79 | ### Changed 80 | - 调整部分通知为警告级别 81 | 82 | ### Fixed 83 | - 修复过早领取宝箱的问题 84 | 85 | 86 | ## v0.7.0 (2018-04-20) 87 | 88 | ### Added 89 | - 项目重构,拥抱 composer 90 | - 全面更换客户端 API 91 | - 添加用户登录机制 ([#22](https://github.com/metowolf/BilibiliHelper/issues/22)) 92 | 93 | ### Changed 94 | - Require PHP 5.4.0 or newer 95 | 96 | ### Fixed 97 | - 修复宝箱验证码问题 ([#27](https://github.com/metowolf/BilibiliHelper/issues/27)) 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 metowolf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ------------------------------------------------------------------------------------ 24 | 25 | https://github.com/copyliu/bililive_dm 26 | 27 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 28 | Version 2, December 2004 29 | 30 | Copyright (C) 2004 Sam Hocevar 31 | 32 | Everyone is permitted to copy and distribute verbatim or modified 33 | copies of this license document, and changing it is allowed as long 34 | as the name is changed. 35 | 36 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 37 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 38 | 39 | 0. You just DO WHAT THE FUCK YOU WANT TO. 40 | 41 | ------------------------------------------------------------------------------------ 42 | 43 | And wait, the most important, you shall star/+1/like the project(s) in project url 44 | section above first, and then thank the author(s) in Copyright section. 45 | 46 | Here are some suggested ways: 47 | 48 | - Email the authors a thank-you letter, and make friends with him/her/them. 49 | - Report bugs or issues. 50 | - Tell friends what a wonderful project this is. 51 | - And, sure, you can just express thanks in your mind without telling the world. 52 | 53 | Contributors of this project by forking have the option to add his/her name and 54 | forked project url at copyright and project url sections, but shall not delete 55 | or modify anything else in these two sections. 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | 7 |

8 | 9 | 10 | # BilibiliHelper 11 | B 站直播实用脚本(**旧版**) 12 | 13 | ## 说明 14 | 15 | `tools` 内工具用于批量生成配置文件 16 | `multiple .php` 启动所有位于`config`下的配置文件 17 | 18 | ## 功能组件 19 | 20 | |plugin |version |description | 21 | |---------|---------|--------------| 22 | |Auth |18.05.07 |帐号登录组件 | 23 | |Capsule |18.05.09 |扭蛋机(普通) | 24 | |DailyBag |18.05.07 |每日礼包领取 | 25 | |Group |18.05.09 |应援团签到 | 26 | |Heart |18.05.07 |双端直播间心跳 | 27 | |Silver |18.05.07 |免费宝箱领取 | 28 | |SmallTV |18.05.08 |小电视抽奖 | 29 | |Task |18.05.07 |每日任务 | 30 | 31 | 32 | ## 未完成功能 33 | |待续| 34 | |-------| 35 | |总督检测| 36 | |节奏风暴| 37 | 38 | 39 | ## 环境依赖 40 | |Requirement| 41 | |-------| 42 | |PHP (>=5.6 or >=7.0)| 43 | |php-openssl| 44 | |php-curl| 45 | |php-sockets| 46 | 47 | 通常使用 `composer` 工具会自动检测上述依赖问题。 48 | 49 | * 项目 `composer.lock` 基于镜像生成 https://pkg.phpcomposer.com/ 50 | 51 | ## 使用指南 52 | 53 | 1. 下载(克隆)项目代码,初始化项目 54 | ``` 55 | $ git clone https://github.com/metowolf/BilibiliHelper.git 56 | $ cd BilibiliHelper 57 | $ cp config.example config 58 | ``` 59 | 2. 使用 composer 工具进行安装。**如果不了解 composer 工具的使用,可以直接到 https://github.com/metowolf/BilibiliHelper/releases 下载完整代码包,解压后跳到第三步。** 60 | ``` 61 | $ composer install 62 | ``` 63 | 3. 按照说明修改配置文件 `config`,只需填写帐号密码即可 64 | 4. 运行测试 65 | ``` 66 | $ php index.php 67 | ``` 68 | 69 |

70 | 71 | ## 升级指南 72 | 73 | 1. 进入项目目录 74 | ``` 75 | $ cd BilibiliHelper 76 | ``` 77 | 2. 拉取最新代码 78 | ``` 79 | $ git pull 80 | ``` 81 | **大版本更新需要重新覆盖配置文件,并重新填写设置** 82 | ``` 83 | $ mv config config.old 84 | $ cp config.example config 85 | ``` 86 | 3. 更新依赖库 87 | ``` 88 | $ composer install 89 | ``` 90 | 4. 如果使用 systemd 等,需要重启服务 91 | ``` 92 | $ systemctl restart bilibili 93 | ``` 94 | 95 | ## 部署指南 96 | 如果你将 BilibiliHelper 部署到线上服务器时,则需要配置一个进程监控器来监测 `php index.php` 命令,在它意外退出时自动重启。 97 | 98 | 通常可以使用以下的方式 99 | - systemd (推荐) 100 | - Supervisor 101 | - screen 102 | - nohup 103 | 104 | ## systemd 脚本 105 | ``` 106 | # /usr/lib/systemd/system/bilibili.service 107 | 108 | [Unit] 109 | Description=Bilibili Helper Manager 110 | Documentation=https://github.com/metowolf/BilibiliHelper 111 | After=network.target 112 | 113 | [Service] 114 | ExecStart=/usr/bin/php /path/to/your/BilibiliHelper/index.php 115 | Restart=always 116 | 117 | [Install] 118 | WantedBy=multi-user.target 119 | ``` 120 | 121 | ## Supervisor 配置 122 | ``` 123 | [program:bilibili] 124 | process_name=%(program_name)s 125 | command=php /path/to/your/BilibiliHelper/index.php 126 | autostart=true 127 | autorestart=true 128 | redirect_stderr=true 129 | stdout_logfile=/tmp/bilibili.log 130 | ``` 131 | 132 | ## 报错通知问题 133 | 脚本出现 error 级别的报错,会调用通知地址进行提醒,这里推荐两个服务 134 | 135 | |服务|官网| 136 | |---|---| 137 | |Server酱|https://sc.ftqq.com/| 138 | |TelegramBot|https://core.telegram.org/bots/api| 139 | 140 | 示范如下 141 | ``` 142 | # Server酱 143 | # 自行替换 144 | APP_CALLBACK="https://sc.ftqq.com/.send?text={message}" 145 | 146 | # TelegramBot 147 | # 自行替换 148 | APP_CALLBACK="https://api.telegram.org/bot/sendMessage?chat_id=&text={message}" 149 | ``` 150 | 151 | `{message}` 部分会自动替换成错误信息,接口采用 get 方式发送 152 | 153 | 154 | ## 直播间 ID 问题 155 | config 文件中有个 `ROOM_ID` 配置,填写此项可以清空临过期礼物给指定直播间。 156 | 157 | 通常可以在直播间页面的 url 获取到它 158 | ``` 159 | http://live.bilibili.com/3746256 160 | ``` 161 | 162 | 所有直播间号码小于 1000 的直播间为短号,该脚本在每次启动会自动修正,无需关心, 163 | 164 | 165 | ## 小电视抽奖 166 | config 文件中开放了三个相关设置,这里作下说明 167 | 168 | |key|description| 169 | |---|---| 170 | |SOCKET_ROOM_ID|弹幕监控直播间,这里通常选择音乐台等 24 小时直播,并且弹幕较少的房间,例如 23058| 171 | |SMALLTV_RATE|小电视抽奖的比率,可以设置 0-100 的整数,作用是随机加入抽奖的概率| 172 | |SMALLTV_HOURS|小电视抽奖的时段,采用 24 小时制,通常设置为正常人看直播的时段,数字间用英文逗号隔开| 173 | 174 | 175 | 176 | ## 分支 177 | - 多帐号 https://github.com/NeverBehave/BilibiliHelper 178 | - lkeme https://github.com/lkeme/BiliHelper 179 | 180 | 181 | ## License 许可证 182 | 183 | 本项目基于 MIT 协议发布,并增加了 SATA 协议。 184 | 185 | 当你使用了使用 SATA 的开源软件或文档的时候,在遵守基础许可证的前提下,你必须马不停蹄地给你所使用的开源项目 “点赞” ,比如在 GitHub 上 star,然后你必须感谢这个帮助了你的开源项目的作者,作者信息可以在许可证头部的版权声明部分找到。 186 | 187 | 本项目的所有代码文件、配置项,除另有说明外,均基于上述介绍的协议发布,具体请看分支下的 LICENSE。 188 | 189 | 此处的文字仅用于说明,条款以 LICENSE 文件中的内容为准。 190 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metowolf/bilibili", 3 | "description": "B 站自动领瓜子、直播挂机脚本", 4 | "type": "project", 5 | "require": { 6 | "php": ">=5.6.0", 7 | "ext-curl": "*", 8 | "ext-openssl": "*", 9 | "ext-sockets": "*", 10 | "vlucas/phpdotenv": "^2.4", 11 | "monolog/monolog": "^1.23", 12 | "bramus/monolog-colored-line-formatter": "^2.0", 13 | "guzzlehttp/guzzle": "^6.3", 14 | "wrench/wrench": "~2.0", 15 | "clue/socket-raw": "^1.2" 16 | }, 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "metowolf", 21 | "email": "i@i-meto.com", 22 | "homepage": "https://i-meto.com" 23 | } 24 | ], 25 | "autoload": { 26 | "psr-4": { 27 | "BilibiliHelper\\Plugin\\": "src/plugins", 28 | "BilibiliHelper\\Lib\\": "src/libs" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "eb7a65abd9eb979fe71917a93d8a0760", 8 | "packages": [ 9 | { 10 | "name": "bramus/ansi-php", 11 | "version": "3.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/bramus/ansi-php.git", 15 | "reference": "c0dcf9a949e1c6111bbbb52a8ead34776064b209" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://files.phpcomposer.com/files/bramus/ansi-php/c0dcf9a949e1c6111bbbb52a8ead34776064b209.zip", 20 | "reference": "c0dcf9a949e1c6111bbbb52a8ead34776064b209", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.4.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "~4.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-4": { 32 | "Bramus\\Ansi\\": "src/" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Bramus Van Damme", 42 | "email": "bramus@bram.us", 43 | "homepage": "https://www.bram.us/" 44 | } 45 | ], 46 | "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps", 47 | "time": "2015-01-08T15:02:37+00:00" 48 | }, 49 | { 50 | "name": "bramus/monolog-colored-line-formatter", 51 | "version": "2.0.3", 52 | "source": { 53 | "type": "git", 54 | "url": "https://github.com/bramus/monolog-colored-line-formatter.git", 55 | "reference": "6bff15eee00afe2690642535b0f1541f10852c2b" 56 | }, 57 | "dist": { 58 | "type": "zip", 59 | "url": "https://files.phpcomposer.com/files/bramus/monolog-colored-line-formatter/6bff15eee00afe2690642535b0f1541f10852c2b.zip", 60 | "reference": "6bff15eee00afe2690642535b0f1541f10852c2b", 61 | "shasum": "" 62 | }, 63 | "require": { 64 | "bramus/ansi-php": "~3.0", 65 | "php": ">=5.4.0" 66 | }, 67 | "require-dev": { 68 | "monolog/monolog": "~1.0", 69 | "phpunit/phpunit": "~4.0" 70 | }, 71 | "type": "library", 72 | "autoload": { 73 | "psr-4": { 74 | "Bramus\\Monolog\\": "src/" 75 | } 76 | }, 77 | "notification-url": "https://packagist.org/downloads/", 78 | "license": [ 79 | "MIT" 80 | ], 81 | "authors": [ 82 | { 83 | "name": "Bramus Van Damme", 84 | "email": "bramus@bram.us", 85 | "homepage": "https://www.bram.us/" 86 | } 87 | ], 88 | "description": "Colored Line Formatter for Monolog", 89 | "time": "2015-01-07T22:12:35+00:00" 90 | }, 91 | { 92 | "name": "clue/socket-raw", 93 | "version": "v1.2.0", 94 | "source": { 95 | "type": "git", 96 | "url": "https://github.com/clue/php-socket-raw.git", 97 | "reference": "8a10282bcb1e45e076a8a8285237681380e55ea5" 98 | }, 99 | "dist": { 100 | "type": "zip", 101 | "url": "https://files.phpcomposer.com/files/clue/php-socket-raw/8a10282bcb1e45e076a8a8285237681380e55ea5.zip", 102 | "reference": "8a10282bcb1e45e076a8a8285237681380e55ea5", 103 | "shasum": "" 104 | }, 105 | "require": { 106 | "ext-sockets": "*", 107 | "php": ">=5.3" 108 | }, 109 | "type": "library", 110 | "autoload": { 111 | "psr-4": { 112 | "Socket\\Raw\\": "src" 113 | } 114 | }, 115 | "notification-url": "https://packagist.org/downloads/", 116 | "license": [ 117 | "MIT" 118 | ], 119 | "authors": [ 120 | { 121 | "name": "Christian Lück", 122 | "email": "christian@lueck.tv" 123 | } 124 | ], 125 | "description": "Simple and lightweight OOP wrapper for PHP's low level sockets extension (ext-sockets)", 126 | "homepage": "https://github.com/clue/php-socket-raw", 127 | "keywords": [ 128 | "Socket", 129 | "client", 130 | "datagram", 131 | "dgram", 132 | "icmp", 133 | "ipv6", 134 | "server", 135 | "stream", 136 | "tcp", 137 | "udg", 138 | "udp", 139 | "unix" 140 | ], 141 | "time": "2015-03-18T23:48:01+00:00" 142 | }, 143 | { 144 | "name": "guzzlehttp/guzzle", 145 | "version": "6.3.3", 146 | "source": { 147 | "type": "git", 148 | "url": "https://github.com/guzzle/guzzle.git", 149 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" 150 | }, 151 | "dist": { 152 | "type": "zip", 153 | "url": "https://files.phpcomposer.com/files/guzzle/guzzle/407b0cb880ace85c9b63c5f9551db498cb2d50ba.zip", 154 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", 155 | "shasum": "" 156 | }, 157 | "require": { 158 | "guzzlehttp/promises": "^1.0", 159 | "guzzlehttp/psr7": "^1.4", 160 | "php": ">=5.5" 161 | }, 162 | "require-dev": { 163 | "ext-curl": "*", 164 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", 165 | "psr/log": "^1.0" 166 | }, 167 | "suggest": { 168 | "psr/log": "Required for using the Log middleware" 169 | }, 170 | "type": "library", 171 | "extra": { 172 | "branch-alias": { 173 | "dev-master": "6.3-dev" 174 | } 175 | }, 176 | "autoload": { 177 | "files": [ 178 | "src/functions_include.php" 179 | ], 180 | "psr-4": { 181 | "GuzzleHttp\\": "src/" 182 | } 183 | }, 184 | "notification-url": "https://packagist.org/downloads/", 185 | "license": [ 186 | "MIT" 187 | ], 188 | "authors": [ 189 | { 190 | "name": "Michael Dowling", 191 | "email": "mtdowling@gmail.com", 192 | "homepage": "https://github.com/mtdowling" 193 | } 194 | ], 195 | "description": "Guzzle is a PHP HTTP client library", 196 | "homepage": "http://guzzlephp.org/", 197 | "keywords": [ 198 | "client", 199 | "curl", 200 | "framework", 201 | "http", 202 | "http client", 203 | "rest", 204 | "web service" 205 | ], 206 | "time": "2018-04-22T15:46:56+00:00" 207 | }, 208 | { 209 | "name": "guzzlehttp/promises", 210 | "version": "v1.3.1", 211 | "source": { 212 | "type": "git", 213 | "url": "https://github.com/guzzle/promises.git", 214 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" 215 | }, 216 | "dist": { 217 | "type": "zip", 218 | "url": "https://files.phpcomposer.com/files/guzzle/promises/a59da6cf61d80060647ff4d3eb2c03a2bc694646.zip", 219 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", 220 | "shasum": "" 221 | }, 222 | "require": { 223 | "php": ">=5.5.0" 224 | }, 225 | "require-dev": { 226 | "phpunit/phpunit": "^4.0" 227 | }, 228 | "type": "library", 229 | "extra": { 230 | "branch-alias": { 231 | "dev-master": "1.4-dev" 232 | } 233 | }, 234 | "autoload": { 235 | "psr-4": { 236 | "GuzzleHttp\\Promise\\": "src/" 237 | }, 238 | "files": [ 239 | "src/functions_include.php" 240 | ] 241 | }, 242 | "notification-url": "https://packagist.org/downloads/", 243 | "license": [ 244 | "MIT" 245 | ], 246 | "authors": [ 247 | { 248 | "name": "Michael Dowling", 249 | "email": "mtdowling@gmail.com", 250 | "homepage": "https://github.com/mtdowling" 251 | } 252 | ], 253 | "description": "Guzzle promises library", 254 | "keywords": [ 255 | "promise" 256 | ], 257 | "time": "2016-12-20T10:07:11+00:00" 258 | }, 259 | { 260 | "name": "guzzlehttp/psr7", 261 | "version": "1.4.2", 262 | "source": { 263 | "type": "git", 264 | "url": "https://github.com/guzzle/psr7.git", 265 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" 266 | }, 267 | "dist": { 268 | "type": "zip", 269 | "url": "https://files.phpcomposer.com/files/guzzle/psr7/f5b8a8512e2b58b0071a7280e39f14f72e05d87c.zip", 270 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", 271 | "shasum": "" 272 | }, 273 | "require": { 274 | "php": ">=5.4.0", 275 | "psr/http-message": "~1.0" 276 | }, 277 | "provide": { 278 | "psr/http-message-implementation": "1.0" 279 | }, 280 | "require-dev": { 281 | "phpunit/phpunit": "~4.0" 282 | }, 283 | "type": "library", 284 | "extra": { 285 | "branch-alias": { 286 | "dev-master": "1.4-dev" 287 | } 288 | }, 289 | "autoload": { 290 | "psr-4": { 291 | "GuzzleHttp\\Psr7\\": "src/" 292 | }, 293 | "files": [ 294 | "src/functions_include.php" 295 | ] 296 | }, 297 | "notification-url": "https://packagist.org/downloads/", 298 | "license": [ 299 | "MIT" 300 | ], 301 | "authors": [ 302 | { 303 | "name": "Michael Dowling", 304 | "email": "mtdowling@gmail.com", 305 | "homepage": "https://github.com/mtdowling" 306 | }, 307 | { 308 | "name": "Tobias Schultze", 309 | "homepage": "https://github.com/Tobion" 310 | } 311 | ], 312 | "description": "PSR-7 message implementation that also provides common utility methods", 313 | "keywords": [ 314 | "http", 315 | "message", 316 | "request", 317 | "response", 318 | "stream", 319 | "uri", 320 | "url" 321 | ], 322 | "time": "2017-03-20T17:10:46+00:00" 323 | }, 324 | { 325 | "name": "monolog/monolog", 326 | "version": "1.23.0", 327 | "source": { 328 | "type": "git", 329 | "url": "https://github.com/Seldaek/monolog.git", 330 | "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" 331 | }, 332 | "dist": { 333 | "type": "zip", 334 | "url": "https://files.phpcomposer.com/files/Seldaek/monolog/fd8c787753b3a2ad11bc60c063cff1358a32a3b4.zip", 335 | "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", 336 | "shasum": "" 337 | }, 338 | "require": { 339 | "php": ">=5.3.0", 340 | "psr/log": "~1.0" 341 | }, 342 | "provide": { 343 | "psr/log-implementation": "1.0.0" 344 | }, 345 | "require-dev": { 346 | "aws/aws-sdk-php": "^2.4.9 || ^3.0", 347 | "doctrine/couchdb": "~1.0@dev", 348 | "graylog2/gelf-php": "~1.0", 349 | "jakub-onderka/php-parallel-lint": "0.9", 350 | "php-amqplib/php-amqplib": "~2.4", 351 | "php-console/php-console": "^3.1.3", 352 | "phpunit/phpunit": "~4.5", 353 | "phpunit/phpunit-mock-objects": "2.3.0", 354 | "ruflin/elastica": ">=0.90 <3.0", 355 | "sentry/sentry": "^0.13", 356 | "swiftmailer/swiftmailer": "^5.3|^6.0" 357 | }, 358 | "suggest": { 359 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 360 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server", 361 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 362 | "ext-mongo": "Allow sending log messages to a MongoDB server", 363 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 364 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", 365 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", 366 | "php-console/php-console": "Allow sending log messages to Google Chrome", 367 | "rollbar/rollbar": "Allow sending log messages to Rollbar", 368 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 369 | "sentry/sentry": "Allow sending log messages to a Sentry server" 370 | }, 371 | "type": "library", 372 | "extra": { 373 | "branch-alias": { 374 | "dev-master": "2.0.x-dev" 375 | } 376 | }, 377 | "autoload": { 378 | "psr-4": { 379 | "Monolog\\": "src/Monolog" 380 | } 381 | }, 382 | "notification-url": "https://packagist.org/downloads/", 383 | "license": [ 384 | "MIT" 385 | ], 386 | "authors": [ 387 | { 388 | "name": "Jordi Boggiano", 389 | "email": "j.boggiano@seld.be", 390 | "homepage": "http://seld.be" 391 | } 392 | ], 393 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services", 394 | "homepage": "http://github.com/Seldaek/monolog", 395 | "keywords": [ 396 | "log", 397 | "logging", 398 | "psr-3" 399 | ], 400 | "time": "2017-06-19T01:22:40+00:00" 401 | }, 402 | { 403 | "name": "psr/http-message", 404 | "version": "1.0.1", 405 | "source": { 406 | "type": "git", 407 | "url": "https://github.com/php-fig/http-message.git", 408 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 409 | }, 410 | "dist": { 411 | "type": "zip", 412 | "url": "https://files.phpcomposer.com/files/php-fig/http-message/f6561bf28d520154e4b0ec72be95418abe6d9363.zip", 413 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 414 | "shasum": "" 415 | }, 416 | "require": { 417 | "php": ">=5.3.0" 418 | }, 419 | "type": "library", 420 | "extra": { 421 | "branch-alias": { 422 | "dev-master": "1.0.x-dev" 423 | } 424 | }, 425 | "autoload": { 426 | "psr-4": { 427 | "Psr\\Http\\Message\\": "src/" 428 | } 429 | }, 430 | "notification-url": "https://packagist.org/downloads/", 431 | "license": [ 432 | "MIT" 433 | ], 434 | "authors": [ 435 | { 436 | "name": "PHP-FIG", 437 | "homepage": "http://www.php-fig.org/" 438 | } 439 | ], 440 | "description": "Common interface for HTTP messages", 441 | "homepage": "https://github.com/php-fig/http-message", 442 | "keywords": [ 443 | "http", 444 | "http-message", 445 | "psr", 446 | "psr-7", 447 | "request", 448 | "response" 449 | ], 450 | "time": "2016-08-06T14:39:51+00:00" 451 | }, 452 | { 453 | "name": "psr/log", 454 | "version": "1.0.2", 455 | "source": { 456 | "type": "git", 457 | "url": "https://github.com/php-fig/log.git", 458 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" 459 | }, 460 | "dist": { 461 | "type": "zip", 462 | "url": "https://files.phpcomposer.com/files/php-fig/log/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d.zip", 463 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 464 | "shasum": "" 465 | }, 466 | "require": { 467 | "php": ">=5.3.0" 468 | }, 469 | "type": "library", 470 | "extra": { 471 | "branch-alias": { 472 | "dev-master": "1.0.x-dev" 473 | } 474 | }, 475 | "autoload": { 476 | "psr-4": { 477 | "Psr\\Log\\": "Psr/Log/" 478 | } 479 | }, 480 | "notification-url": "https://packagist.org/downloads/", 481 | "license": [ 482 | "MIT" 483 | ], 484 | "authors": [ 485 | { 486 | "name": "PHP-FIG", 487 | "homepage": "http://www.php-fig.org/" 488 | } 489 | ], 490 | "description": "Common interface for logging libraries", 491 | "homepage": "https://github.com/php-fig/log", 492 | "keywords": [ 493 | "log", 494 | "psr", 495 | "psr-3" 496 | ], 497 | "time": "2016-10-10T12:19:37+00:00" 498 | }, 499 | { 500 | "name": "vlucas/phpdotenv", 501 | "version": "v2.4.0", 502 | "source": { 503 | "type": "git", 504 | "url": "https://github.com/vlucas/phpdotenv.git", 505 | "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" 506 | }, 507 | "dist": { 508 | "type": "zip", 509 | "url": "https://files.phpcomposer.com/files/vlucas/phpdotenv/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c.zip", 510 | "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", 511 | "shasum": "" 512 | }, 513 | "require": { 514 | "php": ">=5.3.9" 515 | }, 516 | "require-dev": { 517 | "phpunit/phpunit": "^4.8 || ^5.0" 518 | }, 519 | "type": "library", 520 | "extra": { 521 | "branch-alias": { 522 | "dev-master": "2.4-dev" 523 | } 524 | }, 525 | "autoload": { 526 | "psr-4": { 527 | "Dotenv\\": "src/" 528 | } 529 | }, 530 | "notification-url": "https://packagist.org/downloads/", 531 | "license": [ 532 | "BSD-3-Clause-Attribution" 533 | ], 534 | "authors": [ 535 | { 536 | "name": "Vance Lucas", 537 | "email": "vance@vancelucas.com", 538 | "homepage": "http://www.vancelucas.com" 539 | } 540 | ], 541 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 542 | "keywords": [ 543 | "dotenv", 544 | "env", 545 | "environment" 546 | ], 547 | "time": "2016-09-01T10:05:43+00:00" 548 | }, 549 | { 550 | "name": "wrench/wrench", 551 | "version": "v2.0.8", 552 | "source": { 553 | "type": "git", 554 | "url": "https://github.com/varspool/Wrench.git", 555 | "reference": "92fb5d1c5d7a6f65884ade658b6271de93edafc2" 556 | }, 557 | "dist": { 558 | "type": "zip", 559 | "url": "https://files.phpcomposer.com/files/varspool/Wrench/92fb5d1c5d7a6f65884ade658b6271de93edafc2.zip", 560 | "reference": "92fb5d1c5d7a6f65884ade658b6271de93edafc2", 561 | "shasum": "" 562 | }, 563 | "require": { 564 | "ext-sockets": "*", 565 | "php": ">=5.3" 566 | }, 567 | "require-dev": { 568 | "phpunit/phpunit": "~4.5", 569 | "squizlabs/php_codesniffer": "*" 570 | }, 571 | "type": "library", 572 | "extra": { 573 | "branch-alias": { 574 | "dev-master": "2.0.x-dev" 575 | } 576 | }, 577 | "autoload": { 578 | "psr-0": { 579 | "Wrench": "lib/" 580 | } 581 | }, 582 | "notification-url": "https://packagist.org/downloads/", 583 | "license": [ 584 | "WTFPL" 585 | ], 586 | "authors": [ 587 | { 588 | "name": "Dominic Scheirlinck", 589 | "email": "dominic@varspool.com", 590 | "homepage": "http://www.somethingemporium.com/" 591 | }, 592 | { 593 | "name": "Simon Samtleben", 594 | "email": "web@lemmingzshadow.net", 595 | "homepage": "http://lemmingzshadow.net/" 596 | }, 597 | { 598 | "name": "Nico Kaiser", 599 | "email": "nico@kaiser.me", 600 | "homepage": "http://siriux.net/" 601 | } 602 | ], 603 | "description": "PHP WebSocket client/server library", 604 | "homepage": "http://github.com/varspool/Wrench", 605 | "keywords": [ 606 | "WebSockets", 607 | "hybi", 608 | "websocket" 609 | ], 610 | "time": "2017-02-12T02:08:51+00:00" 611 | } 612 | ], 613 | "packages-dev": [], 614 | "aliases": [], 615 | "minimum-stability": "stable", 616 | "stability-flags": [], 617 | "prefer-stable": false, 618 | "prefer-lowest": false, 619 | "platform": { 620 | "php": ">=5.6.0", 621 | "ext-curl": "*", 622 | "ext-openssl": "*", 623 | "ext-sockets": "*" 624 | }, 625 | "platform-dev": [] 626 | } 627 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !config.example -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | get('config'); 32 | $config = $t::parse($filename); 33 | 34 | while (true) { 35 | foreach ($plugins as $plugin) { 36 | $t = $app->get($plugin); 37 | $t::run($config); 38 | } 39 | sleep(10); 40 | } -------------------------------------------------------------------------------- /multiple.php: -------------------------------------------------------------------------------- 1 | overload(); 36 | // 账户设置 37 | $conf->required(['APP_USER', 'APP_PASS'])->notEmpty(); 38 | $conf->required(['ACCESS_TOKEN', 'REFRESH_TOKEN', 'COOKIE_JAR']); 39 | // 功能设置 40 | $conf->required(['ROOM_ID', 'SOCKET_ROOM_ID', 'SMALLTV_RATE'])->isInteger(); 41 | $conf->required(['SMALLTV_HOURS']); 42 | // 网络设置 43 | $conf->required(['NETWORK_PROXY']); 44 | $conf->required(['SOCKET_SERVER_PORT'])->isInteger(); 45 | $conf->required(['SOCKET_SERVER_ADDR']); 46 | // 日志设置 47 | $conf->required(['APP_DEBUG', 'APP_MULTIPLE'])->allowedValues(['true', 'false']); 48 | $conf->required(['APP_USER_IDENTITY', 'CALLBACK_URL']); 49 | $conf->required(['CALLBACK_LEVEL'])->isInteger(); 50 | 51 | } catch (\Exception $e) { 52 | echo $e->getMessage(), PHP_EOL; 53 | echo "当前配置文件 config 不完整,请重新覆盖并填写配置文件", PHP_EOL, PHP_EOL; 54 | echo " $ mv config config.old", PHP_EOL; 55 | echo " $ cp config.example config", PHP_EOL, PHP_EOL; 56 | exit(1); 57 | } 58 | 59 | return [ 60 | 'path' => __DIR__ . '/../../' . $configName, 61 | 'config' => [ 62 | // 账户设置 63 | 'APP_USER' => getenv('APP_USER'), 64 | 'APP_PASS' => getenv('APP_PASS'), 65 | 'ACCESS_TOKEN' => getenv('ACCESS_TOKEN'), 66 | 'REFRESH_TOKEN' => getenv('REFRESH_TOKEN'), 67 | 'COOKIE_JAR' => empty(getenv('COOKIE_JAR')) ? '[]' : getenv('COOKIE_JAR'), 68 | // 功能设置 69 | 'ROOM_ID' => intval(getenv('ROOM_ID')), 70 | 'SOCKET_ROOM_ID' => intval(getenv('SOCKET_ROOM_ID')), 71 | 'SMALLTV_RATE' => intval(getenv('SMALLTV_RATE')), 72 | 'SMALLTV_HOURS' => array_map('intval', explode(',', getenv('SMALLTV_HOURS'))), 73 | // 网络设置 74 | 'NETWORK_PROXY' => getenv('NETWORK_PROXY'), 75 | 'SOCKET_SERVER_PORT' => intval(getenv('SOCKET_SERVER_PORT')), 76 | 'SOCKET_SERVER_ADDR' => getenv('SOCKET_SERVER_ADDR'), 77 | // 日志设置 78 | 'APP_DEBUG' => getenv('APP_DEBUG') === 'true', 79 | 'APP_MULTIPLE' => getenv('APP_MULTIPLE') === 'true', 80 | 'APP_USER_IDENTITY' => getenv('APP_USER_IDENTITY'), 81 | 'CALLBACK_URL' => getenv('CALLBACK_URL'), 82 | 'CALLBACK_LEVEL' => intval(getenv('CALLBACK_LEVEL')), 83 | ], 84 | ]; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/libs/Curl.php: -------------------------------------------------------------------------------- 1 | __construct(true, $cookie); 41 | } 42 | 43 | protected static function configureInstance() 44 | { 45 | $options = [ 46 | 'headers' => [ 47 | 'Accept' => '*/*', 48 | 'Accept-Encoding' => 'gzip', 49 | 'Accept-Language' => 'zh-cn', 50 | 'Connection' => 'keep-alive', 51 | 'Content-Type' => 'application/x-www-form-urlencoded', 52 | 'User-Agent' => 'bili-universal/6680 CFNetwork/897.15 Darwin/17.5.0', 53 | 'Referer' => 'https://live.bilibili.com/'.static::$config['config']['SOCKET_ROOM_ID'], 54 | ], 55 | 'timeout' => 20.0, 56 | 'http_errors' => false, 57 | ]; 58 | if (!empty(static::$config['config']['NETWORK_PROXY'])) { 59 | $options['proxy'] = static::$config['config']['NETWORK_PROXY']; 60 | } 61 | 62 | self::$instance = new \GuzzleHttp\Client($options); 63 | } 64 | 65 | public static function get($url, $params = []) 66 | { 67 | Log::debug('GET: '.$url); 68 | $payload = [ 69 | 'cookies' => self::getJar(), 70 | ]; 71 | if (count($params)) { 72 | $payload['query'] = $params; 73 | } 74 | $request = self::getClient()->get($url, $payload); 75 | $body = $request->getBody(); 76 | Log::debug($body); 77 | return $body; 78 | } 79 | 80 | public static function post($url, $params = []) 81 | { 82 | Log::debug('POST: '.$url); 83 | $payload = [ 84 | 'cookies' => self::getJar(), 85 | ]; 86 | if (count($params)) { 87 | $payload['form_params'] = $params; 88 | } 89 | $request = self::getClient()->post($url, $payload); 90 | $body = $request->getBody(); 91 | Log::debug($body); 92 | return $body; 93 | } 94 | 95 | public static function cookie() 96 | { 97 | $cookies = self::getJar()->toArray(); 98 | $cookies = json_encode($cookies); 99 | Log::debug($cookies); 100 | return $cookies; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/libs/Helper.php: -------------------------------------------------------------------------------- 1 | registerAll(); 34 | } 35 | 36 | private function registerAll() 37 | { 38 | $this->set('auth', Auth::getInstance()); 39 | $this->set('capsule', Capsule::getInstance()); 40 | $this->set('config', Config::getInstance()); 41 | $this->set('dailyBag', DailyBag::getInstance()); 42 | $this->set('giftSend', GiftSend::getInstance()); 43 | $this->set('group', Group::getInstance()); 44 | $this->set('heart', Heart::getInstance()); 45 | $this->set('silver', Silver::getInstance()); 46 | $this->set('smallTV', SmallTV::getInstance()); 47 | $this->set('task', Task::getInstance()); 48 | //$this->set('websocket', Danmaku::getInstance()); 49 | $this->set('socketClient', SocketClient::getInstance()); 50 | } 51 | 52 | public function get($name) 53 | { 54 | if ($this->isRegister($name)) { 55 | return $this->helper[$name]; 56 | } 57 | else { 58 | return false; 59 | } 60 | } 61 | 62 | public function set($name, $value) 63 | { 64 | if (!isset($name) || !isset($value)) { 65 | return false; 66 | } 67 | $this->helper[$name] = $value; 68 | return $this->helper[$name]; 69 | } 70 | 71 | private function isRegister($name) 72 | { 73 | return isset($this->helper[$name]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/libs/Log.php: -------------------------------------------------------------------------------- 1 | setFormatter(new ColoredLineFormatter(null, "[%datetime%] %channel%.%level_name%: %message%\n")); 40 | 41 | $logger = new Logger('Bilibili'); 42 | $logger->pushHandler($handler); 43 | 44 | self::$instance = $logger; 45 | } 46 | 47 | private static function prefix() 48 | { 49 | if (static::$config['config']['APP_MULTIPLE']) { 50 | return '[' . (empty($t = static::$config['config']['APP_USER_IDENTITY']) ? static::$config['config']['APP_USER'] : $t) . ']'; 51 | } 52 | return ''; 53 | } 54 | 55 | public static function debug($message, array $context = []) 56 | { 57 | $message = self::prefix() . $message; 58 | self::getLogger()->addDebug($message, $context); 59 | } 60 | 61 | public static function info($message, array $context = []) 62 | { 63 | $message = self::prefix() . $message; 64 | self::getLogger()->addInfo($message, $context); 65 | self::callback(Logger::INFO, 'INFO', $message); 66 | } 67 | 68 | public static function notice($message, array $context = []) 69 | { 70 | $message = self::prefix() . $message; 71 | self::getLogger()->addNotice($message, $context); 72 | self::callback(Logger::NOTICE, 'NOTICE', $message); 73 | } 74 | 75 | public static function warning($message, array $context = []) 76 | { 77 | $message = self::prefix() . $message; 78 | self::getLogger()->addWarning($message, $context); 79 | self::callback(Logger::WARNING, 'WARNING', $message); 80 | } 81 | 82 | public static function error($message, array $context = []) 83 | { 84 | $message = self::prefix() . $message; 85 | self::getLogger()->addError($message, $context); 86 | self::callback(Logger::ERROR, 'ERROR', $message); 87 | } 88 | 89 | public static function callback($levelId, $level, $message) 90 | { 91 | if (empty(static::$config['config']['CALLBACK_URL'])) { 92 | return; 93 | } 94 | $callback_level = intval(static::$config['config']['CALLBACK_LEVEL']); 95 | if ($levelId >= $callback_level) { 96 | $url = str_replace('{account}', self::prefix(), static::$config['config']['CALLBACK_URL']); 97 | $url = str_replace('{level}', $level, $url); 98 | $url = str_replace('{message}', urlencode($message), $url); 99 | Curl::get($url); 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/plugins/Auth.php: -------------------------------------------------------------------------------- 1 | time()) { 30 | return; 31 | } 32 | 33 | if (static::config('ACCESS_TOKEN') == '') { 34 | static::loginPassword(); 35 | } else { 36 | static::loginToken(); 37 | } 38 | 39 | static::checkCookie(); 40 | 41 | static::data('lock', time() + 600); 42 | } 43 | 44 | protected static function loginPassword() 45 | { 46 | $data = static::getPublicKey(); 47 | 48 | $user = static::config('APP_USER'); 49 | $pass = static::config('APP_PASS'); 50 | $key = $data['data']['key']; 51 | $hash = $data['data']['hash']; 52 | openssl_public_encrypt($hash.$pass, $crypt, $key); 53 | 54 | static::getToken($user, base64_encode($crypt)); 55 | static::saveCookie(); 56 | } 57 | 58 | protected static function loginToken() 59 | { 60 | if (! static::checkToken()) { 61 | Log::warning('检测到令牌即将过期'); 62 | Log::info('申请更换令牌'); 63 | if (! static::refresh()) { 64 | Log::warning('更换令牌失败'); 65 | Log::info('使用帐号密码方式登录'); 66 | static::loginPassword(); 67 | } 68 | static::saveCookie(); 69 | } 70 | } 71 | 72 | protected static function getCookie() 73 | { 74 | $payload = []; 75 | $data = Curl::get('https://passport.bilibili.com/api/login/sso', static::sign($payload)); 76 | 77 | static::saveCookie(); 78 | } 79 | 80 | protected static function saveCookie() 81 | { 82 | static::config('COOKIE_JAR', Curl::cookie()); 83 | } 84 | 85 | protected static function checkCookie() 86 | { 87 | $payload = [ 88 | 'ts' => time(), 89 | ]; 90 | $data = Curl::get('https://api.live.bilibili.com/User/getUserInfo', $payload); 91 | $data = json_decode($data, true); 92 | 93 | if (isset($data['code']) && $data['code'] != 'REPONSE_OK') { 94 | Log::error('检测到 Cookie 过期'); 95 | static::getCookie(); 96 | } 97 | } 98 | 99 | protected static function checkToken() 100 | { 101 | $payload = [ 102 | 'access_token' => static::config('ACCESS_TOKEN'), 103 | ]; 104 | $data = Curl::get('https://passport.bilibili.com/api/oauth2/info', static::sign($payload)); 105 | $data = json_decode($data, true); 106 | 107 | if (isset($data['code']) && $data['code']) { 108 | Log::error('令牌验证失败'); 109 | return false; 110 | } 111 | Log::info('令牌验证成功,有效期: '.date('Y-m-d H:i:s', $data['ts']+$data['data']['expires_in'])); 112 | 113 | return $data['data']['expires_in'] > 86400; 114 | } 115 | 116 | protected static function refresh() 117 | { 118 | $payload = [ 119 | 'access_token' => static::config('ACCESS_TOKEN'), 120 | 'refresh_token' => static::config('REFRESH_TOKEN'), 121 | ]; 122 | $data = Curl::post('https://passport.bilibili.com/api/oauth2/refreshToken', static::sign($payload)); 123 | $data = json_decode($data, true); 124 | 125 | if (isset($data['code']) && $data['code']) { 126 | Log::error('续签令牌失败', ['msg' => $data['message']]); 127 | return false; 128 | } else { 129 | Log::notice('续签令牌成功'); 130 | } 131 | 132 | return true; 133 | } 134 | 135 | protected static function getPublicKey() 136 | { 137 | $payload = []; 138 | $data = Curl::post('https://passport.bilibili.com/api/oauth2/getKey', static::sign($payload)); 139 | $data = json_decode($data, true); 140 | 141 | if (isset($data['code']) && $data['code']) { 142 | Log::error('公钥获取失败', ['msg' => $data['message']]); 143 | } 144 | Log::info('公钥获取成功'); 145 | 146 | return $data; 147 | } 148 | 149 | protected static function getToken($username, $password) 150 | { 151 | $payload = [ 152 | 'subid' => 1, 153 | 'permission' => 'ALL', 154 | 'username' => $username, 155 | 'password' => $password, 156 | 'captcha' => '', 157 | ]; 158 | $data = Curl::post('https://passport.bilibili.com/api/v2/oauth2/login', static::sign($payload)); 159 | $data = json_decode($data, true); 160 | 161 | if (isset($data['code']) && $data['code']) { 162 | Log::error('帐号登录失败'); 163 | throw new \Exception($data['message']); 164 | } 165 | Log::notice('帐号登录成功'); 166 | 167 | static::config('ACCESS_TOKEN', $data['data']['token_info']['access_token']); 168 | static::config('REFRESH_TOKEN', $data['data']['token_info']['refresh_token']); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/plugins/Base.php: -------------------------------------------------------------------------------- 1 | static::$config['config']['ACCESS_TOKEN'], 80 | 'actionKey' => 'appkey', 81 | 'appkey' => $appkey, 82 | 'build' => '6680', 83 | 'device' => 'phone', 84 | 'mobi_app' => 'iphone', 85 | 'platform' => 'ios', 86 | 'ts' => time(), 87 | ]; 88 | $payload = array_merge($payload, $default); 89 | if (isset($payload['sign'])) { 90 | unset($payload['sign']); 91 | } 92 | ksort($payload); 93 | $data = http_build_query($payload); 94 | $payload['sign'] = md5($data . $appsecret); 95 | 96 | return $payload; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/plugins/Capsule.php: -------------------------------------------------------------------------------- 1 | time()) { 30 | return; 31 | } 32 | 33 | $count = static::info(); 34 | $step = 100; 35 | while ($count && $step) { 36 | while ($count >= $step) { 37 | $count = static::open($step); 38 | sleep(mt_rand(0, 5)); 39 | } 40 | $step = intval($step / 10); 41 | } 42 | 43 | static::data('lock', time() + 86400); 44 | } 45 | 46 | public static function info() 47 | { 48 | $payload = []; 49 | $data = Curl::post('https://api.live.bilibili.com/AppUser/capsuleInfo', static::sign($payload)); 50 | $data = json_decode($data, true); 51 | 52 | if (isset($data['code']) && $data['code']) { 53 | Log::warning("扭蛋币余额查询异常"); 54 | return 0; 55 | } 56 | Log::info("当前还有 {$data['data']['normal']['coin']} 枚扭蛋币"); 57 | 58 | return $data['data']['normal']['coin']; 59 | } 60 | 61 | public static function open($num) 62 | { 63 | $payload = [ 64 | 'count' => $num, 65 | 'type' => 'normal', 66 | ]; 67 | $data = Curl::get('https://api.live.bilibili.com/AppUser/capsuleInfoOpen', static::sign($payload)); 68 | $data = json_decode($data, true); 69 | 70 | if (isset($data['code']) && $data['code']) { 71 | Log::warning("扭蛋失败,稍后重试"); 72 | return 0; 73 | } 74 | 75 | if (isset($data['data']['text'])) { 76 | foreach ($data['data']['text'] as $vo) { 77 | Log::notice("扭蛋成功,获得 {$vo['num']} 个{$vo['name']}"); 78 | } 79 | } 80 | 81 | return isset($data['data']['coin']) ? $data['data']['coin'] : 0; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/plugins/DailyBag.php: -------------------------------------------------------------------------------- 1 | time()) { 45 | return; 46 | } 47 | 48 | static::heart(); 49 | static::receive(); 50 | } 51 | 52 | protected static function type($id) 53 | { 54 | $option = [ 55 | 0x0002 => 'WS_OP_HEARTBEAT', 56 | 0x0003 => 'WS_OP_HEARTBEAT_REPLY', 57 | 0x0005 => 'WS_OP_MESSAGE', 58 | 0x0007 => 'WS_OP_USER_AUTHENTICATION', 59 | 0x0008 => 'WS_OP_CONNECT_SUCCESS', 60 | ]; 61 | return isset($option[$id]) ? $option[$id] : "WS_OP_UNKNOW($id)"; 62 | } 63 | 64 | protected static function parse($raw) 65 | { 66 | $data = json_decode($raw, true); 67 | if ($data['cmd'] == 'SYS_MSG' && isset($data['tv_id'])) { 68 | Log::debug($raw); 69 | Log::notice("直播间 {$data['real_roomid']} 开启了第 {$data['tv_id']} 轮小电视抽奖"); 70 | static::$config['data'][static::PLUGIN_NAME]['smalltv'][$data['tv_id']] = $data['real_roomid']; 71 | } 72 | } 73 | 74 | protected static function split($bin) 75 | { 76 | if (strlen($bin)) { 77 | $head = unpack('N*', substr($bin, 0, 16)); 78 | $bin = substr($bin, 16); 79 | 80 | $length = isset($head[1]) ? $head[1] : 16; 81 | $type = isset($head[3]) ? $head[3] : 0x0000; 82 | $body = substr($bin, 0, $length-16); 83 | 84 | Log::debug(static::type($type)." (len=$length)"); 85 | if ($type == 0x0005) { 86 | static::parse($body); 87 | } 88 | 89 | $bin = substr($bin, $length-16); 90 | if (strlen($bin)) { 91 | static::split($bin); 92 | } 93 | } 94 | } 95 | 96 | protected static function receive() 97 | { 98 | $responses = static::data('websocket')->receive(); 99 | if (is_array($responses)) { 100 | foreach ($responses as $response) { 101 | static::split($response->getPayload()); 102 | } 103 | } 104 | } 105 | 106 | protected static function connect() 107 | { 108 | Log::notice('连接弹幕服务器'); 109 | if (!static::data('websocket')->connect()) { 110 | Log::error('连接弹幕服务器失败'); 111 | static::data('lock', time()+60); 112 | return; 113 | } 114 | static::data('websocket')->sendData( 115 | static::packMsg(json_encode([ 116 | 'uid' => 0, 117 | 'roomid' => static::config('SOCKET_ROOM_ID'), 118 | 'protover' => 1, 119 | 'platform' => 'web', 120 | 'clientver' => '1.4.0', 121 | ]), 0x0007) 122 | ); 123 | } 124 | 125 | protected static function disconnect() 126 | { 127 | Log::info('断开弹幕服务器'); 128 | static::data('websocket')->disconnect(); 129 | } 130 | 131 | protected static function heart() { 132 | if (!static::data('websocket')->isConnected()) { 133 | static::connect(); 134 | return; 135 | } 136 | if (static::data('heartlock') <= time()) { 137 | if (static::data('websocket')->sendData(static::packMsg('', 0x0002))) { 138 | static::data('heartlock', time()+30); 139 | } 140 | } 141 | } 142 | 143 | protected static function packMsg($value, $option) { 144 | $head = pack('NnnNN', 0x10 + strlen($value), 0x10, 0x01, $option, 0x0001); 145 | $str = $head.$value; 146 | static::split($str); 147 | return $str; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/plugins/GiftSend.php: -------------------------------------------------------------------------------- 1 | time()) { 36 | return; 37 | } 38 | 39 | $data = static::getBagList(); 40 | 41 | if (isset($data['data']['list'])) { 42 | foreach ($data['data']['list'] as $vo) { 43 | if ($vo['expire_at'] >= $data['data']['time'] && $vo['expire_at'] <= $data['data']['time'] + 3600) { 44 | static::giftSend($vo); 45 | sleep(mt_rand(0, 5)); 46 | } 47 | } 48 | } 49 | 50 | static::data('lock', time() + 600); 51 | } 52 | 53 | 54 | protected static function giftSend($value) 55 | { 56 | $payload = [ 57 | 'coin_type' => 'silver', 58 | 'gift_id' => $value['gift_id'], 59 | 'ruid' => static::data('ruid'), 60 | 'uid' => static::data('uid'), 61 | 'biz_id' => static::data('roomid'), 62 | 'gift_num' => $value['gift_num'], 63 | 'data_source_id' => '', 64 | 'data_behavior_id' => '', 65 | 'bag_id' => $value['bag_id'] 66 | ]; 67 | 68 | $data = Curl::post('https://api.live.bilibili.com/gift/v2/live/bag_send', static::sign($payload)); 69 | $data = json_decode($data, true); 70 | 71 | if (isset($data['code']) && $data['code']) { 72 | Log::error("尝试向直播间投喂{$value['gift_name']}失败"); 73 | } else { 74 | Log::notice("成功向直播间 {$payload['biz_id']} 投喂了 {$value['gift_num']} 个{$value['gift_name']}"); 75 | } 76 | } 77 | 78 | protected static function getBagList() 79 | { 80 | $payload = []; 81 | $data = Curl::get('https://api.live.bilibili.com/gift/v2/gift/bag_list', static::sign($payload)); 82 | $data = json_decode($data, true); 83 | 84 | if (isset($data['code']) && $data['code']) { 85 | Log::error('背包查看失败'); 86 | return null; 87 | } 88 | 89 | return $data; 90 | } 91 | 92 | protected static function getRoomInfo() 93 | { 94 | Log::info('正在补全用户信息'); 95 | 96 | $payload = []; 97 | $data = Curl::get('https://account.bilibili.com/api/myinfo/v2', static::sign($payload)); 98 | $data = json_decode($data, true); 99 | 100 | if (isset($data['code']) && $data['code']) { 101 | Log::error('获取用户信息失败'); 102 | return false; 103 | } 104 | 105 | static::data('uid', $data['mid']); 106 | 107 | 108 | Log::info('正在补全直播间信息'); 109 | 110 | $payload = [ 111 | 'id' => static::config('ROOM_ID'), 112 | ]; 113 | $data = Curl::get('https://api.live.bilibili.com/room/v1/Room/get_info', static::sign($payload)); 114 | $data = json_decode($data, true); 115 | 116 | if (isset($data['code']) && $data['code']) { 117 | Log::error('获取直播间信息失败'); 118 | return false; 119 | } 120 | 121 | static::data('ruid', $data['data']['uid']); 122 | static::data('roomid', $data['data']['room_id']); 123 | 124 | return true; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/plugins/Group.php: -------------------------------------------------------------------------------- 1 | time()) { 31 | return; 32 | } 33 | 34 | $groups = static::getList(); 35 | $count = count($groups); 36 | foreach ($groups as $group) { 37 | $count -= static::signIn($group); 38 | } 39 | 40 | if ($count == 0) { 41 | static::data('lock', strtotime(date('Y-m-d 23:59:59')) + 600); 42 | } else { 43 | static::data('lock', time() + 3600); 44 | } 45 | } 46 | 47 | protected static function getList() 48 | { 49 | $payload = []; 50 | $data = Curl::post('https://api.vc.bilibili.com/link_group/v1/member/my_groups', static::sign($payload)); 51 | $data = json_decode($data, true); 52 | 53 | if (isset($data['code']) && $data['code']) { 54 | Log::warning("查询应援团名单异常"); 55 | return []; 56 | } 57 | 58 | if (empty($data['data']['list'])) { 59 | Log::notice('没有需要签到的应援团'); 60 | return []; 61 | } 62 | 63 | return $data['data']['list']; 64 | } 65 | 66 | public static function signIn($value) 67 | { 68 | $payload = [ 69 | 'group_id' => $value['group_id'], 70 | 'owner_id' => $value['owner_uid'], 71 | ]; 72 | $data = Curl::post('https://api.vc.bilibili.com/link_setting/v1/link_setting/sign_in', static::sign($payload)); 73 | $data = json_decode($data, true); 74 | 75 | if (isset($data['code']) && $data['code']) { 76 | Log::warning("应援团 {$value['group_name']} 签到异常"); 77 | return false; 78 | } 79 | 80 | if ($data['data']['status']) { 81 | Log::notice("应援团 {$value['group_name']} 已经签到过了"); 82 | } else { 83 | Log::notice("应援团 {$value['group_name']} 签到成功,增加 {$data['data']['add_num']} 点亲密度"); 84 | } 85 | 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/plugins/Heart.php: -------------------------------------------------------------------------------- 1 | time()) { 30 | return; 31 | } 32 | 33 | $roomId = static::config('ROOM_ID'); 34 | static::web($roomId); 35 | static::mobile($roomId); 36 | 37 | static::data('lock', time() + 300); 38 | } 39 | 40 | public static function web($roomId) 41 | { 42 | $payload = []; 43 | $data = Curl::post('https://api.live.bilibili.com/User/userOnlineHeart', static::sign($payload)); 44 | $data = json_decode($data, true); 45 | 46 | if (isset($data['code']) && $data['code']) { 47 | Log::warning("直播间 $roomId 心跳异常 (web)"); 48 | } else { 49 | Log::info("向直播间 $roomId 发送心跳包 (web)"); 50 | } 51 | } 52 | 53 | public static function mobile($roomId) 54 | { 55 | $payload = [ 56 | 'room_id' => $roomId, 57 | ]; 58 | $data = Curl::post('https://api.live.bilibili.com/mobile/userOnlineHeart', static::sign($payload)); 59 | $data = json_decode($data, true); 60 | 61 | if (isset($data['code']) && $data['code']) { 62 | Log::warning("直播间 $roomId 心跳异常 (APP)"); 63 | } else { 64 | Log::info("向直播间 $roomId 发送心跳包 (APP)"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/plugins/Silver.php: -------------------------------------------------------------------------------- 1 | time()) { 34 | return; 35 | } 36 | 37 | if (!static::data('task')) { 38 | static::getTask(); 39 | } else { 40 | static::openTask(); 41 | } 42 | } 43 | 44 | protected static function openTask() 45 | { 46 | $payload = []; 47 | $data = Curl::get('https://api.live.bilibili.com/mobile/freeSilverAward', static::sign($payload)); 48 | $data = json_decode($data, true); 49 | 50 | if (isset($data['code']) && $data['code']) { 51 | Log::warning('开启宝箱失败'); 52 | static::data('lock', time() + mt_rand(60, 120)); 53 | return; 54 | } 55 | 56 | Log::notice("开启宝箱成功,瓜子 {$data['data']['silver']}(+{$data['data']['awardSilver']})"); 57 | 58 | static::data('task', 0); 59 | static::data('lock', time() + mt_rand(5, 20)); 60 | } 61 | 62 | protected static function getTask() 63 | { 64 | $payload = []; 65 | $data = Curl::get('https://api.live.bilibili.com/mobile/freeSilverCurrentTask', static::sign($payload)); 66 | $data = json_decode($data, true); 67 | 68 | if (isset($data['code']) && $data['code'] == -10017) { 69 | Log::notice($data['message']); 70 | static::data('lock', strtotime(date('Y-m-d 23:59:59')) + 600); 71 | return; 72 | } 73 | 74 | if (isset($data['code']) && $data['code']) { 75 | Log::error('领取宝箱任务失败'); 76 | return; 77 | } 78 | 79 | Log::notice("领取宝箱成功,内含 {$data['data']['silver']} 个瓜子"); 80 | Log::info("等待 {$data['data']['minute']} 分钟后打开宝箱"); 81 | 82 | static::data('task', $data['data']['time_start']); 83 | static::data('lock', time() + $data['data']['minute'] * 60 + mt_rand(5, 30)); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/plugins/SmallTV.php: -------------------------------------------------------------------------------- 1 | $roomid) { 33 | static::$config['data'][static::PLUGIN_NAME]['smallTV'][$tvid] = [ 34 | 'roomid' => $roomid, 35 | 'tvid' => $tvid, 36 | 'status' => 0, 37 | 'lock' => time() + mt_rand(5, 30), 38 | ]; 39 | Log::info("直播间 {$roomid} 加入队列"); 40 | } 41 | 42 | foreach (static::data('smallTV') as $vo) { 43 | if ($vo['status'] == 0 && $vo['lock'] <= time()) { 44 | if (static::check($vo)) { 45 | static::join($vo); 46 | } else { 47 | static::drop($vo); 48 | } 49 | } 50 | if ($vo['status'] == 1 && $vo['lock'] <= time()) { 51 | static::notice($vo); 52 | } 53 | } 54 | } 55 | 56 | protected static function drop($value) 57 | { 58 | unset(static::$config['data'][static::PLUGIN_NAME]['smallTV'][$value['tvid']]); 59 | } 60 | 61 | protected static function check($value) 62 | { 63 | if (!in_array(intval(date('H')), static::config('SMALLTV_HOURS'))) { 64 | Log::notice('当前为休息时段,放弃小电视抽奖'); 65 | return false; 66 | } 67 | 68 | if (mt_rand(0, 100) >= static::config('SMALLTV_RATE')) { 69 | Log::notice('根据抽奖比率设置 (' . static::config('SMALLTV_RATE') . '%),放弃小电视抽奖'); 70 | return false; 71 | } 72 | 73 | Log::info('检查直播间 ' . $value['roomid']); 74 | 75 | $payload = [ 76 | 'id' => $value['roomid'], 77 | ]; 78 | $data = Curl::get('https://api.live.bilibili.com/room/v1/Room/room_init', static::sign($payload)); 79 | $data = json_decode($data, true); 80 | 81 | if (isset($data['code']) && $data['code']) { 82 | Log::error("获取直播间 {$value['roomid']} 信息失败"); 83 | return false; 84 | } 85 | 86 | if ($data['data']['is_hidden'] || $data['data']['is_locked'] || $data['data']['encrypted']) { 87 | Log::warning("直播间 {$value['roomid']} 可能存在问题,放弃小电视抽奖"); 88 | return false; 89 | } 90 | 91 | $payload = [ 92 | 'roomid' => $value['roomid'], 93 | ]; 94 | $data = Curl::get('https://api.live.bilibili.com/gift/v3/smalltv/check', static::sign($payload)); 95 | $data = json_decode($data, true); 96 | 97 | if (!count($data['data']['list'])) { 98 | Log::warning("直播间 {$value['roomid']} 小电视列表为空,放弃小电视抽奖"); 99 | return false; 100 | } 101 | 102 | static::entryAction($value['roomid']); 103 | 104 | return true; 105 | } 106 | 107 | protected static function entryAction($value) 108 | { 109 | Log::info("进入直播间 $value"); 110 | $payload = [ 111 | 'room_id' => $value, 112 | ]; 113 | Curl::post('https://api.live.bilibili.com/room/v1/Room/room_entry_action', static::sign($payload)); 114 | 115 | Heart::web($value); 116 | } 117 | 118 | protected static function join($value) 119 | { 120 | $payload = [ 121 | 'raffleId' => $value['tvid'], 122 | 'roomid' => $value['roomid'], 123 | 'type' => 'Gift', 124 | ]; 125 | $data = Curl::get('https://api.live.bilibili.com/gift/v3/smalltv/join', static::sign($payload)); 126 | $data = json_decode($data, true); 127 | 128 | if (isset($data['code']) && $data['code']) { 129 | Log::error("小电视 #{$value['tvid']} 抽奖加入失败"); 130 | static::drop($value); 131 | return; 132 | } 133 | 134 | static::$config['data'][static::PLUGIN_NAME]['smallTV'][$value['tvid']]['status'] = 1; 135 | static::$config['data'][static::PLUGIN_NAME]['smallTV'][$value['tvid']]['lock'] = time() + $data['data']['time'] + rand(5, 60); 136 | } 137 | 138 | protected static function notice($value) 139 | { 140 | $payload = [ 141 | 'type' => 'small_tv', 142 | 'raffleId' => $value['tvid'], 143 | ]; 144 | $data = Curl::get('https://api.live.bilibili.com/gift/v3/smalltv/notice', static::sign($payload)); 145 | $data = json_decode($data, true); 146 | 147 | if (isset($data['msg']) && $data['msg'] != 'ok') { 148 | Log::error("小电视 #{$value['tvid']} 抽奖失败"); 149 | static::drop($value); 150 | } 151 | 152 | if ($data['data']['status'] == 3) { 153 | Log::info("小电视 #{$value['tvid']} 抽奖中"); 154 | return; 155 | } 156 | 157 | Log::notice("在直播间 {$value['roomid']} 获得 {$data['data']['gift_num']} 个{$data['data']['gift_name']}"); 158 | static::drop($value); 159 | } 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/plugins/SocketClient.php: -------------------------------------------------------------------------------- 1 | createClient('tcp://' . getenv('SOCKET_SERVER_ADDR') . ':' . getenv('SOCKET_SERVER_PORT')) 29 | ->setBlocking(false); 30 | static::data('socket', $socket); 31 | } catch (\Exception $e) { 32 | Log::warning('无法连接到指定监听服务器!将会在下一个循环重试。', [$e->getMessage()]); 33 | } 34 | } 35 | } 36 | 37 | protected static function work() 38 | { 39 | if (static::data('socket') !== NULL) { 40 | self::checkConnection(); 41 | $content = self::getContent(); 42 | $parse = self::parse($content); 43 | self::unpack($parse); 44 | Log::debug('解包完成!', $parse); 45 | } 46 | } 47 | 48 | protected static function checkConnection() 49 | { 50 | $socket = static::data('socket'); 51 | try { 52 | $socket->write('success'); 53 | Log::debug('监听服务器心跳包已正常发送!', []); 54 | } catch (\Exception $e) { 55 | // Looks like disconnect 56 | Log::warning('与监听服务器的连接好像断开了!', [$e->getMessage()]); 57 | $socket->close(); 58 | static::$config['data'][static::PLUGIN_NAME]['socket'] = NULL; 59 | } 60 | } 61 | 62 | protected static function getContent() 63 | { 64 | $result = ''; 65 | $socket = static::data('socket'); 66 | while (true) { 67 | try { 68 | $tmp = $socket->read(1); 69 | $result .= $tmp; 70 | } catch (\Exception $e) { 71 | break; 72 | // Finished 73 | } 74 | } 75 | if (empty($result)) { 76 | Log::debug('本次没有获得数据!', []); 77 | return '[]'; 78 | } 79 | Log::debug('本次获得了数据:' . $result, []); 80 | return $result; 81 | } 82 | 83 | protected static function parse($data) 84 | { 85 | $result = json_decode($data, true); 86 | if ($result === NULL) { 87 | Log::warning('监控服务器传入了无法解析的数据!:' . $data, []); 88 | return []; 89 | } else if (empty($result)) { 90 | return []; 91 | } 92 | return $result; 93 | } 94 | 95 | protected static function unpack(array $data) 96 | { 97 | foreach ($data as $pluginName => $section) { 98 | foreach ($section as $sectionName => $value) { 99 | foreach ($value as $pair) { 100 | foreach ($pair as $k => $v) { 101 | self::setValue($pluginName, $sectionName, $k, $v); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | protected static function setValue($pluginName, $section, $key, $value) 109 | { 110 | static::$config['data'][$pluginName][$section][$key] = $value; 111 | Log::debug('正在设置数据中: ' . $pluginName . ' ' . $section . ' ' . $key . ' ' . $value, []); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/plugins/Task.php: -------------------------------------------------------------------------------- 1 | time()) { 31 | return; 32 | } 33 | 34 | Log::info('检查每日任务'); 35 | $data = static::check(); 36 | 37 | static::double_watch_info($data); 38 | static::sign_info($data); 39 | 40 | if (count(static::data('done')) >= 2) { 41 | static::data('done', []); 42 | static::data('lock', strtotime(date('Y-m-d 23:59:59')) + 600); 43 | } else { 44 | static::data('lock', time() + 3600); 45 | } 46 | } 47 | 48 | protected static function check() 49 | { 50 | $payload = []; 51 | $data = Curl::get('https://api.live.bilibili.com/i/api/taskInfo', static::sign($payload)); 52 | $data = json_decode($data, true); 53 | 54 | if (isset($data['code']) && $data['code']) { 55 | Log::error('每日任务检查失败'); 56 | } 57 | 58 | return $data; 59 | } 60 | 61 | protected static function sign_info($value) 62 | { 63 | if (!isset($value['data']['sign_info'])) return; 64 | if (in_array('sign_info', static::data('done'))) return; 65 | 66 | Log::info('检查任务「每日签到」'); 67 | 68 | $info = $value['data']['sign_info']; 69 | 70 | if ($info['status'] == 1) { 71 | Log::info('「每日签到」奖励已经领取'); 72 | static::data('done', array_merge(static::data('done'), ['sign_info'])); 73 | return; 74 | } 75 | 76 | $payload = []; 77 | $data = Curl::get('https://api.live.bilibili.com/appUser/getSignInfo', static::sign($payload)); 78 | $data = json_decode($data, true); 79 | 80 | if (isset($data['code']) && $data['code']) { 81 | Log::error('「每日签到」失败'); 82 | } else { 83 | Log::notice('「每日签到」成功'); 84 | static::data('done', array_merge(static::data('done'), ['sign_info'])); 85 | } 86 | } 87 | 88 | protected static function double_watch_info($value) 89 | { 90 | if (!isset($value['data']['double_watch_info'])) return; 91 | if (in_array('double_watch_info', static::data('done'))) return; 92 | 93 | Log::info('检查任务「双端观看直播」'); 94 | 95 | $info = $value['data']['double_watch_info']; 96 | 97 | if ($info['status'] == 2) { 98 | Log::info('「双端观看直播」奖励已经领取'); 99 | static::data('done', array_merge(static::data('done'), ['double_watch_info'])); 100 | return; 101 | } 102 | 103 | if ($info['mobile_watch'] != 1 || $info['web_watch'] != 1) { 104 | Log::warning('「双端观看直播」未完成,请等待'); 105 | return; 106 | } 107 | 108 | $payload = [ 109 | 'task_id' => 'double_watch_task', 110 | ]; 111 | $data = Curl::post('https://api.live.bilibili.com/activity/v1/task/receive_award', static::sign($payload)); 112 | $data = json_decode($data, true); 113 | 114 | if (isset($data['code']) && $data['code']) { 115 | Log::error('「双端观看直播」奖励领取失败'); 116 | } else { 117 | Log::notice('「双端观看直播」奖励领取成功'); 118 | foreach ($info['awards'] as $vo) { 119 | Log::info(sprintf("获得 %s × %d", $vo['name'], $vo['num'])); 120 | } 121 | static::data('done', array_merge(static::data('done'), ['double_watch_info'])); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /tools/configGenerator.php: -------------------------------------------------------------------------------- 1 | $f) { 17 | $template = envReplace($f, $config[$index], $template); 18 | } 19 | file_put_contents(__DIR__ . '/../config/' . $config[0], $template); 20 | } 21 | 22 | function envReplace($key, $value, $content) 23 | { 24 | return preg_replace( 25 | '/^' . $key . '=.*' . '/m', 26 | $key . '=' . $value, 27 | $content 28 | ); 29 | } -------------------------------------------------------------------------------- /tools/configs.example: -------------------------------------------------------------------------------- 1 | ####################### 2 | # 账户设置 # 3 | ####################### 4 | 5 | # 帐号 6 | APP_USER= 7 | 8 | # 密码 9 | APP_PASS= 10 | 11 | # 令牌(自动生成) 12 | ACCESS_TOKEN= 13 | REFRESH_TOKEN= 14 | COOKIE_JAR= 15 | 16 | ####################### 17 | # 功能设置 # 18 | ####################### 19 | 20 | # 礼物赠送直播间 21 | ROOM_ID=3746256 22 | 23 | # 弹幕监控直播间 24 | SOCKET_ROOM_ID=23058 25 | 26 | # 小电视百分比 27 | SMALLTV_RATE=0 28 | 29 | # 小电视时间段 30 | SMALLTV_HOURS=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 31 | 32 | ####################### 33 | # 网络设置 # 34 | ####################### 35 | 36 | # 网络代理 37 | NETWORK_PROXY= 38 | 39 | ####################### 40 | # 日志设置 # 41 | ####################### 42 | 43 | # 调试模式 44 | APP_DEBUG=false 45 | 46 | # 多账号区别输出 47 | APP_MULTIPLE=true 48 | 49 | # 账号别名,如果为空则默认使用登陆账号作为标示 50 | APP_USER_IDENTITY= 51 | 52 | # 日志回调地址 53 | # 54 | # {level} 日志等级 55 | # {message} 日志信息 56 | # {account} 账户标识符 57 | # 58 | CALLBACK_URL="http://www.example.com/api.send?text=[{level}]: {message}" 59 | 60 | # 错误回调级别 61 | # 62 | # INFO 200 63 | # NOTICE 250 64 | # WARNING 300 65 | # ERROR 400 66 | # 67 | CALLBACK_LEVEL=400 68 | 69 | # 监听服务器设定 70 | SOCKET_SERVER_ADDR=127.0.0.1 71 | SOCKET_SERVER_PORT=5353 --------------------------------------------------------------------------------