[init] Initialization

This commit is contained in:
Lkeme 2019-07-13 13:34:03 +08:00
parent fa0ac82c2f
commit 8671c08265
36 changed files with 4518 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Created by .ignore support plugin (hsz.mobi)
### Example user template template
### Example user template
# IntelliJ project files
.idea
.idea/
index1.php
user/
record/
temp/
tmp/
*.iml
out
gen
/vendor/
/configs/
/tests/
config
*.log
Traits/
README1.md
conf/user.conf
/conf/user.conf
/log/

13
CHANGELOG.md Normal file
View File

@ -0,0 +1,13 @@
# Release Notes
# 本项目Log
## v0.0.1.190713 alpha (2019-07-13)
### Added
- Initialization
### Changed
- Initialization
### Fixed
- Initialization

224
DOC.md Normal file
View File

@ -0,0 +1,224 @@
<p align="center"><img width="300px" src="https://i.loli.net/2018/04/20/5ad97bd395912.jpeg"></p>
<p align="center">
<img src="https://img.shields.io/badge/version-0.0.1.190713 alpha-green.svg?longCache=true&style=for-the-badge">
<img src="https://img.shields.io/badge/license-mit-blue.svg?longCache=true&style=for-the-badge">
</p>
# BiliHelper
B 站直播实用脚本
## 功能组件
|plugin |version |description |
|--------------------|--------------------|--------------------|
|Daily |19.07.13 |每日背包奖励 |
|GiftSend |19.07.13 |自动清空过期礼物 |
|Heart |19.07.13 |双端直播间心跳 |
|Login |19.07.13 |帐号登录组件 |
|Silver |19.07.13 |自动领宝箱 |
|Task |19.07.13 |每日任务 |
|GiftHeart |19.07.13 |心跳礼物 |
|Silver2Coin |19.07.13 |银瓜子换硬币 |
|MaterialObject |19.07.13 |实物抽奖 |
|GroupSignIn |19.07.13 |应援团签到 |
|Storm |19.07.13 |节奏风暴 |
|Notice |19.07.13 |Server酱 |
|RaffleHandler |19.07.13 |统一活动抽奖 |
|MasterSite |19.07.13 |主站(观看、分享、投币)|
|Guard |19.07.13 |舰长上船亲密度 |
## 打赏赞助
![](https://i.loli.net/2018/04/07/5ac79ff8c2900.png)
## 未完成功能
|待续 |
|-----------|
|添加多用户 |
|待添加 |
## 环境依赖
|Requirement |
|--------------------|
|PHP >=7.0 |
|php_curl |
|php_sockets |
|php_openssl |
|php_json |
|php_zlib |
|php_mbstring |
|待添加 |
通常使用 `composer` 工具会自动检测上述依赖问题。
## Composer
* 项目 `composer.lock` 基于阿里云Composer镜像生成
+ 阿里云(推荐)
```
# 使用帮助
https://developer.aliyun.com/composer
# 使用命令
> composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
```
* 腾讯云(备用)
```
# 使用帮助
https://mirrors.cloud.tencent.com/composer/
# 使用命令
composer config -g repos.packagist composer https://mirrors.cloud.tencent.com/composer/
```
## 打赏赞助
![](https://i.loli.net/2019/07/13/5d2963e5cc1eb22973.png)
> 待添加
## 使用指南
1. 下载(克隆)项目代码,初始化项目
```
$ git clone https://github.com/lkeme/BiliHelper-personal.git
$ cd BiliHelper/conf
$ cp user.conf.example user.conf
```
2. 使用 [composer](https://getcomposer.org/download/) 工具进行安装
```
$ composer install
```
3. 按照说明修改配置文件 `user.conf`,只需填写帐号密码即可
4. 运行测试
```
$ php index.php
```
> 以下是`多开方案`,单个账户可以无视
5. 复制一份example配置文件修改账号密码即可
```
$ php index.php example.conf
```
6. 请保证配置文件存在,否则默认加载`user.conf`配置文件
<p align="center"><img width="680px" src="https://i.loli.net/2018/04/21/5adb497dc3ece.png"></p>
## 升级指南
1. 进入项目目录
```
$ cd BiliHelper-personal
```
2. 拉取最新代码
```
$ git pull
```
3. 更新依赖库
```
$ composer install
```
4. 如果使用 systemd 等,需要重启服务
```
$ systemctl restart bilibili
```
## 部署指南
如果你将 BiliHelper-personal 部署到线上服务器时,则需要配置一个进程监控器来监测 `php index.php` 命令,在它意外退出时自动重启。
通常可以使用以下的方式
- systemd (推荐)
- Supervisor
- screen (自用)
- nohup
## systemd 脚本
```
# /usr/lib/systemd/system/bilibili.service
[Unit]
Description=Bili Helper Manager
Documentation=https://github.com/lkeme/BiliHelper-personal
After=network.target
[Service]
ExecStart=/usr/bin/php /path/to/your/BiliHelper-personal/index.php
Restart=always
[Install]
WantedBy=multi-user.target
```
## Supervisor 配置
```
[program:bilibili]
process_name=%(program_name)s
command=php /path/to/your/BiliHelper-personal/index.php
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/tmp/bilibili.log
```
## 报错通知问题
脚本出现 error 级别的报错,会调用通知地址进行提醒,这里推荐两个服务
|服务|官网|
|---|---|
|Server酱|https://sc.ftqq.com/|
|TelegramBot|https://core.telegram.org/bots/api|
示范如下
```
# Server酱
# 自行替换 <SCKEY>
APP_CALLBACK="https://sc.ftqq.com/<SCKEY>.send?text={message}"
# TelegramBot
# 自行替换 <TOKEN> <CHAR_ID>
APP_CALLBACK="https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=<CHAR_ID>&text={message}"
```
`{message}` 部分会自动替换成错误信息,接口采用 get 方式发送
## 直播间 ID 问题
`user.conf` 文件中有个 `ROOM_ID` 配置,填写此项可以清空临过期礼物给指定直播间。
通常可以在直播间页面的 url 获取到它
```
http://live.bilibili.com/9522051
```
所有直播间号码小于 1000 的直播间为短号,该脚本在每次启动会自动修正,无需关心,
## 相关
> 本项目基于[BilibiliHelper](https://github.com/metowolf/BilibiliHelper)项目
> 基于父项目的架构开发,在此感谢父项目的开发
> 保留父项目没必要修改的信息,另外欢迎重构(Haha)
## License 许可证
BiliHelper is under the MIT license.
本项目基于 MIT 协议发布,并增加了 SATA 协议。
当你使用了使用 SATA 的开源软件或文档的时候,在遵守基础许可证的前提下,你必须马不停蹄地给你所使用的开源项目 “点赞” ,比如在 GitHub 上 star然后你必须感谢这个帮助了你的开源项目的作者作者信息可以在许可证头部的版权声明部分找到。
本项目的所有代码文件、配置项,除另有说明外,均基于上述介绍的协议发布,具体请看分支下的 LICENSE。
此处的文字仅用于说明,条款以 LICENSE 文件中的内容为准。

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# BiliHelper
B 站直播实用脚本
## 公告
Currently for Personal Edition **0.0.1.190713 alpha**
## 文档
* [使用文档 / DOC.md](./DOC.md)
* [更新日志 / CHANGELOG.md](./CHANGELOG.md)
## 交流
Group: [55308141](https://jq.qq.com/?_wv=1027&k=5AIDaJg)
## 打赏
![](https://i.loli.net/2019/07/13/5d2963e5cc1eb22973.png)
## 效果
![](https://i.loli.net/2019/07/13/5d296961a4bae41364.png)

31
composer.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "lkeme/bilihelper",
"description": "B 站自动领瓜子、直播助手、直播挂机脚本、主站助手 - PHP 版Personal",
"type": "project",
"require": {
"php": ">=7.0.0",
"ext-curl": "*",
"ext-openssl": "*",
"ext-sockets": "*",
"ext-json": "*",
"ext-zlib": "*",
"ext-mbstring": "*",
"vlucas/phpdotenv": "^2.4",
"monolog/monolog": "^1.23",
"bramus/monolog-colored-line-formatter": "^2.0",
"clue/socket-raw": "1.4"
},
"license": "MIT",
"authors": [
{
"name": "Lkeme",
"email": "Useri@live.cn",
"homepage": "https://mudew.com"
}
],
"autoload": {
"psr-4": {
"lkeme\\BiliHelper\\": "src/"
}
}
}

439
composer.lock generated Normal file
View File

@ -0,0 +1,439 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6fca2d61232110e041d8cb569c30d02f",
"packages": [
{
"name": "bramus/ansi-php",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/bramus/ansi-php.git",
"reference": "79d30c30651b0c6f23cf85503c779c72ac74ab8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bramus/ansi-php/zipball/79d30c30651b0c6f23cf85503c779c72ac74ab8a",
"reference": "79d30c30651b0c6f23cf85503c779c72ac74ab8a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Bramus\\Ansi\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bramus Van Damme",
"email": "bramus@bram.us",
"homepage": "https://www.bram.us/"
}
],
"description": "ANSI Control Functions and ANSI Control Sequences (Colors, Erasing, etc.) for PHP CLI Apps",
"time": "2019-02-12T15:05:30+00:00"
},
{
"name": "bramus/monolog-colored-line-formatter",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/bramus/monolog-colored-line-formatter.git",
"reference": "6bff15eee00afe2690642535b0f1541f10852c2b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bramus/monolog-colored-line-formatter/zipball/6bff15eee00afe2690642535b0f1541f10852c2b",
"reference": "6bff15eee00afe2690642535b0f1541f10852c2b",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"bramus/ansi-php": "~3.0",
"php": ">=5.4.0"
},
"require-dev": {
"monolog/monolog": "~1.0",
"phpunit/phpunit": "~4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Bramus\\Monolog\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bramus Van Damme",
"email": "bramus@bram.us",
"homepage": "https://www.bram.us/"
}
],
"description": "Colored Line Formatter for Monolog",
"time": "2015-01-07T22:12:35+00:00"
},
{
"name": "clue/socket-raw",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/clue/php-socket-raw.git",
"reference": "2f6654445233407900c9a804490cecd8e4f2ae86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/clue/php-socket-raw/zipball/2f6654445233407900c9a804490cecd8e4f2ae86",
"reference": "2f6654445233407900c9a804490cecd8e4f2ae86",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-sockets": "*",
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^6.0 || ^5.2 || ^4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"Socket\\Raw\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@lueck.tv"
}
],
"description": "Simple and lightweight OOP wrapper for PHP's low level sockets extension (ext-sockets)",
"homepage": "https://github.com/clue/php-socket-raw",
"keywords": [
"Socket",
"client",
"datagram",
"dgram",
"icmp",
"ipv6",
"server",
"stream",
"tcp",
"udg",
"udp",
"unix"
],
"time": "2019-01-22T11:08:01+00:00"
},
{
"name": "monolog/monolog",
"version": "1.24.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"time": "2018-11-05T09:00:11+00:00"
},
{
"name": "psr/log",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2018-11-20T15:27:04+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "82ebae02209c21113908c229e9883c419720738a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
"reference": "82ebae02209c21113908c229e9883c419720738a",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.11-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v2.6.1",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5",
"reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": ">=5.3.9",
"symfony/polyfill-ctype": "^1.9"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev"
}
},
"autoload": {
"psr-4": {
"Dotenv\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Vance Lucas",
"email": "vance@vancelucas.com",
"homepage": "http://www.vancelucas.com"
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"keywords": [
"dotenv",
"env",
"environment"
],
"time": "2019-01-29T11:11:52+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.0.0",
"ext-curl": "*",
"ext-openssl": "*",
"ext-sockets": "*",
"ext-json": "*",
"ext-zlib": "*",
"ext-mbstring": "*"
},
"platform-dev": []
}

