mirror of
https://github.com/lyz05/danmaku.git
synced 2026-02-02 17:59:53 +08:00
refactor: 将各视频站弹幕获取模块改为类实现
This commit is contained in:
7
app.js
7
app.js
@@ -9,8 +9,10 @@ require("dotenv")
|
||||
.config();
|
||||
|
||||
// 引入一个个路由模块
|
||||
const danmakuRouter = require("./routes/danmaku");
|
||||
const danmakuRouter = require("./routes/danmaku.mjs").default;
|
||||
const app = express();
|
||||
// 加载路由
|
||||
app.use("/", danmakuRouter);
|
||||
// 启用gzip压缩
|
||||
app.use(compression());
|
||||
|
||||
@@ -33,8 +35,7 @@ app.use("/assets", [
|
||||
express.static(__dirname + "/node_modules/leancloud-storage/dist",{maxAge: 86400*1000}),
|
||||
]);
|
||||
|
||||
// 加载路由
|
||||
app.use("/", danmakuRouter);
|
||||
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
//引入API组件
|
||||
const Bilibili = require("./bilibili");
|
||||
const Mgtv = require("./mgtv");
|
||||
const Tencentvideo = require("./tencentvideo");
|
||||
const Youku = require("./youku");
|
||||
const Iqiyi = require("./iqiyi");
|
||||
const Gamer = require("./gamer");
|
||||
// 实例化API组件
|
||||
const bilibili = new Bilibili();
|
||||
const mgtv = new Mgtv();
|
||||
const tencentvideo = new Tencentvideo();
|
||||
const youku = new Youku();
|
||||
const iqiyi = new Iqiyi();
|
||||
const gamer = new Gamer();
|
||||
//TODO 优化代码
|
||||
module.exports = { bilibili, mgtv, tencentvideo, youku, iqiyi, gamer };
|
||||
54
routes/api/base.mjs
Normal file
54
routes/api/base.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
export default class BaseSource {
|
||||
// 构造函数,初始化通用配置
|
||||
constructor() {
|
||||
this.name = "";
|
||||
this.domain = "";
|
||||
this.example_urls = [];
|
||||
}
|
||||
|
||||
content_template = {
|
||||
timepoint: 0, // 弹幕发送时间(秒)
|
||||
ct: 1, // 弹幕类型,1-3 为滚动弹幕、4 为底部、5 为顶端、6 为逆向、7 为精确、8 为高级
|
||||
size: 25, //字体大小,25 为中,18 为小
|
||||
color: 16777215, //弹幕颜色,RGB 颜色转为十进制后的值,16777215 为白色
|
||||
unixtime: Math.floor(Date.now() / 1000), //Unix 时间戳格式
|
||||
uid: 0, //发送人的 id
|
||||
content: "",
|
||||
}
|
||||
|
||||
time_to_second(time) {
|
||||
const t = time.split(":");
|
||||
let s = 0;
|
||||
let m = 1;
|
||||
while (t.length > 0) {
|
||||
s += m * parseInt(t.pop(), 10);
|
||||
m *= 60;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// 解析传入的视频网址,获取弹幕请求地址(Promise数组)
|
||||
async resolve(url) {
|
||||
throw new Error("Method 'resolve()' must be implemented.");
|
||||
}
|
||||
|
||||
// 请求弹幕资源,返回标准化弹幕内容
|
||||
async parse(promises) {
|
||||
throw new Error("Method 'parse()' must be implemented.");
|
||||
}
|
||||
|
||||
// 综合处理入口,返回最终弹幕内容
|
||||
async work(url) {
|
||||
const promises = await this.resolve(url);
|
||||
if (!this.error_msg) {
|
||||
console.log(this.name, "api lens:", promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
}
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: this.error_msg? this.error_msg: "ok"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
const urlmodule = require("url");
|
||||
const axios = require("axios");
|
||||
import urlmodule from "url";
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
|
||||
function Bilibili() {
|
||||
this.name = "B站";
|
||||
this.domain = "bilibili.com";
|
||||
this.example_urls = [
|
||||
"https://www.bilibili.com/video/av170001",
|
||||
"https://www.bilibili.com/video/av170001?p=2",
|
||||
"https://www.bilibili.com/video/BV17x411w7KC?p=3",
|
||||
"https://www.bilibili.com/bangumi/play/ep691614"
|
||||
];
|
||||
export default class BilibiliSource extends BaseSource {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "B站";
|
||||
this.domain = "bilibili.com";
|
||||
this.example_urls = [
|
||||
"https://www.bilibili.com/video/av170001",
|
||||
"https://www.bilibili.com/video/av170001?p=2",
|
||||
"https://www.bilibili.com/video/BV17x411w7KC?p=3",
|
||||
"https://www.bilibili.com/bangumi/play/ep691614"
|
||||
];
|
||||
}
|
||||
|
||||
this.resolve = async (url) => {
|
||||
async resolve(url) {
|
||||
// 相关API
|
||||
const api_video_info = "https://api.bilibili.com/x/web-interface/view";
|
||||
const api_epid_cid = "https://api.bilibili.com/pgc/view/web/season";
|
||||
@@ -60,7 +64,7 @@ function Bilibili() {
|
||||
|
||||
};
|
||||
|
||||
this.work = async (url) => {
|
||||
async work(url) {
|
||||
const urls = await this.resolve(url);
|
||||
if (!this.error_msg) {
|
||||
this.url = urls[0];
|
||||
@@ -73,12 +77,16 @@ function Bilibili() {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Bilibili;
|
||||
// module.exports = Bilibili;
|
||||
|
||||
if(!module.parent) {
|
||||
const b = new Bilibili();
|
||||
b.work(b.example_urls[0]).then(() => {
|
||||
console.log(b.content);
|
||||
console.log(b.title);
|
||||
});
|
||||
}
|
||||
// if(!module.parent) {
|
||||
// const b = new Bilibili();
|
||||
// b.work(b.example_urls[0]).then(() => {
|
||||
// console.log(b.content);
|
||||
// console.log(b.title);
|
||||
// });
|
||||
// }
|
||||
// 判断是否直接运行当前文件(跨平台)
|
||||
// if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
// console.log("单独运行")
|
||||
// }
|
||||
@@ -1,16 +1,20 @@
|
||||
const urlmodule = require("url");
|
||||
const axios = require("axios");
|
||||
const {time_to_second, content_template} = require("./utils");
|
||||
import urlmodule from "url";
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
|
||||
function Gamer() {
|
||||
this.name = "巴哈姆特動畫瘋";
|
||||
this.domain = "gamer.com.tw";
|
||||
this.example_urls = [
|
||||
"https://ani.gamer.com.tw/animeVideo.php?sn=41645",
|
||||
"https://ani.gamer.com.tw/animeVideo.php?sn=41889"
|
||||
];
|
||||
export default class GamerSource extends BaseSource {
|
||||
|
||||
this.resolve = async (url) => {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "巴哈姆特動畫瘋";
|
||||
this.domain = "gamer.com.tw";
|
||||
this.example_urls = [
|
||||
"https://ani.gamer.com.tw/animeVideo.php?sn=41645",
|
||||
"https://ani.gamer.com.tw/animeVideo.php?sn=41889"
|
||||
];
|
||||
}
|
||||
|
||||
async resolve(url) {
|
||||
// 相关API
|
||||
const api_video_info = "https://api.gamer.com.tw/anime/v1/video.php";
|
||||
const api_danmu = "https://api.gamer.com.tw/anime/v1/danmu.php";
|
||||
@@ -42,7 +46,7 @@ function Gamer() {
|
||||
|
||||
};
|
||||
|
||||
this.parse = async (promises) => {
|
||||
async parse(promises) {
|
||||
//筛选出成功的请求
|
||||
let datas = (await Promise.allSettled(promises))
|
||||
.filter(x => x.status === "fulfilled")
|
||||
@@ -51,7 +55,7 @@ function Gamer() {
|
||||
for (let i = 0; i < datas.length; i++) {
|
||||
const data = datas[i].data;
|
||||
for (const item of data.danmu) {
|
||||
const content = JSON.parse(JSON.stringify(content_template));
|
||||
const content = JSON.parse(JSON.stringify(this.content_template));
|
||||
content.timepoint = item.time / 10;
|
||||
content.content = item.text;
|
||||
content.uid = item.userid;
|
||||
@@ -63,23 +67,13 @@ function Gamer() {
|
||||
return contents;
|
||||
}
|
||||
|
||||
this.work = async (url) => {
|
||||
const promises = await this.resolve(url);
|
||||
console.log(this.name, "api lens:", promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: this.error_msg? this.error_msg: "ok"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Gamer;
|
||||
// module.exports = Gamer;
|
||||
|
||||
if(!module.parent) {
|
||||
const g = new Gamer();
|
||||
g.work(g.example_urls[0]).then(res=>{
|
||||
console.log(res)
|
||||
});
|
||||
}
|
||||
// if(!module.parent) {
|
||||
// const g = new Gamer();
|
||||
// g.work(g.example_urls[0]).then(res=>{
|
||||
// console.log(res)
|
||||
// });
|
||||
// }
|
||||
@@ -1,21 +1,21 @@
|
||||
const axios = require("axios");
|
||||
const pako = require("pako");
|
||||
const {
|
||||
time_to_second,
|
||||
content_template,
|
||||
} = require("./utils");
|
||||
const memory = require("../../utils/memory");
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
import pako from "pako";
|
||||
import memory from "../../utils/memory.js";
|
||||
|
||||
function Iqiyi() {
|
||||
this.name = "爱奇艺";
|
||||
this.domain = "iqiyi.com";
|
||||
this.example_urls = [
|
||||
"https://www.iqiyi.com/v_bb6gsxzz78.html",
|
||||
"https://www.iqiyi.com/v_19rr1lm35o.html",
|
||||
];
|
||||
export default class IqiyiSource extends BaseSource {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "爱奇艺";
|
||||
this.domain = "iqiyi.com";
|
||||
this.example_urls = [
|
||||
"https://www.iqiyi.com/v_bb6gsxzz78.html",
|
||||
"https://www.iqiyi.com/v_19rr1lm35o.html",
|
||||
];
|
||||
}
|
||||
|
||||
// 新的tvid获取方法
|
||||
this.get_tvid = async (url) => {
|
||||
async get_tvid(url) {
|
||||
const id = /v_(\w+)/.exec(url)[1];
|
||||
const api = `https://pcw-api.iq.com/api/decode/${id}?platformId=3&modeCode=intl&langCode=sg`;
|
||||
const response = await axios.get(api);
|
||||
@@ -23,13 +23,13 @@ function Iqiyi() {
|
||||
};
|
||||
|
||||
// 获取视频基础信息
|
||||
this.get_video_info = async (tvid) => {
|
||||
async get_video_info(tvid) {
|
||||
const api = `https://pcw-api.iqiyi.com/video/video/baseinfo/${tvid}`;
|
||||
const response = await axios.get(api);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
this.resolve = async (url) => {
|
||||
async resolve(url) {
|
||||
// 1. 获取tvid
|
||||
const tvid = await this.get_tvid(url);
|
||||
|
||||
@@ -68,21 +68,21 @@ function Iqiyi() {
|
||||
return promises;
|
||||
};
|
||||
|
||||
function extract(xml, tag) {
|
||||
extract(xml, tag) {
|
||||
const reg = new RegExp(`<${tag}>(.*?)</${tag}>`, "g");
|
||||
const res = xml.match(reg)
|
||||
?.map(x => x.substring(tag.length + 2, x.length - tag.length - 3));
|
||||
return res || [];
|
||||
}
|
||||
|
||||
this.xml2json = (xml, contents, length) => {
|
||||
const danmaku = extract(xml, "content");
|
||||
const showTime = extract(xml, "showTime");
|
||||
const color = extract(xml, "color");
|
||||
xml2json(xml, contents, length) {
|
||||
const danmaku = this.extract(xml, "content");
|
||||
const showTime = this.extract(xml, "showTime");
|
||||
const color = this.extract(xml, "color");
|
||||
|
||||
const step = Math.ceil(danmaku.length * length / 10000);
|
||||
for (let i = 0; i < danmaku.length; i += step) {
|
||||
const content = JSON.parse(JSON.stringify(content_template));
|
||||
const content = JSON.parse(JSON.stringify(this.content_template));
|
||||
content.timepoint = showTime[i];
|
||||
content.color = parseInt(color[i], 16);
|
||||
content.content = danmaku[i];
|
||||
@@ -91,7 +91,7 @@ function Iqiyi() {
|
||||
}
|
||||
};
|
||||
|
||||
this.parse = async (promises) => {
|
||||
async parse(promises) {
|
||||
memory();
|
||||
let datas = (await Promise.allSettled(promises))
|
||||
.filter(x => x.status === "fulfilled")
|
||||
@@ -110,25 +110,15 @@ function Iqiyi() {
|
||||
return contents;
|
||||
};
|
||||
|
||||
this.work = async (url) => {
|
||||
const promises = await this.resolve(url);
|
||||
console.log(this.name, "api lens:", promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: "ok"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Iqiyi;
|
||||
// module.exports = Iqiyi;
|
||||
|
||||
if (!module.parent) {
|
||||
const m = new Iqiyi();
|
||||
m.work(m.example_urls[0])
|
||||
.then(() => {
|
||||
console.log(m.title);
|
||||
memory();
|
||||
});
|
||||
}
|
||||
// if (!module.parent) {
|
||||
// const m = new Iqiyi();
|
||||
// m.work(m.example_urls[0])
|
||||
// .then(() => {
|
||||
// console.log(m.title);
|
||||
// memory();
|
||||
// });
|
||||
// }
|
||||
@@ -1,16 +1,20 @@
|
||||
const urlmodule = require("url");
|
||||
const axios = require("axios");
|
||||
const {time_to_second, content_template} = require("./utils");
|
||||
import urlmodule from "url";
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
|
||||
function Mgtv() {
|
||||
this.name = "芒果TV";
|
||||
this.domain = "mgtv.com";
|
||||
this.example_urls = [
|
||||
"https://www.mgtv.com/b/336727/8087768.html",
|
||||
"https://www.mgtv.com/b/459529/17730031.html" //api lens 90
|
||||
];
|
||||
|
||||
this.resolve = async (url) => {
|
||||
export default class MgtvSource extends BaseSource {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "芒果TV";
|
||||
this.domain = "mgtv.com";
|
||||
this.example_urls = [
|
||||
"https://www.mgtv.com/b/336727/8087768.html",
|
||||
"https://www.mgtv.com/b/459529/17730031.html" //api lens 90
|
||||
];
|
||||
}
|
||||
|
||||
async resolve(url) {
|
||||
const api_video_info = "https://pcweb.api.mgtv.com/video/info";
|
||||
const api_danmaku = "https://galaxy.bz.mgtv.com/rdbarrage";
|
||||
const q = urlmodule.parse(url, true);
|
||||
@@ -22,15 +26,15 @@ function Mgtv() {
|
||||
const time = res.data.data.info.time;
|
||||
|
||||
const step = 60 * 1000;
|
||||
const end_time = time_to_second(time) * 1000;
|
||||
const end_time = this.time_to_second(time) * 1000;
|
||||
let promises = [];
|
||||
for (let i = 0; i < end_time; i += step) {
|
||||
promises.push(axios({method: "get", url: api_danmaku, params: {vid, cid, time: i}}));
|
||||
}
|
||||
return promises;
|
||||
};
|
||||
}
|
||||
|
||||
this.parse = async (promises) => {
|
||||
async parse(promises) {
|
||||
let contents = [];
|
||||
const results = await Promise.allSettled(promises);
|
||||
let datas = results.filter(result => result.status === 'fulfilled')
|
||||
@@ -39,7 +43,7 @@ function Mgtv() {
|
||||
if (data.data.items === null)
|
||||
continue;
|
||||
for (const item of data.data.items) {
|
||||
const content = JSON.parse(JSON.stringify(content_template));
|
||||
const content = JSON.parse(JSON.stringify(this.content_template));
|
||||
content.timepoint = item.time / 1000;
|
||||
content.content = item.content;
|
||||
content.uid = item.uid;
|
||||
@@ -48,29 +52,17 @@ function Mgtv() {
|
||||
}
|
||||
// contents = make_response(contents);
|
||||
return contents;
|
||||
};
|
||||
|
||||
this.work = async (url) => {
|
||||
const promises = await this.resolve(url);
|
||||
console.log(this.name,"api lens:", promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: "ok"
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Mgtv;
|
||||
// module.exports = Mgtv;
|
||||
|
||||
if (!module.parent) {
|
||||
const m = new Mgtv();
|
||||
// if (!module.parent) {
|
||||
// const m = new Mgtv();
|
||||
|
||||
m.work(m.example_urls[0]).then(() => {
|
||||
console.log(m.content);
|
||||
console.log(m.title);
|
||||
});
|
||||
}
|
||||
// m.work(m.example_urls[0]).then(() => {
|
||||
// console.log(m.content);
|
||||
// console.log(m.title);
|
||||
// });
|
||||
// }
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
const urlmodule = require("url");
|
||||
const axios = require("axios");
|
||||
const whacko = require("whacko");
|
||||
const {content_template} = require("./utils");
|
||||
import urlmodule from "url";
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
import whacko from "whacko";
|
||||
|
||||
function Tencentvideo() {
|
||||
this.name = "腾讯视频";
|
||||
this.domain = "v.qq.com";
|
||||
this.example_urls = [
|
||||
"https://v.qq.com/x/cover/53q0eh78q97e4d1/x00174aq5no.html",//api lens 50
|
||||
"https://v.qq.com/x/cover/mzc00200fph94nw/l00448ijvve.html",//api lens 91
|
||||
"https://v.qq.com/x/cover/mzc00200fhhxx8d/h0046u6z1iu.html",//api lens 215 OOM
|
||||
"https://v.qq.com/x/cover/0s4fa14ciz3ohd0/p0047w54ncc.html",//api lens 297 OOM
|
||||
];
|
||||
export default class TencentvideoSource extends BaseSource {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "腾讯视频";
|
||||
this.domain = "v.qq.com";
|
||||
this.example_urls = [
|
||||
"https://v.qq.com/x/cover/53q0eh78q97e4d1/x00174aq5no.html",//api lens 50
|
||||
"https://v.qq.com/x/cover/mzc00200fph94nw/l00448ijvve.html",//api lens 91
|
||||
"https://v.qq.com/x/cover/mzc00200fhhxx8d/h0046u6z1iu.html",//api lens 215 OOM
|
||||
"https://v.qq.com/x/cover/0s4fa14ciz3ohd0/p0047w54ncc.html",//api lens 297 OOM
|
||||
];
|
||||
}
|
||||
|
||||
this.resolve = async (url) => {
|
||||
async resolve(url) {
|
||||
const api_danmaku_base = "https://dm.video.qq.com/barrage/base/";
|
||||
const api_danmaku_segment = "https://dm.video.qq.com/barrage/segment/";
|
||||
const q = urlmodule.parse(url, true);
|
||||
@@ -43,9 +46,9 @@ function Tencentvideo() {
|
||||
promises.push(axios.get(`${api_danmaku_segment}${vid}/${item.segment_name}`));
|
||||
}
|
||||
return promises;
|
||||
};
|
||||
}
|
||||
|
||||
this.parse = async (promises) => {
|
||||
async parse(promises) {
|
||||
let contents = [];
|
||||
const results = await Promise.allSettled(promises);
|
||||
let datas = results.filter(result => result.status === 'fulfilled')
|
||||
@@ -53,7 +56,7 @@ function Tencentvideo() {
|
||||
|
||||
for (const data of datas) {
|
||||
for (const item of data.barrage_list) {
|
||||
const content = JSON.parse(JSON.stringify(content_template));
|
||||
const content = JSON.parse(JSON.stringify(this.content_template));
|
||||
content.timepoint = item.time_offset / 1000;
|
||||
if (item.content_style.color) {
|
||||
const content_style = JSON.stringify(item.content_style.color);
|
||||
@@ -65,30 +68,17 @@ function Tencentvideo() {
|
||||
}
|
||||
// contents = make_response(contents);
|
||||
return contents;
|
||||
};
|
||||
|
||||
this.work = async (url) => {
|
||||
const promises = await this.resolve(url);
|
||||
if (!this.error_msg) {
|
||||
console.log(this.name, "api lens:", promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
}
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: this.error_msg ? this.error_msg : "ok"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Tencentvideo;
|
||||
// module.exports = Tencentvideo;
|
||||
|
||||
if (!module.parent) {
|
||||
console.log("main");
|
||||
const t = new Tencentvideo();
|
||||
t.work(t.example_urls[0]).then(() => {
|
||||
console.log(t.content);
|
||||
console.log(t.title);
|
||||
});
|
||||
}
|
||||
// if (!module.parent) {
|
||||
// console.log("main");
|
||||
// const t = new Tencentvideo();
|
||||
// t.work(t.example_urls[0]).then(() => {
|
||||
// console.log(t.content);
|
||||
// console.log(t.title);
|
||||
// });
|
||||
// }
|
||||
@@ -1,22 +0,0 @@
|
||||
const content_template = {
|
||||
timepoint: 0, // 弹幕发送时间(秒)
|
||||
ct: 1, // 弹幕类型,1-3 为滚动弹幕、4 为底部、5 为顶端、6 为逆向、7 为精确、8 为高级
|
||||
size: 25, //字体大小,25 为中,18 为小
|
||||
color: 16777215, //弹幕颜色,RGB 颜色转为十进制后的值,16777215 为白色
|
||||
unixtime: Math.floor(Date.now() / 1000), //Unix 时间戳格式
|
||||
uid: 0, //发送人的 id
|
||||
content: "",
|
||||
};
|
||||
|
||||
function time_to_second(time) {
|
||||
const t = time.split(":");
|
||||
let s = 0;
|
||||
let m = 1;
|
||||
while (t.length > 0) {
|
||||
s += m * parseInt(t.pop(), 10);
|
||||
m *= 60;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
module.exports = {time_to_second, content_template};
|
||||
@@ -1,19 +1,22 @@
|
||||
const urlmodule = require('url');
|
||||
const axios = require('axios');
|
||||
const cookie = require('cookie');
|
||||
const crypto = require('crypto');
|
||||
const { content_template } = require('./utils');
|
||||
import urlmodule from "url";
|
||||
import axios from "axios";
|
||||
import BaseSource from "./base.mjs";
|
||||
import cookie from "cookie";
|
||||
import crypto from "crypto";
|
||||
|
||||
function Youku() {
|
||||
this.name = '优酷';
|
||||
this.domain = 'v.youku.com';
|
||||
this.example_urls = [
|
||||
'https://v.youku.com/v_show/id_XNTE5NjUxNjUyOA==.html',
|
||||
'https://v.youku.com/v_show/id_XMTc1OTE4ODI5Ng==.html',
|
||||
'https://v.youku.com/v_show/id_XNTkxNDY2Nzg2MA==.html'
|
||||
];
|
||||
export default class YoukuSource extends BaseSource {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = '优酷';
|
||||
this.domain = 'v.youku.com';
|
||||
this.example_urls = [
|
||||
'https://v.youku.com/v_show/id_XNTE5NjUxNjUyOA==.html',
|
||||
'https://v.youku.com/v_show/id_XMTc1OTE4ODI5Ng==.html',
|
||||
'https://v.youku.com/v_show/id_XNTkxNDY2Nzg2MA==.html'
|
||||
];
|
||||
}
|
||||
|
||||
this.get_tk_enc = async () => {
|
||||
async get_tk_enc() {
|
||||
const api_url = 'https://acs.youku.com/h5/mtop.com.youku.aplatform.weakget/1.0/?jsv=2.5.1&appKey=24679788';
|
||||
let cookies = undefined;
|
||||
// 服务端可能报错:"x-retcode": "FAIL_SYS_INTERNAL_FAULT"
|
||||
@@ -26,8 +29,8 @@ function Youku() {
|
||||
targetCookie = Object.assign(targetCookie, cookie.parse(cookieStr));
|
||||
}
|
||||
return targetCookie;
|
||||
};
|
||||
this.get_cna = async () => {
|
||||
}
|
||||
async get_cna() {
|
||||
const api_url = 'https://log.mmstat.com/eg.js';
|
||||
const res = await axios.get(api_url);
|
||||
const cookies = res.headers['set-cookie'];
|
||||
@@ -36,22 +39,22 @@ function Youku() {
|
||||
targetCookie = Object.assign(targetCookie, cookie.parse(cookieStr));
|
||||
}
|
||||
return targetCookie['cna'];
|
||||
};
|
||||
}
|
||||
|
||||
const yk_msg_sign = (msg) => {
|
||||
yk_msg_sign(msg) {
|
||||
const md5 = crypto.createHash('md5');
|
||||
return md5.update(msg + 'MkmC9SoIw6xCkSKHhJ7b5D2r51kBiREr')
|
||||
.digest('hex');
|
||||
};
|
||||
}
|
||||
|
||||
const yk_t_sign = (token, t, appkey, data) => {
|
||||
yk_t_sign(token, t, appkey, data) {
|
||||
const text = [token, t, appkey, data].join('&');
|
||||
const md5 = crypto.createHash('md5');
|
||||
return md5.update(text)
|
||||
.digest('hex');
|
||||
};
|
||||
}
|
||||
|
||||
const get_vinfos_by_video_id = async (url) => {
|
||||
async get_vinfos_by_video_id(url) {
|
||||
const q = urlmodule.parse(url, true);
|
||||
const path = q.pathname.split('/');
|
||||
const video_id = path.slice(-1)[0].split('.')[0].slice(3);
|
||||
@@ -70,9 +73,9 @@ function Youku() {
|
||||
console.log('video_id:', video_id, 'duration:', duration, 'title:', this.title);
|
||||
return [video_id, duration];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.resolve = async (url) => {
|
||||
async resolve(url) {
|
||||
const cna = await this.get_cna();
|
||||
const tk_enc = await this.get_tk_enc();
|
||||
const headers = {
|
||||
@@ -81,7 +84,7 @@ function Youku() {
|
||||
'Referer': 'https://v.youku.com',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
|
||||
};
|
||||
const [vid, duration] = await get_vinfos_by_video_id(url);
|
||||
const [vid, duration] = await this.get_vinfos_by_video_id(url);
|
||||
|
||||
const max_mat = Math.floor(duration / 60) + 1;
|
||||
let promises = [];
|
||||
@@ -104,14 +107,14 @@ function Youku() {
|
||||
const buff = Buffer.from(str, 'utf-8');
|
||||
const msg_b64encode = buff.toString('base64');
|
||||
msg['msg'] = msg_b64encode;
|
||||
msg['sign'] = yk_msg_sign(msg_b64encode);
|
||||
msg['sign'] = this.yk_msg_sign(msg_b64encode);
|
||||
const data = JSON.stringify(msg);
|
||||
const t = Date.now();
|
||||
const params = {
|
||||
'jsv': '2.5.6',
|
||||
'appKey': '24679788',
|
||||
't': t,
|
||||
'sign': yk_t_sign(tk_enc['_m_h5_tk'].slice(0, 32), t, '24679788', data),
|
||||
'sign': this.yk_t_sign(tk_enc['_m_h5_tk'].slice(0, 32), t, '24679788', data),
|
||||
'api': 'mopen.youku.danmu.list',
|
||||
'v': '1.0',
|
||||
'type': 'originaljson',
|
||||
@@ -125,9 +128,9 @@ function Youku() {
|
||||
}));
|
||||
}
|
||||
return promises;
|
||||
};
|
||||
}
|
||||
|
||||
this.parse = async (promises) => {
|
||||
async parse(promises) {
|
||||
let contents = [];
|
||||
const results = await Promise.allSettled(promises);
|
||||
let datas = results.filter(result => result.status === 'fulfilled')
|
||||
@@ -142,7 +145,7 @@ function Youku() {
|
||||
// 接口请求情况
|
||||
// console.log(i, res.ret[0])
|
||||
for (const danmu of danmus) {
|
||||
const content = JSON.parse(JSON.stringify(content_template));
|
||||
const content = JSON.parse(JSON.stringify(this.content_template));
|
||||
content.timepoint = danmu['playat'] / 1000;
|
||||
if (danmu.propertis.color) {
|
||||
content.color = JSON.parse(danmu.propertis).color;
|
||||
@@ -153,27 +156,17 @@ function Youku() {
|
||||
}
|
||||
// contents = make_response(contents);
|
||||
return contents;
|
||||
};
|
||||
}
|
||||
|
||||
this.work = async (url) => {
|
||||
const promises = await this.resolve(url);
|
||||
console.log(this.name, 'api lens:', promises.length);
|
||||
this.content = await this.parse(promises);
|
||||
return {
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
msg: 'ok'
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Youku;
|
||||
// module.exports = Youku;
|
||||
|
||||
if (!module.parent) {
|
||||
const b = new Youku();
|
||||
b.work(b.example_urls[2])
|
||||
.then(() => {
|
||||
// console.log(b.content);
|
||||
console.log(b.title);
|
||||
});
|
||||
}
|
||||
// if (!module.parent) {
|
||||
// const b = new Youku();
|
||||
// b.work(b.example_urls[2])
|
||||
// .then(() => {
|
||||
// // console.log(b.content);
|
||||
// console.log(b.title);
|
||||
// });
|
||||
// }
|
||||
@@ -1,18 +1,13 @@
|
||||
const express = require("express");
|
||||
const axios = require("axios");
|
||||
import express from "express";
|
||||
import axios from "axios";
|
||||
import { createSourceList } from "./sources.mjs";
|
||||
import memory from "../utils/memory.js";
|
||||
import db from "../utils/db.js";
|
||||
import { inflateRawSync } from "zlib";
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
const URL = require("url");
|
||||
const {
|
||||
bilibili,
|
||||
mgtv,
|
||||
tencentvideo,
|
||||
youku,
|
||||
iqiyi,
|
||||
gamer,
|
||||
} = require("./api/base");
|
||||
const list = [bilibili, mgtv, tencentvideo, youku, iqiyi, gamer];
|
||||
const memory = require("../utils/memory");
|
||||
const db = require("../utils/db");
|
||||
const list = createSourceList();
|
||||
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||
|
||||
// 返回对象{msg: "ok", title: "标题", content: []}
|
||||
@@ -81,10 +76,10 @@ async function resolve(req, res) {
|
||||
// 记录视频信息
|
||||
db.videoInfoInsert({url,title:ret.title})
|
||||
//B站视频,直接重定向
|
||||
if (ret.url)
|
||||
if (ret.url) {
|
||||
res.redirect(ret.url);
|
||||
else {
|
||||
res.set('Cache-Control', 'public, max-age=86400'); // one year
|
||||
} else {
|
||||
res.set('Cache-Control', 'public, max-age=86400'); // 缓存一天
|
||||
res.render("danmaku-xml", { contents: ret.content });
|
||||
}
|
||||
}
|
||||
@@ -131,4 +126,5 @@ router.get("/delete", async function (req, res) {
|
||||
res.send(`成功请求删除三个月以前的记录,删除情况请查看日志`);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// module.exports = router;
|
||||
export default router;
|
||||
19
routes/sources.mjs
Normal file
19
routes/sources.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import BilibiliSource from "./api/bilibili.mjs";
|
||||
import MgtvSource from "./api/mgtv.mjs";
|
||||
import TencentvideoSource from "./api/tencentvideo.mjs";
|
||||
import YoukuSource from "./api/youku.mjs";
|
||||
import IqiyiSource from "./api/iqiyi.mjs";
|
||||
import GamerSource from "./api/gamer.mjs";
|
||||
|
||||
export function createSourceList() {
|
||||
const sourceClasses = [
|
||||
BilibiliSource,
|
||||
MgtvSource,
|
||||
TencentvideoSource,
|
||||
YoukuSource,
|
||||
IqiyiSource,
|
||||
GamerSource
|
||||
];
|
||||
|
||||
return sourceClasses.map(SourceClass => new SourceClass());
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
let chai = require("chai");
|
||||
let chaiHttp = require("chai-http");
|
||||
let app = require("../app");
|
||||
const { bilibili, mgtv, tencentvideo, youku, iqiyi, gamer } = require("../routes/api/base");
|
||||
const list = [bilibili, mgtv, tencentvideo, youku, iqiyi, gamer];
|
||||
import chai from "chai";
|
||||
import chaiHttp from "chai-http";
|
||||
import app from "../app.js";
|
||||
import { createSourceList } from "../routes/sources.mjs";
|
||||
|
||||
const list = createSourceList();
|
||||
chai.should();
|
||||
chai.use(chaiHttp);
|
||||
|
||||
@@ -43,14 +44,15 @@ describe("App", () => {
|
||||
chai.request(app)
|
||||
.get("/")
|
||||
.query({url})
|
||||
.redirects(0)
|
||||
.end((err, res) => {
|
||||
if (err) {
|
||||
//B站弹幕获取会遇到解压错误
|
||||
err.code.should.equal("Z_DATA_ERROR")
|
||||
}
|
||||
if (res) {
|
||||
res.should.have.status(200);
|
||||
res.header["content-type"].should.equal("application/xml; charset=utf-8");
|
||||
if (name === "B站") {
|
||||
res.should.redirect;
|
||||
} else {
|
||||
res.should.have.status(200);
|
||||
res.header["content-type"].should.equal("application/xml; charset=utf-8");
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
32
utils/db.js
32
utils/db.js
@@ -125,24 +125,24 @@ async function deleteAccess() {
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const ret = await deleteAccess();
|
||||
console.log(ret);
|
||||
}
|
||||
// async function main() {
|
||||
// const ret = await deleteAccess();
|
||||
// console.log(ret);
|
||||
// }
|
||||
|
||||
if (!module.parent) {
|
||||
accessInsert({
|
||||
'ip': '127.0.0.1',
|
||||
'url': 'https://www.mgtv.com/b/336727/8087768.html',
|
||||
'UA': 'PostmanRuntime/7.37.3'
|
||||
});
|
||||
// if (!module.parent) {
|
||||
// accessInsert({
|
||||
// 'ip': '127.0.0.1',
|
||||
// 'url': 'https://www.mgtv.com/b/336727/8087768.html',
|
||||
// 'UA': 'PostmanRuntime/7.37.3'
|
||||
// });
|
||||
|
||||
videoInfoInsert({
|
||||
'url': 'https://www.mgtv.com/b/336727/8087768.html',
|
||||
'title': '婚前21天'
|
||||
});
|
||||
// videoInfoInsert({
|
||||
// 'url': 'https://www.mgtv.com/b/336727/8087768.html',
|
||||
// 'title': '婚前21天'
|
||||
// });
|
||||
|
||||
main();
|
||||
}
|
||||
// main();
|
||||
// }
|
||||
|
||||
module.exports = { errorInsert, accessInsert, accessCountQuery, videoInfoInsert, hotlistQuery, deleteAccess };
|
||||
|
||||
Reference in New Issue
Block a user