feat: Wbi sign

This commit is contained in:
guozhigq
2023-07-14 13:38:23 +08:00
parent 180ba11089
commit 8fd1efd8bf
12 changed files with 260 additions and 8 deletions

View File

@ -196,4 +196,9 @@ class Api {
// qn 80:流畅150:高清400:蓝光10000:原画20000:4K, 30000:杜比
static const String liveRoomInfo =
'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
View 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'],
};
}
}
}

View 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'];
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
Widget author(item, context) {
@ -7,9 +8,8 @@ Widget author(item, context) {
child: Row(
children: [
GestureDetector(
onTap: () {
print('个人主页');
},
onTap: () =>
Get.toNamed('/member?mid=${item.modules.moduleAuthor.mid}'),
child: NetworkImgLayer(
width: 40,
height: 40,

View File

@ -94,7 +94,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.center,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
Colors.transparent,
@ -115,9 +115,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
color: Colors.white),
child: Row(
children: [
pBadge(content.durationText ?? '', context, null,
null, 0, 0,
type: 'gray'),
Text(content.durationText ?? ''),
if (content.durationText != null)
const SizedBox(width: 10),
Text(content.stat.play + '次围观'),

View 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']);
}
}
}

View File

@ -0,0 +1,4 @@
library member;
export './controller.dart';
export './view.dart';

View 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();
}
}

View File

@ -10,6 +10,7 @@ import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/hot/index.dart';
import 'package:pilipala/pages/later/index.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/search/index.dart';
import 'package:pilipala/pages/searchResult/index.dart';
@ -62,5 +63,7 @@ class Routes {
GetPage(name: '/fan', page: () => const FansPage()),
// 直播详情
GetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
// 用户中心
GetPage(name: '/member', page: () => const MemberPage()),
];
}

130
lib/utils/wbi_sign.dart Normal file
View 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;
}
}

View File

@ -210,7 +210,7 @@ packages:
source: hosted
version: "0.3.3+4"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67

View File

@ -86,6 +86,7 @@ dependencies:
path: package
custom_sliding_segmented_control: ^1.7.5
loading_more_list: ^5.0.3
crypto: any
dev_dependencies:
flutter_test: