支持主动导入弹幕
This commit is contained in:
parent
cece95a5a4
commit
474b97a40a
71
app/Http/Controllers/DanmakuConstructController.php
Normal file
71
app/Http/Controllers/DanmakuConstructController.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\VideoDanmakus;
|
||||||
|
use App\Models\Videos;
|
||||||
|
use App\Util\DanmakuUtil;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class DanmakuConstructController extends BaseController
|
||||||
|
{
|
||||||
|
public function page(Request $request)
|
||||||
|
{
|
||||||
|
$view = view("danmaku.construct.batch_import");
|
||||||
|
if ($request->has("video_bvid")) {
|
||||||
|
$bvid = $request->get("video_bvid");
|
||||||
|
$video = Videos::query()->where("bvid", "=", $bvid)->first();
|
||||||
|
if ($video == null) {
|
||||||
|
$view->withErrors([
|
||||||
|
"video_bvid" => "系统无此对应视频",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$request->session()->flashInput([
|
||||||
|
"video_bvid" => $bvid
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function do_import(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'video_bvid' => ['required'],
|
||||||
|
'platform_id' => ['required', 'int'],
|
||||||
|
'file' => ['required']
|
||||||
|
]);
|
||||||
|
$payload = $request->only(["video_bvid", "platform_id"]);
|
||||||
|
$files = $request->file("file");
|
||||||
|
if (!is_array($files)) {
|
||||||
|
$files = [$files];
|
||||||
|
}
|
||||||
|
$video = Videos::query()->where("bvid", "=", $payload["video_bvid"])->first();
|
||||||
|
if ($video == null) {
|
||||||
|
return back()->withInput()->withErrors([
|
||||||
|
"video_bvid" => "系统无此对应视频",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$danmakus = DanmakuUtil::parse_danmaku($file->getFileInfo());
|
||||||
|
DB::beginTransaction();
|
||||||
|
try {
|
||||||
|
foreach ($danmakus as &$danmaku) {
|
||||||
|
$danmaku['video_bvid'] = $video->bvid;
|
||||||
|
$danmaku['platform_id'] = $payload["platform_id"];
|
||||||
|
unset($danmaku);
|
||||||
|
}
|
||||||
|
VideoDanmakus::insert($danmakus);
|
||||||
|
DB::commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return back()->withInput()->withErrors([
|
||||||
|
"file" => "文件导入异常:" . $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redirect("/danmakus/" . $payload["video_bvid"]);
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class VideoDanmakus extends Model
|
class VideoDanmakus extends Model
|
||||||
{
|
{
|
||||||
|
protected $guarded = [];
|
||||||
protected $table = "video_danmakus";
|
protected $table = "video_danmakus";
|
||||||
protected $dateFormat = 'U';
|
protected $dateFormat = 'U';
|
||||||
|
public $timestamps = false;
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime:Y-m-d H:i:s',
|
||||||
|
];
|
||||||
|
protected $fillable = ["from", "from_mid", "content"];
|
||||||
public function video(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
public function video(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Videos::class, "video_bvid", "bvid");
|
return $this->belongsTo(Videos::class, "video_bvid", "bvid");
|
||||||
|
32
app/Util/DanmakuUtil.php
Normal file
32
app/Util/DanmakuUtil.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Util;
|
||||||
|
|
||||||
|
use SplFileInfo;
|
||||||
|
|
||||||
|
class DanmakuUtil
|
||||||
|
{
|
||||||
|
public static function parse_danmaku(SplFileInfo $file): array
|
||||||
|
{
|
||||||
|
$document = new \DOMDocument();
|
||||||
|
$document->load($file->getRealPath());
|
||||||
|
$danmaku_items = $document->getElementsByTagName("d");
|
||||||
|
$result = [];
|
||||||
|
/** @var \DOMNode $item */
|
||||||
|
foreach ($danmaku_items as $item) {
|
||||||
|
$paramsNode = $item->attributes->getNamedItem("p");
|
||||||
|
$param_list = mb_split(",", $paramsNode->value);
|
||||||
|
if (sizeof($param_list) < 7) {
|
||||||
|
throw new \Exception("弹幕格式异常");
|
||||||
|
}
|
||||||
|
$userNode = $item->attributes->getNamedItem("user");
|
||||||
|
$result[] = [
|
||||||
|
"from" => $userNode->value,
|
||||||
|
"from_mid" => $param_list[6],
|
||||||
|
"content" => $item->textContent,
|
||||||
|
"created_at" => intval($param_list[4])/1000,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.3|^8.0",
|
"php": "^7.3|^8.0",
|
||||||
|
"ext-dom": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"fruitcake/laravel-cors": "^2.0",
|
"fruitcake/laravel-cors": "^2.0",
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/danmakus">稿件查询</a>
|
<a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/danmakus">稿件查询</a>
|
||||||
<a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/programs" title="数据不全,待补充">节目查询</a>
|
<a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/programs" title="数据不全,待补充">节目查询</a>
|
||||||
@auth("web")
|
@auth("web")
|
||||||
<a class="{{ (request()->is('programs/construct', 'programs/construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/programs/construct">节目建设</a>
|
<a class="{{ (request()->is('construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} px-3 py-2 rounded-md text-sm font-medium" href="/construct/programs">节目建设</a>
|
||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/danmakus">稿件查询</a>
|
<a class="{{ (request()->is('danmakus', 'danmakus/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/danmakus">稿件查询</a>
|
||||||
<a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/programs" title="数据不全,待补充">节目查询</a>
|
<a class="{{ (request()->is('programs', '/', 'programs/*/video')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/programs" title="数据不全,待补充">节目查询</a>
|
||||||
@auth("web")
|
@auth("web")
|
||||||
<a class="{{ (request()->is('programs/construct', 'programs/construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/programs/construct">节目建设</a>
|
<a class="{{ (request()->is('construct/*')) ? 'bg-gray-900 text-white' : 'text-gray-700 hover:bg-gray-700 hover:text-white' }} block px-3 py-2 rounded-md text-base font-medium" href="/construct/programs">节目建设</a>
|
||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
35
resources/views/danmaku/construct/batch_import.blade.php
Normal file
35
resources/views/danmaku/construct/batch_import.blade.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>弹幕导入</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="{{ mix('/css/app.css') }}" rel="stylesheet"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@include("common.header")
|
||||||
|
<form class="w-full lg:w-1/2 lg:ml-6 border-2" action="" method="post" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
<label class="block my-2">
|
||||||
|
BVID
|
||||||
|
<input class="form-input border-0 border-b-2 w-full" type="text" name="video_bvid" required value="{{ old('video_bvid') }}">
|
||||||
|
</label>
|
||||||
|
<label class="block my-2">
|
||||||
|
弹幕类型
|
||||||
|
<span class="block form-input border-0 border-b-2 w-full">
|
||||||
|
<input type="radio" value="1" name="platform_id" checked>B站
|
||||||
|
<input type="radio" value="2" name="platform_id" disabled>西瓜
|
||||||
|
<input type="radio" value="3" name="platform_id" disabled>抖音
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="block my-2">
|
||||||
|
弹幕文件
|
||||||
|
<input class="form-input border-0 border-b-2 w-full" multiple type="file" name="file[]" accept="text/xml">
|
||||||
|
</label>
|
||||||
|
@include("common.form_error")
|
||||||
|
<div class="block my-2 text-center">
|
||||||
|
<input class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white" type="submit">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@include("common.footer")
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -68,6 +68,9 @@
|
|||||||
@if(sizeof($video_pivots) === 0 && $comment)
|
@if(sizeof($video_pivots) === 0 && $comment)
|
||||||
<a href="{{ url(route("program.construct.from_comment", ["comment"=>$comment->id])) }}" class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white">一键导入评论中的节目单</a>
|
<a href="{{ url(route("program.construct.from_comment", ["comment"=>$comment->id])) }}" class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white">一键导入评论中的节目单</a>
|
||||||
@endif
|
@endif
|
||||||
|
@if($video->danmakus->count() === 0)
|
||||||
|
<a href="{{ url(route("danmaku.construct.batch_import", ["video_bvid"=>$video->bvid])) }}" class="px-6 py-2 inline-block rounded-full bg-cyan-600 text-white">导入直播弹幕</a>
|
||||||
|
@endif
|
||||||
@endauth
|
@endauth
|
||||||
@include("common.footer")
|
@include("common.footer")
|
||||||
</body>
|
</body>
|
||||||
|
@ -31,8 +31,10 @@ Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnControll
|
|||||||
Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register");
|
Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register");
|
||||||
Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit");
|
Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit");
|
||||||
Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout");
|
Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout");
|
||||||
|
// 弹幕建设
|
||||||
// 建设部分
|
// 建设部分
|
||||||
Route::prefix("/programs/construct")->middleware("auth:web")->group(function (Router $router) {
|
Route::prefix("/construct")->middleware("auth:web")->group(function (Router $router) {
|
||||||
|
Route::prefix("/programs")->group(function (Router $router) {
|
||||||
// 节目建设
|
// 节目建设
|
||||||
$router->get('/', ["\\App\\Http\\Controllers\\ProgramConstructController","index"])->name("program.construct.list");
|
$router->get('/', ["\\App\\Http\\Controllers\\ProgramConstructController","index"])->name("program.construct.list");
|
||||||
$router->get('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","add"])->name("program.construct.add");
|
$router->get('/add', ["\\App\\Http\\Controllers\\ProgramConstructController","add"])->name("program.construct.add");
|
||||||
@ -61,6 +63,12 @@ Route::prefix("/programs/construct")->middleware("auth:web")->group(function (Ro
|
|||||||
$router->get('/append/broadcast_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "broadcast_list"])->name("program.construct.append.broadcast_list");
|
$router->get('/append/broadcast_list', ["\\App\\Http\\Controllers\\ProgramAppendConstructController", "broadcast_list"])->name("program.construct.append.broadcast_list");
|
||||||
$router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit");
|
$router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit");
|
||||||
$router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit");
|
$router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit");
|
||||||
|
});
|
||||||
|
// 弹幕维护
|
||||||
|
Route::prefix("/danmaku")->group(function (Router $router) {
|
||||||
|
$router->get("/batch_import", ["\\App\\Http\\Controllers\\DanmakuConstructController", "page"])->name("danmaku.construct.batch_import.page");
|
||||||
|
$router->post("/batch_import", ["\\App\\Http\\Controllers\\DanmakuConstructController", "do_import"])->name("danmaku.construct.batch_import");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Route::prefix("/user")->middleware("auth:web")->group(function (Router $router) {
|
Route::prefix("/user")->middleware("auth:web")->group(function (Router $router) {
|
||||||
$router->post("/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_options"])->name("user.webauthn.bind.options");
|
$router->post("/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_options"])->name("user.webauthn.bind.options");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user