├── .gitignore ├── CHANGELOG.md ├── DOC.md ├── Dockerfile ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── conf └── user.conf.example ├── index.php ├── src ├── Base.php ├── Common.php ├── Curl.php ├── Daily.php ├── Danmu.php ├── DataTreating.php ├── File.php ├── GiftHeart.php ├── GiftSend.php ├── GroupSignIn.php ├── Guard.php ├── Heart.php ├── Live.php ├── Log.php ├── Login.php ├── MasterSite.php ├── MaterialObject.php ├── Notice.php ├── Pk.php ├── RaffleHandler.php ├── Sign.php ├── Silver.php ├── Silver2Coin.php ├── Socket.php ├── Storm.php ├── Task.php ├── User.php ├── Websocket.php └── Winning.php └── tools ├── README.md └── activeSendMsg.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | .idea/ 8 | index1.php 9 | user/ 10 | record/ 11 | temp/ 12 | tmp/ 13 | *.iml 14 | out 15 | gen 16 | /vendor/ 17 | config 18 | *.log 19 | Traits/ 20 | README1.md 21 | conf/user.conf 22 | /conf/user.conf 23 | /log/ 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | # 本项目Log 3 | 4 | ## v0.0.10.191210(2019-12-10) 5 | 6 | ### Added 7 | - 大乱斗抽奖 8 | 9 | ### Changed 10 | - 登录验证码识别API 11 | - 实物抽奖敏感词 12 | - 舰长上船API 13 | 14 | ### Fixed 15 | - 签到回显 16 | - 抽奖回显 17 | 18 | ## v0.0.9.191030(2019-10-30) 19 | 20 | ### Added 21 | - 22 | 23 | ### Changed 24 | - 更新部分API 25 | 26 | ### Fixed 27 | - 28 | 29 | 30 | ## v0.0.9.190731(2019-08-01) 31 | 32 | ### Added 33 | - 34 | 35 | ### Changed 36 | - 更新部分API 37 | - 38 | 39 | ### Fixed 40 | - 41 | 42 | ## v0.0.9.190730(2019-07-30) 43 | 44 | ### Added 45 | - 46 | 47 | ### Changed 48 | - 更新 raffle api 49 | - 50 | 51 | ### Fixed 52 | - 53 | 54 | ## v0.0.9.190713(2019-07-13) 55 | 56 | ### Added 57 | - 58 | 59 | ### Changed 60 | - 更改验证码识别服务器 61 | - 62 | 63 | ### Fixed 64 | - 修复一个风暴回显 65 | - 66 | 67 | ## v0.0.9(2019-03-04) 68 | 69 | ### Added 70 | - 添加验证码登录(验证码识别) 71 | - 添加活跃弹幕(心理安慰) 72 | - 请求重试过滤url 73 | - 74 | 75 | ### Changed 76 | - 舰长服务器请求过滤 77 | - 78 | 79 | ### Fixed 80 | - 网络请求延迟 81 | - 82 | 83 | ## v0.0.8(2019-01-03) 84 | 85 | ### Added 86 | - 87 | 88 | ### Changed 89 | - 更新部分API 90 | - 删除部分无意义引用 91 | - 92 | 93 | ### Fixed 94 | - 网络请求延迟 95 | - 96 | 97 | ## v0.0.8(2018-12-10) 98 | 99 | ### Added 100 | - 自动获取活动关键字(测试功能) 101 | - 102 | 103 | ### Changed 104 | - 更新部分API 105 | - 删除风暴关键字 106 | - 网络丢失判断 107 | - 108 | 109 | ### Fixed 110 | - 111 | 112 | ## v0.0.7(2018-10-21) 113 | 114 | ### Added 115 | - 116 | 117 | ### Changed 118 | - 重构上船抽奖流程 119 | - 更新请求参数 120 | - 舰长加入封禁时间内 121 | - 更新新活动关键词 122 | - 上船去重 123 | - 124 | 125 | ### Fixed 126 | - 127 | 128 | 129 | ## v0.0.6(2018-10-16) 130 | 131 | ### Added 132 | - 视频投币上限检测(还没测试) 133 | - 134 | 135 | ### Changed 136 | - 视频投币每个视频默认1个硬币 137 | - 138 | 139 | ### Fixed 140 | - 141 | 142 | 143 | 144 | ## v0.0.6(2018-10-10) 145 | 146 | ### Added 147 | - 多个视频投币(日榜) 148 | - 149 | 150 | ### Changed 151 | - 视频投币每个视频默认2个硬币(更新前默认1) 152 | - 友爱社签到更新8小时一次 153 | - 154 | 155 | ### Fixed 156 | - 修复舰长亲密度提示 157 | - 修复CID解析报错 158 | - 159 | 160 | 161 | ## v0.0.6(2018-10-04) 162 | 163 | ### Added 164 | - 主站经验任务(观看、分享、投币) 165 | - 舰长上船(当前勋章亲密度,否则辣条*1) 166 | - 167 | 168 | ### Changed 169 | - Curl other请求\重试次数 170 | - 实物抽奖修改一些参数 171 | - 匹配用户信息添加User 172 | - 173 | 174 | ### Fixed 175 | - 不知道修复了什么 176 | - 177 | 178 | ## v0.0.5(2018-09-29) 179 | 180 | ### Added 181 | - 主站经验任务(观看、分享) 182 | - 183 | 184 | ### Changed 185 | - 忘记修改了什么 186 | - 187 | 188 | ### Fixed 189 | - 修复账号封禁提醒 190 | - 191 | 192 | 193 | ## v0.0.4(2018-08-21) 194 | 195 | ### Added 196 | - 197 | 198 | ### Changed 199 | - 添加超时重试提交 200 | - 添加自定义活动关键字 201 | - 添加了不要脸的广告 202 | - 203 | 204 | ### Fixed 205 | - 206 | 207 | 208 | ## v0.0.4(2018-08-03) 209 | 210 | ### Added 211 | - WebSocket 212 | - 213 | 214 | ### Changed 215 | - 替换Socket为WebSocket 216 | - 217 | 218 | ### Fixed 219 | - 重复抽奖 220 | - 银瓜子兑换硬币接口 221 | - 222 | 223 | ## v0.0.4(2018-07-26) 224 | 225 | ### Added 226 | - 添加实物抽奖自动机制 227 | - 228 | 229 | ### Changed 230 | - 去掉抽奖延迟 231 | - 去掉移动端一个log 232 | - 实物抽奖范围80 233 | - 234 | 235 | ### Fixed 236 | - 什么都没修复 237 | - 238 | 239 | 240 | ## v0.0.4(2018-07-25) 241 | 242 | ### Added 243 | - 添加么么茶等活动 244 | - 添加活动双端抽奖(双倍辣条) 245 | - 246 | 247 | ### Changed 248 | - 实物抽奖加入丢弃列表 249 | - 优化实物抽奖逻辑 250 | - 重写活动抽奖 251 | - 统一抽奖接口 252 | - 合并盛夏么么茶|C位光环|小电视飞船|摩天大楼等 253 | - 更改一些LOG提示 254 | - 去掉抽奖延迟 255 | - 更新了配置文件 256 | - 257 | 258 | ### Fixed 259 | - 260 | 261 | ## v0.0.3(2018-07-14) 262 | 263 | ### Added 264 | - 添加最新活动“C位光环” 265 | 266 | ### Changed 267 | - 修改实物抽奖Flag 268 | 269 | ### Fixed 270 | - 271 | 272 | 273 | ## v0.0.3(2018-06-05) 274 | 275 | ### Added 276 | - 277 | 278 | ### Changed 279 | - 280 | 281 | ### Fixed 282 | - 修复死循环导致内存溢出崩溃 283 | 284 | 285 | ## v0.0.3(2018-05-19) 286 | 287 | ### Added 288 | - 289 | 290 | ### Changed 291 | - 小电视抽奖结果从5次调整到10次 292 | 293 | ### Fixed 294 | - 修复强制返回int 报错 295 | 296 | ## v0.0.3(2018-05-18) 297 | 298 | ### Added 299 | - 添加COOKIE 300 | - 添加发送弹幕 301 | - 添加摩天大楼抽奖 302 | - 添加本地写入日志 303 | 304 | 305 | ### Changed 306 | - 摩天大楼需要监听开播直播间 307 | - 故暂时放弃作者的直播间 308 | - 修改为3号(音悦台)直播间 309 | 310 | ### Fixed 311 | - 修复几个BUG 312 | 313 | 314 | ## v0.0.2(2018-05-07) 315 | 316 | ### Added 317 | - 添加几个可选任务 318 | 319 | ### Changed 320 | - 321 | 322 | ### Fixed 323 | - 324 | 325 | ## v0.0.2(2018-05-02) 326 | 327 | ### Added 328 | - 添加检测配置文件是否存在 329 | 330 | ### Changed 331 | - 添加瓜子兑换的延迟 332 | 333 | ### Fixed 334 | - 尝试修复CPU占用问题 335 | - 336 | 337 | ## v0.0.2(2018-04-27) 338 | 339 | ### Added 340 | - 新增节奏风暴 341 | - 新增Server酱推送 342 | - 新增实物中奖检测 343 | - 新增多开方案 344 | - 345 | 346 | ### Changed 347 | - 重构 index 348 | - 349 | 350 | ### Fixed 351 | - 修改写入配置逻辑 352 | - 修复配置重载逻辑 353 | - 354 | 355 | ## v0.0.1(2018-04-25) 356 | 357 | ### Added 358 | - 新增心跳礼物 359 | - 新增小电视抽奖 360 | - 新增双端银瓜子兑换硬币 361 | - 新增应援团签到 362 | - 新增实物抽奖 363 | - 364 | 365 | ### Changed 366 | - 修改 README 367 | - 368 | 369 | ### Fixed 370 | - 371 | 372 | 373 | 374 | # 父项目Log 375 | ## v0.7.2 (2018-04-22) 376 | 377 | ### Added 378 | - 新增令牌刷新机制 379 | - 新增日志通知级别设置 380 | 381 | ### Changed 382 | - 调整部分日志文案 383 | - 修正 README 的错误 ([#29](https://github.com/metowolf/BilibiliHelper/pull/29)) 384 | 385 | ### Fixed 386 | - 修复每日任务无法领取的问题 387 | - 修复部分逻辑错误 388 | 389 | 390 | ## v0.7.1 (2018-04-21) 391 | 392 | ### Changed 393 | - 调整部分通知为警告级别 394 | 395 | ### Fixed 396 | - 修复过早领取宝箱的问题 397 | 398 | 399 | ## v0.7.0 (2018-04-20) 400 | 401 | ### Added 402 | - 项目重构,拥抱 composer 403 | - 全面更换客户端 API 404 | - 添加用户登录机制 ([#22](https://github.com/metowolf/BilibiliHelper/issues/22)) 405 | 406 | ### Changed 407 | - Require PHP 5.4.0 or newer 408 | 409 | ### Fixed 410 | - 修复宝箱验证码问题 ([#27](https://github.com/metowolf/BilibiliHelper/issues/27)) 411 | -------------------------------------------------------------------------------- /DOC.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | 7 |

8 | 9 | 10 | # BiliHelper 11 | 12 | B 站直播实用脚本 13 | 14 | > [企鹅群](https://jq.qq.com/?_wv=1027&k=5AIDaJg) (只作用于反馈BUG, 没事别加) 15 | 16 | ## 功能组件 17 | 18 | |plugin |version |description | 19 | |--------------------|--------------------|--------------------| 20 | |Daily |18.04.21 |每日背包奖励 | 21 | |GiftSend |18.04.21 |自动清空过期礼物 | 22 | |Heart |18.07.21 |双端直播间心跳 | 23 | |Login |19.12.10 |帐号登录组件 | 24 | |Silver |18.12.10 |自动领宝箱 | 25 | |Task |19.12.10 |每日任务 | 26 | |GiftHeart |18.04.25 |心跳礼物 | 27 | |Silver2Coin |18.07.25 |银瓜子换硬币 | 28 | |MaterialObject |19.12.10 |实物抽奖 | 29 | |GroupSignIn |18.10.10 |应援团签到 | 30 | |Storm |18.04.26 |节奏风暴 | 31 | |Notice |18.07.25 |Server酱 | 32 | |RaffleHandler |19.12.10 |小电视飞船 | 33 | |RaffleHandler |19.12.10 |摩天大樓 | 34 | |RaffleHandler |19.12.10 |小金人 | 35 | |MasterSite |18.10.16 |主站(观看、分享、投币)| 36 | |Guard |19.12.10 |舰长上船亲密度 | 37 | |PK |19.12.10 |PK大乱斗 | 38 | 39 | 40 | ## 打赏赞助 41 | 42 | ![](https://i.loli.net/2019/07/13/5d2963e5cc1eb22973.png) 43 | 44 | > 有意的打赏个阔落,无意的可以跳过. 45 | 46 | ## 广告 47 | > 暂无. 48 | 49 | ## 未完成功能 50 | 51 | |待续 | 52 | |-----------| 53 | |优化节奏风暴| 54 | |添加防封机制| 55 | |自动代理访问| 56 | |添加多用户 | 57 | |待添加 | 58 | 59 | ## 环境依赖 60 | 61 | |Requirement | 62 | |--------------------| 63 | |PHP >=7.0 | 64 | |php_curl | 65 | |php_sockets | 66 | |php_openssl | 67 | |待添加 | 68 | 69 | 通常使用 `composer` 工具会自动检测上述依赖问题。 70 | 71 | * 项目 `composer.lock` 基于镜像生成 https://laravel-china.org/composer 72 | 73 | 74 | ## 使用指南 75 | 76 | 1. 下载(克隆)项目代码,初始化项目 77 | ``` 78 | $ git clone https://github.com/lkeme/BiliHelper.git 79 | $ cd BiliHelper/conf 80 | $ cp user.conf.example user.conf 81 | ``` 82 | 2. 使用 [composer](https://getcomposer.org/download/) 工具进行安装 83 | ``` 84 | $ composer install 85 | ``` 86 | 3. 按照说明修改配置文件 `user.conf`,只需填写帐号密码即可 87 | 4. 运行测试 88 | ``` 89 | $ php index.php 90 | ``` 91 | > 以下是`多开方案`,单个账户可以无视 92 | 5. 复制一份example配置文件,修改账号密码即可 93 | ``` 94 | $ php index.php example.conf 95 | ``` 96 | 6. 请保证配置文件存在,否则默认加载`user.conf`配置文件 97 | 98 |

