// ==UserScript==
// @name 巴哈姆特動畫瘋小幫手:封面圖 & 自動開始 & 留言連結 & 彈幕熱圖
// @namespace http://tampermonkey.net/
// @version 1.7.5
// @description 幫巴哈姆特動畫瘋加上封面 & 自動播放 & 留言區的直連連結 & 彈幕熱圖
// @author Rplus
// @match https://ani.gamer.com.tw/animeVideo.php?sn=*
// @license WTFPL
// @grant GM_registerMenuCommand
// @grant GM.setValue
// @grant GM.getValue
// @run-at document-end
// ==/UserScript==
(async function() {
let options = await GM.getValue('options');
if (!options) {
options = {
cover: true,
autostart: true,
permalink: true,
heatmap: true,
heatmapVisibility: true,
};
GM.setValue('options', options);
};
let optionsText = {
cover: '封面圖',
autostart: '自動開始',
permalink: '留言連結',
heatmap: '彈幕熱圖',
};
function triggerConfig(type) {
options[type] = !options[type];
GM.setValue('options', options);
}
GM_registerMenuCommand(getMenuText('cover'), () => triggerConfig('cover'), 'C');
GM_registerMenuCommand(getMenuText('autostart'), () => triggerConfig('autostart'), 'A');
GM_registerMenuCommand(getMenuText('permalink'), () => triggerConfig('permalink'), 'P');
GM_registerMenuCommand(getMenuText('heatmap'), () => triggerConfig('heatmap'), 'H');
GM_registerMenuCommand('init', () => init(), 'I');
function getMenuText(type) {
return `${options[type] ? '✅ 已啟用' : '❎ 已停用'}:${optionsText[type]}`;
}
unsafeWindow.addEventListener('load', init);
unsafeWindow.navigation.addEventListener('navigate', () => {
setTimeout(init, 2000);
});
unsafeWindow.BahaWall.showUpload = (e) => {
e.closest('.reply-input')?.querySelector('input[type="file"]')?.click();
}
document.querySelector('#w-post-box').addEventListener('click', (e) => {
if (e.target.tagName !== 'INPUT' || e.target.type !== 'file') {
return;
}
e.target.accept = 'image/*';
})
function init() {
if (options.cover) {
initCover();
}
// latest duration
// animefun.breakPoint.breakPoint
if (options.autostart) {
// auto start when it is not comment permalink
if (location.search.indexOf('pcid') === -1) {
checkReady();
}
}
// right click to add permalink
if (options.permalink) {
document.querySelector('.webview_commendlist').addEventListener('contextmenu', right_click_comment_to_add_permalink);
}
// danmu heatmap
if (options.heatmap) {
danmuHelper();
}
}
function right_click_comment_to_add_permalink(e) {
let span = e.target;
if (span.className !== 'reply_time') { return; }
let a = span.parentElement.querySelector('a.reply_menu');
if (!a) { return; }
e.preventDefault();
let config = JSON.parse(a.dataset?.tippyMenuComment);
let qs = new URLSearchParams({
sn: new URLSearchParams(location.search).get('sn'),
pcid: config.pid || config.cid,
});
if (config.pid) {
qs.append('cid', config.cid);
}
let url = `https://ani.gamer.com.tw/animeVideo.php?` + qs.toString();
span.className += ' inited';
span.innerHTML = `<a href="${url}" target="_blank"># ${span.textContent}</a>`;
}
function initCover() {
let cover = (unsafeWindow.ani_video_html5_api?.poster !== location.href && unsafeWindow.ani_video_html5_api?.poster) || unsafeWindow.animefun.poster;
// insert poster
// document.querySelector('h1')?.insertAdjacentHTML('afterbegin', `
// <a href="${cover}" target="_blank">
// <img src="${cover}" style="float: left; height: 2em; margin-top: 4px; margin-right: 8px;" />
// </a>`);
let h1 = document.querySelector('h1');
if (h1) {
h1.innerHTML = `
<a href="${cover}" target="_blank">
<img src="${cover}" style="float: left; height: 2em; margin-top: 4px; margin-right: 8px;" />
</a>${h1.textContent}`;
}
// insert published time
let timeTag = document.querySelector('.anime_info_detail .uploadtime');
let time = timeTag?.textContent.split(':')?.[1];
if (time) {
timeTag.textContent += ` (${getRelatedDays(time)}天前)`;
}
}
function getRelatedDays(time = new Date()) {
return ((new Date() - new Date(time))/(1000*60*60*24)).toFixed();
}
function checkReady(counter = 0) {
setTimeout(() => {
console.log(111, unsafeWindow.adult);
if (unsafeWindow.AnimeRoute && unsafeWindow.adult && unsafeWindow.animefun) {
unsafeWindow.adult.click();
} else {
if (counter < 40) {
checkReady(counter + 1);
}
}
}, 1000)
}
function danmuHelper() {
console.log('danmuHelper')
let _danmu = unsafeWindow.animefun?.danmu;
let sn = new URLSearchParams(location.search)?.get('sn');
if (_danmu && _danmu.length) {
danmuAnal(_danmu);
} else {
fetch('https://ani.gamer.com.tw/ajax/danmuGet.php', {
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
cache: 'force-cache',
body: `sn=${sn}`,
method: "POST",
}).then(r => r.json()).then(danmuAnal);
}
}
function updateVideoDuration(e) {
let key = '';
let target = document.querySelector('.danmu-heatmap');
let new_v = document.getElementById('ani_video_html5_api')?.duration;
let old_v = parseFloat(getComputedStyle(target).getPropertyValue('--video-duration'));
if (new_v && old_v && new_v > old_v && isFinite(new_v)) {
console.log('video-source-duration', {new_v, old_v});
target.style.setProperty('--video-source-duration', new_v);
document.getElementById('ani_video_html5_api').removeEventListener('durationchange', updateVideoDuration);
}
}
let timer = 0;
function obVideo() {
if (!document.getElementById('ani_video_html5_api') && timer < 20) {
setTimeout(obVideo, 1000);
} else {
document.getElementById('ani_video_html5_api').addEventListener('durationchange', updateVideoDuration);
}
}
function danmuAnal(danmu) {
console.log('danmuAnal')
// let byUser = danmu.reduce((all, i) => {
// let uid = i.userid;
// if (!all[uid]) { all[uid] = []; }
// all[uid].push(i);
// return all;
// }, {});
let danmu_duration = danmu[danmu.length - 1].time / 10;
obVideo();
let danmu_set_item = document.querySelector('.ani-setting-item.danmu');
if (!danmu_set_item) {
document.querySelector('#ani-tab-content-2 .ani-setting-section:not(.is-seperate) .ani-setting-item')?.insertAdjacentHTML('afterend', `
<div class="ani-setting-item ani-flex ani-setting-item--danmu">
<div class="ani-setting-label">彈幕熱圖</div>
<div class="ani-set-flex-right">
<div class="ani-checkbox">
<label class="ani-checkbox__label">
<input type="checkbox" id="danmu-heatmap-ckbox" ${options.heatmapVisibility ? 'checked' : ''} />
<div class="ani-checkbox__button"></div>
</label>
</div>
</div>
</div>
`);
}
document.querySelector('#danmu-heatmap-ckbox')?.addEventListener('change', (e) => {
document.querySelector('.danmu-heatmap').hidden = !e.target.checked;
triggerConfig('heatmapVisibility');
});
let d1 = document.querySelector('.danmu-heatmap');
let s1 = document.querySelector('.danmu-heatmap-style');
console.log({d1})
if (d1) { d1.remove(); }
if (s1) { s1.remove(); }
// heatmap
let dots = `<div class="danmu-heatmap" ${options.heatmapVisibility ? '' : 'hidden'}>` + danmu.map(i => {
return `<i data-time="${i.time / 10}" style="--danmu-time: ${i.time / 10}" title="${i.text}"></i>`;
}).join('') + '</div>';
let dots_style = `<style class="danmu-heatmap-style">
.danmu-heatmap {
--video-duration: var(--video-source-duration, ${danmu_duration});
position: absolute;
left: 0;
right: 0;
top: 100%;
z-index: 1;
height: 1em;
overflow: hidden;
background-color: #000;
}
.danmu-heatmap i {
position: absolute;
left: calc(var(--danmu-time, 1) / var(--video-duration) * 100%);
margin-left: -0.25em;
width: 0.5em;
height: 1em;
background: #fff;
opacity: var(--dh-op, ${danmu.length > 1000 ? 0.05 : 0.1});
}
.danmu-heatmap i:hover {
opacity: .8;
z-index: 2;
background: #ff0;
}
.reply_time a {
color: unset;
}
</style>`;
let videoframe = document.querySelector('.videoframe');
videoframe.style.position = 'relative';
videoframe.insertAdjacentHTML('beforeend', dots + dots_style);
videoframe.querySelector('.danmu-heatmap').addEventListener('click', danmuJump);
unsafeWindow.navigation.addEventListener('navigate', () => {
videoframe.querySelector('.danmu-heatmap').removeEventListener('click', danmuJump);
});
}
function danmuJump(e) {
if (e.target.tagName !== 'I') { return; }
jumpVideoTime(+e.target.dataset?.time);
}
function jumpVideoTime(time = 0) {
document.getElementById('ani_video_html5_api').currentTime = time;
}
})();