feat: Wbi sign
This commit is contained in:
@ -196,4 +196,9 @@ class Api {
|
|||||||
// qn 80:流畅,150:高清,400:蓝光,10000:原画,20000:4K, 30000:杜比
|
// qn 80:流畅,150:高清,400:蓝光,10000:原画,20000:4K, 30000:杜比
|
||||||
static const String liveRoomInfo =
|
static const String liveRoomInfo =
|
||||||
'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo';
|
'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo';
|
||||||
|
|
||||||
|
// 用户信息 需要Wbi签名
|
||||||
|
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482
|
||||||
|
static const String memberInfo =
|
||||||
|
'https://api.bilibili.com/x/space/wbi/acc/info';
|
||||||
}
|
}
|
||||||
|
34
lib/http/member.dart
Normal file
34
lib/http/member.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/models/member/info.dart';
|
||||||
|
|
||||||
|
class MemberHttp {
|
||||||
|
static Future memberInfo({String? params}) async {
|
||||||
|
var res = await Request().get(Api.memberInfo + params!);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': MemberInfoModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future memberStat({int? mid}) async {
|
||||||
|
var res = await Request().get(Api.userStat, data: {mid: mid});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
print(res.data['data']);
|
||||||
|
// return {'status': true, 'data': FansDataModel.fromJson(res.data['data'])};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
lib/models/member/info.dart
Normal file
29
lib/models/member/info.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
class MemberInfoModel {
|
||||||
|
MemberInfoModel({
|
||||||
|
this.mid,
|
||||||
|
this.name,
|
||||||
|
this.sex,
|
||||||
|
this.face,
|
||||||
|
this.sign,
|
||||||
|
this.level,
|
||||||
|
this.isFollowed,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
String? name;
|
||||||
|
String? sex;
|
||||||
|
String? face;
|
||||||
|
String? sign;
|
||||||
|
int? level;
|
||||||
|
bool? isFollowed;
|
||||||
|
|
||||||
|
MemberInfoModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
mid = json['mid'];
|
||||||
|
name = json['name'];
|
||||||
|
sex = json['sex'];
|
||||||
|
face = json['face'];
|
||||||
|
sign = json['sign'];
|
||||||
|
level = json['level'];
|
||||||
|
isFollowed = json['is_followed'];
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
Widget author(item, context) {
|
Widget author(item, context) {
|
||||||
@ -7,9 +8,8 @@ Widget author(item, context) {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () =>
|
||||||
print('个人主页');
|
Get.toNamed('/member?mid=${item.modules.moduleAuthor.mid}'),
|
||||||
},
|
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
@ -94,7 +94,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: const LinearGradient(
|
gradient: const LinearGradient(
|
||||||
begin: Alignment.center,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: <Color>[
|
colors: <Color>[
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
@ -115,9 +115,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
pBadge(content.durationText ?? '', context, null,
|
Text(content.durationText ?? ''),
|
||||||
null, 0, 0,
|
|
||||||
type: 'gray'),
|
|
||||||
if (content.durationText != null)
|
if (content.durationText != null)
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(content.stat.play + '次围观'),
|
Text(content.stat.play + '次围观'),
|
||||||
|
28
lib/pages/member/controller.dart
Normal file
28
lib/pages/member/controller.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
|
class MemberController extends GetxController {
|
||||||
|
late int mid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
|
getInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfo() async {
|
||||||
|
String params = await WbiSign().makSign({
|
||||||
|
'mid': mid,
|
||||||
|
'token': '',
|
||||||
|
'platform': 'web',
|
||||||
|
'web_location': 1550101,
|
||||||
|
});
|
||||||
|
params = '?$params';
|
||||||
|
var res = await MemberHttp.memberInfo(params: params);
|
||||||
|
if (res['status']) {
|
||||||
|
print(res['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
lib/pages/member/index.dart
Normal file
4
lib/pages/member/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library member;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
20
lib/pages/member/view.dart
Normal file
20
lib/pages/member/view.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
|
|
||||||
|
class MemberPage extends StatefulWidget {
|
||||||
|
const MemberPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemberPage> createState() => _MemberPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberPageState extends State<MemberPage> {
|
||||||
|
final MemberController _memberController = Get.put(MemberController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold();
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import 'package:pilipala/pages/home/index.dart';
|
|||||||
import 'package:pilipala/pages/hot/index.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/pages/later/index.dart';
|
import 'package:pilipala/pages/later/index.dart';
|
||||||
import 'package:pilipala/pages/liveRoom/view.dart';
|
import 'package:pilipala/pages/liveRoom/view.dart';
|
||||||
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/pages/preview/index.dart';
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/searchResult/index.dart';
|
import 'package:pilipala/pages/searchResult/index.dart';
|
||||||
@ -62,5 +63,7 @@ class Routes {
|
|||||||
GetPage(name: '/fan', page: () => const FansPage()),
|
GetPage(name: '/fan', page: () => const FansPage()),
|
||||||
// 直播详情
|
// 直播详情
|
||||||
GetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
GetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
||||||
|
// 用户中心
|
||||||
|
GetPage(name: '/member', page: () => const MemberPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
130
lib/utils/wbi_sign.dart
Normal file
130
lib/utils/wbi_sign.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Wbi签名 用于生成 REST API 请求中的 w_rid 和 wts 字段
|
||||||
|
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md
|
||||||
|
// import md5 from 'md5'
|
||||||
|
// import axios from 'axios'
|
||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class WbiSign {
|
||||||
|
List mixinKeyEncTab = [
|
||||||
|
46,
|
||||||
|
47,
|
||||||
|
18,
|
||||||
|
2,
|
||||||
|
53,
|
||||||
|
8,
|
||||||
|
23,
|
||||||
|
32,
|
||||||
|
15,
|
||||||
|
50,
|
||||||
|
10,
|
||||||
|
31,
|
||||||
|
58,
|
||||||
|
3,
|
||||||
|
45,
|
||||||
|
35,
|
||||||
|
27,
|
||||||
|
43,
|
||||||
|
5,
|
||||||
|
49,
|
||||||
|
33,
|
||||||
|
9,
|
||||||
|
42,
|
||||||
|
19,
|
||||||
|
29,
|
||||||
|
28,
|
||||||
|
14,
|
||||||
|
39,
|
||||||
|
12,
|
||||||
|
38,
|
||||||
|
41,
|
||||||
|
13,
|
||||||
|
37,
|
||||||
|
48,
|
||||||
|
7,
|
||||||
|
16,
|
||||||
|
24,
|
||||||
|
55,
|
||||||
|
40,
|
||||||
|
61,
|
||||||
|
26,
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
60,
|
||||||
|
51,
|
||||||
|
30,
|
||||||
|
4,
|
||||||
|
22,
|
||||||
|
25,
|
||||||
|
54,
|
||||||
|
21,
|
||||||
|
56,
|
||||||
|
59,
|
||||||
|
6,
|
||||||
|
63,
|
||||||
|
57,
|
||||||
|
62,
|
||||||
|
11,
|
||||||
|
36,
|
||||||
|
20,
|
||||||
|
34,
|
||||||
|
44,
|
||||||
|
52
|
||||||
|
];
|
||||||
|
// 对 imgKey 和 subKey 进行字符顺序打乱编码
|
||||||
|
String getMixinKey(orig) {
|
||||||
|
String temp = '';
|
||||||
|
for (int i = 0; i < mixinKeyEncTab.length; i++) {
|
||||||
|
temp += orig.split('')[mixinKeyEncTab[i]];
|
||||||
|
}
|
||||||
|
return temp.substring(0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为请求参数进行 wbi 签名
|
||||||
|
String encWbi(params, imgKey, subKey) {
|
||||||
|
String mixinKey = getMixinKey(imgKey + subKey);
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
int currTime = (now.millisecondsSinceEpoch / 1000).round();
|
||||||
|
RegExp chrFilter = RegExp(r"[!\'\(\)*]");
|
||||||
|
List query = [];
|
||||||
|
Map newParams = Map.from(params)..addAll({"wts": currTime}); // 添加 wts 字段
|
||||||
|
// 按照 key 重排参数
|
||||||
|
List keys = newParams.keys.toList()..sort();
|
||||||
|
for (var i in keys) {
|
||||||
|
query.add(
|
||||||
|
'${Uri.encodeComponent(i)}=${Uri.encodeComponent(newParams[i].toString().replaceAll(chrFilter, ''))}');
|
||||||
|
}
|
||||||
|
String queryStr = query.join('&');
|
||||||
|
String wbiSign =
|
||||||
|
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
||||||
|
print('w_rid: $wbiSign');
|
||||||
|
return '$queryStr&w_rid=$wbiSign';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最新的 img_key 和 sub_key
|
||||||
|
static Future<Map<String, dynamic>> getWbiKeys() async {
|
||||||
|
var resp =
|
||||||
|
await Request().get('https://api.bilibili.com/x/web-interface/nav');
|
||||||
|
var jsonContent = resp.data['data'];
|
||||||
|
|
||||||
|
String imgUrl = jsonContent['wbi_img']['img_url'];
|
||||||
|
String subUrl = jsonContent['wbi_img']['sub_url'];
|
||||||
|
return {
|
||||||
|
'imgKey': imgUrl
|
||||||
|
.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length)
|
||||||
|
.split('.')[0],
|
||||||
|
'subKey': subUrl
|
||||||
|
.substring(subUrl.lastIndexOf('/') + 1, subUrl.length)
|
||||||
|
.split('.')[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
makSign(Map<String, dynamic> params) async {
|
||||||
|
// params 为需要加密的请求参数
|
||||||
|
Map<String, dynamic> wbiKeys = await getWbiKeys();
|
||||||
|
String query = encWbi(params, wbiKeys['imgKey'], wbiKeys['subKey']);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
@ -210,7 +210,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+4"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
||||||
|
@ -86,6 +86,7 @@ dependencies:
|
|||||||
path: package
|
path: package
|
||||||
custom_sliding_segmented_control: ^1.7.5
|
custom_sliding_segmented_control: ^1.7.5
|
||||||
loading_more_list: ^5.0.3
|
loading_more_list: ^5.0.3
|
||||||
|
crypto: any
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user