// ==UserScript==
// @name BYRBT Bangumi Info
// @author Deparsoul
// @description 一键生成新番信息
// @namespace https://greasyfork.org/users/726
// @include http*://bt.byr.cn*/upload.php?type=404*
// @include http*://bt.byr.cn*/details.php*
// @include http*://bt.byr.cn*/edit.php*
// @icon http://bt.byr.cn/favicon.ico
// @require https://cdn.jsdelivr.net/gh/deparsoul/[email protected]/bundle.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/resemble.min.js
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @connect mikanani.me
// @connect bgm.tv
// @connect movie.douban.com
// @connect anydb.depar.cc
// @connect *
// @version 20200105
// ==/UserScript==
let GM_scriptVersion = '';
if (GM_info && GM_info.script) {
GM_scriptVersion = GM_info.script.version || GM_scriptVersion;
}
(function ($) {
//$('form').attr('action', 'test'); // 测试用,防止意外提交表单
let type = $('#type,#browsecat>option:selected,#oricat>option:selected').text();
if (type !== '动漫') return;
$('#kdescr, .ckeditor').closest('tr').before('<tr id="bangumi_info_row"><td class="rowhead nowrap">生成新番信息</td><td><input type="button" id="bangumi_info_process" value="开始"></td></tr>');
$('#bangumi_info_process').click(function () {
$('head').prepend('<meta name="referrer" content="no-referrer">'); // 防止在引用外链图片时发送 referrer
$(this).replaceWith(`当前状态:<span id="bangumi_info_global_status"></span><br>
<div id="bangumi_info_filename"></div>
<br>
第一阶段:由种子文件猜测 mikanani.me 网址,如果没有使用原种发种,可能无法正确获得网址,你可以手动填入类似 https://mikanani.me/Home/Episode/* 的网址,点击继续<br>
<input type="text" id="bangumi_info_source" class="bangumi_info_url bangumi_info_reference"><button id="bangumi_info_source_process">继续</button><br>
<div id="bangumi_info_source_preview"></div>
<br>
第二阶段:由 mikanani.me 获取新番封面,以及 bgm.tv 网址,你可以手动填入 bgm.tv 网址,点击继续<br>
<input type="text" id="bangumi_info_bgm" class="bangumi_info_url bangumi_info_reference"><button id="bangumi_info_bgm_process">继续</button><br>
<div id="bangumi_info_bgm_preview"></div>
<br>
可选阶段:如果你对由 bgm.tv 获取的新番【STORY】部分不满意(空白或未翻译等),你可以尝试从 douban.com 获取这些信息,点击继续<br>
<input type="text" id="bangumi_info_douban" class="bangumi_info_url"><button id="bangumi_info_douban_process">继续</button><br>
<div id="bangumi_info_douban_preview"></div>
<br>
封面(优先使用 <a id="bangumi_info_cover_source" class="bangumi_info_cover_link">种子封面</a>,否则尝试使用 <a id="bangumi_info_cover_bgm" class="bangumi_info_cover_link">bgm.tv 封面</a>):<span id="bangumi_info_cover_status"></span><br>
<input type="text" id="bangumi_info_cover" class="bangumi_info_url">文件名:<input type="text" id="bangumi_info_cover_filename"><button id="bangumi_info_cover_check">检查</button><button id="bangumi_info_cover_upload" style="display: none;">上传</button><br>
<img src="" alt="封面预览" id="bangumi_info_cover_preview"><br>
<div id="bangumi_info_auto_fields_wrapper"></div>
<br>
预览<br>
<div id="bangumi_info_preview"></div>
<button class="bangumi_info_fill">点击用以上内容覆盖简介</button><br>
<style>
#bangumi_info_row button {
font-size: 9pt;
}
input.bangumi_info_url {
width: 40em;
}
#bangumi_info_cover_preview {
max-width: 200px;
max-height: 200px;
}
#bangumi_info_preview {
border: 1px solid;
padding: 1em;
}
.bangumi_info_cover_link[href],
.bangumi_info_auto_field {
cursor: pointer;
border: 1px solid #999999;
display: inline-block;
margin: 0 2px 2px 0;
padding: 2px 5px;
}
.bangumi_info_auto_field_name {
color: gray;
}
.bangumi_info_auto_field_new .bangumi_info_auto_field_val {
font-weight: bold;
color: red;
}
</style>`.replace(/(<button )/g, '$1type="button" ')); // 将 type 指定为 button 避免触发表单提交
// URL 文本框
$('.bangumi_info_url').click(function () {
// 单击全选
$(this).select();
}).dblclick(function () {
// 双击在新窗口打开
let url = $(this).val().trim();
if (url) window.open(url, '_blank');
});
let coverBlob;
let coverReady;
let autoFields = new AutoFields(renderPreview);
autoFields.init('#bangumi_info_auto_fields_wrapper');
// 点击填充封面链接
$('#bangumi_info_row').on('click', '.bangumi_info_cover_link', function () {
let href = $(this).attr('href');
if (href) $('#bangumi_info_cover').val(href).change();
return false;
});
$('#bangumi_info_cover').change(function () {
let src = $(this).val().trim();
coverReady = false;
renderPreview();
if (src) {
let match = src.match(/^(?:https?:\/\/([^\/]+))?.*?([^\/]+)$/i);
if (!match) return log('网址无效', -1, 'cover');
$('#bangumi_info_cover_filename').val(match[2]).change();
log('正在读取', 0, 'cover');
GM_request(src, 'blob').then(blob => {
log('读取完成', 1, 'cover');
coverBlob = blob;
$('#bangumi_info_cover_preview').attr('src', URL.createObjectURL(blob));
if (match[1] && match[1] !== 'bt.byr.cn') {
$('#bangumi_info_cover_check').click();
} else {
log('bt.byr.cn 上存存在以下图片,你可以使用该图片发种', 1, 'cover');
coverReady = true;
renderPreview();
}
}).catch(error => {
console.error(error);
log('读取失败', -1, 'cover');
});
}
});
$('#bangumi_info_cover_filename').change(function () {
let input = $('#bangumi_info_cover_filename');
let filename = input.val();
// 过滤文件名
filename = filename.replace(/[^A-Za-z0-9._]/g, '');
filename = filename.replace(/0x/ig, '');
input.val(filename);
$('#bangumi_info_cover_upload').hide(); // 需要先检查才能判断是否需要上传
});
$('#bangumi_info_cover_check').click(function () {
let filename = $('#bangumi_info_cover_filename').val();
log('正在检查 bt.byr.cn 上是否有已有该图片', 0, 'cover');
let byr = `https://bt.byr.cn/ckfinder/userfiles/images/${filename}`;
GM_request(`${byr}?ModPagespeed=off`, 'blob').then(blob => {
// 检查文件内容是否一致
if (!coverBlob) {
notSame();
} else {
log('正在对比图片,这可能需要一段时间,请稍等', 0, 'cover');
resemble(coverBlob).compareTo(blob).scaleToSameSize().ignoreAntialiasing().onComplete(result => {
console.log(result);
if (result.rawMisMatchPercentage < 1) {
$('#bangumi_info_cover').val(byr).change();
} else {
notSame();
}
});
}
function notSame() {
log('bt.byr.cn 存在同名文件,但是内容不同(或者无法确定是否相同),你可以尝试改名上传,或者 ', -1, 'cover');
$('<a class="bangumi_info_cover_link">尝试使用该文件</a>').attr('href', byr).appendTo('#bangumi_info_cover_status');
}
}).catch(() => {
log('bt.byr.cn 上没有同名文件,请尝试上传', -1, 'cover');
$('#bangumi_info_cover_upload').show();
});
});
$('#bangumi_info_cover_upload').click(function () {
if (!coverBlob)
return log('没有可上传的图片', -1, 'cover');
log('正在上传图片', 0, 'cover');
uploadImage(coverBlob, $('#bangumi_info_cover_filename').val(), src => {
log('上传完成', 1, 'cover');
$('#bangumi_info_cover').val(src).change();
});
});
let useHashOriginal = true;
let id = location.href.match(/id=\d+/);
if (id) {
log('正在读取种子文件');
GM_request(`https://bt.byr.cn/download.php?${id}`, 'arraybuffer').then(processTorrent);
}
let file = $('input#torrent');
if (file.length) {
log('请选择种子文件,可以通过种子文件自动生成新番信息,或者你可以按照下方提示手动操作');
useHashOriginal = false;
file.change(function () {
let f = this.files[0];
if (f) reader(f, 'arraybuffer').then(processTorrent);
}).change();
}
function processTorrent(torrent) {
torrent = parseTorrent(torrent);
let hash = useHashOriginal ? torrent.infoHashOriginal : torrent.infoHash;
$('#bangumi_info_source').val(`https://mikanani.me/Home/Episode/${hash}`);
$('#bangumi_info_source_process').click();
// parse file name
let filename = torrent.name;
$('#bangumi_info_filename').text(`种子文件名:${filename}`).prepend('<br>');
autoFields.reset();
autoFields.add('subteam', getMatch(filename, /^\[([^\]]+)]/), 'torrent');
autoFields.add('comic_cname');
autoFields.add('comic_ename', getMatch(filename, /^\[[^\]]+]\[([^\]]+)]/).replace(/_/g, ' '), 'torrent');
autoFields.add('comic_episode', getMatch(filename, /\[(\d{2,3}(?:\.\d)?)(?:v\d|\s*end)?]/i), 'torrent');
autoFields.add('comic_quality', getMatch(filename, /(720|1080)/).replace(/(\d+)/, '$1p'), 'torrent');
autoFields.add('comic_filetype', getMatch(filename, /(mp4|mkv)/i).toUpperCase(), 'torrent');
autoFields.log('已分析文件名');
}
$('#bangumi_info_source_process').click(function () {
log('正在读取 mikanani.me episode');
let preview = $('#bangumi_info_source_preview');
preview.text('');
GM_request($('#bangumi_info_source').val()).then(parsePage).then(page => {
if (!page.find('.episode-desc').length) throw 'mikanani.me 302'; // 判断是否为种子页面,还是被重定向到了首页
preview.text(page.find('.episode-title').text());
let cover = page.find('.episode-desc img:eq(0)').data('src');
if (cover && !cover.startsWith('/')) // 如果以斜杠开头表明该种子本来没有封面
$('#bangumi_info_cover_source').attr('href', cover).click();
let href = page.find('.bangumi-title a').attr('href');
if (!href) return log('无法获取 bgm.tv 网址', -1);
href = `https://mikanani.me${href}`;
log('正在读取 mikanani.me bangumi');
GM_request(href).then(parsePage).then(page => {
$('#bangumi_info_bgm').val(page.find('[href^="http://bgm.tv/subject/"],[href^="https://bgm.tv/subject/"]').attr('href').replace('http:', 'https:'));
$('#bangumi_info_bgm_process').click();
});
}).catch(error => {
console.error(error);
log('mikanani.me 网址无效', -1);
});
});
let descr = {};
$('#bangumi_info_bgm_process').click(function () {
log('正在读取 bgm.tv');
let preview = $('#bangumi_info_bgm_preview');
preview.text('');
let url = $('#bangumi_info_bgm').val();
GM_request(url).then(parsePage).then(page => {
let cover = page.find('.infobox>div>a').attr('href');
if (cover) {
$('#bangumi_info_cover_bgm').attr('href', `https:${cover}`);
if (!$('#bangumi_info_cover').val()) $('#bangumi_info_cover_bgm').click();
}
let title = page.find('.nameSingle>a');
preview.text([title.attr('title'), title.text()].join(' / '));
$('#bangumi_info_douban').removeClass('bangumi_info_reference');
// 将 html 按照换行分割成数组,并提取文本
descr.STORY = (page.find('#subject_summary').html() || '').split('<br>').map(html => $('<p>').html(html).text());
let staff = page.find('#infobox');
autoFields.reset('bgm');
autoFields.add('bgmtv_url', url, 'bgm');
autoFields.add('comic_cname', getMatch(staff.text(), /中文名:\s*(.*)/), 'bgm');
let date = staff.text().match(/放送开始:\s*(\d+)年(\d+)月(\d+)日/);
if (date) {
date.shift();
autoFields.addDate(new Date(date.join('-')), 'bgm');
}
autoFields.log('已分析 bgm.tv');
autoFields.lookup();
let li = staff.find('a').closest('li'); // 找出包含链接的行
// 将 title 中的中文填入
staff.find('a').each(function () {
let a = $(this);
let t = a.attr('title');
if (t) a.text(t.trim());
});
descr.STAFF = staff.find('li').slice(li.first().index(), li.last().index() + 1).map(function () {
let li = $(this);
let tip = li.find('.tip');
let key = tip.text().replace(': ', '');
tip.remove();
let val = li.text();
return `${key}:${val}`;
}).get().slice(0, 9); // 数量限制
log('正在读取 bgm.tv characters');
GM_request('https://bgm.tv' + page.find('a.more:contains("更多角色")').attr('href')).then(parsePage).then(page => {
descr.CAST = page.find('#columnInSubjectA>div>div.clearit').map(function () {
let div = $(this);
let h2 = div.find('>h2');
let char = h2.find('span.tip').text().trim().replace('/ ', '') || h2.find('>a').text().trim();
let cv = div.find('>div.clearit>p').map(function () {
let p = $(this);
return (p.find('small').text() || p.find('>a').text()).trim() || null;
}).get().join('、');
if (char && cv)
return `${char}:${cv}`;
else
return null;
}).get();
renderPreview();
log('生成完成', 1);
});
});
});
$('#bangumi_info_douban_process').click(function () {
log('正在读取 douban.com');
let preview = $('#bangumi_info_douban_preview');
preview.text('');
let url = $('#bangumi_info_douban').addClass('bangumi_info_reference').val();
console.log(url);
GM_request(url).then(parsePage).then(page => {
let title = page.find('h1').text();
preview.text(title);
descr.STORY = page.find('span[property="v:summary"]').text().trim().split(/\n\s*/);
renderPreview();
log('已根据 douban.com 填写了【STORY】部分,如果希望换回 bgm.tv 的信息,请点击第二阶段中的 继续 按钮', 1);
});
});
let fill = $('.bangumi_info_fill');
fill.hide();
let preview = $('#bangumi_info_preview');
fill.click(function () {
CKEDITOR.instances.descr.setData(preview.html());
});
function renderPreview() {
preview.html('');
let row = autoFields.row();
if (row) {
preview.append(`<div><img src="${row}"></div>`);
}
let cover = $('#bangumi_info_cover').val();
if (coverReady) preview.append(`<img src="${cover}" style="max-width: 80%;">`);
for (let key in descr) {
$(`<p>【${key}】</p>`).appendTo(preview);
$(`<p></p>`).html(descr[key].map(escapeHtml).join('<br>')).appendTo(preview);
}
let meta = $(`<p class="byrbt_bangumi_info" data-version="${GM_scriptVersion}" style="font-size: 0.8em; opacity: 0.5; margin-bottom: 0;">以上内容由 BYRBT Bangumi Info 自动生成</p>`);
let reference = $('.bangumi_info_reference').map(function () {
let href = $(this).val().trim();
if (!href) return null;
let a = $(`<a target="_blank" rel="noopener noreferrer"></a>`).attr('href', href).text(href);
let c = 'byrbt_bangumi_info_reference';
a.addClass(c).addClass($(this).attr('id').replace('bangumi_info', c));
return a[0].outerHTML;
}).get();
if (reference.length) {
reference = reference.join(',');
meta.append(`,信息来自:${reference}`);
}
meta.appendTo(preview);
if ($('.ckeditor').length)
fill.show();
else
fill.hide();
}
});
// 封装 GM 的 xhr 函数,返回 Promise
function GM_request(url, responseType, method) {
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: method || 'GET',
url,
responseType,
onload: xhr => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr);
}
},
onerror: xhr => {
reject(xhr);
}
});
});
}
function reader(blob, as) {
return new Promise(function (resolve, reject) {
let reader = new FileReader();
if (as === 'arraybuffer')
reader.readAsArrayBuffer(blob);
else
reader.readAsDataURL(blob);
reader.onload = function () {
resolve(reader.result);
};
});
}
function uploadImage(blob, filename, callback) {
let formData = new FormData();
formData.append('upload', blob, filename);
let xhr = new XMLHttpRequest();
xhr.open('POST', '/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images&CKEditor=descr&CKEditorFuncNum=2&langCode=zh-cn');
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
let match = xhr.responseText.match(/:\/\/bt\.byr\.cn\/ckfinder\/userfiles\/images\/[^']+/);
if (match) callback(`https${match[0]}`);
}
};
}
function escapeHtml(html) {
return $('<p>').text(html).html();
}
function parsePage(html) {
html = html.replace(/\s+src=/ig, ' data-src='); // 避免在解析 html 时加载其中的图片
return $(html);
}
// 显示当前状态
function log(msg, state, type) {
type = type || 'global';
let elem = $(`#bangumi_info_${type}_status`);
let color = 'blue';
if (state > 0) color = 'green';
if (state < 0) color = 'red';
elem.text(msg).css('color', color);
}
function getMatch(str, reg, group) {
group = group || 1;
let match = str.match(reg);
if (match && match[group])
return match[group];
else
return '';
}
function AutoFields(renderPreview) {
let names = {
"subteam": "字幕组",
"comic_cname": "中文名",
"comic_ename": "英文名",
"comic_episode": "集数",
"comic_quality": "分辨率",
"comic_source": "片源",
"comic_filetype": "动漫文件格式",
"comic_year": "发行时间",
"comic_country": "动漫国别",
"small_descr": "副标题",
"url": "IMDb链接",
"dburl": "豆瓣链接",
"bgmtv_url": "Bangumi 番组计划链接",
};
let fields = {};
let lastDate;
let wrapper, div;
let row = null, rowFlag = true;
return {
init(selector) {
wrapper = $(selector);
wrapper.html('<br>字段填写建议,不一定准确,请注意检查,点击自动填写(仅限发布页面,<span class="bangumi_info_auto_field_new"><span class="bangumi_info_auto_field_val">高亮</span></span>表示与目前填写的内容不同),Ctrl+点击复制到剪贴板:<span id="bangumi_info_auto_fields_status"></span><div id="bangumi_info_auto_fields"></div><button type="button" id="bangumi_info_row_toggle"></button>');
div = $('#bangumi_info_auto_fields');
div.on('click', '.bangumi_info_auto_field', function (e) {
let span = $(this);
if (e.ctrlKey || e.altKey) {
GM_setClipboard(span.data('val'));
} else {
let input = $('[name=' + span.data('key') + ']');
input.val(span.data('val')).keyup();
}
});
$('input').keyup(() => {
this.render();
});
wrapper.hide();
$('#bangumi_info_row_toggle').click(() => {
rowFlag = !rowFlag;
this.render();
});
},
log(msg, state) {
log(msg, state, 'auto_fields');
},
reset(source) {
if (!source) {
fields = {};
} else {
for (let key in fields) {
fields[key] = fields[key].filter(current => current.s !== source);
}
}
this.render();
},
get(key) {
if(!fields.hasOwnProperty(key))
return [];
return fields[key].reduce((result, current) => result.indexOf(current.v) < 0 ? result.concat(current.v) : result, []);
},
add(key, val, source) {
if (!fields.hasOwnProperty(key))
fields[key] = [];
if (!val) return;
fields[key].push({v: val, s: source});
this.render();
},
addDate(date, source) {
lastDate = new Date(date.getTime());
this.add('comic_year', format(date), source);
let month = date.getMonth() + 1;
//console.log(month);
if (month % 3 === 0) {
date.setMonth(month);
this.add('comic_year', format(date), source);
}
function format(date) {
return date.toISOString().replace(/(\d+)-(\d+).*/, '$1.$2');
}
},
lookup() {
row = null;
let date = lastDate;
this.reset('anydb');
let titles = this.get('comic_cname').concat(this.get('comic_ename'));
if (date && titles.length) {
date = date.getTime() / 1000;
let query = titles.map(function (title) {
return 'titles[]=' + encodeURIComponent(title);
});
query.push('date=' + date);
let url = 'https://anydb.depar.cc/anime/query?_cf_cache=1&_titles=1&' + query.join('&');
console.log(url);
this.log('正在从 anydb 查询更多信息');
GM_request(url).then(JSON.parse).then(data => {
if (!data.success) return;
let m = data.matched;
if (m.bgm)
row = 'https://anydb.depar.cc/anime/bar.svg?bgm=' + m.bgm.url.match(/(\d+)/)[1];
if (m.douban)
this.add('dburl', m.douban.url.replace(/https:(.*)\//, 'http:$1'), 'anydb');
if (m.douban)
$('#bangumi_info_douban').val(m.douban.url);
m._titles.forEach(t => {
if (t.l === 'zh')
this.add('comic_cname', t.t, 'anydb');
if (t.l === 'en' || t.l === 'x-jat')
this.add('comic_ename', t.t, 'anydb');
});
this.log('分析完成', 1);
});
}
},
row() {
return rowFlag && row;
},
render() {
//console.log(fields);
div.html('');
for (let key in fields) {
this.get(key).forEach(val => {
let input = $('[name=' + key + ']');
let name = names[key] || key;
let span = $('<span class="bangumi_info_auto_field"><span class="bangumi_info_auto_field_name"></span>:<span class="bangumi_info_auto_field_val"></span></span>');
span.children().eq(0).text(name);
span.children().eq(1).text(val);
span.data('key', key);
span.data('val', val);
if (input.length && input.val() !== val) span.addClass('bangumi_info_auto_field_new');
div.append(span);
});
}
wrapper.hide();
if ($('.bangumi_info_auto_field').length)
wrapper.show();
$('#bangumi_info_row_toggle').text((rowFlag ? '关闭' : '启用') + '分数条(测试版)').toggle(row !== null);
renderPreview();
}
};
}
})(jQuery);