114
conf/user.conf.example Normal file
View File

@ -0,0 +1,114 @@
#######################
# 账户设置 #
#######################
# 帐号|密码
APP_USER=
APP_PASS=
# 令牌(自动生成)
ACCESS_TOKEN=
REFRESH_TOKEN=
COOKIE=
#######################
# 功能设置 #
#######################
# 推送服务器
USE_SERVER=true
SERVER_ADDR=
SERVER_KEY=
# 舰长总督
USE_GUARD=true
# 主站助手
USE_MASTER_SITE=true
# 活跃弹幕|弹幕房间(为空则随机)|弹幕内容(为空随机,但是URL不能为空)
USE_DANMU=true
DANMU_ROOMID=9522051
DANMU_CONTENT=
CONTENT_FETCH="https://v1.hitokoto.cn/?encode=text"
# 视频投币|投币稿件数(每日任务最大5)|自定义稿件ID,空则随机(AV_NUM=1生效)
USE_ADD_COIN=false
ADD_COIN_AV_NUM=1
ADD_COIN_AV=33492180
# SERVER酱,用于推送消息
USE_SCKEY=
# 切换HTTPS,为真则使用https协议
USE_HTTPS=true
# 是否使用代理(前提保证有效代理)
USE_PROXY=false
PROXY_IP=127.0.0.1
PROXY_PORT=8888
# 直播间ID用于礼物赠送
ROOM_ID=9522051
# 弹幕监控房间(为空则随机)
SOCKET_ROOM_ID=9522051
# 节奏风暴|丢弃率(0-100)|尝试数(范围值)
USE_STORM=true
STORM_DROPRATE=0
STORM_ATTEMPT=30,50
# 统一活动
USE_ACTIVE=true
# 银瓜子兑换硬币
USE_SILVER2COIN=true
# 实物抽奖
USE_MO=true
#######################
# 日志设置 #
#######################
# 写入日志
APP_WRITELOG=false
# 日志路径
APP_WRITELOGPATH=log
# 调试模式
APP_DEBUG=false
# 用户名,可自定义
APP_UNAME=
# 多账号区别输出
APP_MULTIPLE=false
# 账号别名,如果为空则默认使用登陆账号作为标示
APP_USER_IDENTITY=
# 日志回调地址
APP_CALLBACK="http://www.example.com/api.send?text={account}[{level}]: {message}"
# 错误回调级别
#
# DEBUG 100
# INFO 200
# NOTICE 250
# WARNING 300
# ERROR 400
#
APP_CALLBACK_LEVEL=400
#######################
# 固定设置 #
#######################
# 用于打包请求
ACTIONENTRY=7
# 用于发送心跳
ACTIONHEARTBEAT=2

94
index.php Normal file
View File

@ -0,0 +1,94 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
//autoload
require 'vendor/autoload.php';
use Dotenv\Dotenv;
set_time_limit(0);
header("Content-Type:text/html; charset=utf-8");
date_default_timezone_set('Asia/Shanghai');
class Index
{
public static $conf_file = null;
public static $dotenv = null;
/**
* @param $conf_file
* @throws \Exception
*/
public static function run($conf_file)
{
self::$conf_file = $conf_file;
self::loadConfigFile();
while (true) {
if (!Login::check()) {
self::$dotenv->overload();
}
Daily::run();
MasterSite::run();
Danmu::run();
GiftSend::run();
Heart::run();
Silver::run();
Task::run();
Silver2Coin::run();
GroupSignIn::run();
Live::run();
GiftHeart::run();
Winning::run();
MaterialObject::run();
TcpClinet::run();
Storm::run();
RaffleHandler::run();
Guard::run();
Statistics::run();
usleep(0.1 * 1000000);
}
}
protected static function loadConfigFile()
{
$file_path = __DIR__ . '/conf/' . self::$conf_file;
if (is_file($file_path) && self::$conf_file != 'user.conf') {
$load_files = [
self::$conf_file,
];
} else {
$default_file_path = __DIR__ . '/conf/user.conf';
if (!is_file($default_file_path)) {
exit('默认加载配置文件不存在,请按照文档添加配置文件!');
}
$load_files = [
'user.conf',
];
}
foreach ($load_files as $load_file) {
self::$dotenv = new Dotenv(__DIR__ . '/conf', $load_file);
self::$dotenv->load();
}
// load ACCESS_KEY
Login::run();
self::$dotenv->overload();
}
}
// LOAD
$conf_file = isset($argv[1]) ? $argv[1] : 'user.conf';
// RUN
Index::run($conf_file);

183
src/Curl.php Normal file
View File

@ -0,0 +1,183 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Curl
{
public static $headers = array(
'Accept' => '*/*',
'Accept-Encoding' => 'gzip',
'Accept-Language' => 'zh-cn',
'Connection' => 'keep-alive',
'Content-Type' => 'application/x-www-form-urlencoded',
'User-Agent' => 'bili-universal/8230 CFNetwork/975.0.3 Darwin/18.2.0',
// 'Referer' => 'https://live.bilibili.com/',
);
private static function getHeaders($headers)
{
return array_map(function ($k, $v) {
return $k . ': ' . $v;
}, array_keys($headers), $headers);
}
public static function post($url, $payload = null, $headers = null, $timeout = 30)
{
$url = self::http2https($url);
Log::debug($url);
$header = is_null($headers) ? self::getHeaders(self::$headers) : self::getHeaders($headers);
// 重试次数
$ret_count = 300;
$waring = 280;
while ($ret_count) {
// 网络断开判断 延时方便连接网络
if ($ret_count < $waring) {
Log::warning("正常等待网络连接状态恢复正常...");
sleep(mt_rand(5, 10));
}
try {
$curl = curl_init();
if (!is_null($payload)) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, is_array($payload) ? http_build_query($payload) : $payload);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_ENCODING, 'gzip');
curl_setopt($curl, CURLOPT_IPRESOLVE, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
// 超时 重要
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
if (($cookie = getenv('COOKIE')) != "") {
curl_setopt($curl, CURLOPT_COOKIE, $cookie);
}
if (getenv('USE_PROXY') == 'true') {
curl_setopt($curl, CURLOPT_PROXY, getenv('PROXY_IP'));
curl_setopt($curl, CURLOPT_PROXYPORT, getenv('PROXY_PORT'));
}
$raw = curl_exec($curl);
if ($err_no = curl_errno($curl)) {
throw new \Exception(curl_error($curl));
}
if ($raw === false || strpos($raw, 'timeout') !== false) {
Log::warning('重试,获取的资源无效!');
$ret_count--;
continue;
}
Log::debug($raw);
curl_close($curl);
return $raw;
} catch (\Exception $e) {
Log::warning("重试,Curl请求出错,{$e->getMessage()}!");
$ret_count--;
continue;
}
}
exit('重试次数过多,请检查代码,退出!');
}
public static function other($url, $payload = null, $headers = null, $cookie = null, $timeout = 30)
{
Log::debug($url);
$header = is_null($headers) ? self::getHeaders(self::$headers) : self::getHeaders($headers);
// 重试次数
$ret_count = 30;
while ($ret_count) {
try {
$curl = curl_init();
if (!is_null($payload)) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, is_array($payload) ? http_build_query($payload) : $payload);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_ENCODING, 'gzip');
curl_setopt($curl, CURLOPT_IPRESOLVE, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
// 超时 重要
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
// 认证
if (!is_null($cookie)) {
curl_setopt($curl, CURLOPT_COOKIE, $cookie);
}
if (getenv('USE_PROXY') == 'true') {
curl_setopt($curl, CURLOPT_PROXY, getenv('PROXY_IP'));
curl_setopt($curl, CURLOPT_PROXYPORT, getenv('PROXY_PORT'));
}
$raw = curl_exec($curl);
if ($err_no = curl_errno($curl)) {
throw new \Exception(curl_error($curl));
}
if ($raw === false || strpos($raw, 'timeout') !== false) {
Log::warning('重试,获取的资源无效!');
$ret_count--;
continue;
}
Log::debug($raw);
curl_close($curl);
return $raw;
} catch (\Exception $e) {
Log::warning("重试,Curl请求出错,{$e->getMessage()}!");
$ret_count--;
continue;
}
}
exit('重试次数过多,请检查代码,退出!');
}
public static function get($url, $payload = null, $headers = null)
{
if (!is_null($payload)) {
$url .= '?' . http_build_query($payload);
}
return self::post($url, null, $headers);
}
protected static function http2https($url)
{
switch (getenv('USE_HTTPS')) {
case 'false':
if (strpos($url, 'ttps://')) {
$url = str_replace('https://', 'http://', $url);
}
break;
case 'true':
if (strpos($url, 'ttp://')) {
$url = str_replace('http://', 'https://', $url);
}
break;
default:
Log::warning('当前协议设置不正确,请检查配置文件!');
die();
break;
}
return $url;
}
}

40
src/Daily.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Daily
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time()) {
return;
}
self::dailyBag();
self::$lock = time() + 3600;
}
protected static function dailyBag()
{
$payload = [];
$data = Curl::get('https://api.live.bilibili.com/gift/v2/live/receive_daily_bag', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('每日礼包领取失败!', ['msg' => $data['message']]);
} else {
Log::notice('每日礼包领取成功');
}
}
}

130
src/Danmu.php Normal file
View File

@ -0,0 +1,130 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Danmu
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time() || getenv('USE_DANMU') == 'false') {
return;
}
$room_id = empty(getenv('DANMU_ROOMID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('DANMU_ROOMID'));
$msg = empty(getenv('DANMU_CONTENT')) ? self::getMsgInfo() : getenv('DANMU_CONTENT');
$info = [
'roomid' => $room_id,
'content' => $msg,
];
if (self::privateSendMsg($info)) {
self::$lock = time() + 3600;
return;
}
self::$lock = time() + 30;
}
// 获取随机弹幕
private static function getMsgInfo()
{
/*
* 整理一部分API收集于网络侵权麻烦联系我删除.
* 如果设置项不能用可以选择,只保证代码发布时正常.
* 格式全部为TEXT请自己测试好了再放进配置文件.
* -7. https://api.lwl12.com/hitokoto/v1?encode=realjso
* -6. https://api.ly522.com/yan.php?format=text
* -5. https://v1.hitokoto.cn/?encode=text
* -4. https://api.jysafe.cn/yy/
* -3. https://m.mom1.cn/api/
* -2. https://api.ooopn.com/yan/api.php?type=text
* -1. https://api.imjad.cn/hitokoto/
* 0. https://www.ly522.com/hitokoto/
* 1. https://www.tddiao.online/word/
* 2. https://api.guoch.xyz/
* 3. http://www.ooomg.cn/dutang
* 4. https://api.gushi.ci/rensheng.txt
* 5. https://api.itswincer.com/hitokoto/v2/
* 6. http://api.imiliy.cn/get/
* 7. http://api.dsecret.com/yiyan/
* 8. https://api.xygeng.cn/dailywd/api/api.php
*/
try {
$url = getenv('CONTENT_FETCH');
if (empty($url)) {
exit('活跃弹幕配置错误,请检查相应配置');
}
$data = Curl::get($url);
$punctuations = ['', ',', '。', '!', '.', ';', '——'];
foreach ($punctuations as $punctuation) {
if (strpos($data, $punctuation)) {
$data = explode($punctuation, $data)[0];
break;
}
}
return $data;
} catch (\Exception $e) {
return $e;
}
}
//转换信息
private static function convertInfo()
{
preg_match('/bili_jct=(.{32})/', getenv('COOKIE'), $token);
$token = isset($token[1]) ? $token[1] : '';
return $token;
}
//发送弹幕通用模块
private static function sendMsg($info)
{
$raw = Curl::get('https://api.live.bilibili.com/room/v1/Room/room_init?id=' . $info['roomid']);
$de_raw = json_decode($raw, true);
$payload = [
'color' => '16777215',
'fontsize' => 25,
'mode' => 1,
'msg' => $info['content'],
'rnd' => 0,
'roomid' => $de_raw['data']['room_id'],
'csrf' => self::convertInfo(),
'csrf_token' => self::convertInfo(),
];
return Curl::post('https://api.live.bilibili.com/msg/send', Sign::api($payload));
}
//使用发送弹幕模块
private static function privateSendMsg($info)
{
//TODO 暂时性功能 有需求就修改
$raw = self::sendMsg($info);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 1001) {
Log::warning($de_raw['msg']);
return false;
}
if (!$de_raw['code']) {
Log::info('弹幕发送成功!');
return true;
}
Log::error("弹幕发送失败, {$de_raw['msg']}");
return false;
}
}

