refa: article (#757)

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
dom
2025-04-26 14:54:22 +08:00
committed by GitHub
parent 64f7ba2a1a
commit 40fb93f036
87 changed files with 2628 additions and 993 deletions

View File

@@ -781,4 +781,9 @@ class Api {
static const String articleInfo = '/x/article/viewinfo';
static const String dynamicReport = '/x/dynamic/feed/dynamic_report/add';
// https://github.com/SocialSisterYi/bilibili-API-collect/pull/1242
static const String articleView = '/x/article/view';
static const String opusDetail = '/x/polymer/web-dynamic/v1/opus/detail';
}

View File

@@ -1,4 +1,6 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/article_view/data.dart';
import 'package:PiliPlus/models/dynamics/opus_detail/data.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/wbi_sign.dart';
@@ -157,4 +159,38 @@ class DynamicsHttp {
return {'status': false, 'msg': res.data['message']};
}
}
static Future articleView({
required dynamic cvId,
}) async {
var res = await Request().get(
Api.articleView,
queryParameters: await WbiSign.makSign({
'id': cvId,
'gaia_source': 'main_web',
}),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': ArticleData.fromJson(res.data['data'])};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future opusDetail({
required dynamic opusId,
}) async {
var res = await Request().get(
Api.opusDetail,
queryParameters: await WbiSign.makSign({
'id': opusId,
'features': 'htmlNewStyle',
}),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': OpusData.fromJson(res.data['data'])};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
}

View File

@@ -1,196 +0,0 @@
import 'dart:convert';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/url_utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as parser;
import 'index.dart';
class HtmlHttp {
// article
static Future reqHtml(id, dynamicType) async {
var response = await Request().get(
"https://www.bilibili.com/opus/$id",
extra: {'ua': 'pc'},
options: Options(
followRedirects: false,
validateStatus: (status) => true,
),
);
if (response.data is! String && response.data is! List<int>) {
return;
}
try {
if (response.data.contains('Redirecting to')) {
RegExpMatch? cvid =
RegExp(r'/([a-zA-Z]+)/(cv\d+)').firstMatch(response.data);
if (cvid?.group(2) != null) {
return await reqReadHtml(cvid?.group(2), cvid?.group(1), false);
}
RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)');
Match match = regex.firstMatch(response.data)!;
String matchedString = match.group(0)!;
response = await Request().get(
'https:$matchedString/',
extra: {'ua': 'pc'},
);
}
dom.Document rootTree = parser.parse(response.data);
// log(response.data.body.toString());
dom.Element body = rootTree.body!;
dom.Element appDom = body.querySelector('#app')!;
dom.Element authorHeader = appDom.querySelector('.fixed-author-header')!;
// 头像
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
avatar = 'https:${avatar.split('@')[0]}';
String uname = authorHeader
.querySelector('.fixed-author-header__author__name')!
.text;
// 动态详情
dom.Element opusDetail = appDom.querySelector('.opus-detail')!;
// 发布时间
String updateTime =
opusDetail.querySelector('.opus-module-author__pub__text')!.text;
//
String opusContent =
opusDetail.querySelector('.opus-module-content')!.innerHtml;
String? test;
try {
test = opusDetail
.querySelector('.horizontal-scroll-album__pic__img')!
.innerHtml;
} catch (_) {}
List comment = opusDetail
.querySelector('.bili-comment-container')!
.className
.split(' ')[1]
.split('-');
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
dynamic mid;
Map? favorite;
try {
final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});');
final match = regex.firstMatch(response.data);
if (match != null) {
final json = jsonDecode(match.group(1)!);
mid = json['detail']['basic']['uid'];
favorite = json['detail']['modules'].last['module_stat']['favorite'];
}
} catch (e) {
debugPrint('req html: $e');
}
return {
'status': true,
'mid': mid,
'avatar': avatar,
'uname': uname,
'updateTime': updateTime,
'content': (test ?? '') + opusContent,
'commentType': int.parse(comment[1]),
'commentId': int.parse(comment[2]),
'favorite': favorite,
};
} catch (err) {
debugPrint('err: $err');
}
}
// read
static Future reqReadHtml(id, dynamicType, [bool redirect = true]) async {
if (redirect) {
String? redirectUrl = await UrlUtils.parseRedirectUrl(
'https://www.bilibili.com/$dynamicType/$id/');
if (redirectUrl != null) {
return await reqHtml(redirectUrl.split('/').last, dynamicType);
}
}
var response = await Request().get(
"https://www.bilibili.com/$dynamicType/$id/",
extra: {'ua': 'pc'},
options: Options(
headers: {
'cookie': 'opus-goback=1',
},
),
);
if (response.data is! String && response.data is! List<int>) {
return;
}
try {
dom.Document rootTree = parser.parse(response.data);
dom.Element body = rootTree.body!;
dom.Element appDom = body.querySelector('#app')!;
dom.Element authorHeader = appDom.querySelector('.up-left')!;
// 头像
// String avatar =
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
// 正则寻找形如"author":{"mid":\d+,"name":".*","face":"xxxx"的匹配项
final match =
RegExp(r'"author":\{"mid":(\d+)?,"name":".+?","face":"(.+?)"')
.firstMatch(response.data)!;
String mid = match.group(1)!;
String avatar = match.group(2)!.replaceAll(r'\u002F', '/').split('@')[0];
// debugPrint(avatar);
String uname = authorHeader.querySelector('.up-name')!.text.trim();
// 动态详情
dom.Element opusDetail = appDom.querySelector('.article-content')!;
// 发布时间
// String updateTime =
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
// debugPrint(updateTime);
//
dynamic opusContent =
opusDetail.querySelector('#read-article-holder')?.innerHtml ?? '';
bool isJsonContent = false;
if (opusContent.isEmpty) {
final regex = RegExp(r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\});');
final match = regex.firstMatch(response.data);
if (match != null) {
final jsonString = match.group(1);
if (jsonString != null) {
try {
dynamic json = jsonDecode(jsonString);
opusContent = json['readInfo']['content'];
try {
opusContent = (json['readInfo']?['opus']?['content']
?['paragraphs'] as List)
.map((item) => ArticleContentModel.fromJson(item))
.toList();
isJsonContent = true;
} catch (e) {
debugPrint('second: $e');
}
} catch (e) {
debugPrint('first: $e');
}
}
}
}
String number = RegExp(r'\d+').firstMatch(id)!.group(0)!;
return {
'status': true,
'mid': mid,
'avatar': avatar,
'uname': uname,
'updateTime': '',
'content': opusContent,
'isJsonContent': isJsonContent,
'commentType': 12,
'commentId': int.parse(number),
};
} catch (e) {
debugPrint(e.toString());
}
}
}