Skip to content

Commit

Permalink
Support login
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenjoezhang committed Jun 7, 2024
1 parent 18079cd commit 09c1a2c
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 144 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,5 @@ package-lock.json
dist/

.DS_Store

login_info*
136 changes: 28 additions & 108 deletions app/js/downloader.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
const fs = require("fs");
const crypto = require("crypto");
const http = require("http");
const https = require("https");
const progress = require("progress-stream");
const { requestWeb } = require("./request");

const REGEX_PLAY_INFO = /<script>window\.__playinfo__=(.*?)<\/script>/;
const REGEX_INITIAL_STATE = /__INITIAL_STATE__=(.*?);\(function\(\)/;

class Task {
constructor(url) {
Expand All @@ -13,126 +16,43 @@ class Task {

class Downloader {
constructor() {
this.type = "";
this.id = "";
this.url = "";
this.aid = -1;
this.bvid = -1;
this.pid = 1;
this.cid = -1;
this.playUrl = null;
this.videoData = null;
this.name = "";
this.links = [];
this.tasks = [];
}

getVideoUrl(videoUrl) {
this.url = "";
const mapping = {
"BV": "https://www.bilibili.com/video/",
"bv": "https://www.bilibili.com/video/",
"av": "https://www.bilibili.com/video/",
"ep": "https://www.bilibili.com/bangumi/play/",
"ss": "https://www.bilibili.com/bangumi/play/"
};
for (const [key, value] of Object.entries(mapping)) {
if (videoUrl.includes(key)) {
this.type = key;
this.id = key + videoUrl.split(key)[1];
this.url = value + this.id;
break;
}
}
getVideoInfoFromInitialState(state) {
this.aid = state.aid;
this.bvid = state.bvid;
this.pid = state.p;
this.cid = state.videoData.cid;
this.videoData = state.videoData;
}

async getAid() {
const { type, url } = this;
if (!url) return;
return fetch(url)
.then(response => response.text())
.then(result => {
let data = result.match(/__INITIAL_STATE__=(.*?);\(function\(\)/)[1];
data = JSON.parse(data);
console.log("INITIAL STATE", data);
if (type === "BV" || type === "bv" || type === "av") {
this.aid = data.videoData.aid;
this.pid = parseInt(url.split("p=")[1], 10) || 1;
this.cid = data.videoData.pages[this.pid - 1].cid;
}
else if (type === "ep") {
this.aid = data.epInfo.aid;
this.cid = data.epInfo.cid;
}
else if (type === "ss") {
this.aid = data.epList[0].aid;
this.cid = data.epList[0].cid;
}
})
.catch(error => showError("获取视频 aid 出错!"));
}
async getPlayUrlWebPage(url) {
const referer = 'https://www.bilibili.com';
const response = await requestWeb(url, referer);
const matchInitialState = response.match(REGEX_INITIAL_STATE);
const matchPlayInfo = response.match(REGEX_PLAY_INFO);
if (!matchInitialState || !matchPlayInfo) return;
const initialState = JSON.parse(matchInitialState[1]);
const playUrl = JSON.parse(matchPlayInfo[1]);

async getInfo() {
const { aid, cid } = this;
if (!cid) {
showError("获取视频 cid 出错!");
return;
}
getDanmaku(); //获取cid后,获取下载链接和弹幕信息
return fetch("https://api.bilibili.com/x/web-interface/view?aid=" + aid)
.then(response => response.json())
.catch(error => showError("获取视频信息出错!"));
}
this.getVideoInfoFromInitialState(initialState);

async getData(fallback) {
const { cid, type } = this;
let playUrl;
if (fallback) {
const params = `cid=${cid}&module=movie&player=1&quality=112&ts=1`;
const sign = crypto.createHash("md5").update(params + "9b288147e5474dd2aa67085f716c560d").digest("hex");
playUrl = `https://bangumi.bilibili.com/player/web_api/playurl?${params}&sign=${sign}`;
} else {
if (type === "BV" || type === "bv" || type === "av") {
const params = `appkey=iVGUTjsxvpLeuDCf&cid=${cid}&otype=json&qn=112&quality=112&type=`;
const sign = crypto.createHash("md5").update(params + "aHRmhWMLkdeMuILqORnYZocwMBpMEOdt").digest("hex");
playUrl = `https://interface.bilibili.com/v2/playurl?${params}&sign=${sign}`;
} else {
playUrl = `https://api.bilibili.com/pgc/player/web/playurl?qn=80&cid=${cid}`;
}
if (!playUrl) {
this.playUrl = null;
} else if (playUrl.data) {
this.playUrl = playUrl.data;
} else if (playUrl.result) {
this.playUrl = playUrl.result;
}
return fetch(playUrl)
.then(response => response.text())
.then(result => {
const data = fallback ? this.parseData(result) : JSON.parse(result);
const target = data.durl || data.result.durl;
console.log("PLAY URL", data);
if (target) {
this.links = target.map(part => part.url);
return {
fallback, data
};
} else {
if (fallback) throw Error();
return this.getData(true);
}
})
.catch(error => {
showError("获取 PlayUrl 或下载链接出错!由于B站限制,只能下载低清晰度视频。");
});
}

parseData(target) {
const data = $(target);
const result = {};
result.durl = [];
result.quality = data.find("quality").text();
data.find("durl").each((i, o) => {
const part = $(o);
result.durl.push({
url: part.find("url").text(),
order: part.find("order").text(),
length: part.find("length").text(),
size: part.find("size").text()
});
});
return result;
}

downloadByIndex(part, file, callback = () => {}) {
Expand Down
98 changes: 98 additions & 0 deletions app/js/login/login-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// /workspaces/downkyicore/DownKyi.Core/BiliApi/Login/LoginHelper.cs
const fs = require('fs');
// Required to use response.headers.raw
const fetch = require('node-fetch');
const path = require('path');
const { CookieJar, Cookie } = require('tough-cookie');

const LOCAL_LOGIN_INFO = path.join(__dirname, 'login_info');
// const SECRET_KEY = 'EsOat*^y1QR!&0J6';

class LoginHelper {
// /workspaces/downkyicore/DownKyi.Core/Utils/ObjectHelper.cs
static parseCookie(url) {
const cookieJar = new CookieJar();

if (!url) {
return cookieJar;
}

const strList = url.split('?');
if (strList.length < 2) {
return cookieJar;
}

const strList2 = strList[1].split('&');
if (strList2.length === 0) {
return cookieJar;
}

// 获取expires
const expires = strList2.find(it => it.includes('Expires')).split('=')[1];
const dateTime = new Date(Date.now() + parseInt(expires) * 1000);

for (const item of strList2) {
const strList3 = item.split('=');
if (strList3.length < 2) {
continue;
}

const name = strList3[0];
const value = strList3[1];

// 不需要
if (name === 'Expires' || name === 'gourl') {
continue;
}

// 添加cookie
cookieJar.setCookieSync(new Cookie({
key: name,
value: value.replace(',', '%2c'),
domain: '.bilibili.com',
path: '/',
expires: dateTime
}).toString(), 'http:https://.bilibili.com');
}

return cookieJar;
}

static async saveLoginInfoCookies(url) {
const tempFile = `${LOCAL_LOGIN_INFO}-${Date.now()}`;
const cookieJar = LoginHelper.parseCookie(url);

try {
console.log(url)
// const response = await fetch(url, { headers: { Cookie: cookieJar.getCookieStringSync(url) } });
// const setCookieHeader = response.headers.raw()['set-cookie'];
// window.setCookieHeader = response.headers;
// if (setCookieHeader) {
// setCookieHeader.forEach(cookie => {
// cookieJar.setCookieSync(cookie, url);
// });
// }
const cookieJSON = JSON.stringify(cookieJar.toJSON());
fs.writeFileSync(tempFile, cookieJSON);
fs.copyFileSync(tempFile, LOCAL_LOGIN_INFO);
fs.unlinkSync(tempFile);
} catch (err) {
console.error('SaveLoginInfoCookies()发生异常:', err);
}
}

static getLoginInfoCookies() {
if (!fs.existsSync(LOCAL_LOGIN_INFO)) {
return null;
}

const tempFile = `${LOCAL_LOGIN_INFO}-${Date.now()}`;
fs.copyFileSync(LOCAL_LOGIN_INFO, tempFile);

const cookieJar = CookieJar.fromJSON(fs.readFileSync(tempFile, 'utf-8'));
fs.unlinkSync(tempFile);
return cookieJar;
}
}

module.exports = LoginHelper;
48 changes: 48 additions & 0 deletions app/js/login/login-qr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// /workspaces/downkyicore/DownKyi.Core/BiliApi/Login/LoginHelper.cs
const QRCode = require('qrcode');

class LoginQR {
static async getLoginUrl() {
try {
const response = await fetch('https://passport.bilibili.com/x/passport-login/web/qrcode/generate');
const data = await response.json();
return data;
} catch (error) {
console.error('GetLoginUrl()发生异常:', error);
return null;
}
}

static async getLoginStatus(qrcodeKey, goUrl = 'https://www.bilibili.com') {
try {
const response = await fetch(`https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=${qrcodeKey}`);
const data = await response.json();
return data;
} catch (error) {
console.error('GetLoginStatus()发生异常:', error);
return null;
}
}

// static async getLoginQRCode() {
// try {
// const loginUrlOrigin = await this.getLoginUrl();
// return await this.getLoginQRCodeFromUrl(loginUrlOrigin.data.url);
// } catch (error) {
// console.error('GetLoginQRCode()发生异常:', error);
// return null;
// }
// }

static async getLoginQRCodeFromUrl(url) {
try {
const qrCode = await QRCode.toDataURL(url, { width: 200, height: 200 });
return qrCode;
} catch (error) {
console.error('生成二维码发生异常:', error);
return null;
}
}
}

module.exports = LoginQR;

0 comments on commit 09c1a2c

Please sign in to comment.