49
src/DataTreating.php Normal file
View File

@ -0,0 +1,49 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class DataTreating
{
/**
* @desc 抽奖分发
* @param array $data
*/
public static function distribute(array $data)
{
// var_dump($data);
// room_id raffle_id raffle_title raffle_type
try {
$info = ['rid' => $data['room_id'], 'lid' => $data['raffle_id']];
} catch (\Exception $e) {
return;
}
switch ($data['raffle_type']) {
case "storm":
// 风暴
Storm::pushToQueue($info);
break;
case "raffle":
// 礼物
RaffleHandler::pushToQueue($info);
break;
case "guard":
// 舰长
Guard::pushToQueue($info);
break;
case "small_tv":
// 小电视
RaffleHandler::pushToQueue($info);
break;
default:
break;
}
}
}

31
src/File.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class File
{
// RUN
public static function run()
{
}
// PUT CONF
public static function writeNewEnvironmentFileWith($key, $value)
{
file_put_contents(__DIR__ . '/../conf/' . Index::$conf_file, preg_replace(
'/^' . $key . '=' . getenv($key) . '/m',
$key . '=' . $value,
file_get_contents(__DIR__ . '/../conf/' . Index::$conf_file)
));
// 写入系统变量
putenv($key . '=' . $value);
}
}

66
src/GiftHeart.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class GiftHeart
{
public static $lock = 0;
// RUN
public static function run()
{
if (self::$lock > time()) {
return;
}
if (self::giftheart()) {
self::$lock = time() + 60 * 60;
return;
}
self::$lock = time() + 5 * 60;
}
// GIFT HEART
protected static function giftheart(): bool
{
$payload = [
'roomid' => getenv('ROOM_ID'),
];
$raw = Curl::get('https://api.live.bilibili.com/gift/v2/live/heart_gift_receive', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == -403) {
Log::info($de_raw['msg']);
$payload = [
'ruid' => 17561885,
];
Curl::get('https://api.live.bilibili.com/eventRoom/index', Sign::api($payload));
return true;
}
if ($de_raw['code'] != 0) {
Log::warning($de_raw['msg']);
return false;
}
if ($de_raw['data']['heart_status'] == 0) {
Log::info('没有礼物可以领了呢!');
return true;
}
if (isset($de_raw['data']['gift_list'])) {
foreach ($de_raw['data']['gift_list'] as $vo) {
Log::info("{$de_raw['msg']},礼物 {$vo['gift_name']} ({$vo['day_num']}/{$vo['day_limit']})");
}
return false;
}
}
}

109
src/GiftSend.php Normal file
View File

@ -0,0 +1,109 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class GiftSend
{
public static $lock = 0;
protected static $uid = 0;
protected static $ruid = 0;
protected static $roomid = 0;
protected static function getRoomInfo()
{
Log::info('正在生成直播间信息...');
$payload = [];
$data = Curl::get('https://account.bilibili.com/api/myinfo/v2', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('获取帐号信息失败!', ['msg' => $data['message']]);
Log::warning('清空礼物功能禁用!');
self::$lock = time() + 100000000;
return;
}
self::$uid = $data['mid'];
$payload = [
'id' => getenv('ROOM_ID'),
];
$data = Curl::get('https://api.live.bilibili.com/room/v1/Room/get_info', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('获取主播房间号失败!', ['msg' => $data['message']]);
Log::warning('清空礼物功能禁用!');
self::$lock = time() + 100000000;
return;
}
Log::info('直播间信息生成完毕!');
self::$ruid = $data['data']['uid'];
self::$roomid = $data['data']['room_id'];
}
public static function run()
{
if (empty(self::$ruid)) {
self::getRoomInfo();
}
if (self::$lock > time()) {
return;
}
$payload = [];
$data = Curl::get('https://api.live.bilibili.com/gift/v2/gift/bag_list', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('背包查看失败!', ['msg' => $data['message']]);
}
if (isset($data['data']['list'])) {
foreach ($data['data']['list'] as $vo) {
if ($vo['expire_at'] >= $data['data']['time'] && $vo['expire_at'] <= $data['data']['time'] + 3600) {
self::send($vo);
sleep(3);
}
}
}
self::$lock = time() + 600;
}
protected static function send($value)
{
$payload = [
'coin_type' => 'silver',
'gift_id' => $value['gift_id'],
'ruid' => self::$ruid,
'uid' => self::$uid,
'biz_id' => self::$roomid,
'gift_num' => $value['gift_num'],
'data_source_id' => '',
'data_behavior_id' => '',
'bag_id' => $value['bag_id']
];
$data = Curl::post('https://api.live.bilibili.com/gift/v2/live/bag_send', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('送礼失败!', ['msg' => $data['message']]);
} else {
Log::notice("成功向 {$payload['biz_id']} 投喂了 {$value['gift_num']}{$value['gift_name']}");
}
}
}

74
src/GroupSignIn.php Normal file
View File

@ -0,0 +1,74 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class GroupSignIn
{
public static $lock = 0;
// RUN
public static function run()
{
if (self::$lock > time()) {
return;
}
$groups = self::getGroupList();
if (empty($groups)) {
self::$lock = time() + 24 * 60 * 60;
return;
}
foreach ($groups as $group) {
self::signInGroup($group);
}
self::$lock = time() + 8 * 60 * 60;
}
//GROUP LIST
protected static function getGroupList(): array
{
$payload = [];
$raw = Curl::get('https://api.vc.bilibili.com/link_group/v1/member/my_groups', Sign::api($payload));
$de_raw = json_decode($raw, true);
if (empty($de_raw['data']['list'])) {
Log::notice('你没有需要签到的应援团!');
return [];
}
return $de_raw['data']['list'];
}
//SIGN IN
protected static function signInGroup(array $groupInfo): bool
{
$payload = [
'group_id' => $groupInfo['group_id'],
'owner_id' => $groupInfo['owner_uid'],
];
$raw = Curl::get('https://api.vc.bilibili.com/link_setting/v1/link_setting/sign_in', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] != '0') {
Log::warning('在应援团{' . $groupInfo['group_name'] . '}中签到失败,原因待查');
// TODO
return false;
}
if ($de_raw['data']['status'] == '0') {
Log::info('在应援团{' . $groupInfo['group_name'] . '}中签到成功,增加{' . $de_raw['data']['add_num'] . '点}亲密度');
} else {
Log::notice('在应援团{' . $groupInfo['group_name'] . '}中不要重复签到');
}
return true;
}
}

126
src/Guard.php Normal file
View File

@ -0,0 +1,126 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Guard
{
const KEY = '总督舰长';
const SWITCH = 'USE_GUARD';
public static $lock = 0;
private static $wait_list = [];
private static $finsh_list = [];
private static $all_list = [];
public static function run()
{
if (getenv(self::SWITCH) == 'false') {
return;
}
if (self::$lock > time()) {
return;
}
self::startLottery();
}
/**
* 抽奖逻辑
* @return bool
*/
protected static function startLottery(): bool
{
$max_num = 3;
while ($max_num) {
$guard = array_shift(self::$wait_list);
if (is_null($guard)) {
break;
}
$guard_lid = $guard['lid'];
$guard_rid = $guard['rid'];
Live::goToRoom($guard_rid);
Statistics::addJoinList(self::KEY);
$data = self::lottery($guard_rid, $guard_lid);
if ($data['code'] == 0) {
Statistics::addSuccessList(self::KEY);
Log::notice("房间 {$guard_rid} 编号 {$guard_lid} " . self::KEY . ": {$data['data']['message']}");
} elseif ($data['code'] == 400 && $data['msg'] == '你已经领取过啦') {
Log::info("房间 {$guard_rid} 编号 {$guard_lid} " . self::KEY . ": {$data['msg']}");
} else {
Log::warning("房间 {$guard_rid} 编号 {$guard_lid} " . self::KEY . ": {$data['msg']}");
}
$max_num--;
}
return true;
}
/**
* 请求抽奖
* @param $rid
* @param $lid
* @return array
*/
private static function lottery($rid, $lid): array
{
$user_info = User::parseCookies();
$url = "https://api.live.bilibili.com/lottery/v2/lottery/join";
$payload = [
"roomid" => $rid,
"id" => $lid,
"type" => "guard",
"csrf_token" => $user_info['token']
];
$raw = Curl::post($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
return $de_raw;
}
/**
* 重复检测
* @param int $lid
* @return bool
*/
private static function toRepeatLid(int $lid): bool
{
if (in_array($lid, self::$all_list)) {
return true;
}
if (count(self::$all_list) > 2000) {
self::$all_list = [];
}
array_push(self::$all_list, $lid);
return false;
}
/**
* 数据推入队列
* @param array $data
* @return bool
*/
public static function pushToQueue(array $data): bool
{
if (getenv(self::SWITCH) == 'false') {
return false;
}
if (self::toRepeatLid($data['lid'])) {
return false;
}
Statistics::addPushList(self::KEY);
self::$wait_list = array_merge(self::$wait_list, [['rid' => $data['rid'], 'lid' => $data['lid']]]);
$wait_num = count(self::$wait_list);
if ($wait_num > 2) {
Log::info("当前队列中共有 {$wait_num}" . self::KEY . "待抽奖");
}
return true;
}
}

58
src/Heart.php Normal file
View File

@ -0,0 +1,58 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Heart
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time()) {
return;
}
self::pc();
self::mobile();
self::$lock = time() + 300;
}
protected static function pc()
{
$payload = [
'room_id' => getenv('ROOM_ID'),
];
$data = Curl::post('https://api.live.bilibili.com/User/userOnlineHeart', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('WEB端 直播间心跳停止惹~', ['msg' => $data['message']]);
} else {
Log::info('WEB端 发送心跳正常!');
}
}
protected static function mobile()
{
$payload = [
'room_id' => getenv('ROOM_ID'),
];
$data = Curl::post('https://api.live.bilibili.com/mobile/userOnlineHeart', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('APP端 直播间心跳停止惹~', ['msg' => $data['message']]);
} else {
Log::info('APP端 发送心跳正常!');
}
}
}

158
src/Live.php Normal file
View File

@ -0,0 +1,158 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Live
{
public static $lock = 0;
// RUN
public static function run()
{
// TODO
self::isSleep();
}
// GET RANDOW ROOM_ID
public static function getUserRecommend()
{
while (1) {
$raw = Curl::get('https://api.live.bilibili.com/area/liveList?area=all&order=online&page=' . random_int(0, 5));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] != '0') {
continue;
}
break;
}
$rand_num = random_int(1, 29);
return $de_raw['data'][$rand_num]['roomid'];
}
// GET REALROOM_ID
public static function getRealRoomID($room_id)
{
$raw = Curl::get('https://api.live.bilibili.com/room/v1/Room/room_init?id=' . $room_id);
$de_raw = json_decode($raw, true);
if ($de_raw['code']) {
Log::warning($room_id . ' : ' . $de_raw['msg']);
return false;
}
if ($de_raw['data']['is_hidden']) {
return false;
}
if ($de_raw['data']['is_locked']) {
return false;
}
if ($de_raw['data']['encrypted']) {
return false;
}
return $de_raw['data']['room_id'];
}
// 钓鱼检测
public static function fishingDetection($room_id): bool
{
if (self::getRealRoomID($room_id)) {
return false;
}
return true;
}
// RANDOM DELAY
public static function randFloat($min = 0, $max = 3): bool
{
$rand = $min + mt_rand() / mt_getrandmax() * ($max - $min);
sleep($rand);
return true;
}
//TO ROOM
public static function goToRoom($room_id): bool
{
$payload = [
'room_id' => $room_id,
];
Curl::post('https://api.live.bilibili.com/room/v1/Room/room_entry_action', Sign::api($payload));
// Log::info('进入直播间[' . $room_id . ']抽奖!');
return true;
}
// get Millisecond
public static function getMillisecond()
{
list($t1, $t2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
}
// IS SLEEP
public static function isSleep()
{
if (self::$lock > time()) {
return;
}
self::$lock = time() + 5 * 60;
$hour = date('H');
if ($hour >= 2 && $hour < 6) {
self::bannedVisit('sleep');
Log::warning('休眠时间,暂停非必要任务,4小时后自动开启!');
return;
}
$payload = [];
$raw = Curl::get('https://api.live.bilibili.com/mobile/freeSilverAward', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['msg'] == '访问被拒绝') {
self::bannedVisit('ban');
Log::warning('账号拒绝访问,暂停非必要任务,凌晨自动开启!');
}
return;
}
//被封禁访问
public static function bannedVisit($arg)
{
//获取当前时间
$block_time = strtotime(date("Y-m-d H:i:s"));
if ($arg == 'ban') {
$unblock_time = strtotime(date("Y-m-d", strtotime("+1 day", $block_time)));
} elseif ($arg == 'sleep') {
// TODO
$unblock_time = $block_time + 4 * 60 * 60;
} else {
$unblock_time = time();
}
$second = time() + ceil($unblock_time - $block_time) + 5 * 60;
$hour = floor(($second - time()) / 60 / 60);
if ($arg == 'ban') {
// 推送被ban信息
Notice::run('banned', $hour);
}
self::$lock = $second;
Silver::$lock = $second;
MaterialObject::$lock = $second;
Websocket::$lock = $second;
GiftHeart::$lock = $second;
Guard::$lock = $second;
RaffleHandler::$lock = $second;
RaffleHandler::$rw_lock = $second;
return;
}
}

110
src/Log.php Normal file
View File

@ -0,0 +1,110 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Bramus\Monolog\Formatter\ColoredLineFormatter;
class Log
{
protected static $instance;
static public function getLogger()
{
if (!self::$instance) {
self::configureInstance();
}
return self::$instance;
}
protected static function configureInstance()
{
$logger = new Logger('Bilibili');
$handler = new StreamHandler('php://stdout', getenv('APP_DEBUG') == 'true' ? Logger::DEBUG : Logger::INFO);
$handler->setFormatter(new ColoredLineFormatter());
$logger->pushHandler($handler);
self::$instance = $logger;
}
private static function prefix()
{
if (getenv('APP_MULTIPLE') == 'true') {
return '[' . (empty($t = getenv('APP_USER_IDENTITY')) ? getenv('APP_USER') : $t) . ']';
}
return '';
}
private static function writeLog($type, $message)
{
if (getenv('APP_WRITELOG') == 'true') {
$path = './' . getenv("APP_WRITELOGPATH") . '/';
if (!file_exists($path)) {
mkdir($path);
chmod($path, 0777);
}
$filename = $path . getenv('APP_USER') . ".log";
$date = date('[Y-m-d H:i:s] ');
$data = $date . ' Log.' . $type . ' ' . $message . PHP_EOL;
file_put_contents($filename, $data, FILE_APPEND);
}
return;
}
public static function debug($message, array $context = [])
{
self::writeLog('DEBUG', $message);
self::getLogger()->addDebug($message, $context);
}
public static function info($message, array $context = [])
{
$message = self::prefix() . $message;
self::writeLog('INFO', $message);
self::getLogger()->addInfo($message, $context);
self::callback(Logger::INFO, 'INFO', $message);
}
public static function notice($message, array $context = [])
{
$message = self::prefix() . $message;
self::writeLog('NOTICE', $message);
self::getLogger()->addNotice($message, $context);
self::callback(Logger::NOTICE, 'NOTICE', $message);
}
public static function warning($message, array $context = [])
{
$message = self::prefix() . $message;
self::writeLog('WARNING', $message);
self::getLogger()->addWarning($message, $context);
self::callback(Logger::WARNING, 'WARNING', $message);
}
public static function error($message, array $context = [])
{
$message = self::prefix() . $message;
self::writeLog('ERROR', $message);
self::getLogger()->addError($message, $context);
self::callback(Logger::ERROR, 'ERROR', $message);
}
public static function callback($levelId, $level, $message)
{
$callback_level = (('APP_CALLBACK_LEVEL') == '') ? (Logger::ERROR) : intval(getenv('APP_CALLBACK_LEVEL'));
if ($levelId >= $callback_level) {
$url = str_replace('{account}', self::prefix(), getenv('APP_CALLBACK'));
$url = str_replace('{level}', $level, $url);
$url = str_replace('{message}', urlencode($message), $url);
Curl::get($url);
}
}
}

204
src/Login.php Normal file
View File

@ -0,0 +1,204 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Login
{
public static $lock = 0;
public static function run()
{
Log::info('开始启动程序...');
if (getenv('ACCESS_TOKEN') == "") {
Log::info('令牌载入中...');
self::login();
}
// 重载配置文件
Index::$dotenv->overload();
Log::info('正在检查令牌合法性...');
if (!self::info()) {
Log::warning('令牌即将过期');
Log::info('申请更换令牌中...');
if (!self::refresh()) {
Log::warning('无效令牌,正在重新申请...');
self::login();
}
}
self::$lock = time() + 3600;
}
public static function check()
{
if (self::$lock > time()) {
return true;
}
self::$lock = time() + 7200;
if (!self::info()) {
Log::warning('令牌即将过期');
Log::info('申请更换令牌中...');
if (!self::refresh()) {
Log::warning('无效令牌,正在重新申请...');
self::login();
}
return false;
}
return true;
}
protected static function info()
{
$access_token = getenv('ACCESS_TOKEN');
$payload = [
'access_token' => $access_token,
];
$data = Curl::get('https://passport.bilibili.com/api/v2/oauth2/info', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::error('检查令牌失败', ['msg' => $data['message']]);
return false;
}
Log::info('令牌有效期: ' . date('Y-m-d H:i:s', $data['ts'] + $data['data']['expires_in']));
return $data['data']['expires_in'] > 14400;
}
public static function refresh()
{
$access_token = getenv('ACCESS_TOKEN');
$refresh_token = getenv('REFRESH_TOKEN');
$payload = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
];
$data = Curl::post('https://passport.bilibili.com/api/oauth2/refreshToken', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::error('重新生成令牌失败', ['msg' => $data['message']]);
return false;
}
Log::info('令牌生成完毕!');
$access_token = $data['data']['access_token'];
File::writeNewEnvironmentFileWith('ACCESS_TOKEN', $access_token);
Log::info(' > access token: ' . $access_token);
$refresh_token = $data['data']['refresh_token'];
File::writeNewEnvironmentFileWith('REFRESH_TOKEN', $refresh_token);
Log::info(' > refresh token: ' . $refresh_token);
return true;
}
protected static function login($captcha = '', $headers = [])
{
$user = getenv('APP_USER');
$pass = getenv('APP_PASS');
if (empty($user) || empty($pass)) {
Log::error('空白的帐号和口令!');
die();
}
// get PublicKey
Log::info('正在载入安全模块...');
$payload = [];
$data = Curl::post('https://passport.bilibili.com/api/oauth2/getKey', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::error('公钥获取失败', ['msg' => $data['message']]);
die();
} else {
Log::info('安全模块载入完毕!');
}
$public_key = $data['data']['key'];
$hash = $data['data']['hash'];
openssl_public_encrypt($hash . $pass, $crypt, $public_key);
for ($i = 0; $i < 30; $i++) {
// login
Log::info('正在获取令牌...');
$payload = [
'subid' => 1,
'permission' => 'ALL',
'username' => $user,
'password' => base64_encode($crypt),
'captcha' => $captcha,
];
$data = Curl::post('https://passport.bilibili.com/api/v2/oauth2/login', Sign::api($payload), $headers);
$data = json_decode($data, true);
if (isset($data['code']) && $data['code'] == -105) {
$captcha_data = static::loginWithCaptcha();
$captcha = $captcha_data['captcha'];
$headers = $captcha_data['headers'];
continue;
}
break;
}
if (isset($data['code']) && $data['code']) {
Log::error('登录失败', ['msg' => $data['message']]);
die();
}
self::saveCookie($data);
Log::info('令牌获取成功!');
$access_token = $data['data']['token_info']['access_token'];
File::writeNewEnvironmentFileWith('ACCESS_TOKEN', $access_token);
Log::info(' > access token: ' . $access_token);
$refresh_token = $data['data']['token_info']['refresh_token'];
File::writeNewEnvironmentFileWith('REFRESH_TOKEN', $refresh_token);
Log::info(' > refresh token: ' . $refresh_token);
return;
}
protected static function loginWithCaptcha()
{
Log::info('登陆需要验证 ,启动验证码登陆!');
$headers = [
'Accept' => 'application/json, text/plain, */*',
'User-Agent' => 'bili-universal/8230 CFNetwork/975.0.3 Darwin/18.2.0',
'Host' => 'passport.bilibili.com',
'Cookie' => 'sid=blhelper'
];
$data = Curl::other('https://passport.bilibili.com/captcha', null, $headers);
$data = base64_encode($data);
$captcha = static::ocrCaptcha($data);
return [
'captcha' => $captcha,
'headers' => $headers,
];
}
private static function ocrCaptcha($captcha_img)
{
$payload = [
'image' => (string)$captcha_img
];
$headers = [
'Content-Type' => 'application/json',
];
$data = Curl::other('http://captcha.biem.club:19951/', json_encode($payload), $headers);
$de_raw = json_decode($data, true);
Log::info("验证码识别结果 {$de_raw['message']}");
return $de_raw['message'];
}
private static function saveCookie($data)
{
Log::info('COOKIE获取成功!');
//临时保存cookie
$temp = '';
$cookies = $data['data']['cookie_info']['cookies'];
foreach ($cookies as $cookie) {
$temp .= $cookie['name'] . '=' . $cookie['value'] . ';';
}
File::writeNewEnvironmentFileWith('COOKIE', $temp);
Log::info(' > auth cookie: ' . $temp);
return;
}
}

289
src/MasterSite.php Normal file
View File

