// 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 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:hive/hive.dart'; import '../http/index.dart'; import 'storage.dart'; class WbiSign { static Box localCache = GStrorage.localCache; final 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(String orig) { String temp = ''; for (int i = 0; i < mixinKeyEncTab.length; i++) { temp += orig.split('')[mixinKeyEncTab[i]]; } return temp.substring(0, 32); } // 为请求参数进行 wbi 签名 Map encWbi( Map params, String imgKey, String subKey) { final String mixinKey = getMixinKey(imgKey + subKey); final DateTime now = DateTime.now(); final int currTime = (now.millisecondsSinceEpoch / 1000).round(); final RegExp chrFilter = RegExp(r"[!\'\(\)*]"); final List query = []; final Map newParams = Map.from(params) ..addAll({"wts": currTime}); // 添加 wts 字段 // 按照 key 重排参数 final List keys = newParams.keys.toList()..sort(); for (String i in keys) { query.add( '${Uri.encodeComponent(i)}=${Uri.encodeComponent(newParams[i].toString().replaceAll(chrFilter, ''))}'); } final String queryStr = query.join('&'); final String wbiSign = md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid return {'wts': currTime.toString(), 'w_rid': wbiSign}; } // 获取最新的 img_key 和 sub_key 可以从缓存中获取 static Future> getWbiKeys() async { final DateTime nowDate = DateTime.now(); if (localCache.get(LocalCacheKey.wbiKeys) != null && DateTime.fromMillisecondsSinceEpoch( localCache.get(LocalCacheKey.timeStamp) as int) .day == nowDate.day) { final Map cacheWbiKeys = localCache.get('wbiKeys'); return Map.from(cacheWbiKeys); } var resp = await Request().get('https://api.bilibili.com/x/web-interface/nav'); var jsonContent = resp.data['data']; final String imgUrl = jsonContent['wbi_img']['img_url']; final String subUrl = jsonContent['wbi_img']['sub_url']; final Map wbiKeys = { 'imgKey': imgUrl .substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length) .split('.')[0], 'subKey': subUrl .substring(subUrl.lastIndexOf('/') + 1, subUrl.length) .split('.')[0] }; localCache.put(LocalCacheKey.wbiKeys, wbiKeys); localCache.put(LocalCacheKey.timeStamp, nowDate.millisecondsSinceEpoch); return wbiKeys; } Future> makSign(Map params) async { // params 为需要加密的请求参数 final Map wbiKeys = await getWbiKeys(); final Map query = params ..addAll(encWbi(params, wbiKeys['imgKey'], wbiKeys['subKey'])); return query; } }