├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------