@ -0,0 +1,289 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class MasterSite
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time() || getenv('USE_MASTER_SITE') == 'false') {
return;
}
if (self::watchAid() && self::shareAid() && self::coinAdd()) {
self::$lock = time() + 24 * 60 * 60;
return;
}
self::$lock = time() + 3600;
}
// 投币
private static function reward($aid): bool
{
$user_info = User::parseCookies();
$url = "https://api.bilibili.com/x/web-interface/coin/add";
$payload = [
"aid" => $aid,
"multiply" => "1",
"cross_domain" => "true",
"csrf" => $user_info['token']
];
$headers = [
'Host' => "api.bilibili.com",
'Origin' => "https://www.bilibili.com",
'Referer' => "https://www.bilibili.com/video/av{$aid}",
'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",
];
$raw = Curl::post($url, Sign::api($payload), $headers);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
Log::notice("主站任务: av{$aid}投币成功!");
return true;
} else {
Log::warning("主站任务: av{$aid}投币失败!");
return false;
}
}
// 投币日志
protected static function coinLog(): int
{
$url = "https://api.bilibili.com/x/member/web/coin/log";
$payload = [];
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
$logs = $de_raw['data']['list'];
$coins = 0;
foreach ($logs as $log) {
$log_ux = strtotime($log['time']);
$log_date = date('Y-m-d', $log_ux);
$now_date = date('Y-m-d');
if ($log_date != $now_date) {
break;
}
if (strpos($log['reason'], "打赏") !== false) {
switch ($log['delta']) {
case -1:
$coins += 1;
break;
case -2:
$coins += 2;
break;
default:
break;
}
}
}
return $coins;
}
// 投币操作
protected static function coinAdd(): bool
{
switch (getenv('USE_ADD_COIN')) {
case 'false':
break;
case 'true':
$av_num = getenv('ADD_COIN_AV_NUM');
$av_num = (int)$av_num;
if ($av_num == 0) {
Log::warning('当前视频投币设置不正确,请检查配置文件!');
die();
}
if ($av_num == 1) {
$aid = !empty(getenv('ADD_COIN_AV')) ? getenv('ADD_COIN_AV') : self::getRandomAid();
self::reward($aid);
} else {
$coins = $av_num - self::coinLog();
if ($coins <= 0) {
Log::info('今日投币上限已满!');
break;
}
$aids = self::getDayRankingAids($av_num);
foreach ($aids as $aid) {
self::reward($aid);
}
}
break;
default:
Log::warning('当前视频投币设置不正确,请检查配置文件!');
die();
break;
}
return true;
}
// 获取随机AID
private static function getRandomAid(): string
{
$page = random_int(1, 100000);
$payload = [];
$url = "https://api.bilibili.com/x/web-interface/newlist?&pn={$page}&ps=1";
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
$aid = $de_raw['data']['archives'][0]['aid'];
if (is_null($aid) || empty($aid) || $aid == '') {
$aid = random_int(10000000, 30000000);
}
return (string)$aid;
}
// 日榜AID
private static function getDayRankingAids($num): array
{
// day: 日榜1 三榜3 周榜7 月榜30
$payload = [];
$aids = [];
$rand_nums = [];
$url = "https://api.bilibili.com/x/web-interface/ranking?rid=0&day=1&type=1&arc_type=0";
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
for ($i = 0; $i < $num; $i++) {
while (true) {
$rand_num = random_int(1, 100);
if (in_array($rand_num, $rand_nums)) {
continue;
} else {
array_push($rand_nums, $rand_num);
break;
}
}
$aid = $de_raw['data']['list'][$rand_nums[$i]]['aid'];
array_push($aids, $aid);
}
return $aids;
}
// 分享视频
private static function shareAid(): bool
{
# aid = 稿件av号
$url = "https://api.bilibili.com/x/web-interface/share/add";
$av_info = self::parseAid();
$user_info = User::parseCookies();
$payload = [
'aid' => $av_info['aid'],
'jsonp' => "jsonp",
'csrf' => $user_info['token'],
];
$headers = [
'Host' => "api.bilibili.com",
'Origin' => "https://www.bilibili.com",
'Referer' => "https://www.bilibili.com/video/av{$av_info['aid']}",
'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",
];
$raw = Curl::post($url, Sign::api($payload), $headers);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
Log::notice("主站任务: av{$av_info['aid']}分享成功!");
return true;
} else {
Log::warning("主站任务: av{$av_info['aid']}分享失败!");
return false;
}
}
// 观看视频
private static function watchAid(): bool
{
$url = "https://api.bilibili.com/x/report/click/h5";
$av_info = self::parseAid();
$user_info = User::parseCookies();
$payload = [
'aid' => $av_info['aid'],
'cid' => $av_info['cid'],
'part' => 1,
'did' => $user_info['sid'],
'ftime' => time(),
'jsonp' => "jsonp",
'lv' => "",
'mid' => $user_info['uid'],
'csrf' => $user_info['token'],
'stime' => time()
];
$headers = [
'Host' => "api.bilibili.com",
'Origin' => "https://www.bilibili.com",
'Referer' => "https://www.bilibili.com/video/av{$av_info['aid']}",
'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",
];
$raw = Curl::post($url, Sign::api($payload), $headers);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
$url = "https://api.bilibili.com/x/report/web/heartbeat";
$payload = [
"aid" => $av_info['aid'],
"cid" => $av_info['cid'],
"mid" => $user_info['uid'],
"csrf" => $user_info['token'],
"jsonp" => "jsonp",
"played_time" => "0",
"realtime" => $av_info['duration'],
"pause" => false,
"dt" => "7",
"play_type" => "1",
'start_ts' => time()
];
$raw = Curl::post($url, Sign::api($payload), $headers);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
sleep(5);
$payload['played_time'] = $av_info['duration'] - 1;
$payload['play_type'] = 0;
$payload['start_ts'] = time();
$raw = Curl::post($url, Sign::api($payload), $headers);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
Log::notice("主站任务: av{$av_info['aid']}观看成功!");
return true;
}
}
}
Log::warning("主站任务: av{$av_info['aid']}观看失败!");
return false;
}
// 解析AID到CID
private static function parseAid(): array
{
while (true) {
$aid = self::getRandomAid();
$url = "https://api.bilibili.com/x/web-interface/view?aid={$aid}";
$raw = Curl::get($url);
$de_raw = json_decode($raw, true);
if ($de_raw['code'] != 0) {
continue;
} else {
if (!array_key_exists('cid', $de_raw['data'])) {
continue;
}
}
$cid = $de_raw['data']['cid'];
$duration = $de_raw['data']['duration'];
break;
}
return [
'aid' => $aid,
'cid' => $cid,
'duration' => $duration
];
}
}

182
src/MaterialObject.php Normal file
View File

@ -0,0 +1,182 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class MaterialObject
{
// 时间锁
public static $lock = 0;
// 丢弃列表
private static $discard_aid_list = [];
// 起始 和 结束
private static $start_aid = 0;
private static $end_aid = 0;
// RUN
public static function run()
{
if (getenv('USE_MO') == 'false') {
return;
}
if (self::$lock > time()) {
return;
}
// 计算AID TODO 待优化
self::calculateAid(150, 550);
self::drawLottery();
self::$lock = time() + random_int(5, 10) * 60;
}
/**
* @use 实物抽奖
* @return bool
*/
protected static function drawLottery(): bool
{
$block_key_list = ['测试', '加密', 'test', 'TEST', '钓鱼', '炸鱼', '调试'];
$flag = 5;
for ($i = self::$start_aid; $i < self::$end_aid; $i++) {
if (!$flag) {
break;
}
// 在丢弃列表里 跳过
if (in_array($i, self::$discard_aid_list)) {
continue;
}
$payload = [
'aid' => $i,
];
$url = 'https://api.live.bilibili.com/lottery/v1/box/getStatus';
// 请求 && 解码
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
// -403 没有抽奖
if ($de_raw['code'] != '0') {
$flag--;
continue;
}
// 如果最后一个结束时间已过 加入丢弃
$lotterys = $de_raw['data']['typeB'];
$total = count($lotterys);
if ($lotterys[$total - 1]['join_end_time'] < time()) {
array_push(self::$discard_aid_list, $i);
continue;
}
// 如果存在敏感词 加入丢弃
$title = $de_raw['data']['title'];
foreach ($block_key_list as $block_key) {
if (strpos($title, $block_key) !== false) {
array_push(self::$discard_aid_list, $i);
continue;
}
}
$num = 1;
foreach ($lotterys as $lottery) {
$join_end_time = $lottery['join_end_time'];
$join_start_time = $lottery['join_start_time'];
if ($join_end_time > time() && time() > $join_start_time) {
switch ($lottery['status']) {
case 3:
Log::info("实物[{$i}]抽奖: 当前轮次已经结束!");
break;
case 1:
Log::info("实物[{$i}]抽奖: 当前轮次已经抽过了!");
break;
case -1:
Log::info("实物[{$i}]抽奖: 当前轮次暂未开启!");
break;
case 0:
Log::info("实物[{$i}]抽奖: 当前轮次正在抽奖中!");
$payload = [
'aid' => $i,
'number' => $num,
];
$raw = Curl::get('https://api.live.bilibili.com/lottery/v1/box/draw', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 0) {
Log::notice("实物[{$i}]抽奖: 成功!");
}
$num++;
break;
default:
Log::info("实物[{$i}]抽奖: 当前轮次状态码[{$lottery['status'] }]未知!");
break;
}
}
}
}
return true;
}
/**
* @use 计算 开始结束的AID
* @param $min
* @param $max
* @return bool
*/
private static function calculateAid($min, $max): bool
{
if (self::$end_aid != 0 && self::$start_aid != 0) {
return false;
}
while (1) {
$middle = round(($min + $max) / 2);
if (self::aidPost($middle)) {
if (self::aidPost($middle + random_int(0, 3))) {
$max = $middle;
} else {
$min = $middle;
}
} else {
$min = $middle;
}
if ($max - $min == 1) {
break;
}
}
self::$start_aid = $min - random_int(30, 40);
self::$end_aid = $min + random_int(30, 40);
Log::info("实物抽奖起始值[" . self::$start_aid . "],结束值[" . self::$end_aid . "]");
return true;
}
/**
* @use Aid 请求
* @param $aid
* @return bool
*/
private static function aidPost($aid): bool
{
$payload = [
'aid' => $aid,
];
$raw = Curl::get('https://api.live.bilibili.com/lottery/v1/box/getStatus', Sign::api($payload));
$de_raw = json_decode($raw, true);
// 等于0是有抽奖返回false
if ($de_raw['code'] == 0) {
return false;
}
// 没有抽奖
return true;
}
}

123
src/Notice.php Normal file
View File

@ -0,0 +1,123 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Notice
{
protected static $type = '';
protected static $result = '';
protected static $uname = '';
protected static $sckey = '';
// RUN
public static function run($type, $result = '')
{
if (getenv('USE_SCKEY') == "") {
return;
}
self::$type = $type;
self::$result = $result;
self::$sckey = getenv('USE_SCKEY');
self::$uname = User::userInfo() ? getenv('APP_UNAME') : getenv('APP_USER');
self::infoSendManager();
}
// 信息管理分发
private static function infoSendManager(): bool
{
$nowtime = date('Y-m-d H:i:s');
switch (self::$type) {
case 'raffle':
$info = [
'title' => '活动抽奖结果',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在活动抽奖中获得: ' . self::$result,
];
break;
case 'storm':
$info = [
'title' => '节奏风暴中奖结果',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在节奏风暴抽奖中: ' . self::$result,
];
break;
case 'active':
$info = [
'title' => '活动中奖结果',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 在活动抽奖中获得: ' . self::$result,
];
break;
case 'cookieRefresh':
$info = [
'title' => 'Cookie刷新',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 刷新Cookie: ' . self::$result,
];
break;
case 'loginInit':
break;
case 'todaySign':
$info = [
'title' => '每日签到',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 签到: ' . self::$result,
];
break;
case 'winIng':
$info = [
'title' => '实物中奖',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 实物中奖: ' . self::$result,
];
break;
case 'banned':
$info = [
'title' => '账号封禁',
'content' => '[' . $nowtime . ']' . ' 用户: ' . self::$uname . ' 账号被封禁: 程序开始睡眠,凌晨自动唤醒,距离唤醒还有' . self::$result . '小时',
];
break;
default:
break;
}
self::scSend($info);
return true;
}
// 发送信息
private static function scSend($info)
{
$postdata = http_build_query(
[
'text' => $info['title'],
'desp' => $info['content']
]
);
$opts = ['http' =>
[
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
]
];
try {
$context = stream_context_create($opts);
file_get_contents('https://sc.ftqq.com/' . self::$sckey . '.send', false, $context);
} catch (\Exception $e) {
Log::warning('Server酱推送信息失败,请检查!');
}
return;
//return $result = file_get_contents('https://sc.ftqq.com/' . $this->_sckey . '.send', false, $context);
}
}