99 | 100 | 101 | ## Docker使用指南 102 | 103 | 1. 安装好[Docker](https://yeasy.gitbooks.io/docker_practice/content/install/) 104 | 2. 直接命令行拉取镜像后运行 105 | 106 | ``` 107 | docker run -itd --rm -e USER_NAME=你的B站登录账号 -e USER_PASSWORD=你的B站密码 zsnmwy/bilihelper 108 | ``` 109 | 110 | ``` 111 | 相关参数 112 | 113 | -it 前台运行 114 | -itd 后台运行 115 | ``` 116 | 117 | - 注意: Docker镜像已经包含了所有所需的运行环境,无需在本地环境弄composer。每次启动容器时,都会与项目进行同步以确保版本最新。 118 | 119 | 120 | ## 升级指南 121 | 122 | 1. 进入项目目录 123 | ``` 124 | $ cd BiliHelper 125 | ``` 126 | 2. 拉取最新代码 127 | ``` 128 | $ git pull 129 | ``` 130 | 3. 更新依赖库 131 | ``` 132 | $ composer install 133 | ``` 134 | 4. 如果使用 systemd 等,需要重启服务 135 | ``` 136 | $ systemctl restart bilibili 137 | ``` 138 | 139 | ## 部署指南 140 | 141 | 如果你将 BiliHelper 部署到线上服务器时,则需要配置一个进程监控器来监测 `php index.php` 命令,在它意外退出时自动重启。 142 | 143 | 通常可以使用以下的方式 144 | - systemd (推荐) 145 | - Supervisor 146 | - screen (自用) 147 | - nohup 148 | 149 | ## systemd 脚本 150 | 151 | ``` 152 | # /usr/lib/systemd/system/bilibili.service 153 | 154 | [Unit] 155 | Description=Bili Helper Manager 156 | Documentation=https://github.com/lkeme/BiliHelper 157 | After=network.target 158 | 159 | [Service] 160 | ExecStart=/usr/bin/php /path/to/your/BiliHelper/index.php 161 | Restart=always 162 | 163 | [Install] 164 | WantedBy=multi-user.target 165 | ``` 166 | 167 | ## Supervisor 配置 168 | 169 | ``` 170 | [program:bilibili] 171 | process_name=%(program_name)s 172 | command=php /path/to/your/BiliHelper/index.php 173 | autostart=true 174 | autorestart=true 175 | redirect_stderr=true 176 | stdout_logfile=/tmp/bilibili.log 177 | ``` 178 | 179 | ## 报错通知问题 180 | 181 | 脚本出现 error 级别的报错,会调用通知地址进行提醒,这里推荐两个服务 182 | 183 | |服务|官网| 184 | |---|---| 185 | |Server酱|https://sc.ftqq.com/| 186 | |TelegramBot|https://core.telegram.org/bots/api| 187 | 188 | 示范如下 189 | ``` 190 | # Server酱 191 | # 自行替换 192 | APP_CALLBACK="https://sc.ftqq.com/.send?text={message}" 193 | 194 | # TelegramBot 195 | # 自行替换 196 | APP_CALLBACK="https://api.telegram.org/bot/sendMessage?chat_id=&text={message}" 197 | ``` 198 | 199 | `{message}` 部分会自动替换成错误信息,接口采用 get 方式发送 200 | 201 | 202 | ## 直播间 ID 问题 203 | 204 | `user.conf` 文件中有个 `ROOM_ID` 配置,填写此项可以清空临过期礼物给指定直播间。 205 | 206 | 通常可以在直播间页面的 url 获取到它 207 | ``` 208 | http://live.bilibili.com/9522051 209 | ``` 210 | 211 | 所有直播间号码小于 1000 的直播间为短号,该脚本在每次启动会自动修正,无需关心, 212 | 213 | ## 相关 214 | 215 | > 本项目基于[BilibiliHelper](https://github.com/metowolf/BilibiliHelper)项目 216 | 217 | > 基于父项目的架构开发,在此感谢父项目的开发 218 | 219 | > 保留父项目没必要修改的信息,另外欢迎重构(Haha) 220 | 221 | ## License 许可证 222 | 223 | BiliHelper is under the MIT license. 224 | 225 | 本项目基于 MIT 协议发布,并增加了 SATA 协议。 226 | 227 | 当你使用了使用 SATA 的开源软件或文档的时候,在遵守基础许可证的前提下,你必须马不停蹄地给你所使用的开源项目 “点赞” ,比如在 GitHub 上 star,然后你必须感谢这个帮助了你的开源项目的作者,作者信息可以在许可证头部的版权声明部分找到。 228 | 229 | 本项目的所有代码文件、配置项,除另有说明外,均基于上述介绍的协议发布,具体请看分支下的 LICENSE。 230 | 231 | 此处的文字仅用于说明,条款以 LICENSE 文件中的内容为准。 232 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:alpine 2 | 3 | MAINTAINER zsnmwy 4 | 5 | ENV USER_NAME='' \ 6 | USER_PASSWORD='' \ 7 | CONIFG_PATH='/app/conf/user.conf' 8 | 9 | WORKDIR /app 10 | 11 | RUN docker-php-ext-install sockets 12 | 13 | RUN apk add --no-cache git && \ 14 | git clone https://github.com/lkeme/BiliHelper.git /app && \ 15 | php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');" && \ 16 | php composer-setup.php && \ 17 | php composer.phar install && \ 18 | cp /app/conf/user.conf.example /app/conf/user.conf && \ 19 | rm -r /var/cache/apk && \ 20 | rm -r /usr/share/man 21 | 22 | ENTRYPOINT git pull && \ 23 | php composer.phar install && \ 24 | sed -i ''"$(cat /app/conf/user.conf -n | grep "APP_USER=" | awk '{print $1}')"'c '"$(echo "APP_USER=${USER_NAME}")"'' ${CONIFG_PATH} && \ 25 | sed -i ''"$(cat /app/conf/user.conf -n | grep "APP_PASS=" | awk '{print $1}')"'c '"$(echo "APP_PASS=${USER_PASSWORD}")"'' ${CONIFG_PATH} && \ 26 | php index.php 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lkeme 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BiliHelper 2 | B 站直播实用脚本 3 | 4 | ## 公告 5 | 6 | > 当前版本仓库已不再更新,仅作为存档保留。 7 | 8 | > 新版本仓库地址 [lkeme/BiliHelper-personal](https://github.com/lkeme/BiliHelper-personal) 9 | 10 | ## 交流 11 | 12 | Group: [55308141](https://jq.qq.com/?_wv=1027&k=5AIDaJg) 13 | 14 | ## 文档 15 | * [使用文档 / DOC.md](./DOC.md) 16 | * [更新日志 / CHANGELOG.md](./CHANGELOG.md) 17 | 18 | ## 打赏 19 | 20 | ![](https://i.loli.net/2019/07/13/5d2963e5cc1eb22973.png) 21 | 22 | 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lkeme/bilihelper", 3 | "description": "B 站自动领瓜子、直播助手、直播挂机脚本、主站助手 - PHP 版", 4 | "type": "project", 5 | "require": { 6 | "php": ">=7.0.0", 7 | "ext-curl": "*", 8 | "ext-openssl": "*", 9 | "ext-sockets": "*", 10 | "ext-json": "*", 11 | "vlucas/phpdotenv": "^2.4", 12 | "monolog/monolog": "^1.23", 13 | "bramus/monolog-colored-line-formatter": "^2.0", 14 | "wrench/wrench": "^2.0" 15 | }, 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "Lkeme", 20 | "email": "Useri@live.cn", 21 | "homepage": "https://mudew.com" 22 | } 23 | ], 24 | "autoload": { 25 | "psr-4": { 26 | "lkeme\\BiliHelper\\": "src/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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": "1185123e620ad64dd020072e87188fbf", 8 | "packages": [ 9 | { 10 | "name": "bramus/ansi-php", 11 | "version": "3.0.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/bramus/ansi-php.git", 15 | "reference": "79d30c30651b0c6f23cf85503c779c72ac74ab8a" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/bramus/ansi-php/zipball/79d30c30651b0c6f23cf85503c779c72ac74ab8a", 20 | "reference": "79d30c30651b0c6f23cf85503c779c72ac74ab8a", 21 | "shasum": "", 22 | "mirrors": [ 23 | { 24 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 25 | "preferred": true 26 | } 27 | ] 28 | }, 29 | "require": { 30 | "php": ">=5.4.0" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "~4.0" 34 | }, 35 | "type": "library", 36 | "autoload": { 37 | "psr-4": { 38 | "Bramus\\Ansi\\": "src/" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Bramus Van Damme", 48 | "email": "bramus@bram.us", 49 | "homepage": "https://www.bram.us/" 50 | } 51 | ], 52 | "description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps", 53 | "time": "2019-02-12T15:05:30+00:00" 54 | }, 55 | { 56 | "name": "bramus/monolog-colored-line-formatter", 57 | "version": "2.0.3", 58 | "source": { 59 | "type": "git", 60 | "url": "https://github.com/bramus/monolog-colored-line-formatter.git", 61 | "reference": "6bff15eee00afe2690642535b0f1541f10852c2b" 62 | }, 63 | "dist": { 64 | "type": "zip", 65 | "url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b", 66 | "reference": "6bff15eee00afe2690642535b0f1541f10852c2b", 67 | "shasum": "", 68 | "mirrors": [ 69 | { 70 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 71 | "preferred": true 72 | } 73 | ] 74 | }, 75 | "require": { 76 | "bramus/ansi-php": "~3.0", 77 | "php": ">=5.4.0" 78 | }, 79 | "require-dev": { 80 | "monolog/monolog": "~1.0", 81 | "phpunit/phpunit": "~4.0" 82 | }, 83 | "type": "library", 84 | "autoload": { 85 | "psr-4": { 86 | "Bramus\\Monolog\\": "src/" 87 | } 88 | }, 89 | "notification-url": "https://packagist.org/downloads/", 90 | "license": [ 91 | "MIT" 92 | ], 93 | "authors": [ 94 | { 95 | "name": "Bramus Van Damme", 96 | "email": "bramus@bram.us", 97 | "homepage": "https://www.bram.us/" 98 | } 99 | ], 100 | "description": "Colored Line Formatter for Monolog", 101 | "time": "2015-01-07T22:12:35+00:00" 102 | }, 103 | { 104 | "name": "monolog/monolog", 105 | "version": "1.24.0", 106 | "source": { 107 | "type": "git", 108 | "url": "https://github.com/Seldaek/monolog.git", 109 | "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" 110 | }, 111 | "dist": { 112 | "type": "zip", 113 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", 114 | "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", 115 | "shasum": "", 116 | "mirrors": [ 117 | { 118 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 119 | "preferred": true 120 | } 121 | ] 122 | }, 123 | "require": { 124 | "php": ">=5.3.0", 125 | "psr/log": "~1.0" 126 | }, 127 | "provide": { 128 | "psr/log-implementation": "1.0.0" 129 | }, 130 | "require-dev": { 131 | "aws/aws-sdk-php": "^2.4.9 || ^3.0", 132 | "doctrine/couchdb": "~1.0@dev", 133 | "graylog2/gelf-php": "~1.0", 134 | "jakub-onderka/php-parallel-lint": "0.9", 135 | "php-amqplib/php-amqplib": "~2.4", 136 | "php-console/php-console": "^3.1.3", 137 | "phpunit/phpunit": "~4.5", 138 | "phpunit/phpunit-mock-objects": "2.3.0", 139 | "ruflin/elastica": ">=0.90 <3.0", 140 | "sentry/sentry": "^0.13", 141 | "swiftmailer/swiftmailer": "^5.3|^6.0" 142 | }, 143 | "suggest": { 144 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 145 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server", 146 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 147 | "ext-mongo": "Allow sending log messages to a MongoDB server", 148 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 149 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", 150 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", 151 | "php-console/php-console": "Allow sending log messages to Google Chrome", 152 | "rollbar/rollbar": "Allow sending log messages to Rollbar", 153 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 154 | "sentry/sentry": "Allow sending log messages to a Sentry server" 155 | }, 156 | "type": "library", 157 | "extra": { 158 | "branch-alias": { 159 | "dev-master": "2.0.x-dev" 160 | } 161 | }, 162 | "autoload": { 163 | "psr-4": { 164 | "Monolog\\": "src/Monolog" 165 | } 166 | }, 167 | "notification-url": "https://packagist.org/downloads/", 168 | "license": [ 169 | "MIT" 170 | ], 171 | "authors": [ 172 | { 173 | "name": "Jordi Boggiano", 174 | "email": "j.boggiano@seld.be", 175 | "homepage": "http://seld.be" 176 | } 177 | ], 178 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services", 179 | "homepage": "http://github.com/Seldaek/monolog", 180 | "keywords": [ 181 | "log", 182 | "logging", 183 | "psr-3" 184 | ], 185 | "time": "2018-11-05T09:00:11+00:00" 186 | }, 187 | { 188 | "name": "psr/log", 189 | "version": "1.1.0", 190 | "source": { 191 | "type": "git", 192 | "url": "https://github.com/php-fig/log.git", 193 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" 194 | }, 195 | "dist": { 196 | "type": "zip", 197 | "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 198 | "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", 199 | "shasum": "", 200 | "mirrors": [ 201 | { 202 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 203 | "preferred": true 204 | } 205 | ] 206 | }, 207 | "require": { 208 | "php": ">=5.3.0" 209 | }, 210 | "type": "library", 211 | "extra": { 212 | "branch-alias": { 213 | "dev-master": "1.0.x-dev" 214 | } 215 | }, 216 | "autoload": { 217 | "psr-4": { 218 | "Psr\\Log\\": "Psr/Log/" 219 | } 220 | }, 221 | "notification-url": "https://packagist.org/downloads/", 222 | "license": [ 223 | "MIT" 224 | ], 225 | "authors": [ 226 | { 227 | "name": "PHP-FIG", 228 | "homepage": "http://www.php-fig.org/" 229 | } 230 | ], 231 | "description": "Common interface for logging libraries", 232 | "homepage": "https://github.com/php-fig/log", 233 | "keywords": [ 234 | "log", 235 | "psr", 236 | "psr-3" 237 | ], 238 | "time": "2018-11-20T15:27:04+00:00" 239 | }, 240 | { 241 | "name": "symfony/polyfill-ctype", 242 | "version": "v1.11.0", 243 | "source": { 244 | "type": "git", 245 | "url": "https://github.com/symfony/polyfill-ctype.git", 246 | "reference": "82ebae02209c21113908c229e9883c419720738a" 247 | }, 248 | "dist": { 249 | "type": "zip", 250 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", 251 | "reference": "82ebae02209c21113908c229e9883c419720738a", 252 | "shasum": "", 253 | "mirrors": [ 254 | { 255 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 256 | "preferred": true 257 | } 258 | ] 259 | }, 260 | "require": { 261 | "php": ">=5.3.3" 262 | }, 263 | "suggest": { 264 | "ext-ctype": "For best performance" 265 | }, 266 | "type": "library", 267 | "extra": { 268 | "branch-alias": { 269 | "dev-master": "1.11-dev" 270 | } 271 | }, 272 | "autoload": { 273 | "psr-4": { 274 | "Symfony\\Polyfill\\Ctype\\": "" 275 | }, 276 | "files": [ 277 | "bootstrap.php" 278 | ] 279 | }, 280 | "notification-url": "https://packagist.org/downloads/", 281 | "license": [ 282 | "MIT" 283 | ], 284 | "authors": [ 285 | { 286 | "name": "Symfony Community", 287 | "homepage": "https://symfony.com/contributors" 288 | }, 289 | { 290 | "name": "Gert de Pagter", 291 | "email": "BackEndTea@gmail.com" 292 | } 293 | ], 294 | "description": "Symfony polyfill for ctype functions", 295 | "homepage": "https://symfony.com", 296 | "keywords": [ 297 | "compatibility", 298 | "ctype", 299 | "polyfill", 300 | "portable" 301 | ], 302 | "time": "2019-02-06T07:57:58+00:00" 303 | }, 304 | { 305 | "name": "vlucas/phpdotenv", 306 | "version": "v2.6.1", 307 | "source": { 308 | "type": "git", 309 | "url": "https://github.com/vlucas/phpdotenv.git", 310 | "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" 311 | }, 312 | "dist": { 313 | "type": "zip", 314 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", 315 | "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", 316 | "shasum": "", 317 | "mirrors": [ 318 | { 319 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 320 | "preferred": true 321 | } 322 | ] 323 | }, 324 | "require": { 325 | "php": ">=5.3.9", 326 | "symfony/polyfill-ctype": "^1.9" 327 | }, 328 | "require-dev": { 329 | "phpunit/phpunit": "^4.8.35 || ^5.0" 330 | }, 331 | "type": "library", 332 | "extra": { 333 | "branch-alias": { 334 | "dev-master": "2.6-dev" 335 | } 336 | }, 337 | "autoload": { 338 | "psr-4": { 339 | "Dotenv\\": "src/" 340 | } 341 | }, 342 | "notification-url": "https://packagist.org/downloads/", 343 | "license": [ 344 | "BSD-3-Clause" 345 | ], 346 | "authors": [ 347 | { 348 | "name": "Vance Lucas", 349 | "email": "vance@vancelucas.com", 350 | "homepage": "http://www.vancelucas.com" 351 | } 352 | ], 353 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 354 | "keywords": [ 355 | "dotenv", 356 | "env", 357 | "environment" 358 | ], 359 | "time": "2019-01-29T11:11:52+00:00" 360 | }, 361 | { 362 | "name": "wrench/wrench", 363 | "version": "v2.0.8", 364 | "source": { 365 | "type": "git", 366 | "url": "https://github.com/varspool/Wrench.git", 367 | "reference": "92fb5d1c5d7a6f65884ade658b6271de93edafc2" 368 | }, 369 | "dist": { 370 | "type": "zip", 371 | "url": "https://api.github.com/repos/varspool/Wrench/zipball/92fb5d1c5d7a6f65884ade658b6271de93edafc2", 372 | "reference": "92fb5d1c5d7a6f65884ade658b6271de93edafc2", 373 | "shasum": "", 374 | "mirrors": [ 375 | { 376 | "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", 377 | "preferred": true 378 | } 379 | ] 380 | }, 381 | "require": { 382 | "ext-sockets": "*", 383 | "php": ">=5.3" 384 | }, 385 | "require-dev": { 386 | "phpunit/phpunit": "~4.5", 387 | "squizlabs/php_codesniffer": "*" 388 | }, 389 | "type": "library", 390 | "extra": { 391 | "branch-alias": { 392 | "dev-master": "2.0.x-dev" 393 | } 394 | }, 395 | "autoload": { 396 | "psr-0": { 397 | "Wrench": "lib/" 398 | } 399 | }, 400 | "notification-url": "https://packagist.org/downloads/", 401 | "license": [ 402 | "WTFPL" 403 | ], 404 | "authors": [ 405 | { 406 | "name": "Dominic Scheirlinck", 407 | "email": "dominic@varspool.com", 408 | "homepage": "http://www.somethingemporium.com/" 409 | }, 410 | { 411 | "name": "Simon Samtleben", 412 | "email": "web@lemmingzshadow.net", 413 | "homepage": "http://lemmingzshadow.net/" 414 | }, 415 | { 416 | "name": "Nico Kaiser", 417 | "email": "nico@kaiser.me", 418 | "homepage": "http://siriux.net/" 419 | } 420 | ], 421 | "description": "PHP WebSocket client/server library", 422 | "homepage": "http://github.com/varspool/Wrench", 423 | "keywords": [ 424 | "WebSockets", 425 | "hybi", 426 | "websocket" 427 | ], 428 | "time": "2017-02-12T02:08:51+00:00" 429 | } 430 | ], 431 | "packages-dev": [], 432 | "aliases": [], 433 | "minimum-stability": "stable", 434 | "stability-flags": [], 435 | "prefer-stable": false, 436 | "prefer-lowest": false, 437 | "platform": { 438 | "php": ">=7.0.0", 439 | "ext-curl": "*", 440 | "ext-openssl": "*", 441 | "ext-sockets": "*", 442 | "ext-json": "*" 443 | }, 444 | "platform-dev": [] 445 | } 446 | -------------------------------------------------------------------------------- /conf/user.conf.example: -------------------------------------------------------------------------------- 1 | ####################### 2 | # 账户设置 # 3 | ####################### 4 | 5 | # 帐号|密码 6 | APP_USER= 7 | APP_PASS= 8 | 9 | # 令牌(自动生成) 10 | ACCESS_TOKEN= 11 | REFRESH_TOKEN= 12 | COOKIE= 13 | 14 | ####################### 15 | # 功能设置 # 16 | ####################### 17 | 18 | # 舰长亲密度 19 | USE_GUARD=true 20 | 21 | # PK大乱斗 22 | USE_PK=true 23 | 24 | # 主站助手 25 | USE_MASTER_SITE=true 26 | 27 | # 活跃弹幕(false,true)|弹幕房间(为空则随机)|弹幕内容(为空则随机) 28 | USE_DANMU=true 29 | DANMU_ROOMID=9522051 30 | DANMU_CONTENT= 31 | 32 | # 视频投币(false,true)|稿件数(0overload(); 37 | } 38 | Daily::run(); 39 | MasterSite::run(); 40 | Danmu::run(); 41 | GiftSend::run(); 42 | Heart::run(); 43 | Silver::run(); 44 | Task::run(); 45 | Silver2Coin::run(); 46 | GroupSignIn::run(); 47 | Live::run(); 48 | Guard::run(); 49 | Pk::run(); 50 | GiftHeart::run(); 51 | Winning::run(); 52 | MaterialObject::run(); 53 | DataTreating::run(); 54 | Websocket::run(); 55 | usleep(0.5 * 1000000); 56 | } 57 | } 58 | 59 | protected static function loadConfigFile() 60 | { 61 | $file_path = __DIR__ . '/conf/' . self::$conf_file; 62 | 63 | if (is_file($file_path) && self::$conf_file != 'user.conf') { 64 | $load_files = [ 65 | self::$conf_file, 66 | ]; 67 | } else { 68 | $default_file_path = __DIR__ . '/conf/user.conf'; 69 | if (!is_file($default_file_path)) { 70 | exit('默认加载配置文件不存在,请按照文档添加配置文件!'); 71 | } 72 | 73 | $load_files = [ 74 | 'user.conf', 75 | ]; 76 | } 77 | foreach ($load_files as $load_file) { 78 | self::$dotenv = new Dotenv(__DIR__ . '/conf', $load_file); 79 | self::$dotenv->load(); 80 | } 81 | 82 | // load ACCESS_KEY 83 | Login::run(); 84 | self::$dotenv->overload(); 85 | } 86 | 87 | } 88 | 89 | // LOAD 90 | $conf_file = isset($argv[1]) ? $argv[1] : 'user.conf'; 91 | // RUN 92 | Index::run($conf_file); 93 | -------------------------------------------------------------------------------- /src/Base.php: -------------------------------------------------------------------------------- 1 | getenv('ACCESS_TOKEN'), 57 | 'actionKey' => 'appkey', 58 | 'appkey' => $appkey, 59 | 'build' => '6680', 60 | 'device' => 'phone', 61 | 'mobi_app' => 'iphone', 62 | 'platform' => 'ios', 63 | 'ts' => time(), 64 | ]; 65 | 66 | $payload = array_merge($payload, $default); 67 | if (isset($payload['sign'])) { 68 | unset($payload['sign']); 69 | } 70 | ksort($payload); 71 | $data = http_build_query($payload); 72 | $payload['sign'] = md5($data . $appsecret); 73 | return $payload; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Common.php: -------------------------------------------------------------------------------- 1 | '*/*', 17 | 'Accept-Encoding' => 'gzip', 18 | 'Accept-Language' => 'zh-cn', 19 | 'Connection' => 'keep-alive', 20 | 'Content-Type' => 'application/x-www-form-urlencoded', 21 | 'User-Agent' => 'bili-universal/8470 CFNetwork/978.0.7 Darwin/18.5.0', 22 | // 'Referer' => 'https://live.bilibili.com/', 23 | ); 24 | 25 | private static function getHeaders($headers) 26 | { 27 | return array_map(function ($k, $v) { 28 | return $k . ': ' . $v; 29 | }, array_keys($headers), $headers); 30 | } 31 | 32 | public static function post($url, $payload = null, $headers = null, $timeout = 30) 33 | { 34 | $url = self::http2https($url); 35 | Log::debug($url); 36 | $header = is_null($headers) ? self::getHeaders(self::$headers) : self::getHeaders($headers); 37 | 38 | // 重试次数 39 | $ret_count = 300; 40 | $waring = 270; 41 | 42 | while ($ret_count) { 43 | // 网络断开判断 延时方便连接网络 44 | if ($ret_count < $waring) { 45 | Log::warning("正常等待网络连接状态恢复正常..."); 46 | sleep(10); 47 | } 48 | try { 49 | $curl = curl_init(); 50 | if (!is_null($payload)) { 51 | curl_setopt($curl, CURLOPT_POST, 1); 52 | curl_setopt($curl, CURLOPT_POSTFIELDS, is_array($payload) ? http_build_query($payload) : $payload); 53 | } 54 | curl_setopt($curl, CURLOPT_URL, $url); 55 | curl_setopt($curl, CURLOPT_HTTPHEADER, $header); 56 | curl_setopt($curl, CURLOPT_HEADER, 0); 57 | curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); 58 | curl_setopt($curl, CURLOPT_IPRESOLVE, 1); 59 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 60 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 61 | // 超时 重要 62 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); 63 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout); 64 | if (($cookie = getenv('COOKIE')) != "") { 65 | curl_setopt($curl, CURLOPT_COOKIE, $cookie); 66 | } 67 | if (getenv('USE_PROXY') == 'true') { 68 | curl_setopt($curl, CURLOPT_PROXY, getenv('PROXY_IP')); 69 | curl_setopt($curl, CURLOPT_PROXYPORT, getenv('PROXY_PORT')); 70 | } 71 | $raw = curl_exec($curl); 72 | 73 | if ($err_no = curl_errno($curl)) { 74 | throw new \Exception(curl_error($curl)); 75 | } 76 | 77 | if ($raw === false || strpos($raw, 'timeout') !== false) { 78 | Log::warning('重试,获取的资源无效!'); 79 | $ret_count--; 80 | continue; 81 | } 82 | 83 | Log::debug($raw); 84 | curl_close($curl); 85 | return $raw; 86 | 87 | } catch (\Exception $e) { 88 | Log::warning("重试,Curl请求出错,{$e->getMessage()}!"); 89 | $ret_count--; 90 | continue; 91 | } 92 | } 93 | exit('重试次数过多,请检查代码,退出!'); 94 | } 95 | 96 | public static function other($url, $payload = null, $headers = null, $cookie = null, $filter_url = null, $timeout = 30) 97 | { 98 | Log::debug($url); 99 | $header = is_null($headers) ? self::getHeaders(self::$headers) : self::getHeaders($headers); 100 | 101 | // 重试次数 102 | $ret_count = 30; 103 | while ($ret_count) { 104 | try { 105 | $curl = curl_init(); 106 | if (!is_null($payload)) { 107 | curl_setopt($curl, CURLOPT_POST, 1); 108 | curl_setopt($curl, CURLOPT_POSTFIELDS, is_array($payload) ? http_build_query($payload) : $payload); 109 | } 110 | curl_setopt($curl, CURLOPT_URL, $url); 111 | curl_setopt($curl, CURLOPT_HTTPHEADER, $header); 112 | curl_setopt($curl, CURLOPT_HEADER, 0); 113 | curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); 114 | curl_setopt($curl, CURLOPT_IPRESOLVE, 1); 115 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 116 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); 117 | // 超时 重要 118 | curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); 119 | curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout); 120 | // 认证 121 | if (!is_null($cookie)) { 122 | curl_setopt($curl, CURLOPT_COOKIE, $cookie); 123 | } 124 | if (getenv('USE_PROXY') == 'true') { 125 | curl_setopt($curl, CURLOPT_PROXY, getenv('PROXY_IP')); 126 | curl_setopt($curl, CURLOPT_PROXYPORT, getenv('PROXY_PORT')); 127 | } 128 | $raw = curl_exec($curl); 129 | 130 | if ($err_no = curl_errno($curl)) { 131 | throw new \Exception(curl_error($curl)); 132 | } 133 | 134 | if ($raw === false || strpos($raw, 'timeout') !== false) { 135 | Log::warning('重试,获取的资源无效!'); 136 | $ret_count--; 137 | continue; 138 | } 139 | 140 | Log::debug($raw); 141 | curl_close($curl); 142 | return $raw; 143 | 144 | } catch (\Exception $e) { 145 | if (!is_null($filter_url)) { 146 | Log::warning("Curl请求出错,{$e->getMessage()}!"); 147 | Log::warning("该请求 {$filter_url} 为过滤url,不进行重试!"); 148 | return null; 149 | } 150 | Log::warning("重试,Curl请求出错,{$e->getMessage()}!"); 151 | $ret_count--; 152 | continue; 153 | } 154 | } 155 | exit('重试次数过多,请检查代码,退出!'); 156 | } 157 | 158 | 159 | public static function get($url, $payload = null, $headers = null) 160 | { 161 | if (!is_null($payload)) { 162 | $url .= '?' . http_build_query($payload); 163 | } 164 | return self::post($url, null, $headers); 165 | } 166 | 167 | /** 168 | * @use 单次请求 169 | * @param $method 170 | * @param $url 171 | * @param array $payload 172 | * @param array $headers 173 | * @param int $timeout 174 | * @return false|string 175 | */ 176 | public static function singleRequest($method, $url, $payload = [], $headers = [], $timeout = 10) 177 | { 178 | $url = self::http2https($url); 179 | Log::debug($url); 180 | $options = array( 181 | 'http' => array( 182 | 'method' => strtoupper($method), 183 | 'header' => self::arr2str($headers), 184 | 'content' => http_build_query($payload), 185 | 'timeout' => $timeout, 186 | ), 187 | ); 188 | $result = @file_get_contents($url, false, stream_context_create($options)); 189 | Log::debug($result); 190 | return $result ? $result : null; 191 | } 192 | 193 | /** 194 | * @use 关联数组转字符串 195 | * @param array $array 196 | * @param string $separator 197 | * @return string 198 | */ 199 | private static function arr2str(array $array, string $separator = "\r\n"): string 200 | { 201 | $tmp = ''; 202 | foreach ($array as $key => $value) { 203 | $tmp .= "{$key}:{$value}{$separator}"; 204 | } 205 | return $tmp; 206 | } 207 | 208 | 209 | /** 210 | * @use http(s)转换 211 | * @param string $url 212 | * @return string 213 | */ 214 | protected static function http2https($url) 215 | { 216 | switch (getenv('USE_HTTPS')) { 217 | case 'false': 218 | if (strpos($url, 'ttps://')) { 219 | $url = str_replace('https://', 'http://', $url); 220 | } 221 | break; 222 | case 'true': 223 | if (strpos($url, 'ttp://')) { 224 | $url = str_replace('http://', 'https://', $url); 225 | } 226 | break; 227 | default: 228 | Log::warning('当前协议设置不正确,请检查配置文件!'); 229 | die(); 230 | break; 231 | } 232 | 233 | return $url; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Daily.php: -------------------------------------------------------------------------------- 1 | time()) { 20 | return; 21 | } 22 | self::dailyBag(); 23 | 24 | self::$lock = time() + 3600; 25 | } 26 | 27 | protected static function dailyBag() 28 | { 29 | $payload = []; 30 | $data = Curl::get('https://api.live.bilibili.com/gift/v2/live/receive_daily_bag', Sign::api($payload)); 31 | $data = json_decode($data, true); 32 | 33 | if (isset($data['code']) && $data['code']) { 34 | Log::warning('每日礼包领取失败!', ['msg' => $data['message']]); 35 | } else { 36 | Log::notice('每日礼包领取成功'); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/Danmu.php: -------------------------------------------------------------------------------- 1 | time() || getenv('USE_DANMU') == 'false') { 20 | return; 21 | } 22 | $room_id = empty(getenv('DANMU_ROOMID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('DANMU_ROOMID')); 23 | $msg = empty(getenv('DANMU_CONTENT')) ? self::getMsgInfo() : getenv('DANMU_CONTENT'); 24 | 25 | $info = [ 26 | 'roomid' => $room_id, 27 | 'content' => $msg, 28 | ]; 29 | 30 | if (self::privateSendMsg($info)) { 31 | self::$lock = time() + 3600; 32 | return; 33 | } 34 | 35 | self::$lock = time() + 30; 36 | } 37 | 38 | // 获取随机弹幕 39 | private static function getMsgInfo() 40 | { 41 | /** 42 | * 整理一部分API,收集于网络,侵权麻烦联系我删除. 43 | * 如果设置项不能用可以选择,只保证代码发布时正常. 44 | * 格式全部为TEXT,可以自己替换. 45 | */ 46 | $punctuations = [',', ',', '。', '!', '.', ';', '——']; 47 | $apis = [ 48 | 'https://api.lwl12.com/hitokoto/v1?encode=realjso', 49 | 'https://api.ly522.com/yan.php?format=text', 50 | 'https://v1.hitokoto.cn/?encode=text', 51 | 'https://api.jysafe.cn/yy/', 52 | 'https://m.mom1.cn/api/yan/api.php', 53 | 'https://api.ooopn.com/yan/api.php?type=text', 54 | 'https://api.imjad.cn/hitokoto/', 55 | 'https://www.ly522.com/hitokoto/', 56 | 'https://www.tddiao.online/word/', 57 | 'https://api.guoch.xyz/', 58 | 'http://www.ooomg.cn/dutang/', 59 | 'https://api.gushi.ci/rensheng.txt', 60 | 'https://api.itswincer.com/hitokoto/v2/', 61 | 'http://api.dsecret.com/yiyan/', 62 | 'https://api.xygeng.cn/dailywd/api/api.php', 63 | ]; 64 | shuffle($apis); 65 | try { 66 | foreach ($apis as $url) { 67 | $data = Curl::singleRequest('get', $url); 68 | if (is_null($data)) continue; 69 | foreach ($punctuations as $punctuation) { 70 | if (strpos($data, $punctuation)) { 71 | $data = explode($punctuation, $data)[0]; 72 | break; 73 | } 74 | } 75 | return $data; 76 | } 77 | } catch (\Exception $e) { 78 | return $e; 79 | } 80 | } 81 | 82 | 83 | //发送弹幕通用模块 84 | private static function sendMsg($info) 85 | { 86 | $user_info = User::parseCookies(); 87 | $raw = Curl::get('https://api.live.bilibili.com/room/v1/Room/room_init?id=' . $info['roomid']); 88 | $de_raw = json_decode($raw, true); 89 | 90 | $payload = [ 91 | 'color' => '16777215', 92 | 'fontsize' => 25, 93 | 'mode' => 1, 94 | 'msg' => $info['content'], 95 | 'rnd' => 0, 96 | 'roomid' => $de_raw['data']['room_id'], 97 | 'csrf' => $user_info['token'], 98 | 'csrf_token' => $user_info['token'], 99 | ]; 100 | 101 | return Curl::post('https://api.live.bilibili.com/msg/send', Sign::api($payload)); 102 | } 103 | 104 | //使用发送弹幕模块 105 | private static function privateSendMsg($info) 106 | { 107 | //TODO 暂时性功能 有需求就修改 108 | $raw = self::sendMsg($info); 109 | $de_raw = json_decode($raw, true); 110 | 111 | if ($de_raw['code'] == 1001) { 112 | Log::warning($de_raw['msg']); 113 | return false; 114 | } 115 | 116 | if (!$de_raw['code']) { 117 | Log::info('弹幕发送成功!'); 118 | return true; 119 | } 120 | 121 | Log::error("弹幕发送失败, {$de_raw['msg']}"); 122 | return false; 123 | } 124 | } -------------------------------------------------------------------------------- /src/DataTreating.php: -------------------------------------------------------------------------------- 1 | 'active', 124 | 'title' => $key, 125 | 'room_id' => $resp['real_roomid'] 126 | ]; 127 | } 128 | } 129 | } 130 | 131 | break; 132 | case 'SYS_MSG': 133 | /** 134 | * 系统消息, 广播 135 | */ 136 | // 屏蔽系统公告 137 | if ((strpos($resp['msg'], '系统公告') !== false)) { 138 | break; 139 | } 140 | if (getenv('AUTO_KEYS') == 'false') { 141 | foreach (self::$active_keys as $key) { 142 | if (strpos($resp['msg'], $key) !== false) { 143 | return [ 144 | 'type' => 'active', 145 | 'title' => $key, 146 | 'room_id' => $resp['real_roomid'] 147 | ]; 148 | } 149 | } 150 | var_dump($resp); 151 | } 152 | break; 153 | case 'SPECIAL_GIFT': 154 | /** 155 | * 特殊礼物消息 --节奏风暴 156 | */ 157 | if (array_key_exists('39', $resp['data'])) { 158 | if ($resp['data']['39']['action'] == 'start') { 159 | return [ 160 | 'type' => 'storm', 161 | 'num' => 1, 162 | 'id' => $resp['data']['39']['id'], 163 | ]; 164 | } 165 | } 166 | var_dump($resp['data']); 167 | break; 168 | case 'WELCOME_ACTIVITY': 169 | /** 170 | * 欢迎消息-活动 171 | */ 172 | break; 173 | case 'GUARD_BUY': 174 | /** 175 | * 舰队购买 176 | */ 177 | break; 178 | case 'RAFFLE_START': 179 | /** 180 | * 抽奖开始 181 | */ 182 | break; 183 | case 'RAFFLE_END': 184 | /** 185 | * 抽奖结束 186 | */ 187 | break; 188 | case 'TV_START': 189 | /** 190 | * 小电视抽奖开始 191 | */ 192 | break; 193 | case 'TV_END': 194 | /** 195 | * 小电视抽奖结束 196 | */ 197 | break; 198 | case 'ENTRY_EFFECT': 199 | /** 200 | * 进入房间提示 201 | */ 202 | break; 203 | case 'EVENT_CMD': 204 | /** 205 | * 活动相关 206 | */ 207 | break; 208 | case 'ROOM_SILENT_ON': 209 | /** 210 | * 房间开启禁言 211 | */ 212 | break; 213 | case 'ROOM_SILENT_OFF': 214 | /** 215 | * 房间禁言结束 216 | */ 217 | break; 218 | case 'ROOM_SHIELD': 219 | /** 220 | * 房间屏蔽 221 | */ 222 | break; 223 | case 'COMBO_SEND': 224 | /** 225 | * COMBO赠送 226 | */ 227 | break; 228 | case 'COMBO_END': 229 | /** 230 | * COMBO结束 231 | */ 232 | break; 233 | case 'ROOM_BLOCK_MSG': 234 | /** 235 | * 房间封禁消息 236 | */ 237 | break; 238 | case 'ROOM_ADMINS': 239 | /** 240 | * 管理员变更 241 | */ 242 | break; 243 | case 'CHANGE_ROOM_INFO': 244 | /** 245 | * 房间设置变更 246 | */ 247 | break; 248 | case 'ROOM_SKIN_MSG': 249 | /** 250 | * 房间皮肤消息 251 | */ 252 | break; 253 | case 'WISH_BOTTLE': 254 | /** 255 | * 许愿瓶 256 | */ 257 | break; 258 | case 'CUT_OFF': 259 | /** 260 | * 直播强制切断 261 | */ 262 | break; 263 | case 'ROOM_RANK': 264 | /** 265 | * 周星榜 266 | */ 267 | break; 268 | case 'ROOM_REAL_TIME_MESSAGE_UPDATE': 269 | /* 270 | * 房间时间更新 271 | */ 272 | break; 273 | case 'ACTIVITY_BANNER_UPDATE_BLS': 274 | /* 275 | * BLS活动 276 | */ 277 | break; 278 | case 'NOTICE_MSG': 279 | /** 280 | * 分区通知 281 | * 1 《第五人格》哔哩哔哩直播预选赛六强诞生! 282 | * 2 全区广播:<%user_name%>送给<%user_name%>1个嗨翻全城,快来抽奖吧 283 | * 3 <%user_name%> 在 <%user_name%> 的房间开通了总督并触发了抽奖,点击前往TA的房间去抽奖吧 284 | * 4 欢迎 <%总督 user_name%> 登船 285 | * 5 恭喜 <%user_name%> 获得大奖 <%23333x银瓜子%>, 感谢 <%user_name%> 的赠送 286 | * 6 <%user_name%> 在直播间 <%529%> 使用了 <%20%> 倍节奏风暴,大家快去跟风领取奖励吧!(只报20的) 287 | * 8 全区广播:主播<%user_name%>开启了“任意门”,点击前往TA的房间去抽奖吧! 288 | */ 289 | 290 | $msg_type = $resp['msg_type']; 291 | $real_roomid = $resp['real_roomid']; 292 | $msg_common = str_replace(' ', '', $resp['msg_common']); 293 | 294 | if (in_array($msg_type, [2, 8])) { 295 | if (getenv('AUTO_KEYS') != 'true') { 296 | break; 297 | } 298 | return [ 299 | 'type' => 'active', 300 | 'title' => "统一活动", 301 | 'room_id' => $real_roomid 302 | ]; 303 | 304 | } elseif ($msg_type == 6) { 305 | if (strpos($msg_common, '节奏风暴') !== false) { 306 | return [ 307 | 'type' => 'storm', 308 | 'num' => 20, 309 | 'room_id' => $real_roomid, 310 | ]; 311 | } 312 | } else { 313 | break; 314 | } 315 | 316 | break; 317 | default: 318 | // 新添加的消息类型 319 | // if (!is_null($resp)) { 320 | // var_dump($resp); 321 | // } 322 | return [ 323 | 'type' => 'unkown', 324 | 'raw' => $resp['cmd'], 325 | ]; 326 | break; 327 | } 328 | return false; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/File.php: -------------------------------------------------------------------------------- 1 | time()) { 21 | return; 22 | } 23 | if (self::giftheart()) { 24 | self::$lock = time() + 60 * 60; 25 | return; 26 | } 27 | self::$lock = time() + 5 * 60; 28 | } 29 | 30 | // GIFT HEART 31 | protected static function giftheart(): bool 32 | { 33 | $payload = [ 34 | 'roomid' => getenv('ROOM_ID'), 35 | ]; 36 | $raw = Curl::get('https://api.live.bilibili.com/gift/v2/live/heart_gift_receive', Sign::api($payload)); 37 | $de_raw = json_decode($raw, true); 38 | 39 | if ($de_raw['code'] == -403) { 40 | Log::info($de_raw['msg']); 41 | $payload = [ 42 | 'ruid' => 17561885, 43 | ]; 44 | Curl::get('https://api.live.bilibili.com/eventRoom/index', Sign::api($payload)); 45 | return true; 46 | } 47 | 48 | if ($de_raw['code'] != 0) { 49 | Log::warning($de_raw['msg']); 50 | return false; 51 | } 52 | 53 | if ($de_raw['data']['heart_status'] == 0) { 54 | Log::info('没有礼物可以领了呢!'); 55 | return true; 56 | } 57 | 58 | if (isset($de_raw['data']['gift_list'])) { 59 | foreach ($de_raw['data']['gift_list'] as $vo) { 60 | Log::info("{$de_raw['msg']},礼物 {$vo['gift_name']} ({$vo['day_num']}/{$vo['day_limit']})"); 61 | } 62 | return false; 63 | } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/GiftSend.php: -------------------------------------------------------------------------------- 1 | $data['message']]); 30 | Log::warning('清空礼物功能禁用!'); 31 | self::$lock = time() + 100000000; 32 | return; 33 | } 34 | 35 | self::$uid = $data['data']['uid']; 36 | 37 | $payload = [ 38 | 'id' => getenv('ROOM_ID'), 39 | ]; 40 | $data = Curl::get('https://api.live.bilibili.com/room/v1/Room/room_init', Sign::api($payload)); 41 | $data = json_decode($data, true); 42 | 43 | if (isset($data['code']) && $data['code']) { 44 | Log::warning('获取主播房间号失败!', ['msg' => $data['message']]); 45 | Log::warning('清空礼物功能禁用!'); 46 | self::$lock = time() + 100000000; 47 | return; 48 | } 49 | 50 | Log::info('直播间信息生成完毕!'); 51 | 52 | self::$ruid = $data['data']['uid']; 53 | self::$roomid = $data['data']['room_id']; 54 | } 55 | 56 | public static function run() 57 | { 58 | if (empty(self::$ruid)) { 59 | self::getRoomInfo(); 60 | } 61 | 62 | if (self::$lock > time()) { 63 | return; 64 | } 65 | 66 | $payload = []; 67 | $data = Curl::get('https://api.live.bilibili.com/gift/v2/gift/bag_list', Sign::api($payload)); 68 | $data = json_decode($data, true); 69 | 70 | if (isset($data['code']) && $data['code']) { 71 | Log::warning('背包查看失败!', ['msg' => $data['message']]); 72 | } 73 | 74 | if (isset($data['data']['list'])) { 75 | foreach ($data['data']['list'] as $vo) { 76 | if ($vo['corner_mark'] == '永久'){ 77 | continue; 78 | } 79 | if ($vo['expire_at'] >= $data['data']['time'] && $vo['expire_at'] <= $data['data']['time'] + 3600) { 80 | self::send($vo); 81 | sleep(3); 82 | } 83 | } 84 | } 85 | 86 | self::$lock = time() + 600; 87 | } 88 | 89 | protected static function send($value) 90 | { 91 | $payload = [ 92 | 'coin_type' => 'silver', 93 | 'gift_id' => $value['gift_id'], 94 | 'ruid' => self::$ruid, 95 | 'uid' => self::$uid, 96 | 'biz_id' => self::$roomid, 97 | 'gift_num' => $value['gift_num'], 98 | 'data_source_id' => '', 99 | 'data_behavior_id' => '', 100 | 'bag_id' => $value['bag_id'] 101 | ]; 102 | 103 | $data = Curl::post('https://api.live.bilibili.com/gift/v2/live/bag_send', Sign::api($payload)); 104 | $data = json_decode($data, true); 105 | 106 | if (isset($data['code']) && $data['code']) { 107 | Log::warning('送礼失败!', ['msg' => $data['message']]); 108 | } else { 109 | Log::notice("成功向 {$payload['biz_id']} 投喂了 {$value['gift_num']} 个{$value['gift_name']}"); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/GroupSignIn.php: -------------------------------------------------------------------------------- 1 | time()) { 21 | return; 22 | } 23 | 24 | $groups = self::getGroupList(); 25 | if (empty($groups)) { 26 | self::$lock = time() + 24 * 60 * 60; 27 | return; 28 | } 29 | 30 | foreach ($groups as $group) { 31 | self::signInGroup($group); 32 | } 33 | 34 | self::$lock = time() + 8 * 60 * 60; 35 | } 36 | 37 | //GROUP LIST 38 | protected static function getGroupList(): array 39 | { 40 | $payload = []; 41 | $raw = Curl::get('https://api.vc.bilibili.com/link_group/v1/member/my_groups', Sign::api($payload)); 42 | $de_raw = json_decode($raw, true); 43 | 44 | if (empty($de_raw['data']['list'])) { 45 | Log::notice('你没有需要签到的应援团!'); 46 | return []; 47 | } 48 | return $de_raw['data']['list']; 49 | } 50 | 51 | //SIGN IN 52 | protected static function signInGroup(array $groupInfo): bool 53 | { 54 | $payload = [ 55 | 'group_id' => $groupInfo['group_id'], 56 | 'owner_id' => $groupInfo['owner_uid'], 57 | ]; 58 | $raw = Curl::get('https://api.vc.bilibili.com/link_setting/v1/link_setting/sign_in', Sign::api($payload)); 59 | $de_raw = json_decode($raw, true); 60 | 61 | if ($de_raw['code'] != '0') { 62 | Log::warning('在应援团{' . $groupInfo['group_name'] . '}中签到失败,原因待查'); 63 | // TODO 64 | return false; 65 | } 66 | if ($de_raw['data']['status'] == '0') { 67 | Log::info('在应援团{' . $groupInfo['group_name'] . '}中签到成功,增加{' . $de_raw['data']['add_num'] . '点}亲密度'); 68 | } else { 69 | Log::notice('在应援团{' . $groupInfo['group_name'] . '}中不要重复签到'); 70 | } 71 | 72 | return true; 73 | } 74 | } -------------------------------------------------------------------------------- /src/Guard.php: -------------------------------------------------------------------------------- 1 | static::$lock) { 31 | self::startLottery(); 32 | } 33 | } 34 | return true; 35 | } 36 | 37 | // 抽奖结束 38 | protected static function endLottery($guard_id): bool 39 | { 40 | if (count(static::$lottery_list_end) > 2000) { 41 | static::$lottery_list_end = []; 42 | } 43 | array_push(static::$lottery_list_end, $guard_id); 44 | return true; 45 | } 46 | 47 | // 上船抽奖 48 | protected static function startLottery(): bool 49 | { 50 | $flag = 3; 51 | while ($flag) { 52 | $guard = array_shift(static::$lottery_list_start); 53 | if (is_null($guard)) { 54 | break; 55 | } 56 | // if (!$guard['Status']) { 57 | // continue; 58 | // } 59 | $guard_id = $guard['Id']; 60 | $guard_time = $guard['EndTime']; 61 | 62 | if (in_array($guard_id, static::$lottery_list_end) || $guard_id == 0 || $guard_time < time()) { 63 | continue; 64 | } 65 | $guard_roomid = $guard['RoomId']; 66 | Live::goToRoom($guard_roomid); 67 | $data = self::guardLottery($guard_roomid, $guard_id); 68 | 69 | if ($data['code'] == 0) { 70 | Log::notice("房间[{$guard_roomid}]编号[{$guard_id}]上船:" . (!empty($data['data']['award_text']) ? $data['data']['award_text'] : "{$data['data']['award_name']}x{$data['data']['award_num']}")); 71 | } elseif ($data['code'] == 400 && $data['msg'] == '你已经领取过啦') { 72 | Log::info("房间[{$guard_roomid}]编号[{$guard_id}]上船:{$data['message']}"); 73 | } else { 74 | Log::warning("房间[{$guard_roomid}]编号[{$guard_id}]上船:{$data['message']}"); 75 | } 76 | static::endLottery($guard_id); 77 | $flag--; 78 | } 79 | return true; 80 | } 81 | 82 | // 抽奖 83 | protected static function guardLottery($guard_roomid, $guard_id): array 84 | { 85 | $user_info = User::parseCookies(); 86 | $url = "https://api.live.bilibili.com/xlive/lottery-interface/v3/guard/join"; 87 | $payload = [ 88 | "roomid" => $guard_roomid, 89 | "id" => $guard_id, 90 | "type" => "guard", 91 | "csrf_token" => $user_info['token'], 92 | 'csrf' => $user_info['token'], 93 | 'visit_id' => null, 94 | ]; 95 | $raw = Curl::post($url, Sign::api($payload)); 96 | $de_raw = json_decode($raw, true); 97 | return $de_raw; 98 | } 99 | 100 | // 获取上船列表 101 | protected static function getGuardList(): bool 102 | { 103 | $headers = [ 104 | 'User-Agent' => "bilibili-live-tools/" . mt_rand(1000000, 99999999), 105 | ]; 106 | $raw = Curl::other("http://118.25.108.153:8080/guard", null, $headers, null, '118.25.108.153:8080'); 107 | $de_raw = Common::analyJson($raw, true); 108 | if (!$de_raw) { 109 | Log::info("舰长服务器返回为空或暂时宕机"); 110 | return false; 111 | } 112 | static::$lottery_list_start = array_merge(static::$lottery_list_start, $de_raw); 113 | $guard_num = count(static::$lottery_list_start); 114 | Log::info("当前队列中共有{$guard_num}个舰长待抽奖"); 115 | return true; 116 | } 117 | } -------------------------------------------------------------------------------- /src/Heart.php: -------------------------------------------------------------------------------- 1 | time()) { 20 | return; 21 | } 22 | 23 | self::pc(); 24 | self::mobile(); 25 | 26 | self::$lock = time() + 300; 27 | } 28 | 29 | protected static function pc() 30 | { 31 | $payload = [ 32 | 'room_id' => getenv('ROOM_ID'), 33 | ]; 34 | $data = Curl::post('https://api.live.bilibili.com/User/userOnlineHeart', Sign::api($payload)); 35 | $data = json_decode($data, true); 36 | 37 | if (isset($data['code']) && $data['code']) { 38 | Log::warning('WEB端 直播间心跳停止惹~', ['msg' => $data['message']]); 39 | } else { 40 | Log::info('WEB端 发送心跳正常!'); 41 | } 42 | } 43 | 44 | protected static function mobile() 45 | { 46 | $payload = [ 47 | 'room_id' => getenv('ROOM_ID'), 48 | ]; 49 | $data = Curl::post('https://api.live.bilibili.com/mobile/userOnlineHeart', Sign::api($payload)); 50 | $data = json_decode($data, true); 51 | 52 | if (isset($data['code']) && $data['code']) { 53 | Log::warning('APP端 直播间心跳停止惹~', ['msg' => $data['message']]); 54 | } else { 55 | Log::info('APP端 发送心跳正常!'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Live.php: -------------------------------------------------------------------------------- 1 | $room_id, 86 | 'csrf_token' => $user_info['token'], 87 | 'csrf' => $user_info['token'], 88 | 'visit_id' => null, 89 | ]; 90 | Curl::post('https://api.live.bilibili.com/room/v1/Room/room_entry_action', Sign::api($payload)); 91 | Log::info('进入直播间[' . $room_id . ']抽奖!'); 92 | return true; 93 | } 94 | 95 | // get Millisecond 96 | public static function getMillisecond() 97 | { 98 | list($t1, $t2) = explode(' ', microtime()); 99 | return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000); 100 | } 101 | 102 | // IS SLEEP 103 | public static function isSleep() 104 | { 105 | if (self::$lock > time()) { 106 | return; 107 | } 108 | self::$lock = time() + 5 * 60; 109 | 110 | $hour = date('H'); 111 | if ($hour >= 2 && $hour < 6) { 112 | self::bannedVisit('sleep'); 113 | Log::warning('休眠时间,暂停非必要任务,4小时后自动开启!'); 114 | return; 115 | } 116 | 117 | $payload = []; 118 | $raw = Curl::get('https://api.live.bilibili.com/mobile/freeSilverAward', Sign::api($payload)); 119 | $de_raw = json_decode($raw, true); 120 | if ($de_raw['msg'] == '访问被拒绝') { 121 | self::bannedVisit('ban'); 122 | Log::warning('账号拒绝访问,暂停非必要任务,凌晨自动开启!'); 123 | } 124 | return; 125 | } 126 | 127 | //被封禁访问 128 | public static function bannedVisit($arg) 129 | { 130 | //获取当前时间 131 | $block_time = strtotime(date("Y-m-d H:i:s")); 132 | 133 | if ($arg == 'ban') { 134 | $unblock_time = strtotime(date("Y-m-d", strtotime("+1 day", $block_time))); 135 | } elseif ($arg == 'sleep') { 136 | // TODO 137 | $unblock_time = $block_time + 4 * 60 * 60; 138 | } else { 139 | $unblock_time = time(); 140 | } 141 | 142 | $second = time() + ceil($unblock_time - $block_time) + 5 * 60; 143 | $hour = floor(($second - time()) / 60 / 60); 144 | 145 | if ($arg == 'ban') { 146 | // 推送被ban信息 147 | Notice::run('banned', $hour); 148 | } 149 | 150 | self::$lock = $second; 151 | 152 | Silver::$lock = $second; 153 | MaterialObject::$lock = $second; 154 | Websocket::$lock = $second; 155 | GiftHeart::$lock = $second; 156 | Guard::$lock = $second; 157 | 158 | return; 159 | } 160 | 161 | } -------------------------------------------------------------------------------- /src/Log.php: -------------------------------------------------------------------------------- 1 | setFormatter(new ColoredLineFormatter()); 34 | $logger->pushHandler($handler); 35 | self::$instance = $logger; 36 | } 37 | 38 | private static function prefix() 39 | { 40 | if (getenv('APP_MULTIPLE') == 'true') { 41 | return '[' . (empty($t = getenv('APP_USER_IDENTITY')) ? getenv('APP_USER') : $t) . ']'; 42 | } 43 | return ''; 44 | } 45 | 46 | private static function writeLog($type, $message) 47 | { 48 | if (getenv('APP_WRITELOG') == 'true') { 49 | $path = './' . getenv("APP_WRITELOGPATH") . '/'; 50 | if (!file_exists($path)) { 51 | mkdir($path); 52 | chmod($path, 0777); 53 | } 54 | $filename = $path . getenv('APP_USER') . ".log"; 55 | $date = date('[Y-m-d H:i:s] '); 56 | $data = $date . ' Log.' . $type . ' ' . $message . PHP_EOL; 57 | file_put_contents($filename, $data, FILE_APPEND); 58 | } 59 | return; 60 | } 61 | 62 | public static function debug($message, array $context = []) 63 | { 64 | self::writeLog('DEBUG', $message); 65 | self::getLogger()->addDebug($message, $context); 66 | } 67 | 68 | public static function info($message, array $context = []) 69 | { 70 | $message = self::prefix() . $message; 71 | self::writeLog('INFO', $message); 72 | self::getLogger()->addInfo($message, $context); 73 | self::callback(Logger::INFO, 'INFO', $message); 74 | } 75 | 76 | public static function notice($message, array $context = []) 77 | { 78 | $message = self::prefix() . $message; 79 | self::writeLog('NOTICE', $message); 80 | self::getLogger()->addNotice($message, $context); 81 | self::callback(Logger::NOTICE, 'NOTICE', $message); 82 | } 83 | 84 | public static function warning($message, array $context = []) 85 | { 86 | $message = self::prefix() . $message; 87 | self::writeLog('WARNING', $message); 88 | self::getLogger()->addWarning($message, $context); 89 | self::callback(Logger::WARNING, 'WARNING', $message); 90 | } 91 | 92 | public static function error($message, array $context = []) 93 | { 94 | $message = self::prefix() . $message; 95 | self::writeLog('ERROR', $message); 96 | self::getLogger()->addError($message, $context); 97 | self::callback(Logger::ERROR, 'ERROR', $message); 98 | } 99 | 100 | public static function callback($levelId, $level, $message) 101 | { 102 | $callback_level = (('APP_CALLBACK_LEVEL') == '') ? (Logger::ERROR) : intval(getenv('APP_CALLBACK_LEVEL')); 103 | if ($levelId >= $callback_level) { 104 | $url = str_replace('{account}', self::prefix(), getenv('APP_CALLBACK')); 105 | $url = str_replace('{level}', $level, $url); 106 | $url = str_replace('{message}', urlencode($message), $url); 107 | Curl::get($url); 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /src/Login.php: -------------------------------------------------------------------------------- 1 | overload(); 26 | 27 | Log::info('正在检查令牌合法性...'); 28 | if (!self::info()) { 29 | Log::warning('令牌即将过期'); 30 | Log::info('申请更换令牌中...'); 31 | if (!self::refresh()) { 32 | Log::warning('无效令牌,正在重新申请...'); 33 | self::login(); 34 | } 35 | } 36 | self::$lock = time() + 3600; 37 | } 38 | 39 | public static function check() 40 | { 41 | if (self::$lock > time()) { 42 | return true; 43 | } 44 | self::$lock = time() + 7200; 45 | if (!self::info()) { 46 | Log::warning('令牌即将过期'); 47 | Log::info('申请更换令牌中...'); 48 | if (!self::refresh()) { 49 | Log::warning('无效令牌,正在重新申请...'); 50 | self::login(); 51 | } 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | protected static function info() 58 | { 59 | $access_token = getenv('ACCESS_TOKEN'); 60 | $payload = [ 61 | 'access_token' => $access_token, 62 | ]; 63 | $data = Curl::get('https://passport.bilibili.com/api/v2/oauth2/info', Sign::api($payload)); 64 | $data = json_decode($data, true); 65 | if (isset($data['code']) && $data['code']) { 66 | Log::error('检查令牌失败', ['msg' => $data['message']]); 67 | return false; 68 | } 69 | Log::info('令牌有效期: ' . date('Y-m-d H:i:s', $data['ts'] + $data['data']['expires_in'])); 70 | return $data['data']['expires_in'] > 14400; 71 | } 72 | 73 | public static function refresh() 74 | { 75 | $access_token = getenv('ACCESS_TOKEN'); 76 | $refresh_token = getenv('REFRESH_TOKEN'); 77 | $payload = [ 78 | 'access_token' => $access_token, 79 | 'refresh_token' => $refresh_token, 80 | ]; 81 | $data = Curl::post('https://passport.bilibili.com/api/oauth2/refreshToken', Sign::api($payload)); 82 | $data = json_decode($data, true); 83 | if (isset($data['code']) && $data['code']) { 84 | Log::error('重新生成令牌失败', ['msg' => $data['message']]); 85 | return false; 86 | } 87 | Log::info('令牌生成完毕!'); 88 | $access_token = $data['data']['access_token']; 89 | File::writeNewEnvironmentFileWith('ACCESS_TOKEN', $access_token); 90 | Log::info(' > access token: ' . $access_token); 91 | $refresh_token = $data['data']['refresh_token']; 92 | File::writeNewEnvironmentFileWith('REFRESH_TOKEN', $refresh_token); 93 | Log::info(' > refresh token: ' . $refresh_token); 94 | return true; 95 | } 96 | 97 | protected static function login($captcha = '', $headers = []) 98 | { 99 | $user = getenv('APP_USER'); 100 | $pass = getenv('APP_PASS'); 101 | if (empty($user) || empty($pass)) { 102 | Log::error('空白的帐号和口令!'); 103 | die(); 104 | } 105 | 106 | // get PublicKey 107 | Log::info('正在载入安全模块...'); 108 | $payload = []; 109 | $data = Curl::post('https://passport.bilibili.com/api/oauth2/getKey', Sign::api($payload)); 110 | $data = json_decode($data, true); 111 | if (isset($data['code']) && $data['code']) { 112 | Log::error('公钥获取失败', ['msg' => $data['message']]); 113 | die(); 114 | } else { 115 | Log::info('安全模块载入完毕!'); 116 | } 117 | $public_key = $data['data']['key']; 118 | $hash = $data['data']['hash']; 119 | openssl_public_encrypt($hash . $pass, $crypt, $public_key); 120 | for ($i = 0; $i < 30; $i++) { 121 | // login 122 | Log::info('正在获取令牌...'); 123 | $payload = [ 124 | 'subid' => 1, 125 | 'permission' => 'ALL', 126 | 'username' => $user, 127 | 'password' => base64_encode($crypt), 128 | 'captcha' => $captcha, 129 | ]; 130 | $data = Curl::post('https://passport.bilibili.com/api/v2/oauth2/login', Sign::api($payload), $headers); 131 | $data = json_decode($data, true); 132 | if (isset($data['code']) && $data['code'] == -105) { 133 | $captcha_data = static::loginWithCaptcha(); 134 | $captcha = $captcha_data['captcha']; 135 | $headers = $captcha_data['headers']; 136 | continue; 137 | } 138 | break; 139 | } 140 | if (isset($data['code']) && $data['code']) { 141 | Log::error('登录失败', ['msg' => $data['message']]); 142 | die(); 143 | } 144 | self::saveCookie($data); 145 | Log::info('令牌获取成功!'); 146 | $access_token = $data['data']['token_info']['access_token']; 147 | File::writeNewEnvironmentFileWith('ACCESS_TOKEN', $access_token); 148 | Log::info(' > access token: ' . $access_token); 149 | $refresh_token = $data['data']['token_info']['refresh_token']; 150 | File::writeNewEnvironmentFileWith('REFRESH_TOKEN', $refresh_token); 151 | Log::info(' > refresh token: ' . $refresh_token); 152 | 153 | return; 154 | } 155 | 156 | 157 | protected static function loginWithCaptcha() 158 | { 159 | Log::info('登录需要验证 ,启动验证码登录!'); 160 | $headers = [ 161 | 'Accept' => 'application/json, text/plain, */*', 162 | 'User-Agent' => 'bili-universal/8230 CFNetwork/975.0.3 Darwin/18.2.0', 163 | 'Host' => 'passport.bilibili.com', 164 | 'Cookie' => 'sid=blhelper' 165 | ]; 166 | $data = Curl::other('https://passport.bilibili.com/captcha', null, $headers); 167 | $data = base64_encode($data); 168 | $captcha = static::ocrCaptcha($data); 169 | return [ 170 | 'captcha' => $captcha, 171 | 'headers' => $headers, 172 | ]; 173 | } 174 | 175 | 176 | private static function ocrCaptcha($captcha_img) 177 | { 178 | $payload = [ 179 | 'image' => (string)$captcha_img 180 | ]; 181 | $headers = [ 182 | 'Content-Type' => 'application/json', 183 | ]; 184 | $data = Curl::other('http://47.102.120.84:19951/', json_encode($payload), $headers); 185 | $de_raw = json_decode($data, true); 186 | Log::info("验证码识别结果 {$de_raw['message']}"); 187 | 188 | return $de_raw['message']; 189 | } 190 | 191 | private static function saveCookie($data) 192 | { 193 | Log::info('COOKIE获取成功!'); 194 | //临时保存cookie 195 | $temp = ''; 196 | $cookies = $data['data']['cookie_info']['cookies']; 197 | foreach ($cookies as $cookie) { 198 | $temp .= $cookie['name'] . '=' . $cookie['value'] . ';'; 199 | } 200 | File::writeNewEnvironmentFileWith('COOKIE', $temp); 201 | Log::info(' > auth cookie: ' . $temp); 202 | return; 203 | } 204 | 205 | } -------------------------------------------------------------------------------- /src/MasterSite.php: -------------------------------------------------------------------------------- 1 | time() || getenv('USE_MASTER_SITE') == 'false') { 19 | return; 20 | } 21 | if (self::watchAid() && self::shareAid() && self::coinAdd()) { 22 | self::$lock = time() + 24 * 60 * 60; 23 | return; 24 | } 25 | self::$lock = time() + 3600; 26 | } 27 | 28 | // 投币 29 | private static function reward($aid): bool 30 | { 31 | $user_info = User::parseCookies(); 32 | $url = "https://api.bilibili.com/x/web-interface/coin/add"; 33 | $payload = [ 34 | "aid" => $aid, 35 | "multiply" => "1", 36 | "cross_domain" => "true", 37 | "csrf" => $user_info['token'] 38 | ]; 39 | $headers = [ 40 | 'Host' => "api.bilibili.com", 41 | 'Origin' => "https://www.bilibili.com", 42 | 'Referer' => "https://www.bilibili.com/video/av{$aid}", 43 | 'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36", 44 | ]; 45 | $raw = Curl::post($url, Sign::api($payload), $headers); 46 | $de_raw = json_decode($raw, true); 47 | if ($de_raw['code'] == 0) { 48 | Log::notice("主站任务: av{$aid}投币成功!"); 49 | return true; 50 | } else { 51 | Log::warning("主站任务: av{$aid}投币失败!"); 52 | return false; 53 | } 54 | } 55 | 56 | // 投币日志 57 | protected static function coinLog(): int 58 | { 59 | $url = "https://api.bilibili.com/x/member/web/coin/log"; 60 | $payload = []; 61 | $raw = Curl::get($url, Sign::api($payload)); 62 | $de_raw = json_decode($raw, true); 63 | 64 | $logs = $de_raw['data']['list']; 65 | $coins = 0; 66 | foreach ($logs as $log) { 67 | $log_ux = strtotime($log['time']); 68 | $log_date = date('Y-m-d', $log_ux); 69 | $now_date = date('Y-m-d'); 70 | if ($log_date != $now_date) { 71 | break; 72 | } 73 | if (strpos($log['reason'], "打赏") !== false) { 74 | switch ($log['delta']) { 75 | case -1: 76 | $coins += 1; 77 | break; 78 | case -2: 79 | $coins += 2; 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | } 86 | return $coins; 87 | } 88 | 89 | // 投币操作 90 | protected static function coinAdd(): bool 91 | { 92 | switch (getenv('USE_ADD_COIN')) { 93 | case 'false': 94 | break; 95 | case 'true': 96 | $av_num = getenv('ADD_COIN_AV_NUM'); 97 | $av_num = (int)$av_num; 98 | if ($av_num == 0) { 99 | Log::warning('当前视频投币设置不正确,请检查配置文件!'); 100 | die(); 101 | } 102 | if ($av_num == 1) { 103 | $aid = !empty(getenv('ADD_COIN_AV')) ? getenv('ADD_COIN_AV') : self::getRandomAid(); 104 | self::reward($aid); 105 | } else { 106 | $coins = $av_num - self::coinLog(); 107 | if ($coins <= 0) { 108 | Log::info('今日投币上限已满!'); 109 | break; 110 | } 111 | $aids = self::getDayRankingAids($av_num); 112 | foreach ($aids as $aid) { 113 | self::reward($aid); 114 | } 115 | } 116 | break; 117 | default: 118 | Log::warning('当前视频投币设置不正确,请检查配置文件!'); 119 | die(); 120 | break; 121 | } 122 | return true; 123 | } 124 | 125 | // 获取随机AID 126 | private static function getRandomAid(): string 127 | { 128 | do { 129 | $page = mt_rand(1, 1000); 130 | $payload = []; 131 | $url = "https://api.bilibili.com/x/web-interface/newlist?&pn={$page}&ps=1"; 132 | $raw = Curl::get($url, Sign::api($payload)); 133 | $de_raw = json_decode($raw, true); 134 | // echo "getRandomAid " . count($de_raw['data']['archives']) . PHP_EOL; 135 | // $aid = array_rand($de_raw['data']['archives'])['aid']; 136 | } while (count($de_raw['data']['archives']) == 0); 137 | $aid = $de_raw['data']['archives'][0]['aid']; 138 | return (string)$aid; 139 | } 140 | 141 | // 日榜AID 142 | private static function getDayRankingAids($num): array 143 | { 144 | // day: 日榜1 三榜3 周榜7 月榜30 145 | $payload = []; 146 | $aids = []; 147 | $rand_nums = []; 148 | $url = "https://api.bilibili.com/x/web-interface/ranking?rid=0&day=1&type=1&arc_type=0"; 149 | $raw = Curl::get($url, Sign::api($payload)); 150 | $de_raw = json_decode($raw, true); 151 | for ($i = 0; $i < $num; $i++) { 152 | while (true) { 153 | $rand_num = mt_rand(1, 100); 154 | if (in_array($rand_num, $rand_nums)) { 155 | continue; 156 | } else { 157 | array_push($rand_nums, $rand_num); 158 | break; 159 | } 160 | } 161 | $aid = $de_raw['data']['list'][$rand_nums[$i]]['aid']; 162 | array_push($aids, $aid); 163 | } 164 | 165 | return $aids; 166 | } 167 | 168 | // 分享视频 169 | private static function shareAid(): bool 170 | { 171 | # aid = 稿件av号 172 | $url = "https://api.bilibili.com/x/web-interface/share/add"; 173 | $av_info = self::parseAid(); 174 | $user_info = User::parseCookies(); 175 | $payload = [ 176 | 'aid' => $av_info['aid'], 177 | 'jsonp' => "jsonp", 178 | 'csrf' => $user_info['token'], 179 | ]; 180 | $headers = [ 181 | 'Host' => "api.bilibili.com", 182 | 'Origin' => "https://www.bilibili.com", 183 | 'Referer' => "https://www.bilibili.com/video/av{$av_info['aid']}", 184 | 'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36", 185 | ]; 186 | $raw = Curl::post($url, Sign::api($payload), $headers); 187 | $de_raw = json_decode($raw, true); 188 | if ($de_raw['code'] == 0) { 189 | Log::notice("主站任务: av{$av_info['aid']}分享成功!"); 190 | return true; 191 | } else { 192 | Log::warning("主站任务: av{$av_info['aid']}分享失败!"); 193 | return false; 194 | } 195 | } 196 | 197 | // 观看视频 198 | private static function watchAid(): bool 199 | { 200 | $url = "https://api.bilibili.com/x/report/click/h5"; 201 | $av_info = self::parseAid(); 202 | $user_info = User::parseCookies(); 203 | $payload = [ 204 | 'aid' => $av_info['aid'], 205 | 'cid' => $av_info['cid'], 206 | 'part' => 1, 207 | 'did' => $user_info['sid'], 208 | 'ftime' => time(), 209 | 'jsonp' => "jsonp", 210 | 'lv' => "", 211 | 'mid' => $user_info['uid'], 212 | 'csrf' => $user_info['token'], 213 | 'stime' => time() 214 | ]; 215 | 216 | $headers = [ 217 | 'Host' => "api.bilibili.com", 218 | 'Origin' => "https://www.bilibili.com", 219 | 'Referer' => "https://www.bilibili.com/video/av{$av_info['aid']}", 220 | 'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36", 221 | ]; 222 | 223 | $raw = Curl::post($url, Sign::api($payload), $headers); 224 | $de_raw = json_decode($raw, true); 225 | 226 | if ($de_raw['code'] == 0) { 227 | $url = "https://api.bilibili.com/x/report/web/heartbeat"; 228 | $payload = [ 229 | "aid" => $av_info['aid'], 230 | "cid" => $av_info['cid'], 231 | "mid" => $user_info['uid'], 232 | "csrf" => $user_info['token'], 233 | "jsonp" => "jsonp", 234 | "played_time" => "0", 235 | "realtime" => $av_info['duration'], 236 | "pause" => false, 237 | "dt" => "7", 238 | "play_type" => "1", 239 | 'start_ts' => time() 240 | ]; 241 | $raw = Curl::post($url, Sign::api($payload), $headers); 242 | $de_raw = json_decode($raw, true); 243 | 244 | if ($de_raw['code'] == 0) { 245 | sleep(5); 246 | $payload['played_time'] = $av_info['duration'] - 1; 247 | $payload['play_type'] = 0; 248 | $payload['start_ts'] = time(); 249 | $raw = Curl::post($url, Sign::api($payload), $headers); 250 | $de_raw = json_decode($raw, true); 251 | if ($de_raw['code'] == 0) { 252 | Log::notice("主站任务: av{$av_info['aid']}观看成功!"); 253 | return true; 254 | } 255 | } 256 | } 257 | Log::warning("主站任务: av{$av_info['aid']}观看失败!"); 258 | return false; 259 | } 260 | 261 | // 解析AID到CID 262 | private static function parseAid(): array 263 | { 264 | while (true) { 265 | $aid = self::getRandomAid(); 266 | $url = "https://api.bilibili.com/x/web-interface/view?aid={$aid}"; 267 | $raw = Curl::get($url); 268 | $de_raw = json_decode($raw, true); 269 | if ($de_raw['code'] != 0) { 270 | continue; 271 | } else { 272 | if (!array_key_exists('cid', $de_raw['data'])) { 273 | continue; 274 | } 275 | } 276 | $cid = $de_raw['data']['cid']; 277 | $duration = $de_raw['data']['duration']; 278 | break; 279 | } 280 | 281 | return [ 282 | 'aid' => $aid, 283 | 'cid' => $cid, 284 | 'duration' => $duration 285 | ]; 286 | } 287 | 288 | } -------------------------------------------------------------------------------- /src/MaterialObject.php: -------------------------------------------------------------------------------- 1 | time()) { 30 | return; 31 | } 32 | // 计算AID TODO 待优化 33 | self::calculateAid(150, 550); 34 | self::drawLottery(); 35 | 36 | self::$lock = time() + random_int(5, 10) * 60; 37 | } 38 | 39 | /** 40 | * @use 实物抽奖 41 | * @return bool 42 | */ 43 | protected static function drawLottery(): bool 44 | { 45 | $block_key_list = ['测试', '加密', 'test', 'TEST', '钓鱼', '炸鱼', '调试', "123", "1111", "测试", "測試", "测一测", "ce-shi", "test", "T-E-S-T", "lala", "我是抽奖标题", # 已经出现 46 | "測一測", "TEST", "Test", "t-e-s-t"]; 47 | $flag = 5; 48 | 49 | for ($i = self::$start_aid; $i < self::$end_aid; $i++) { 50 | if (!$flag) { 51 | break; 52 | } 53 | // 在丢弃列表里 跳过 54 | if (in_array($i, self::$discard_aid_list)) { 55 | continue; 56 | } 57 | 58 | $payload = [ 59 | 'aid' => $i, 60 | ]; 61 | $url = 'https://api.live.bilibili.com/lottery/v1/box/getStatus'; 62 | // 请求 && 解码 63 | $raw = Curl::get($url, Sign::api($payload)); 64 | $de_raw = json_decode($raw, true); 65 | // -403 没有抽奖 66 | if ($de_raw['code'] != '0') { 67 | $flag--; 68 | continue; 69 | } 70 | // 如果最后一个结束时间已过 加入丢弃 71 | $lotterys = $de_raw['data']['typeB']; 72 | $total = count($lotterys); 73 | if ($lotterys[$total - 1]['join_end_time'] < time()) { 74 | array_push(self::$discard_aid_list, $i); 75 | continue; 76 | } 77 | 78 | // 如果存在敏感词 加入丢弃 79 | $title = $de_raw['data']['title']; 80 | foreach ($block_key_list as $block_key) { 81 | if (strpos($title, $block_key) !== false) { 82 | array_push(self::$discard_aid_list, $i); 83 | continue; 84 | } 85 | } 86 | 87 | $num = 1; 88 | foreach ($lotterys as $lottery) { 89 | $join_end_time = $lottery['join_end_time']; 90 | $join_start_time = $lottery['join_start_time']; 91 | 92 | if ($join_end_time > time() && time() > $join_start_time) { 93 | switch ($lottery['status']) { 94 | case 3: 95 | Log::info("实物[{$i}]抽奖: 当前轮次已经结束!"); 96 | break; 97 | case 1: 98 | Log::info("实物[{$i}]抽奖: 当前轮次已经抽过了!"); 99 | break; 100 | case -1: 101 | Log::info("实物[{$i}]抽奖: 当前轮次暂未开启!"); 102 | break; 103 | case 0: 104 | Log::info("实物[{$i}]抽奖: 当前轮次正在抽奖中!"); 105 | 106 | $payload = [ 107 | 'aid' => $i, 108 | 'number' => $num, 109 | ]; 110 | $raw = Curl::get('https://api.live.bilibili.com/lottery/v1/box/draw', Sign::api($payload)); 111 | $de_raw = json_decode($raw, true); 112 | 113 | if ($de_raw['code'] == 0) { 114 | Log::notice("实物[{$i}]抽奖: 成功!"); 115 | } 116 | $num++; 117 | break; 118 | 119 | default: 120 | Log::info("实物[{$i}]抽奖: 当前轮次状态码[{$lottery['status'] }]未知!"); 121 | break; 122 | } 123 | } 124 | } 125 | } 126 | return true; 127 | } 128 | 129 | /** 130 | * @use 计算 开始结束的AID 131 | * @param $min 132 | * @param $max 133 | * @return bool 134 | */ 135 | private static function calculateAid($min, $max): bool 136 | { 137 | if (self::$end_aid != 0 && self::$start_aid != 0) { 138 | return false; 139 | } 140 | 141 | while (1) { 142 | $middle = round(($min + $max) / 2); 143 | if (self::aidPost($middle)) { 144 | if (self::aidPost($middle + mt_rand(0, 3))) { 145 | $max = $middle; 146 | } else { 147 | $min = $middle; 148 | } 149 | } else { 150 | $min = $middle; 151 | } 152 | if ($max - $min == 1) { 153 | break; 154 | } 155 | } 156 | 157 | self::$start_aid = $min - mt_rand(30, 40); 158 | self::$end_aid = $min + mt_rand(30, 40); 159 | Log::info("实物抽奖起始值[" . self::$start_aid . "],结束值[" . self::$end_aid . "]"); 160 | return true; 161 | } 162 | 163 | /** 164 | * @use Aid 请求 165 | * @param $aid 166 | * @return bool 167 | */ 168 | private static function aidPost($aid): bool 169 | { 170 | $payload = [ 171 | 'aid' => $aid, 172 | ]; 173 | $raw = Curl::get('https://api.live.bilibili.com/lottery/v1/box/getStatus', Sign::api($payload)); 174 | $de_raw = json_decode($raw, true); 175 | 176 | // 等于0是有抽奖返回false 177 | if ($de_raw['code'] == 0) { 178 | return false; 179 | } 180 | // 没有抽奖 181 | return true; 182 | } 183 | } -------------------------------------------------------------------------------- /src/Notice.php: -------------------------------------------------------------------------------- 1 | '活动抽奖结果', 45 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在活动抽奖中获得: ' . self::$result, 46 | ]; 47 | break; 48 | case 'storm': 49 | $info = [ 50 | 'title' => '节奏风暴中奖结果', 51 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在节奏风暴抽奖中: ' . self::$result, 52 | ]; 53 | break; 54 | case 'active': 55 | $info = [ 56 | 'title' => '活动中奖结果', 57 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在活动抽奖中获得: ' . self::$result, 58 | ]; 59 | break; 60 | case 'cookieRefresh': 61 | $info = [ 62 | 'title' => 'Cookie刷新', 63 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 刷新Cookie: ' . self::$result, 64 | ]; 65 | break; 66 | case 'loginInit': 67 | break; 68 | 69 | case 'todaySign': 70 | $info = [ 71 | 'title' => '每日签到', 72 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 签到: ' . self::$result, 73 | ]; 74 | break; 75 | case 'winIng': 76 | $info = [ 77 | 'title' => '实物中奖', 78 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 实物中奖: ' . self::$result, 79 | ]; 80 | break; 81 | case 'banned': 82 | $info = [ 83 | 'title' => '账号封禁', 84 | 'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 账号被封禁: 程序开始睡眠,凌晨自动唤醒,距离唤醒还有' . self::$result . '小时', 85 | ]; 86 | break; 87 | default: 88 | break; 89 | } 90 | self::scSend($info); 91 | 92 | return true; 93 | } 94 | 95 | // 发送信息 96 | private static function scSend($info) 97 | { 98 | 99 | $postdata = http_build_query( 100 | [ 101 | 'text' => $info['title'], 102 | 'desp' => $info['content'] 103 | ] 104 | ); 105 | 106 | $opts = ['http' => 107 | [ 108 | 'method' => 'POST', 109 | 'header' => 'Content-type: application/x-www-form-urlencoded', 110 | 'content' => $postdata 111 | ] 112 | ]; 113 | try { 114 | $context = stream_context_create($opts); 115 | file_get_contents('https://sc.ftqq.com/' . self::$sckey . '.send', false, $context); 116 | 117 | } catch (\Exception $e) { 118 | Log::warning('Server酱推送信息失败,请检查!'); 119 | } 120 | return; 121 | //return $result = file_get_contents('https://sc.ftqq.com/' . $this->_sckey . '.send', false, $context); 122 | } 123 | } -------------------------------------------------------------------------------- /src/Pk.php: -------------------------------------------------------------------------------- 1 | 2000) { 41 | static::$lottery_list_end = []; 42 | } 43 | array_push(static::$lottery_list_end, $pk_id); 44 | return true; 45 | } 46 | 47 | // 大乱斗抽奖 48 | protected static function startLottery(): bool 49 | { 50 | $flag = 100; 51 | while ($flag) { 52 | $pk = array_shift(static::$lottery_list_start); 53 | if (is_null($pk)) { 54 | break; 55 | } 56 | $pk_id = $pk['Id']; 57 | if (in_array($pk_id, static::$lottery_list_end) || $pk_id == 0) { 58 | continue; 59 | } 60 | $pk_roomid = $pk['RoomId']; 61 | Live::goToRoom($pk_roomid); 62 | $data = self::pkLottery($pk_roomid, $pk_id); 63 | 64 | if ($data['code'] == 0) { 65 | Log::notice("房间[{$pk_roomid}]编号[{$pk_id}]大乱斗:" . (!empty($data['data']['award_text']) ? $data['data']['award_text'] : "{$data['data']['award_name']}x{$data['data']['award_num']}")); 66 | } elseif ($data['code'] == -2 && $data['message'] == '您已参加过抽奖') { 67 | Log::info("房间[{$pk_roomid}]编号[{$pk_id}]大乱斗:{$data['message']}"); 68 | } else { 69 | Log::warning("房间[{$pk_roomid}]编号[{$pk_id}]大乱斗:{$data['message']}"); 70 | } 71 | static::endLottery($pk_id); 72 | $flag--; 73 | } 74 | return true; 75 | } 76 | 77 | // 抽奖 78 | protected static function pkLottery($pk_roomid, $pk_id): array 79 | { 80 | $user_info = User::parseCookies(); 81 | $url = "https://api.live.bilibili.com/xlive/lottery-interface/v1/pk/join"; 82 | $payload = [ 83 | "roomid" => $pk_roomid, 84 | "id" => $pk_id, 85 | "csrf_token" => $user_info['token'], 86 | 'csrf' => $user_info['token'], 87 | 'visit_id' => null, 88 | ]; 89 | $raw = Curl::post($url, Sign::api($payload)); 90 | $de_raw = json_decode($raw, true); 91 | return $de_raw; 92 | } 93 | 94 | // 获取大乱斗列表 95 | protected static function getPkList(): bool 96 | { 97 | $headers = [ 98 | 'User-Agent' => "bilibili-live-tools/" . mt_rand(1000000, 99999999), 99 | ]; 100 | $raw = Curl::other("http://118.25.108.153:8080/pk", null, $headers, null, '118.25.108.153:8080'); 101 | $de_raw = Common::analyJson($raw, true); 102 | if (!$de_raw) { 103 | Log::info("大乱斗服务器返回为空或暂时宕机"); 104 | return false; 105 | } 106 | static::$lottery_list_start = array_merge(static::$lottery_list_start, $de_raw); 107 | $pk_num = count(static::$lottery_list_start); 108 | Log::info("当前队列中共有{$pk_num}个大乱斗待抽奖"); 109 | return true; 110 | } 111 | } -------------------------------------------------------------------------------- /src/RaffleHandler.php: -------------------------------------------------------------------------------- 1 | time()) { 73 | return; 74 | } 75 | // 如果待查询为空 && 去重 76 | if (!count(self::$winning_list_web)) { 77 | self::$lock = time() + 40; 78 | return; 79 | } else { 80 | self::$winning_list_web = array_unique(self::$winning_list_web, SORT_REGULAR); 81 | } 82 | // 查询,每次查询10个 83 | $flag = 0; 84 | foreach (self::$winning_list_web as $winning_web) { 85 | $flag++; 86 | if ($flag > 40) { 87 | break; 88 | } 89 | // 参数 90 | $payload = [ 91 | 'type' => $winning_web['type'], 92 | 'raffleId' => $winning_web['raffle_id'] 93 | ]; 94 | // Web V3 Notice 95 | $url = 'https://api.live.bilibili.com/xlive/lottery-interface/v3/smalltv/Notice'; 96 | // 请求 && 解码 97 | $raw = Curl::get($url, Sign::api($payload)); 98 | $de_raw = json_decode($raw, true); 99 | // 判断 100 | switch ($de_raw['data']['status']) { 101 | // case 3: 102 | // break; 103 | case 2: 104 | // 提示信息 105 | $info = "网页端在直播间[{$winning_web['room_id']}]{$winning_web['title']}[{$winning_web['raffle_id']}]获得"; 106 | $info .= "[{$de_raw['data']['gift_name']}X{$de_raw['data']['gift_num']}]"; 107 | Log::notice($info); 108 | // 推送活动抽奖信息 109 | if ($de_raw['data']['gift_name'] != '辣条' && $de_raw['data']['gift_name'] != '') { 110 | Notice::run('raffle', $info); 111 | } 112 | // 删除查询完成ID 113 | unset(self::$winning_list_web[$flag - 1]); 114 | self::$winning_list_web = array_values(self::$winning_list_web); 115 | break; 116 | default: 117 | break; 118 | } 119 | } 120 | self::$lock = time() + 40; 121 | return; 122 | } 123 | 124 | /** 125 | * @use WEB端检测 126 | * @return array|bool 127 | */ 128 | private static function checkWeb() 129 | { 130 | // 未抽奖列表阀值,否则置空 131 | if (count(self::$lottery_list_web) > 1000) { 132 | self::$lottery_list_web = []; 133 | } 134 | // 参数 135 | $payload = [ 136 | 'roomid' => self::$room_id, 137 | ]; 138 | // Web V3接口 139 | $url = 'https://api.live.bilibili.com/xlive/lottery-interface/v3/smalltv/Check'; 140 | // 请求 && 解码 141 | $raw = Curl::get($url, Sign::api($payload)); 142 | $de_raw = json_decode($raw, true); 143 | // 计数 && 跳出 144 | $total = count($de_raw['data']['list']); 145 | if (!$total) { 146 | // Log::info("网页端直播间 [" . self::$room_id . "] 待抽奖列表为空,放弃本次抽奖!"); 147 | return false; 148 | } 149 | for ($i = 0; $i < $total; $i++) { 150 | /** 151 | * raffleId : 88995 152 | * title : C位光环抽奖 153 | * type : GIFT_30013 154 | */ 155 | $data = [ 156 | 'raffle_id' => $de_raw['data']['list'][$i]['raffleId'], 157 | 'title' => $de_raw['data']['list'][$i]['title'], 158 | 'type' => $de_raw['data']['list'][$i]['type'], 159 | 'wait' => $de_raw['data']['list'][$i]['time_wait'] + strtotime(date("Y-m-d H:i:s")), 160 | 'room_id' => self::$room_id, 161 | ]; 162 | // 重复抽奖检测 163 | if (in_array($data['raffle_id'], array_column(self::$lottery_list_web, 'raffle_id'))) { 164 | continue; 165 | } 166 | // 添加到待抽奖 && 临时 167 | array_push(self::$lottery_list_web, $data); 168 | } 169 | 170 | return true; 171 | } 172 | 173 | /** 174 | * @use APP端检测 175 | * @return array|bool 176 | */ 177 | private static function checkApp() 178 | { 179 | // 未抽奖列表阀值,否则置空 180 | if (count(self::$lottery_list_app) > 1000) { 181 | self::$lottery_list_app = []; 182 | } 183 | // 参数 184 | $payload = [ 185 | 'roomid' => self::$room_id, 186 | ]; 187 | // App旧接口 188 | $url = 'https://api.live.bilibili.com/activity/v1/Common/mobileRoomInfo'; 189 | // 请求 && 解码 190 | $raw = Curl::get($url, Sign::api($payload)); 191 | $de_raw = json_decode($raw, true); 192 | // 计数 && 跳出 193 | $total = count($de_raw['data']['lotteryInfo']); 194 | if (!$total) { 195 | // Log::info("移动端直播间 [" . self::$room_id . "] 抽奖列表为空,丢弃本次抽奖!"); 196 | return false; 197 | } 198 | // 临时数组返回 199 | $temp_list = []; 200 | for ($i = 0; $i < $total; $i++) { 201 | // eventType : GIFT-68149 202 | $data = [ 203 | 'raffle_id' => $de_raw['data']['lotteryInfo'][$i]['eventType'], 204 | 'title' => self::$title, 205 | 'room_id' => self::$room_id 206 | ]; 207 | // 重复抽奖检测 208 | if (in_array($data['raffle_id'], array_column(self::$lottery_list_app, 'raffle_id'))) { 209 | continue; 210 | } 211 | // 添加到待抽奖 && 临时 212 | array_push(self::$lottery_list_app, $data); 213 | array_push($temp_list, $data); 214 | } 215 | 216 | // 判断空值 && 返回数组 217 | if (!count($temp_list)) { 218 | return false; 219 | } 220 | return $temp_list; 221 | } 222 | 223 | /** 224 | * @use WEB加入抽奖 225 | * @param array $datas 226 | * @return bool 227 | */ 228 | private static function joinWeb() 229 | { 230 | $max_num = mt_rand(10, 20); 231 | if (count(self::$lottery_list_web) == 0) { 232 | return false; 233 | } 234 | self::$lottery_list_web = self::arrKeySort(self::$lottery_list_web, 'wait'); 235 | for ($i = 0; $i <= $max_num; $i++) { 236 | $raffle = array_shift(self::$lottery_list_web); 237 | if (is_null($raffle)) { 238 | break; 239 | } 240 | if ($raffle['wait'] > strtotime(date("Y-m-d H:i:s"))) { 241 | array_push(self::$lottery_list_web, $raffle); 242 | continue; 243 | } 244 | self::lotteryWeb($raffle); 245 | } 246 | return true; 247 | } 248 | 249 | 250 | /** 251 | * @use APP加入抽奖 252 | * @param array $datas 253 | * @return bool 254 | */ 255 | private static function joinApp(array $datas) 256 | { 257 | // 统计抽奖个数 && 判断空 258 | $total = count($datas); 259 | if (!$total) { 260 | return false; 261 | } 262 | 263 | foreach ($datas as $data) { 264 | self::lotteryApp($data); 265 | } 266 | return true; 267 | } 268 | 269 | /** 270 | * @use WEB抽奖模块 271 | * @param array $data 272 | */ 273 | private static function lotteryWeb(array $data) 274 | { 275 | // 重复抽奖检测 276 | if (in_array($data['raffle_id'], array_column(self::$winning_list_web, 'raffle_id'))) { 277 | return; 278 | } 279 | $user_info = User::parseCookies(); 280 | // 参数 281 | $payload = [ 282 | 'raffleId' => $data['raffle_id'], 283 | 'roomid' => $data['room_id'], 284 | 'type' => $data['type'], 285 | 'csrf_token' => $user_info['token'], 286 | 'csrf' => $user_info['token'], 287 | 'visit_id' => null, 288 | ]; 289 | // v3 api 暂做保留处理 290 | // $url = 'https://api.live.bilibili.com/gift/v3/smalltv/join'; 291 | // $url = 'https://api.live.bilibili.com/xlive/lottery-interface/v5/smalltv/join'; 292 | $url = 'https://api.live.bilibili.com/gift/v4/smalltv/getAward'; 293 | // 请求 && 解码 294 | $raw = Curl::post($url, Sign::api($payload)); 295 | $de_raw = json_decode($raw, true); 296 | // 抽奖判断 297 | if (isset($de_raw['code']) && $de_raw['code']) { 298 | if ($de_raw['code'] != -405) { 299 | Log::warning("网页端参与{$data['title']}[{$data['raffle_id']}]抽奖,状态: {$de_raw['message']}!"); 300 | print_r($de_raw); 301 | } 302 | } else { 303 | Log::notice("网页端参与了房间[{$data['room_id']}]的{$data['title']}[{$data['raffle_id']}]抽奖, 状态: " . "{$de_raw['data']['gift_name']}x{$de_raw['data']['gift_num']}"); 304 | array_push(self::$winning_list_web, $data); 305 | } 306 | return; 307 | } 308 | 309 | 310 | /** 311 | * @use APP加入抽奖 312 | * @param array $data 313 | */ 314 | private static function lotteryApp(array $data) 315 | { 316 | // 参数 317 | // flower_rain- 318 | // lover_2018 319 | $payload = [ 320 | 'event_type' => $data['raffle_id'], 321 | 'room_id' => self::$room_id, 322 | ]; 323 | // App 旧接口 324 | $url = 'https://api.live.bilibili.com/YunYing/roomEvent'; 325 | // 请求 && 解码 326 | $raw = Curl::get($url, Sign::api($payload)); 327 | $de_raw = json_decode($raw, true); 328 | // 抽奖判断 329 | if (array_key_exists('code', $de_raw) && $de_raw['code'] != 0) { 330 | Log::info("移动端参与{$data['title']}[{$data['raffle_id']}]抽奖,状态: {$de_raw['message']}!"); 331 | } elseif (array_key_exists('code', $de_raw) && $de_raw['code'] == 0) { 332 | Log::notice("移动端参与了房间[{$data['room_id']}]的{$data['title']}[{$data['raffle_id']}]抽奖, 状态: {$de_raw['data']['gift_desc']}!"); 333 | } else { 334 | Log::error("移动端参与{$data['title']}[{$data['raffle_id']}]抽奖,状态: {$de_raw['message']}!"); 335 | print_r($de_raw); 336 | } 337 | return; 338 | } 339 | 340 | /** 341 | * @use 二维数组按key排序 342 | * @param $arr 343 | * @param $key 344 | * @param string $type 345 | * @return array 346 | */ 347 | private static function arrKeySort($arr, $key, $type = 'asc') 348 | { 349 | switch ($type) { 350 | case 'desc': 351 | array_multisort(array_column($arr, $key), SORT_DESC, $arr); 352 | return $arr; 353 | case 'asc': 354 | array_multisort(array_column($arr, $key), SORT_ASC, $arr); 355 | return $arr; 356 | default: 357 | return $arr; 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/Sign.php: -------------------------------------------------------------------------------- 1 | getenv('ACCESS_TOKEN'), 29 | 'actionKey' => 'appkey', 30 | 'appkey' => $appkey, 31 | 'build' => '8230', 32 | 'device' => 'phone', 33 | 'mobi_app' => 'iphone', 34 | 'platform' => 'ios', 35 | 'ts' => time(), 36 | ]; 37 | 38 | $payload = array_merge($payload, $default); 39 | if (isset($payload['sign'])) { 40 | unset($payload['sign']); 41 | } 42 | ksort($payload); 43 | $data = http_build_query($payload); 44 | $payload['sign'] = md5($data . $appsecret); 45 | return $payload; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Silver.php: -------------------------------------------------------------------------------- 1 | time()) { 21 | return; 22 | } 23 | 24 | if (!empty(self::$task)) { 25 | self::pushTask(); 26 | } else { 27 | self::pullTask(); 28 | } 29 | } 30 | 31 | protected static function pushTask() 32 | { 33 | $payload = [ 34 | 'time_end' => self::$task['time_end'], 35 | 'time_start' => self::$task['time_start'] 36 | ]; 37 | $data = Curl::get('https://api.live.bilibili.com/mobile/freeSilverAward', Sign::api($payload)); 38 | $data = json_decode($data, true); 39 | 40 | if ($data['code'] == -800) { 41 | self::$lock = time() + 12 * 60 * 60; 42 | Log::warning("领取宝箱失败,{$data['message']}!"); 43 | return; 44 | } 45 | 46 | if ($data['code'] == -903) { 47 | Log::warning("领取宝箱失败,{$data['message']}!"); 48 | self::$task = []; 49 | self::$lock = time() + 60; 50 | return; 51 | } 52 | 53 | if (isset($data['code']) && $data['code']) { 54 | Log::warning("领取宝箱失败,{$data['message']}!"); 55 | self::$lock = time() + 60; 56 | return; 57 | } 58 | 59 | Log::info("领取宝箱成功,Silver: {$data['data']['silver']}(+{$data['data']['awardSilver']})"); 60 | 61 | self::$task = []; 62 | self::$lock = time() + 10; 63 | } 64 | 65 | protected static function pullTask() 66 | { 67 | $payload = []; 68 | $data = Curl::get('https://api.live.bilibili.com/lottery/v1/SilverBox/getCurrentTask', Sign::api($payload)); 69 | $data = json_decode($data, true); 70 | 71 | if (isset($data['code']) && $data['code'] == -10017) { 72 | Log::notice($data['message']); 73 | self::$lock = time() + 24 * 60 * 60; 74 | return; 75 | } 76 | 77 | if (isset($data['code']) && $data['code']) { 78 | Log::error("check freeSilverCurrentTask failed! Error message: {$data['message']}"); 79 | die(); 80 | } 81 | 82 | Log::info("获得一个宝箱,内含 {$data['data']['silver']} 个瓜子"); 83 | Log::info("等待 {$data['data']['minute']} 分钟"); 84 | 85 | self::$task = [ 86 | 'time_start' => $data['data']['time_start'], 87 | 'time_end' => $data['data']['time_end'], 88 | ]; 89 | 90 | self::$lock = time() + $data['data']['minute'] * 60 + 5; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Silver2Coin.php: -------------------------------------------------------------------------------- 1 | time() || getenv('USE_SILVER2COIN') == 'false') { 20 | return; 21 | } 22 | if (self::appSilver2coin() && self::pcSilver2coin()) { 23 | self::$lock = time() + 24 * 60 * 60; 24 | return; 25 | } 26 | self::$lock = time() + 3600; 27 | } 28 | 29 | 30 | // APP API 31 | protected static function appSilver2coin(): bool 32 | { 33 | sleep(1); 34 | $payload = []; 35 | $raw = Curl::get('https://api.live.bilibili.com/AppExchange/silver2coin', Sign::api($payload)); 36 | $de_raw = json_decode($raw, true); 37 | 38 | if (!$de_raw['code'] && $de_raw['msg'] == '兑换成功') { 39 | Log::info('[APP]银瓜子兑换硬币: ' . $de_raw['msg']); 40 | } elseif ($de_raw['code'] == 403) { 41 | Log::info('[APP]银瓜子兑换硬币: ' . $de_raw['msg']); 42 | } else { 43 | Log::warning('[APP]银瓜子兑换硬币: ' . $de_raw['msg']); 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | // PC API 50 | protected static function pcSilver2coin(): bool 51 | { 52 | sleep(1); 53 | $payload = []; 54 | $url = "https://api.live.bilibili.com/pay/v1/Exchange/silver2coin"; 55 | $url1 = "https://api.live.bilibili.com/exchange/silver2coin"; 56 | 57 | $raw = Curl::get($url, Sign::api($payload)); 58 | $de_raw = json_decode($raw, true); 59 | if ($de_raw['code'] == -403) { 60 | return false; 61 | } 62 | Log::info('[PC]银瓜子兑换硬币: ' . $de_raw['msg']); 63 | // TODO 64 | return true; 65 | } 66 | } -------------------------------------------------------------------------------- /src/Socket.php: -------------------------------------------------------------------------------- 1 | time()) { 26 | return; 27 | } 28 | self::$lock = time() + 0.5; 29 | 30 | self::start(); 31 | $message = self::decodeMessage(); 32 | if (!$message) { 33 | unset($message); 34 | self::resetConnection(); 35 | return; 36 | } 37 | $data = DataTreating::socketJsonToArray($message); 38 | if (!$data) { 39 | return; 40 | } 41 | DataTreating::socketArrayToDispose($data); 42 | return; 43 | } 44 | 45 | // KILL 46 | protected static function killConnection() 47 | { 48 | socket_clear_error(self::$socket_connection); 49 | socket_shutdown(self::$socket_connection); 50 | socket_close(self::$socket_connection); 51 | self::$socket_connection = null; 52 | } 53 | 54 | // RECONNECT 55 | protected static function resetConnection() 56 | { 57 | $errorcode = socket_last_error(); 58 | $errormsg = socket_strerror($errorcode); 59 | unset($errormsg); 60 | unset($errorcode); 61 | self::killConnection(); 62 | // Log::warning('SOCKET连接断开,5秒后重新连接...'); 63 | // sleep(5); 64 | self::start(); 65 | return; 66 | } 67 | 68 | // SOCKET READER 69 | protected static function readerSocket(int $length) 70 | { 71 | return socket_read(self::$socket_connection, $length); 72 | } 73 | 74 | // DECODE MESSAGE 75 | protected static function decodeMessage() 76 | { 77 | $res = ''; 78 | $tmp = ''; 79 | while (1) { 80 | while ($out = self::readerSocket(16)) { 81 | $res = unpack('N', $out); 82 | unset($out); 83 | if ($res[1] != 16) { 84 | break; 85 | } 86 | } 87 | if (isset($res[1])) { 88 | $length = $res[1] - 16; 89 | if ($length > 65535) { 90 | continue; 91 | } 92 | if ($length <= 0) { 93 | return false; 94 | } 95 | return self::readerSocket($length); 96 | } 97 | return false; 98 | } 99 | } 100 | 101 | // START 102 | protected static function start() 103 | { 104 | if (is_null(self::$socket_connection)) { 105 | $room_id = empty(getenv('SOCKET_ROOM_ID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('SOCKET_ROOM_ID')); 106 | $room_id = intval($room_id); 107 | if ($room_id) { 108 | self::getSocketServer($room_id); 109 | self::connectServer($room_id, self::$socket_ip, self::$socket_port); 110 | } 111 | } 112 | self::sendHeartBeatPkg(); 113 | return; 114 | } 115 | 116 | // SEND HEART 117 | protected static function sendHeartBeatPkg() 118 | { 119 | if (self::$heart_lock > time()) { 120 | return; 121 | } 122 | $action_heart_beat = intval(getenv('ACTIONHEARTBEAT')); 123 | $str = pack('NnnNN', 16, 16, 1, $action_heart_beat, 1); 124 | socket_write(self::$socket_connection, $str, strlen($str)); 125 | Log::info('发送心跳包到弹幕服务器!'); 126 | self::$heart_lock = time() + 30; 127 | return; 128 | } 129 | 130 | // SOCKET CONNECT 131 | protected static function connectServer($room_id, $ip, $port) 132 | { 133 | $falg = 10; 134 | while ($falg) { 135 | try { 136 | $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 137 | socket_connect($socket, $ip, $port); 138 | $str = self::packMsg($room_id); 139 | socket_write($socket, $str, strlen($str)); 140 | self::$socket_connection = $socket; 141 | // TODO 142 | Log::info("连接到弹幕服务器[{$room_id}]成功!"); 143 | return; 144 | } catch (\Exception $e) { 145 | Log::info("连接到弹幕服务器[{$room_id}]失败!"); 146 | Log::warning($e); 147 | $falg -= 1; 148 | } 149 | } 150 | Log::info("连接弹幕服务器[{$room_id}]错误次数过多,检查网络!"); 151 | exit(); 152 | } 153 | 154 | // PACK DATA 155 | protected static function packMsg($room_id) 156 | { 157 | $action_entry = intval(getenv('ACTIONENTRY')); 158 | $data = sprintf("{\"uid\":%d%08d,\"roomid\":%d}", 159 | mt_rand(1000000, 2999999), 160 | mt_rand(0, 99999999), 161 | intval($room_id) 162 | ); 163 | return pack('NnnNN', 16 + strlen($data), 16, 1, $action_entry, 1) . $data; 164 | } 165 | 166 | // GET SERVER 167 | protected static function getSocketServer(int $room_id): bool 168 | { 169 | while (1) { 170 | try { 171 | $payload = [ 172 | 'room_id' => $room_id, 173 | ]; 174 | $data = Curl::get('https://api.live.bilibili.com/room/v1/Danmu/getConf', Sign::api($payload)); 175 | $data = json_decode($data, true); 176 | 177 | // TODO 判断 178 | if (isset($data['code']) && $data['code']) { 179 | continue; 180 | } 181 | 182 | self::$socket_ip = gethostbyname($data['data']['host']); 183 | self::$socket_port = $data['data']['port']; 184 | 185 | break; 186 | } catch (\Exception $e) { 187 | Log::warning("获取弹幕服务器出错,错误信息[{$e}]!"); 188 | continue; 189 | } 190 | } 191 | return true; 192 | } 193 | } -------------------------------------------------------------------------------- /src/Storm.php: -------------------------------------------------------------------------------- 1 | $id, 59 | "color" => "16772431", 60 | "captcha_token" => "", 61 | "captcha_phrase" => "", 62 | "token" => "", 63 | "csrf_token" => "", 64 | ]; 65 | $raw = Curl::post('https://api.live.bilibili.com/lottery/v1/Storm/join', Sign::api($payload)); 66 | $de_raw = json_decode($raw, true); 67 | if ($de_raw['code'] == 429 || $de_raw['code'] == -429) { 68 | self::$realname_check = false; 69 | return false; 70 | } 71 | if ($de_raw['code'] == 0) { 72 | Log::notice($de_raw['data']['mobile_content']); 73 | return false; 74 | } 75 | if ($de_raw['msg'] == '节奏风暴不存在') { 76 | Log::notice('节奏风暴已结束!'); 77 | return false; 78 | } 79 | if ($de_raw['msg'] == '已经领取奖励') { 80 | Log::notice('节奏风暴已经领取!'); 81 | return false; 82 | } 83 | if ($de_raw['msg'] == '访问被拒绝') { 84 | Log::notice('账号已被封禁!'); 85 | return false; 86 | } 87 | return true; 88 | } 89 | 90 | // 检查ID 91 | protected static function stormCheckId($room_id) 92 | { 93 | $raw = Curl::get('https://api.live.bilibili.com/lottery/v1/Storm/check?roomid=' . $room_id); 94 | $de_raw = json_decode($raw, true); 95 | 96 | if (empty($de_raw['data']) || $de_raw['data']['hasJoin'] != 0) { 97 | return false; 98 | } 99 | return [ 100 | 'id' => $de_raw['data']['id'] 101 | ]; 102 | } 103 | 104 | // 测试 app接口 105 | public static function testStorm() 106 | { 107 | $payload = [ 108 | 'roomid' => getenv('ROOM_ID') 109 | ]; 110 | $raw = Curl::post('https://api.live.bilibili.com/lottery/v1/Storm/join', Sign::api($payload)); 111 | $de_raw = json_decode($raw, true); 112 | print_r($de_raw); 113 | exit(); 114 | } 115 | } -------------------------------------------------------------------------------- /src/Task.php: -------------------------------------------------------------------------------- 1 | time()) { 20 | return; 21 | } 22 | 23 | Log::info('正在检查每日任务...'); 24 | 25 | $data = self::check(); 26 | 27 | if (isset($data['data']['double_watch_info'])) { 28 | self::double_watch_info($data['data']['double_watch_info']); 29 | } 30 | if (isset($data['data']['sign_info'])) { 31 | self::sign_info($data['data']['sign_info']); 32 | } 33 | 34 | self::$lock = time() + 3600; 35 | } 36 | 37 | protected static function check() 38 | { 39 | $payload = []; 40 | $data = Curl::get('https://api.live.bilibili.com/i/api/taskInfo', Sign::api($payload)); 41 | $data = json_decode($data, true); 42 | 43 | if (isset($data['code']) && $data['code']) { 44 | Log::warning('每日任务检查失败!', ['msg' => $data['message']]); 45 | } 46 | 47 | return $data; 48 | } 49 | 50 | protected static function sign_info($info) 51 | { 52 | Log::info('检查任务「每日签到」...'); 53 | 54 | if ($info['status'] == 1) { 55 | Log::notice('该任务已完成'); 56 | return; 57 | } 58 | 59 | $payload = []; 60 | $data = Curl::get('https://api.live.bilibili.com/sign/doSign', Sign::api($payload)); 61 | $data = json_decode($data, true); 62 | 63 | if (isset($data['code']) && $data['code']) { 64 | Log::warning('签到失败', ['msg' => $data['message']]); 65 | } else { 66 | Log::info('签到成功'); 67 | // 推送签到信息 68 | Notice::run('todaySign', $data['message']); 69 | } 70 | } 71 | 72 | protected static function double_watch_info($info) 73 | { 74 | Log::info('检查任务「双端观看直播」...'); 75 | 76 | if ($info['status'] == 2) { 77 | Log::notice('已经领取奖励'); 78 | return; 79 | } 80 | 81 | if ($info['mobile_watch'] != 1 || $info['web_watch'] != 1) { 82 | Log::notice('任务未完成,请等待'); 83 | return; 84 | } 85 | 86 | $user_info = User::parseCookies(); 87 | $payload = [ 88 | 'task_id' => 'double_watch_task', 89 | 'csrf_token' => $user_info['token'], 90 | 'csrf' => $user_info['token'], 91 | ]; 92 | $data = Curl::post('https://api.live.bilibili.com/activity/v1/task/receive_award', Sign::api($payload)); 93 | $data = json_decode($data, true); 94 | 95 | if (isset($data['code']) && $data['code']) { 96 | Log::warning("「双端观看直播」任务奖励领取失败,{$data['message']}!"); 97 | } else { 98 | Log::info('奖励领取成功!'); 99 | foreach ($info['awards'] as $vo) { 100 | Log::info(sprintf("获得 %s × %d", $vo['name'], $vo['num'])); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/User.php: -------------------------------------------------------------------------------- 1 | $token, 76 | 'uid' => $uid, 77 | 'sid' => $sid, 78 | ]; 79 | } 80 | } -------------------------------------------------------------------------------- /src/Websocket.php: -------------------------------------------------------------------------------- 1 | time()) { 31 | return; 32 | } 33 | static::init(); 34 | static::heart(); 35 | static::receive(); 36 | 37 | } 38 | 39 | 40 | /** 41 | * @use 初始化 42 | */ 43 | protected static function init() 44 | { 45 | if (!static::$websocket) { 46 | $client = new Client( 47 | 'ws://broadcastlv.chat.bilibili.com:2244/sub', 48 | 'http://live.bilibili.com' 49 | ); 50 | static::$websocket = $client; 51 | } 52 | 53 | if (!static::$room_id) { 54 | static::$room_id = empty(getenv('SOCKET_ROOM_ID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('SOCKET_ROOM_ID')); 55 | } 56 | return; 57 | } 58 | 59 | /** 60 | * @use 连接 61 | */ 62 | protected static function connect() 63 | { 64 | Log::info("连接弹幕服务器"); 65 | if (!static::$websocket->connect()) { 66 | Log::error('连接弹幕服务器失败'); 67 | static::$lock = time() + 60; 68 | return; 69 | } 70 | static::$websocket->sendData( 71 | static::packMsg(json_encode([ 72 | 'uid' => 0, 73 | 'roomid' => static::$room_id, 74 | 'protover' => 1, 75 | 'platform' => 'web', 76 | 'clientver' => '1.4.1', 77 | ]), 0x0007) 78 | ); 79 | } 80 | 81 | /** 82 | * @use 断开连接 83 | */ 84 | protected static function disconnect() 85 | { 86 | Log::info('断开弹幕服务器'); 87 | static::$websocket->disconnect(); 88 | } 89 | 90 | 91 | /** 92 | * @use 读取数据 93 | */ 94 | protected static function receive() 95 | { 96 | $responses = static::$websocket->receive(); 97 | if (is_array($responses) && !empty($responses)) { 98 | foreach ($responses as $response) { 99 | static::split($response->getPayload()); 100 | } 101 | static::receive(); 102 | } 103 | 104 | } 105 | 106 | /** 107 | * @use 发送心跳 108 | */ 109 | protected static function heart() 110 | { 111 | if (!static::$websocket->isConnected()) { 112 | static::connect(); 113 | return; 114 | } 115 | if (static::$heart_lock <= time()) { 116 | if (static::$websocket->sendData(static::packMsg('', 0x0002))) { 117 | static::$heart_lock = time() + 30; 118 | } 119 | } 120 | return; 121 | } 122 | 123 | 124 | /** 125 | * @param $id 126 | * @return mixed|string 127 | */ 128 | protected static function type($id) 129 | { 130 | $option = [ 131 | 0x0002 => 'WS_OP_HEARTBEAT', 132 | 0x0003 => 'WS_OP_HEARTBEAT_REPLY', 133 | 0x0005 => 'WS_OP_MESSAGE', 134 | 0x0007 => 'WS_OP_USER_AUTHENTICATION', 135 | 0x0008 => 'WS_OP_CONNECT_SUCCESS', 136 | ]; 137 | return isset($option[$id]) ? $option[$id] : "WS_OP_UNKNOW($id)"; 138 | } 139 | 140 | 141 | /** 142 | * @use 解包 143 | * @param $bin 144 | * @throws \Exception 145 | */ 146 | protected static function split($bin) 147 | { 148 | if (strlen($bin)) { 149 | $head = unpack('Npacklen/nheadlen/nver/Nop/Nseq', substr($bin, 0, 16)); 150 | $bin = substr($bin, 16); 151 | 152 | $length = isset($head['packlen']) ? $head['packlen'] : 16; 153 | $type = isset($head['op']) ? $head['op'] : 0x0000; 154 | $body = substr($bin, 0, $length - 16); 155 | 156 | Log::debug(static::type($type) . " (len=$length)"); 157 | 158 | if (($length - 16) > 65535 || ($length - 16) < 0) { 159 | Log::notice("长度{$length}异常,重新连接服务器!"); 160 | if (static::$websocket->isConnected()) { 161 | static::disconnect(); 162 | } 163 | if (!static::$websocket->isConnected()) { 164 | static::connect(); 165 | } 166 | return; 167 | } 168 | 169 | if ($type == 0x0005 || $type == 0x0003) { 170 | if ($head['ver'] == 2) { 171 | $body = gzuncompress($body); 172 | if (substr($body, 0, 1) != '{') { 173 | static::split($bin); 174 | return; 175 | } 176 | } 177 | $data = DataTreating::socketJsonToArray($body); 178 | if (!$data) { 179 | return; 180 | } 181 | DataTreating::socketArrayToDispose($data); 182 | } 183 | 184 | $bin = substr($bin, $length - 16); 185 | if (strlen($bin)) { 186 | static::split($bin); 187 | } 188 | } 189 | return; 190 | } 191 | 192 | 193 | /** 194 | * @use 打包请求 195 | * @param $value 196 | * @param $option 197 | * @return string 198 | * @throws \Exception 199 | */ 200 | protected static function packMsg($value, $option) 201 | { 202 | $head = pack('NnnNN', 0x10 + strlen($value), 0x10, 0x01, $option, 0x0001); 203 | $str = $head . $value; 204 | static::split($str); 205 | return $str; 206 | } 207 | 208 | /** 209 | * @use 写入log 210 | * @param $message 211 | */ 212 | private static function writeLog($message) 213 | { 214 | $path = './danmu/'; 215 | if (!file_exists($path)) { 216 | mkdir($path); 217 | chmod($path, 0777); 218 | } 219 | $filename = $path . getenv('APP_USER') . ".log"; 220 | $date = date('[Y-m-d H:i:s] '); 221 | $data = "[{$date}]{$message}" . PHP_EOL; 222 | file_put_contents($filename, $data, FILE_APPEND); 223 | return; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Winning.php: -------------------------------------------------------------------------------- 1 | time()) { 31 | return; 32 | } 33 | self::$lock = time() + 24 * 60 * 60; 34 | 35 | $payload = [ 36 | 'page' => '1', 37 | 'month' => '', 38 | ]; 39 | $raw = Curl::post('https://api.live.bilibili.com/lottery/v1/award/award_list', Sign::api($payload)); 40 | $de_raw = json_decode($raw, true); 41 | $month = $de_raw['data']['month_list'][0]['Ym']; 42 | 43 | // TODO 44 | $payload = [ 45 | 'page' => '1', 46 | 'month' => $month, 47 | ]; 48 | $raw = Curl::post('https://api.live.bilibili.com/lottery/v1/award/award_list', Sign::api($payload)); 49 | $de_raw = json_decode($raw, true); 50 | // 没有记录 51 | if (empty($de_raw['data']['list'])) { 52 | return; 53 | } 54 | 55 | $init_time = strtotime(date("y-m-d h:i:s")); //当前时间 56 | foreach ($de_raw['data']['list'] as $gift) { 57 | $next_time = strtotime($gift['create_time']); //礼物时间 58 | $day = ceil(($init_time - $next_time) / 86400); //60s*60min*24h 59 | 60 | if ($day <= 2 && $gift['update_time'] == '') { 61 | $data_info = '您在: ' . $gift['create_time'] . '抽中[' . $gift['gift_name'] . 'X' . $gift['gift_num'] . ']未查看!'; 62 | Log::notice($data_info); 63 | // TODO 推送 log 64 | } 65 | } 66 | return; 67 | } 68 | } -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # BilibiliHelper For Tools 2 | B 站直播实用脚本 -- 小工具 3 | 4 | --- 5 | > activeSendMsg.php 6 | 功能: 发送随机字符到直播间(5s/1次),支持多用户 7 | 8 | 配合主程序使用,修改主程序 9 | > 修改 `$api->_roomRealId = '';` 10 | 11 | 注意,执行了主程序才能执行本程序 12 | 13 | ``` 14 | php activeSendMsg.php 15 | [2018/03/19 13:07:11] 弹幕发送成功 16 | [2018/03/19 13:07:21] 弹幕发送成功 17 | [2018/03/19 13:07:31] 弹幕发送成功 18 | [2018/03/19 13:07:42] 弹幕发送成功 19 | 20 | ``` 21 | 22 | 如果需要一个稳定的直播间读取弹幕,可以使用本程序。 23 | 默认的房间号是我的,你也可以修改 24 | 25 | 如果遇到scandir()爆错, 26 | 找到php.ini,查找disable_functions ,去掉scandir,保存、重启php即可 27 | 28 | --- 29 | 待添加... 30 | -------------------------------------------------------------------------------- /tools/activeSendMsg.php: -------------------------------------------------------------------------------- 1 | _start = time(); 30 | } 31 | 32 | //主函数 33 | public function start() 34 | { 35 | $this->init(); 36 | while (1) { 37 | if ($this->_lock['cookie'] < time()) { 38 | $this->init(); 39 | } 40 | foreach ($this->_userInfoList as $userInfo) { 41 | $this->convertInfo($userInfo); 42 | 43 | $msg = $this->getMsgInfo(); 44 | $info = [ 45 | 'roomid' => $this->_roomid, 46 | 'content' => $msg, 47 | ]; 48 | $this->privateSendMsg($info); 49 | sleep(10); 50 | } 51 | sleep(10); 52 | } 53 | } 54 | 55 | //init 56 | public function init() 57 | { 58 | $this->_lock['cookie'] = $this->_start + 60 * 60; 59 | $this->_userInfoList = []; 60 | $filelist = $this->scanPathFile(); 61 | foreach ($filelist as $file) { 62 | $this->_userInfoList[] = file_get_contents($file); 63 | } 64 | } 65 | 66 | //转换信息 67 | public function convertInfo($userInfo) 68 | { 69 | $this->_cookie = $userInfo; 70 | preg_match('/bili_jct=(.{32})/', $this->_cookie, $token); 71 | $this->_token = isset($token[1]) ? $token[1] : ''; 72 | return; 73 | } 74 | 75 | //扫描文件信息 76 | private function scanPathFile() 77 | { 78 | $fileList = scandir($this->_cookieFilePath); 79 | 80 | $newlist = []; 81 | foreach ($fileList as $filename) { 82 | $file = $this->_cookieFilePath . $filename; 83 | if (is_file($file)) { 84 | $newlist[] = $file; 85 | } 86 | 87 | } 88 | if (empty($newlist)) { 89 | die('没有需要操作的用户信息!'); 90 | } 91 | return $newlist; 92 | } 93 | 94 | //获取随机弹幕信息 95 | private function getMsgInfo() 96 | { 97 | $data = $this->curl($this->_hitokotoApi); 98 | if (strpos($data, ',')) { 99 | $newdata = explode(',', $data); 100 | return $newdata[0]; 101 | } elseif (strpos($data, ',')) { 102 | $newdata = explode(',', $data); 103 | return $newdata[0]; 104 | } elseif (strpos($data, '。')) { 105 | $newdata = explode('。', $data); 106 | return $newdata[0]; 107 | } elseif (strpos($data, '!')) { 108 | $newdata = explode('!', $data); 109 | return $newdata[0]; 110 | } elseif (strpos($data, '.')) { 111 | $newdata = explode('.', $data); 112 | return $newdata[0]; 113 | } elseif (strpos($data, ';')) { 114 | $newdata = explode(';', $data); 115 | return $newdata[0]; 116 | } else { 117 | $newdata = explode('——', $data); 118 | return $newdata[0]; 119 | } 120 | 121 | } 122 | 123 | //发送弹幕通用模块 124 | private function sendMsg($info) 125 | { 126 | $url = $this->_liveStatusApi . $info['roomid']; 127 | $raw = $this->curl($url); 128 | $de_raw = json_decode($raw, true); 129 | 130 | $data = [ 131 | 'color' => '16777215', 132 | 'fontsize' => 25, 133 | 'mode' => 1, 134 | 'msg' => $info['content'], 135 | 'rnd' => 0, 136 | 'roomid' => $de_raw['data']['room_id'], 137 | 'csrf_token' => $this->_token, 138 | ]; 139 | 140 | $data = http_build_query($data); 141 | $length = mb_strlen($data) + 2; 142 | 143 | $headers = array( 144 | 'Host: api.live.bilibili.com', 145 | 'Connection: keep-alive', 146 | 'Content-Length: ' . $length, 147 | 'Accept: application/json, text/javascript, */*; q=0.01', 148 | 'Origin: http://live.bilibili.com', 149 | 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36', 150 | 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8', 151 | 'Referer: http://live.bilibili.com/' . $de_raw['data']['room_id'], 152 | 'Accept-Encoding: gzip, deflate, br', 153 | 'Accept-Language: zh-CN,zh;q=0.8', 154 | 'Cookie: ' . $this->_cookie, 155 | ); 156 | 157 | return $this->curl($this->_liveSendMsg, $data, $headers); 158 | } 159 | 160 | //使用发送弹幕模块 161 | public function privateSendMsg($info) 162 | { 163 | //TODO 暂时性功能 有需求就修改 164 | $raw = $this->sendMsg($info); 165 | $de_raw = json_decode($raw, true); 166 | if ($de_raw['code'] == '0') { 167 | echo '[' . date("Y/m/d H:i:s") . '] ' . '弹幕发送成功' . PHP_EOL; 168 | return true; 169 | } 170 | echo '[' . date("Y/m/d H:i:s") . '] ' . '弹幕发送失败' . PHP_EOL; 171 | return true; 172 | } 173 | 174 | //通用curl 175 | public function curl($url, $data = null, $headers = null) 176 | { 177 | $ch = curl_init(); 178 | curl_setopt($ch, CURLOPT_URL, $url); 179 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 180 | curl_setopt($ch, CURLOPT_HEADER, 0); 181 | if (!empty($data)) { 182 | curl_setopt($ch, CURLOPT_POST, 1); 183 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 184 | } 185 | if (!empty($headers)) { 186 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 187 | } 188 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 189 | if ($this->_deBug) { 190 | curl_setopt($ch, CURLOPT_PROXY, '127.0.0.1'); 191 | curl_setopt($ch, CURLOPT_PROXYPORT, '8888'); 192 | } 193 | $res = curl_exec($ch); 194 | curl_close($ch); 195 | return $res; 196 | } 197 | } 198 | 199 | $api = new activeSendMsg(); 200 | $api->start(); 201 | 202 | 203 | 204 | --------------------------------------------------------------------------------