// ==UserScript==
// @name [skypesky 出品] 影音下载助手,支持豆瓣,网易云等主流平台
// @namespace http://www.skypesky.cn/userjs/search_subtitle_for_douban
// @version 19.02.18
// @description 目前只支持电影下载,音乐下载,后续会加入更多功能
// @author skypesky
// @include http*://movie.douban.com/subject/*
// @include http*://music.163.com/*
// @include http*://www.kugou.com/song/*
// @include http*://y.qq.com/n/yqq/song/*
// @include http*://www.kuwo.cn/yinyue/*
// @include http*://www.ximalaya.com/ertong/*
// @grant GM_xmlhttpRequest
// ==========================connect===========================
// @connect *
// @require https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js
// ==/UserScript==
// 全局变量
const config = {
keyword: `#keyword#`,
showLength: 5
};
// 资源类型
const resource = {
type: {
MUSIC: "music",
SUBTITLE: "subtitle",
VIDEO: "video"
}
};
(function () {
'use strict';
// 影片资源
const videoResourceList = [{
name: `RS05`,
description: `RS05`,
url: `http://www.rs05.com/search.php?s=${config.keyword}`,
successSelector: `#movielist .pure-g`
},
{
name: `看天堂`,
description: `看天堂`,
url: `http://www.kantiantang.com/search?text=${config.keyword}`,
successSelector: `.col-md-8 .page-header`
},
{
name: `迅影网`,
description: `迅影网`,
url: `http://www.xunyingwang.com/search?q=${config.keyword}`,
successSelector: `.img-thumbnail`
},
{
name: `BD影视`,
description: `BD影视`,
url: `http://www.bd-film.co/search.jspx?q=${config.keyword}`,
successSelector: `.list-item`
}
];
// 字幕资源
const subtitleResourceList = [{
name: `字幕库`,
url: `https://www.zimuku.cn/search?q=${config.keyword}`,
description: `字幕库`,
successSelector: `.tborder2`,
}];
// 音乐资源
const musicResourceList = [{
name: `全网音乐通用下载工具(根据下载链接)`,
url: `http://music.sonimei.cn/?url=${config.keyword}`,
description: `全网音乐通用下载`,
successSelector: `.am-margin-bottom-sm`
}];
// 喜马拉雅FM
const ximalayaFMMusicResourceList = [{
name: `喜马拉雅FM专用接口`,
url: `http://music.sonimei.cn/?name=${config.keyword}&type=ximalaya`,
description: `全网音乐免费下载(喜马拉雅FM专用接口)`,
successSelector: `.am-margin-bottom-sm`
}];
// 运行在哪个网站?
const websiteConfigList = [{
name: `豆瓣电影频道网站`,
description: `豆瓣网 | 查找电影,电视剧`,
url: `http*://movie.douban.com/subject/*`,
rule: /\/\/movie.douban.com\/subject\/*/,
resourceList: [{
title: `视频下载`,
type: resource.type.VIDEO,
resource: videoResourceList,
},
{
title: `字幕`,
type: resource.type.SUBTITLE,
resource: subtitleResourceList
}
],
keyword: {
selector: `#content > h1 > span:nth-child(1)`,
encodeURIComponent: false,
},
showView: {
selector: `.subjectwrap`
}
}, {
name: `网易云音乐`,
description: `网易云音乐`,
url: `http*://music.163.com/#/song?id=*`,
rule: /\/\/music.163.com\/(#\/)?song\?id=/,
resourceList: [{
title: `歌曲下载`,
type: resource.type.MUSIC,
resource: musicResourceList
}],
keyword: {
selector: `{url}`,
encodeURIComponent: true,
replace: true,
originStr: "m/song",
nowStr: "m/#/song"
},
showView: {
selector: `div.tit`
}
}, {
name: `酷狗音乐`,
description: `酷狗音乐`,
url: `http*://www.kugou.com/song`,
rule: /\/\/www.kugou.com\/song\//,
resourceList: [{
title: `歌曲下载`,
type: resource.type.MUSIC,
resource: musicResourceList
}],
keyword: {
selector: `{url}`,
encodeURIComponent: true,
},
showView: {
selector: `div.songName > a`
}
}, {
name: `qq音乐介绍页`,
description: `qq音乐介绍页`,
url: `https://y.qq.com/n/yqq/song/004GvGjF3fNU8S.html`,
rule: /\/\/y.qq.com\/n\/yqq\/song\//,
resourceList: [{
title: `歌曲下载`,
type: resource.type.MUSIC,
resource: musicResourceList
}],
keyword: {
selector: `{url}`,
encodeURIComponent: true,
},
showView: {
selector: `.data__name_txt`
}
}, {
name: `酷我音乐播放页`,
description: `酷我音乐播放页`,
url: `http://www.kuwo.cn/yinyue/5253684/`,
rule: /\/\/www.kuwo.cn\/yinyue\//,
resourceList: [{
title: `歌曲下载`,
type: resource.type.MUSIC,
resource: musicResourceList
}],
keyword: {
selector: `{url}`,
encodeURIComponent: true,
},
showView: {
selector: `.goComment`
}
},
// {
// name: `喜马拉雅`,
// description: `喜马拉雅`,
// url: `https://www.ximalaya.com/ertong/12462850/`,
// rule: /\/\/www.ximalaya.com\/ertong\//,
// resourceList: [{
// title: `歌曲下载`,
// type: resource.type.MUSIC,
// resource: ximalayaFMMusicResourceList
// }],
// keyword: {
// selector: `.title._t4_`
// },
// showView: {
// selector: `.title._t4_`
// }
// }
];
// 对于resource来说,拿到应有的属性,就可以获得结果,通过回调函数或者返回结果进行下一步处理
function Resource() {
// 未替换的搜索链接
this.originUrl = ``,
// 搜索资源的关键字
this.keyword = ``,
// 资源检索成功的元素的选择器
this.successSelector = ``,
// 真实的地址
this.realUrl = ``
}
/**
** @desc: 初始化参数
** @param: originUrl => 未替换的搜索链接
** keyword => 查询的关键字
** successSelector => 资源检索成功的元素的选择器
**
*/
Resource.prototype.init = function (originUrl, keyword, successSelector) {
this.originUrl = originUrl;
this.keyword = keyword;
this.successSelector = successSelector;
// 获取真实要访问的地址
this.getRealUrl();
};
/**
** @desc: 检测资源是否存在,存在返回true,不存在返回false
** @param: selector => 选择器
** responseText => 响应的html文本信息
** @return: 资源存在返回true,否则否则返回false
*/
Resource.prototype.exist = function (responseText) {
return $(this.successSelector, responseText).length > 0;
};
/**
** @desc: 通过替换字符串,获取真实的查询网址
** @param:
** @return: 返回真实的查询网址
*/
Resource.prototype.getRealUrl = function () {
this.realUrl = this.originUrl.replace(config.keyword, this.keyword);
return this.realUrl;
}
/**
** @desc: 检索资源,检索成功返回真实链接,检索失败返回null
** @param: callback => 回调函数的地址引用
** @return:
*/
Resource.prototype.search = function (callback) {
GM_xmlhttpRequest({
method: `GET`,
url: this.realUrl,
data: {
title: this.keyword
},
onload: (result) => {
// 回调函数
callback(result, this.exist(result.responseText));
}
});
}
const ServerController = {
_websiteConfig: null,
_keyword: ``,
// 初始化配置
init: function (websiteConfig) {
this.websiteConfig = websiteConfig;
// 要查询的关键字
this.getKeyword();
},
// 运行
run: function () {
this.search();
},
// 获取查询结果
search: function () {
// 查询成功后的资源列表
let successResourceList = [];
// 已查询的数目
let searchedNumber = 0;
// 总共需要查询的数目
let totalNeedSearchNumber = 0;
//单个资源的查询结果
let object = {
type: ``,
title: ``,
resourceList: ``
}
// 查询
for (let i = 0; i < this.websiteConfig.resourceList.length; ++i) {
// 存放单个结果
successResourceList[i] = {
title: this.websiteConfig.resourceList[i].title,
type: this.websiteConfig.resourceList[i].type,
resourceList: []
}
// 统计总共需要查询的数目
totalNeedSearchNumber += this.websiteConfig.resourceList[i].resource.length;
for (let j = 0; j < this.websiteConfig.resourceList[i].resource.length; ++j) {
let resource = new Resource();
// 初始化资源属性
resource.init(this.websiteConfig.resourceList[i].resource[j].url, this.keyword, this.websiteConfig.resourceList[i].resource[j].successSelector);
// 查询
resource.search((result, flag) => {
searchedNumber++;
if (flag) { //查询成功
console.log("search success!");
successResourceList[i].resourceList.push({
description: this.websiteConfig.resourceList[i].resource[j].description,
link: resource.getRealUrl()
});
}
});
}
}
// 检测查询是否全部完成
let task = setInterval(() => {
if (searchedNumber == totalNeedSearchNumber && searchedNumber != 0) { // 已查询的条目等于总的总共需要查询的条目,说明查询任务结束了
// 全部查询完成,显示查询结果,关闭定时器
this.showView(successResourceList);
clearInterval(task);
}
}, 100);
},
// 获取要查询的关键字
getKeyword: function () {
if (this.websiteConfig.keyword.selector == `{url}`) {
this.keyword = window.location.href;
} else {
this.keyword = $(this.websiteConfig.keyword.selector).text().split(` `)[0];
}
this.checkReplace();
console.log("keyword: ", this.keyword, this.websiteConfig.keyword.replace);
this.checkEncodeURIComponent();
return this.keyword;
},
checkReplace: function () {
if (this.websiteConfig.keyword.replace) {
console.log("checkReplace()");
this.keyword = this.keyword.replace(this.websiteConfig.keyword.originStr, this.websiteConfig.keyword.nowStr);
}
},
checkEncodeURIComponent: function () {
if (this.websiteConfig.keyword.encodeURIComponent) {
this.keyword = encodeURIComponent(this.keyword);
}
},
showView: function (successResourceList) {
let view = null;
for (let i = 0; i < successResourceList.length; ++i) {
switch (successResourceList[i].type) {
case resource.type.VIDEO:
{
view = ViewTemplate.defaultVideoView(successResourceList[i].title, successResourceList[i].resourceList);
}
break;
case resource.type.SUBTITLE:
{
view = ViewTemplate.defaultVideoView(successResourceList[i].title, successResourceList[i].resourceList);
}
break;
case resource.type.MUSIC:
{
view = ViewTemplate.defaultDownloadView(successResourceList[i].title, successResourceList[i].resourceList);
}
break;
default:
break;
}
this.after(view);
}
},
after: function (view) {
$.runWhenLoad(this.websiteConfig.showView.selector, () => {
$(this.websiteConfig.showView.selector).after(view);
});
}
}
const ViewTemplate = {
defaultVideoView: function (title, data) {
if (!data || data.length <= 0) {
return this.videoEmptyView(title);
}
let table = ``;
table += `<table style='text-align: center; padding: 14px; width: 100%;border: 2px solid #ddd; margin: 20px auto;'>`;
table += `<caption style="text-align: center; padding-bottom: 4px;">${title}</caption>`
for (let i = 0; i < data.length; i = i + config.showLength) {
table += `<tr>`;
for (let j = i; j < i + config.showLength && data[j]; ++j) {
table += `<td style="padding-top: 10px; padding-bottom: 10px;"><a href='${data[j].link}' target='_blank'>${data[j].description}</td>`;
}
table += `</tr>`;
}
table += `</table>`;
return table;
},
videoEmptyView: function (title) {
let table = ``;
table += `<table style='text-align: center; padding: 14px; width: 100%;border: 2px solid #ddd; margin: 20px auto;'>`;
table += `<caption style="text-align: center; padding-bottom: 4px;">${title}</caption>`
table += `<tr>`;
table += `<td style="padding-top: 10px; padding-bottom: 10px;"><a href='javascript: void(0)' target='_blank'>暂无相关资源</td>`;
table += `</tr>`;
table += `</table>`;
return table;
},
defaultDownloadView: function (title, data) {
let view = "";
for (let i = 0; i < data.length; ++i) {
view += `<button style="margin-left: 12px; padding: 6px 12px; background-color: #fff; border: 1px solid #ccc; background-color: rgb(74, 170, 74); color: #fff;" onclick="window.open('${data[i].link}')">${data[i].description}</button>`;
}
return view;
}
}
// 选择执行的控制器
const Controller = {
// 匹配网站
select: function (configList, keyword) {
for (let i = 0; i < configList.length; ++i) {
if (configList[i].rule.test(keyword)) {
return i;
}
}
return -1;
},
// 匹配执行的控制器
run: function (websiteConfigList) {
let url = window.location.href;
let index = this.select(websiteConfigList, url);
if (index != -1) { //匹配成功
console.log("匹配成功! " + websiteConfigList[index].description, url);
ServerController.init(websiteConfigList[index]);
ServerController.run();
JqueryPlugin.loading();
} else { //匹配失败
console.log("匹配失败!", url);
}
}
}
// jquery拓展
const JqueryPlugin = {
loading: function () {
this.runWhenLoad();
},
runWhenLoad: function () {
$.extend({
runWhenLoad: function (selector, callback) {
if ($(selector).length > 0) {
console.log("x1 success");
callback();
return;
}
let task = setInterval(() => {
console.log("x2 block");
if ($(selector).length > 0) {
console.log("x2 success");
clearInterval(task);
callback();
return;
}
}, 50);
}
});
}
}
$(function () {
console.log("loading...");
Controller.run(websiteConfigList);
});
})();