219
src/RaffleHandler.php Normal file
View File

@ -0,0 +1,219 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class RaffleHandler
{
const KEY = '统一活动';
const SWITCH = 'USE_ACTIVE';
public static $lock = 0;
public static $rw_lock = 0;
private static $wait_list = [];
private static $finsh_list = [];
private static $all_list = [];
public static function run()
{
if (getenv(self::SWITCH) == 'false') {
return;
}
if (self::$lock > time()) {
return;
}
self::startLottery();
}
/**
* 抽奖逻辑
* @return bool
*/
protected static function startLottery(): bool
{
$max_num = mt_rand(5, 10);
while ($max_num) {
$raffle = array_shift(self::$wait_list);
if (is_null($raffle)) {
break;
}
Live::goToRoom($raffle['room_id']);
Statistics::addJoinList(self::KEY);
self::lottery($raffle);
$max_num--;
}
return true;
}
/**
* 检查抽奖列表
* @param $rid
* @return bool
*/
private static function checkWeb($rid): bool
{
$payload = [
'roomid' => $rid
];
$url = 'https://api.live.bilibili.com/gift/v3/smalltv/check';
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
// 计数 && 跳出
$total = count($de_raw['data']['list']);
if (!$total) {
return false;
}
for ($i = 0; $i < $total; $i++) {
/**
* raffleId : 88995
* title : C位光环抽奖
* type : GIFT_30013
*/
$data = [
'raffle_id' => $de_raw['data']['list'][$i]['raffleId'],
'title' => $de_raw['data']['list'][$i]['title'],
'type' => $de_raw['data']['list'][$i]['type'],
'room_id' => $rid
];
if (self::toRepeatLid($data['raffle_id'])) {
continue;
}
Statistics::addPushList(self::KEY);
array_push(self::$wait_list, $data);
}
return true;
}
/**
* @use WEB中奖查询
*/
public static function resultWeb()
{
// 时间锁
if (self::$rw_lock > time()) {
return;
}
// 如果待查询为空 && 去重
if (!count(self::$finsh_list)) {
self::$rw_lock = time() + 40;
return;
}
// 查询每次查询10个
$flag = 0;
foreach (self::$finsh_list as $winning_web) {
$flag++;
if ($flag > 40) {
break;
}
// 参数
$payload = [
'type' => $winning_web['type'],
'raffleId' => $winning_web['raffle_id']
];
// Web V3 Notice
$url = 'https://api.live.bilibili.com/gift/v3/smalltv/notice';
// 请求 && 解码
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
// 判断
switch ($de_raw['data']['status']) {
case 3:
break;
case 2:
Statistics::addSuccessList(self::KEY);
// 提示信息
$info = "房间 {$winning_web['room_id']} 编号 {$winning_web['raffle_id']} {$winning_web['title']}: 获得";
$info .= "{$de_raw['data']['gift_name']}X{$de_raw['data']['gift_num']}";
Log::notice($info);
// 推送活动抽奖信息
if ($de_raw['data']['gift_name'] != '辣条' && $de_raw['data']['gift_name'] != '') {
Notice::run('raffle', $info);
}
// 删除查询完成ID
unset(self::$finsh_list[$flag - 1]);
self::$finsh_list = array_values(self::$finsh_list);
break;
default:
break;
}
}
self::$rw_lock = time() + 40;
return;
}
/**
* @use 请求抽奖
* @param array $data
*/
private static function lottery(array $data)
{
$payload = [
'raffleId' => $data['raffle_id'],
'roomid' => $data['room_id'],
];
$url = 'https://api.live.bilibili.com/gift/v3/smalltv/join';
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
if (isset($de_raw['code']) && $de_raw['code']) {
Log::notice("房间 {$data['raffle_id']} 编号 {$data['room_id']} " . self::KEY . ": {$de_raw['message']}");
} else {
Log::notice("房间 {$data['raffle_id']} 编号 {$data['room_id']} " . self::KEY . ": {$de_raw['msg']}");
array_push(self::$finsh_list, $data);
}
return;
}
/**
* 重复检测
* @param int $lid
* @return bool
*/
private static function toRepeatLid(int $lid): bool
{
if (in_array($lid, self::$all_list)) {
return true;
}
if (count(self::$all_list) > 2000) {
self::$all_list = [];
}
array_push(self::$all_list, $lid);
return false;
}
/**
* 数据推入队列
* @param array $data
* @return bool
*/
public static function pushToQueue(array $data): bool
{
if (getenv(self::SWITCH) == 'false') {
return false;
}
if (Live::fishingDetection($data['rid'])) {
return false;
}
self::checkWeb($data['rid']);
$wait_num = count(self::$wait_list);
if ($wait_num > 2) {
Log::info("当前队列中共有 {$wait_num}" . self::KEY . "待抽奖");
}
return true;
}
}

47
src/Sign.php Normal file
View File

@ -0,0 +1,47 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Sign
{
public static function api($payload)
{
# iOS 6680
$appkey = '27eb53fc9058f8c3';
$appsecret = 'c2ed53a74eeefe3cf99fbd01d8c9c375';
# Android
// $appkey = '1d8b6e7d45233436';
// $appsecret = '560c52ccd288fed045859ed18bffd973';
# 云视听 TV
// $appkey = '4409e2ce8ffd12b8';
// $appsecret = '59b43e04ad6965f34319062b478f83dd';
$default = [
'access_key' => getenv('ACCESS_TOKEN'),
'actionKey' => 'appkey',
'appkey' => $appkey,
'build' => '8230',
'device' => 'phone',
'mobi_app' => 'iphone',
'platform' => 'ios',
'ts' => time(),
];
$payload = array_merge($payload, $default);
if (isset($payload['sign'])) {
unset($payload['sign']);
}
ksort($payload);
$data = http_build_query($payload);
$payload['sign'] = md5($data . $appsecret);
return $payload;
}
}

92
src/Silver.php Normal file
View File

@ -0,0 +1,92 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Silver
{
public static $lock = 0;
protected static $task = [];
public static function run()
{
if (self::$lock > time()) {
return;
}
if (!empty(self::$task)) {
self::pushTask();
} else {
self::pullTask();
}
}
protected static function pushTask()
{
$payload = [
'time_end' => self::$task['time_end'],
'time_start' => self::$task['time_start']
];
$data = Curl::get('https://api.live.bilibili.com/mobile/freeSilverAward', Sign::api($payload));
$data = json_decode($data, true);
if ($data['code'] == -800) {
self::$lock = time() + 12 * 60 * 60;
Log::warning("领取宝箱失败,{$data['message']}!");
return;
}
if ($data['code'] == -903) {
Log::warning("领取宝箱失败,{$data['message']}!");
self::$task = [];
self::$lock = time() + 60;
return;
}
if (isset($data['code']) && $data['code']) {
Log::warning("领取宝箱失败,{$data['message']}!");
self::$lock = time() + 60;
return;
}
Log::notice("领取宝箱成功Silver: {$data['data']['silver']}(+{$data['data']['awardSilver']})");
self::$task = [];
self::$lock = time() + 10;
}
protected static function pullTask()
{
$payload = [];
$data = Curl::get('https://api.live.bilibili.com/lottery/v1/SilverBox/getCurrentTask', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code'] == -10017) {
Log::notice($data['message']);
self::$lock = time() + 24 * 60 * 60;
return;
}
if (isset($data['code']) && $data['code']) {
Log::error("check freeSilverCurrentTask failed! Error message: {$data['message']}");
die();
}
Log::info("获得一个宝箱,内含 {$data['data']['silver']} 个瓜子");
Log::info("开启宝箱需等待 {$data['data']['minute']} 分钟");
self::$task = [
'time_start' => $data['data']['time_start'],
'time_end' => $data['data']['time_end'],
];
self::$lock = time() + $data['data']['minute'] * 60 + 5;
}
}

66
src/Silver2Coin.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Silver2Coin
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time() || getenv('USE_SILVER2COIN') == 'false') {
return;
}
if (self::appSilver2coin() && self::pcSilver2coin()) {
self::$lock = time() + 24 * 60 * 60;
return;
}
self::$lock = time() + 3600;
}
// APP API
protected static function appSilver2coin(): bool
{
sleep(1);
$payload = [];
$raw = Curl::get('https://api.live.bilibili.com/AppExchange/silver2coin', Sign::api($payload));
$de_raw = json_decode($raw, true);
if (!$de_raw['code'] && $de_raw['msg'] == '兑换成功') {
Log::info('[APP]银瓜子兑换硬币: ' . $de_raw['msg']);
} elseif ($de_raw['code'] == 403) {
Log::info('[APP]银瓜子兑换硬币: ' . $de_raw['msg']);
} else {
Log::warning('[APP]银瓜子兑换硬币: ' . $de_raw['msg']);
return false;
}
return true;
}
// PC API
protected static function pcSilver2coin(): bool
{
sleep(1);
$payload = [];
$url = "https://api.live.bilibili.com/pay/v1/Exchange/silver2coin";
$url1 = "https://api.live.bilibili.com/exchange/silver2coin";
$raw = Curl::get($url, Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == -403) {
return false;
}
Log::info('[PC]银瓜子兑换硬币: ' . $de_raw['msg']);
// TODO
return true;
}
}

193
src/Socket.php Normal file
View File

@ -0,0 +1,193 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Socket
{
protected static $socket_connection = null;
protected static $ips = [];
protected static $socket_ip = null;
protected static $socket_port = null;
public static $lock = 0;
protected static $heart_lock = 0;
// RUN
public static function run()
{
if (self::$lock > time()) {
return;
}
self::$lock = time() + 0.5;
self::start();
$message = self::decodeMessage();
if (!$message) {
unset($message);
self::resetConnection();
return;
}
$data = DataTreating::socketJsonToArray($message);
if (!$data) {
return;
}
DataTreating::socketArrayToDispose($data);
return;
}
// KILL
protected static function killConnection()
{
socket_clear_error(self::$socket_connection);
socket_shutdown(self::$socket_connection);
socket_close(self::$socket_connection);
self::$socket_connection = null;
}
// RECONNECT
protected static function resetConnection()
{
$errorcode = socket_last_error();
$errormsg = socket_strerror($errorcode);
unset($errormsg);
unset($errorcode);
self::killConnection();
// Log::warning('SOCKET连接断开,5秒后重新连接...');
// sleep(5);
self::start();
return;
}
// SOCKET READER
protected static function readerSocket(int $length)
{
return socket_read(self::$socket_connection, $length);
}
// DECODE MESSAGE
protected static function decodeMessage()
{
$res = '';
$tmp = '';
while (1) {
while ($out = self::readerSocket(16)) {
$res = unpack('N', $out);
unset($out);
if ($res[1] != 16) {
break;
}
}
if (isset($res[1])) {
$length = $res[1] - 16;
if ($length > 65535) {
continue;
}
if ($length <= 0) {
return false;
}
return self::readerSocket($length);
}
return false;
}
}
// START
protected static function start()
{
if (is_null(self::$socket_connection)) {
$room_id = empty(getenv('SOCKET_ROOM_ID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('SOCKET_ROOM_ID'));
$room_id = intval($room_id);
if ($room_id) {
self::getSocketServer($room_id);
self::connectServer($room_id, self::$socket_ip, self::$socket_port);
}
}
self::sendHeartBeatPkg();
return;
}
// SEND HEART
protected static function sendHeartBeatPkg()
{
if (self::$heart_lock > time()) {
return;
}
$action_heart_beat = intval(getenv('ACTIONHEARTBEAT'));
$str = pack('NnnNN', 16, 16, 1, $action_heart_beat, 1);
socket_write(self::$socket_connection, $str, strlen($str));
Log::info('发送心跳包到弹幕服务器!');
self::$heart_lock = time() + 30;
return;
}
// SOCKET CONNECT
protected static function connectServer($room_id, $ip, $port)
{
$falg = 10;
while ($falg) {
try {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, $ip, $port);
$str = self::packMsg($room_id);
socket_write($socket, $str, strlen($str));
self::$socket_connection = $socket;
// TODO
Log::info("连接到弹幕服务器[{$room_id}]成功!");
return;
} catch (\Exception $e) {
Log::info("连接到弹幕服务器[{$room_id}]失败!");
Log::warning($e);
$falg -= 1;
}
}
Log::info("连接弹幕服务器[{$room_id}]错误次数过多,检查网络!");
exit();
}
// PACK DATA
protected static function packMsg($room_id)
{
$action_entry = intval(getenv('ACTIONENTRY'));
$data = sprintf("{\"uid\":%d%08d,\"roomid\":%d}",
random_int(1000000, 2999999),
random_int(0, 99999999),
intval($room_id)
);
return pack('NnnNN', 16 + strlen($data), 16, 1, $action_entry, 1) . $data;
}
// GET SERVER
protected static function getSocketServer(int $room_id): bool
{
while (1) {
try {
$payload = [
'room_id' => $room_id,
];
$data = Curl::get('https://api.live.bilibili.com/room/v1/Danmu/getConf', Sign::api($payload));
$data = json_decode($data, true);
// TODO 判断
if (isset($data['code']) && $data['code']) {
continue;
}
self::$socket_ip = gethostbyname($data['data']['host']);
self::$socket_port = $data['data']['port'];
break;
} catch (\Exception $e) {
Log::warning("获取弹幕服务器出错,错误信息[{$e}]!");
continue;
}
}
return true;
}
}

83
src/Statistics.php Normal file
View File

@ -0,0 +1,83 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Statistics
{
public static $lock = 0;
// 推送 参加 成功
private static $push_list = [];
private static $join_list = [];
private static $success_list = [];
// 日志
public static function run()
{
if (self::$lock > time()) {
return;
}
self::getStormResult();
self::$lock = time() + 5 * 60;
}
// 添加推送
public static function addPushList(string $key): bool
{
// 初始化三个必要值
if (!array_key_exists($key, self::$push_list)) {
self::$push_list[$key] = [];
self::$join_list[$key] = [];
self::$success_list[$key] = [];
}
array_push(self::$push_list[$key], 1);
return true;
}
// 添加参与
public static function addJoinList(string $key): bool
{
array_push(self::$join_list[$key], 1);
return true;
}
// 添加成功
public static function addSuccessList(string $key): bool
{
array_push(self::$success_list[$key], 1);
return true;
}
// 获取结果
private static function getStormResult(): bool
{
if (empty(self::$push_list)) {
return false;
} else {
Log::info("-----------密----------封---------线------------");
}
foreach (self::$push_list as $key => $val) {
$title = $key;
$push_num = count(self::$push_list[$key]);
$join_num = count(self::$join_list[$key]);
$success_num = count(self::$success_list[$key]);
$content = "{$title}: #推送 {$push_num} #参与 {$join_num} #成功 {$success_num}";
Log::notice($content);
}
Log::info("-----------密----------封---------线------------");
return true;
}
}

199
src/Storm.php Normal file
View File

@ -0,0 +1,199 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Storm
{
const KEY = '节奏风暴';
const SWITCH = 'USE_STORM';
public static $lock = 0;
private static $drop_rate = null;
private static $attempt = null;
private static $wait_list = [];
private static $all_list = [];
/**
* @throws \Exception
*/
public static function run()
{
if (getenv(self::SWITCH) == 'false') {
return;
}
if (self::$lock > time()) {
return;
}
self::init();
self::startLottery();
}
private static function init()
{
if (!is_null(self::$attempt) && !is_null(self::$drop_rate)) {
return;
}
// TODO 信息不完整, 请检查配置文件 没有严格校验
self::$drop_rate = getenv('STORM_DROPRATE') !== "" ? (int)getenv('STORM_DROPRATE') : 0;
self::$attempt = getenv('STORM_ATTEMPT') !== "" ? explode(',', getenv('STORM_ATTEMPT')) : [30, 50];
}
/**
* 抽奖逻辑
* @return bool
* @throws \Exception
*/
protected static function startLottery(): bool
{
while (true) {
$storm = array_shift(self::$wait_list);
if (is_null($storm)) {
break;
}
// 丢弃
if (mt_rand(1, 100) <= (int)self::$drop_rate) {
break;
}
$storm_lid = $storm['lid'];
$storm_rid = $storm['rid'];
Live::goToRoom($storm_rid);
Statistics::addJoinList(self::KEY);
$num = random_int((int)self::$attempt[0], (int)self::$attempt[1]);
for ($i = 1; $i < $num; $i++) {
if (!self::lottery($storm_rid, $storm_lid, $i)) {
break;
}
}
}
return true;
}
/**
* 格式化日志输出
* @param $id
* @param $num
* @param $info
* @return string
*/
private static function formatInfo($id, $num, $info): string
{
return "风暴 {$id} 请求 {$num} 状态 {$info}";
}
/**
* 请求抽奖
* @param $rid
* @param $lid
* @param $num
* @return bool
*/
protected static function lottery($rid, $lid, $num): bool
{
$user_info = User::parseCookies();
$payload = [
"id" => $lid,
"color" => "16772431",
"roomid" => $rid,
"captcha_token" => "",
"captcha_phrase" => "",
"token" => $user_info['token'],
"csrf_token" => $user_info['token'],
"visit_id" => "",
];
$raw = Curl::post('https://api.live.bilibili.com/lottery/v1/Storm/join', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['code'] == 429 || $de_raw['code'] == -429) {
Log::notice(self::formatInfo($lid, $num, '节奏风暴未实名或异常验证码'));
return false;
}
if (isset($de_raw['data']) && empty($de_raw['data'])) {
Log::notice(self::formatInfo($lid, $num, '节奏风暴在小黑屋'));
return false;
}
if ($de_raw['code'] == 0) {
Statistics::addSuccessList(self::KEY);
Log::notice(self::formatInfo($lid, $num, $de_raw['data']['mobile_content']));
return false;
}
if ($de_raw['msg'] == '节奏风暴不存在') {
Log::notice(self::formatInfo($lid, $num, '节奏风暴已结束'));
return false;
}
if ($de_raw['msg'] == '已经领取奖励') {
Log::notice(self::formatInfo($lid, $num, '节奏风暴已经领取'));
return false;
}
if ($de_raw['msg'] == '你错过了奖励,下次要更快一点哦~') {
return true;
}
Log::notice(self::formatInfo($lid, $num, $de_raw['msg']));
return true;
}
/**
* 检查ID
* @param $room_id
* @return array|bool
*/
protected static function stormCheckId($room_id)
{
$raw = Curl::get('https://api.live.bilibili.com/lottery/v1/Storm/check?roomid=' . $room_id);
$de_raw = json_decode($raw, true);
if (empty($de_raw['data']) || $de_raw['data']['hasJoin'] != 0) {
return false;
}
return [
'id' => $de_raw['data']['id'],
];
}
/**
* 重复检测
* @param $lid
* @return bool
*/
private static function toRepeatLid($lid): bool
{
if (in_array($lid, self::$all_list)) {
return true;
}
if (count(self::$all_list) > 2000) {
self::$all_list = [];
}
array_push(self::$all_list, $lid);
return false;
}
/**
* 数据推入队列
* @param array $data
* @return bool
*/
public static function pushToQueue(array $data): bool
{
if (getenv(self::SWITCH) == 'false') {
return false;
}
if (self::toRepeatLid($data['lid'])) {
return false;
}
Statistics::addPushList(self::KEY);
self::$wait_list = array_merge(self::$wait_list, [['rid' => $data['rid'], 'lid' => $data['lid']]]);
$wait_num = count(self::$wait_list);
if ($wait_num > 2) {
Log::info("当前队列中共有 {$wait_num}" . self::KEY . "待抽奖");
}
return true;
}
}

101
src/Task.php Normal file
View File

@ -0,0 +1,101 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Task
{
public static $lock = 0;
public static function run()
{
if (self::$lock > time()) {
return;
}
Log::info('正在检查每日任务...');
$data = self::check();
if (isset($data['data']['double_watch_info'])) {
self::double_watch_info($data['data']['double_watch_info']);
}
if (isset($data['data']['sign_info'])) {
self::sign_info($data['data']['sign_info']);
}
self::$lock = time() + 3600;
}
protected static function check()
{
$payload = [];
$data = Curl::get('https://api.live.bilibili.com/i/api/taskInfo', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('每日任务检查失败!', ['msg' => $data['message']]);
}
return $data;
}
protected static function sign_info($info)
{
Log::info('检查任务「每日签到」...');
if ($info['status'] == 1) {
Log::notice('该任务已完成');
return;
}
$payload = [];
$data = Curl::get('https://api.live.bilibili.com/appUser/getSignInfo', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning('签到失败', ['msg' => $data['message']]);
} else {
Log::info('签到成功');
// 推送签到信息
Notice::run('todaySign', $data['msg']);
}
}
protected static function double_watch_info($info)
{
Log::info('检查任务「双端观看直播」...');
if ($info['status'] == 2) {
Log::notice('已经领取奖励');
return;
}
if ($info['mobile_watch'] != 1 || $info['web_watch'] != 1) {
Log::notice('任务未完成,请等待');
return;
}
$payload = [
'task_id' => 'double_watch_task',
];
$data = Curl::post('https://api.live.bilibili.com/activity/v1/task/receive_award', Sign::api($payload));
$data = json_decode($data, true);
if (isset($data['code']) && $data['code']) {
Log::warning("「双端观看直播」任务奖励领取失败,{$data['message']}!");
} else {
Log::info('奖励领取成功!');
foreach ($info['awards'] as $vo) {
Log::notice(sprintf("获得 %s × %d", $vo['name'], $vo['num']));
}
}
}
}

253
src/TcpClinet.php Normal file
View File

@ -0,0 +1,253 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
use Exception;
use Socket\Raw\Factory;
class TcpClinet
{
public static $lock = 0;
private static $heart_lock = 0;
private static $client = null;
private static $server_addr = null;
private static $server_key = null;
/**
* @desc 入口
*/
public static function run()
{
if (self::$lock > time() || getenv('USE_SERVER') == 'false') {
return;
}
self::init();
self::heartBeat();
self::receive();
}
/**
* @desc 初始化
*/
private static function init()
{
if (empty(getenv('SERVER_ADDR')) || empty(getenv('SERVER_KEY'))) {
exit('推送服务器信息不完整, 请检查配置文件!');
}
if (!self::$server_addr || !self::$server_key) {
self::$server_addr = getenv('SERVER_ADDR');
self::$server_key = getenv('SERVER_KEY');
}
if (!self::$client) {
self::openConnect();
}
}
/**
* @desc 数据封装
* @param $value
* @param $fmt
* @return string
*/
private static function packMsg($value, $fmt = "N")
{
$head = pack($fmt, strlen($value));
$data = $head . $value;
return $data;
}
/**
* @desc 数据解包
* @param $value
* @param string $fmt
* @return array|false
*/
private static function unPackMsg($value, $fmt = "N")
{
$data = unpack($fmt, $value);
return $data[1];
}
/**
* @desc 连接认证
*/
private static function handShake()
{
self::writer(
json_encode([
'code' => 0,
'type' => 'ask',
'data' => [
'key' => self::$server_key,
]
])
);
}
/**
* @desc 心跳
*/
private static function heartBeat()
{
if (self::$heart_lock <= time()) {
if (self::writer("")) {
// 心跳默认35s 调整数据错开错误
self::$heart_lock = time() + 20;
}
}
}
/**
* @desc 读数据
* @param $length
* @return array|bool|false
*/
private static function reader($length)
{
$data = false;
try {
$data = self::$client->read($length);
if (!$data) {
throw new Exception("Connection failure");
}
if ($length == 4) $data = self::unPackMsg($data);
} catch (Exception $exception) {
self::reConnect();
}
return $data;
}
/**
* @desc 写数据
* @param $data
* @return bool
*/
private static function writer($data)
{
$status = false;
try {
$data = self::packMsg($data);
$status = self::$client->write($data);
} catch (Exception $exception) {
self::reConnect();
}
return $status;
}
/**
* @desc 打开连接
*/
private static function openConnect()
{
if (!self::$client) {
try {
$socket = (new Factory())->createClient(self::$server_addr, 40);
self::$client = $socket;
self::handShake();
Log::info("连接到 {$socket->getPeerName()} 推送服务器");
} catch (Exception $e) {
Log::error("连接到推送服务器失败, {$e->getMessage()}");
self::$lock = time() + 60;
}
}
}
/**
* @desc 重新连接
*/
private static function reConnect()
{
Log::info('重新连接到推送服务器');
self::closeConnect();
self::openConnect();
}
/**
* @desc 断开连接
*/
private static function closeConnect()
{
Log::info('断开推送服务器');
try {
self::$client->shutdown();
self::$client->close();
} catch (Exception $exception) {
// var_dump($exception);
}
self::$client = null;
}
/**
* @use 读取数据
*/
private static function receive()
{
$len_body = self::reader(4);
if (!$len_body) {
// 长度为0 ,空信息
return;
}
Log::debug("(len=$len_body)");
$body = self::reader($len_body);
$raw_data = json_decode($body, true);
// 人气值(或者在线人数或者类似)以及心跳
$data_type = $raw_data['type'];
switch ($data_type) {
case 'raffle':
// 抽奖推送
// Log::notice($body);
DataTreating::distribute($raw_data['data']);
break;
case 'entered':
// 握手确认
Log::info("确认到推送服务器 {$raw_data['type']}");
break;
case 'error':
// 致命错误
Log::warning($body);
exit("推送服务器发生致命错误 {$raw_data['data']['msg']}");
break;
case 'heartbeat':
// 服务端心跳推送
// Log::info("推送服务器心跳推送 {$body}");
Log::debug("(heartbeat={$raw_data['data']['now']})");
break;
default:
// 未知信息
var_dump($raw_data);
Log::info("出现未知信息 {$body}");
break;
}
}
/**
* @use 写入log
* @param $message
*/
private static function writeLog($message)
{
$path = './danmu/';
if (!file_exists($path)) {
mkdir($path);
chmod($path, 0777);
}
$filename = $path . getenv('APP_USER') . ".log";
$date = date('[Y-m-d H:i:s] ');
$data = "[{$date}]{$message}" . PHP_EOL;
file_put_contents($filename, $data, FILE_APPEND);
return;
}
}

84
src/User.php Normal file
View File

@ -0,0 +1,84 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class User
{
// RUN
public static function run()
{
}
// 实名检测
public static function realnameCheck(): bool
{
$payload = [];
$raw = Curl::get('https://account.bilibili.com/identify/index', Sign::api($payload));
$de_raw = json_decode($raw, true);
//检查有没有名字,没有则没实名
if (!$de_raw['data']['memberPerson']['realname']) {
return false;
}
return true;
}
// 老爷检测
public static function isMaster(): bool
{
$payload = [
'ts' => Live::getMillisecond(),
];
$raw = Curl::get('https://api.live.bilibili.com/User/getUserInfo', Sign::api($payload));
$de_raw = json_decode($raw, true);
if ($de_raw['msg'] == 'ok') {
if ($de_raw['data']['vip'] || $de_raw['data']['svip']) {
return true;
}
}
return false;
}
// 用户名写入
public static function userInfo(): bool
{
$payload = [
'ts' => Live::getMillisecond(),
];
$raw = Curl::get('https://api.live.bilibili.com/User/getUserInfo', Sign::api($payload));
$de_raw = json_decode($raw, true);
if (getenv('APP_UNAME') != "") {
return true;
}
if ($de_raw['msg'] == 'ok') {
File::writeNewEnvironmentFileWith('APP_UNAME', $de_raw['data']['uname']);
return true;
}
return false;
}
//转换信息
public static function parseCookies(): array
{
$cookies = getenv('COOKIE');
preg_match('/bili_jct=(.{32})/', $cookies, $token);
$token = isset($token[1]) ? $token[1] : '';
preg_match('/DedeUserID=(\d+)/', $cookies, $uid);
$uid = isset($uid[1]) ? $uid[1] : '';
preg_match('/DedeUserID__ckMd5=(.{16})/', $cookies, $sid);
$sid = isset($sid[1]) ? $sid[1] : '';
return [
'token' => $token,
'uid' => $uid,
'sid' => $sid,
];
}
}

217
src/Websocket.php Normal file
View File

@ -0,0 +1,217 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
use Wrench\Client;
class Websocket
{
public static $lock = 0;
protected static $heart_lock = 0;
protected static $websocket = null;
protected static $room_id = 0;
/**
* @use 入口
*/
public static function run()
{
if (static::$lock > time()) {
return;
}
static::init();
static::heart();
static::receive();
}
/**
* @use 初始化
*/
protected static function init()
{
if (!static::$websocket) {
$client = new Client(
'ws://broadcastlv.chat.bilibili.com:2244/sub',
'http://live.bilibili.com'
);
static::$websocket = $client;
}
if (!static::$room_id) {
static::$room_id = empty(getenv('SOCKET_ROOM_ID')) ? Live::getUserRecommend() : Live::getRealRoomID(getenv('SOCKET_ROOM_ID'));
}
return;
}
/**
* @use 连接
*/
protected static function connect()
{
Log::info("连接弹幕服务器");
if (!static::$websocket->connect()) {
Log::error('连接弹幕服务器失败');
static::$lock = time() + 60;
return;
}
static::$websocket->sendData(
static::packMsg(json_encode([
'uid' => 0,
'roomid' => static::$room_id,
'protover' => 1,
'platform' => 'web',
'clientver' => '1.4.1',
]), 0x0007)
);
}
/**
* @use 断开连接
*/
protected static function disconnect()
{
Log::info('断开弹幕服务器');
static::$websocket->disconnect();
}
/**
* @use 读取数据
*/
protected static function receive()
{
$responses = static::$websocket->receive();
if (is_array($responses) && !empty($responses)) {
foreach ($responses as $response) {
static::split($response->getPayload());
}
static::receive();
}
}
/**
* @use 发送心跳
*/
protected static function heart()
{
if (!static::$websocket->isConnected()) {
static::connect();
return;
}
if (static::$heart_lock <= time()) {
if (static::$websocket->sendData(static::packMsg('', 0x0002))) {
static::$heart_lock = time() + 30;
}
}
return;
}
/**
* @param $id
* @return mixed|string
*/
protected static function type($id)
{
$option = [
0x0002 => 'WS_OP_HEARTBEAT',
0x0003 => 'WS_OP_HEARTBEAT_REPLY',
0x0005 => 'WS_OP_MESSAGE',
0x0007 => 'WS_OP_USER_AUTHENTICATION',
0x0008 => 'WS_OP_CONNECT_SUCCESS',
];
return isset($option[$id]) ? $option[$id] : "WS_OP_UNKNOW($id)";
}
/**
* @param $bin
* @throws \Exception
*/
protected static function split($bin)
{
if (strlen($bin)) {
$head = unpack('Npacklen/nheadlen/nver/Nop/Nseq', substr($bin, 0, 16));
$bin = substr($bin, 16);
$length = isset($head['packlen']) ? $head['packlen'] : 16;
$type = isset($head['op']) ? $head['op'] : 0x0000;
$body = substr($bin, 0, $length - 16);
Log::debug(static::type($type) . " (len=$length)");
if (($length - 16) > 65535 || ($length - 16) < 0) {
Log::notice("长度{$length}异常,重新连接服务器!");
if (static::$websocket->isConnected()) {
static::disconnect();
}
if (!static::$websocket->isConnected()) {
static::connect();
}
return;
}
if ($type == 0x0005 || $type == 0x0003) {
if ($head['ver'] == 2) {
$body = gzuncompress($body);
if (substr($body, 0, 1) != '{') {
static::split($bin);
return;
}
}
DataTreating::socketJsonToArray($body);
}
$bin = substr($bin, $length - 16);
if (strlen($bin)) {
static::split($bin);
}
}
return;
}
/**
* @param $value
* @param $option
* @return string
* @throws \Exception
*/
protected static function packMsg($value, $option)
{
$head = pack('NnnNN', 0x10 + strlen($value), 0x10, 0x01, $option, 0x0001);
$str = $head . $value;
static::split($str);
return $str;
}
/**
* @use 写入log
* @param $message
*/
private static function writeLog($message)
{
$path = './danmu/';
if (!file_exists($path)) {
mkdir($path);
chmod($path, 0777);
}
$filename = $path . getenv('APP_USER') . ".log";
$date = date('[Y-m-d H:i:s] ');
$data = "[{$date}]{$message}" . PHP_EOL;
file_put_contents($filename, $data, FILE_APPEND);
return;
}
}

68
src/Winning.php Normal file
View File

@ -0,0 +1,68 @@
<?php
/**
* Website: https://mudew.com/
* Author: Lkeme
* License: The MIT License
* Email: Useri@live.cn
* Updated: 2019
*/
namespace lkeme\BiliHelper;
class Winning
{
public static $lock = 0;
// RUN
public static function run()
{
// 活动统一
RaffleHandler::resultWeb();
// 实物
self::winningRecords();
}
// 中奖记录
protected static function winningRecords()
{
if (self::$lock > time()) {
return;
}
self::$lock = time() + 24 * 60 * 60;
$payload = [
'page' => '1',
'month' => '',
];
$raw = Curl::post('https://api.live.bilibili.com/lottery/v1/award/award_list', Sign::api($payload));
$de_raw = json_decode($raw, true);
$month = $de_raw['data']['month_list'][0]['Ym'];
// TODO
$payload = [
'page' => '1',
'month' => $month,
];
$raw = Curl::post('https://api.live.bilibili.com/lottery/v1/award/award_list', Sign::api($payload));
$de_raw = json_decode($raw, true);
// 没有记录
if (empty($de_raw['data']['list'])) {
return;
}
$init_time = strtotime(date("y-m-d h:i:s")); //当前时间
foreach ($de_raw['data']['list'] as $gift) {
$next_time = strtotime($gift['create_time']); //礼物时间
$day = ceil(($init_time - $next_time) / 86400); //60s*60min*24h
if ($day <= 2 && $gift['update_time'] == '') {
$data_info = '您在: ' . $gift['create_time'] . '抽中[' . $gift['gift_name'] . 'X' . $gift['gift_num'] . ']未查看!';
Log::notice($data_info);
// TODO 推送 log
}
}
return;
}
}