ぼっち・ざ・ろっく!というアニメの最速配信がAbemaで行われているのでそちらで視聴しているが、EDの曲を最後まで聴きたいのに勝手に次話に遷移するのが鬱陶しいので改造する。他にも色々あるので直す。
調べると以下のようになっていた。
// 「次のエピソードへ」ボタンの部分
<div class="com-vod-VODNextProgramInfo">
// ボタンが表示されているときはcom-vod-VODNextProgramInfo--is-showというclassがこの要素に加わる
<div class="com-vod-VODNextProgramInfo__inner">
<button type="button" class="com-vod-VODNextProgramInfo__cancel-button" tabindex="-1" disabled="">
キャンセル
</button>
<div class="com-vod-VODNextProgramInfo__next">
<a href="/video/episode/[話URL]?next=true" tabindex="-1" class="com-a-Link com-a-Link--block">
// 次の話へのリンク
<div class="com-vod-VODNextProgramInfo__next-inner">
<span class="com-vod-VODNextProgramInfo__next-symb width="100%" height="100%" role="img" focusable="false">
<svg aria-label="" aria-hidden="true" width="100%" height="100%" role="img" focusable="false">
<use xlink:href="/assets/images/icons/player/play.svg?v=e4aed34cff834c273461#svg-body"/>
</svg>
</span>
<span>次のエピソード : 10</span> // 10の部分は残り秒数で変化
</div>
</a>
</div>
</div>
</div>
// 時刻を表示する部分(再生が終了したことを判定するために使用)
<div class="com-vod-VideoControlBar__time">
<span class="com-vod-VODTime">
<span class="com-a-Text--regular com-a-Text--dark com-a-Text--s">
<time datetime="PT23M40S">23:40</time> // 現在の再生時刻
<span class="com-vod-VODTime__separator">/</span>
<time datetime="PT23M40S">23:40</time>
</span>
</span>
</div>
// エピソード一覧のアイテム(必要な部分のみ抜粋)
<div class="com-content-list-ContentListEpisodeItem">
<div class="com-content-list-ContentListEpisodeItem__content-container">
<div class="com-content-list-ContentListEpisodeItem__overview">
<a href="/video/episode/[話URL]" class="com-content-list-ContentListEpisodeItem__link">
<p class="com-content-list-ContentListEpisodeItem__title">
<span class="com-a-CollapsedText__container" style="line-height:1.5;max-height:1.5em;-webkit-line-clamp:1">
[話タイトル]
</span>
</p>
</a>
</div>
</div>
</div>
特に何も考えずクラス名で要素を選択していじればよさそう。
話のURLについている?next=true
というパラメータについて、これを付けると最初から再生されてくれることがわかった。(自動で次の話に行って前回の続きから再生されることはないのでそれはそう)エピソード一覧のリンクにもこれを付けてしまえばよい。
ユーザースクリプトで書き換える。筆者はTampermonkeyを使用。
最初はMutationObserverで賢くやろうと思ったが、全く変更を検知できないか数百msごとに1回走るかのどちらかになってしまったので脳死でsetInterval()を使うことにした。いつかリベンジしたくはある。
ボタンが表示されるときには button.com-vod-VODNextProgramInfo__cancel-button
のtabindex
が0になっている。
const closeNextProgramInfo = () => {
const cancelButton = document.querySelector('.com-vod-VODNextProgramInfo__cancel-button');
const v = cancelButton?.attributes?.tabindex.value
if (!!v && v !== '-1') {
cancelButton.click();
}
};
現在再生時刻と終端再生時刻を比較して一致したら再生が終わったと判断して遷移する。最初PHPの感覚で文字列にそのまま60掛け算したらめちゃくちゃバグった。
const clickNextProgramInfo = () => {
const time = document.querySelectorAll("span.com-vod-VODTime time");
if (!time) {
return
}
const nowMS = time[0]?.attributes.datetime.value.match(/^PT(\d+)M(\d+)S/);
const now = parseInt(nowMS?.[1]) * 60 + parseInt(nowMS?.[2]);
const maxMS = time[1]?.attributes.datetime.value.match(/^PT(\d+)M(\d+)S/);
const max = parseInt(maxMS?.[1]) * 60 + parseInt(maxMS?.[2]);
if (now && max && now >= max) {
location.href = document.querySelector('div.com-vod-VODNextProgramInfo__next a').attributes.href.value;
}
};
他のものは定期実行されても問題ない(1度きり何かが起きる)が、これは気を付けないと無限にURLが長くなっていってしまうので注意。stopImmediatePropagation()は、この部分がaタグのデフォルト動作ではなく何らかのクリックイベントで遷移しているっぽく、他のイベントを殺さないとhrefを書き換えても遷移先が変わらなかったため付けている。
const changeLink = () => {
const links = document.querySelectorAll('.com-content-list-ContentListEpisodeItem__link');
links.forEach(function(element) {
const before = element.getAttribute('href')
if (!before.includes('?')) {
element.setAttribute('href', before + '?next=true');
element.addEventListener('click', (event) => {
event.stopImmediatePropagation();
});
}
})
};
setInterval()のドキュメントそのまま。
const intervalID = setInterval(() => {
closeNextProgramInfo();
clickNextProgramInfo();
changeLink();
}, 1000);
datetime
属性が PT1H23M45S
みたいな感じに変わるっぽいので必要な方は頑張ってください君だけの最強のAbemaプレイヤーを手に入れよう!
自己責任で。
// ==UserScript==
// @name Abema Enhancer
// @version 1
// @description なんやかんや
// @match https://abema.tv/*
// @grant none
// ==/UserScript==
(() => {
'use strict';
const closeNextProgramInfo = () => {
const cancelButton = document.querySelector('.com-vod-VODNextProgramInfo__cancel-button');
const v = cancelButton?.attributes?.tabindex.value
if (!!v && v !== '-1') {
cancelButton.click();
}
};
const clickNextProgramInfo = () => {
const time = document.querySelectorAll("span.com-vod-VODTime time");
if (!time) {
return
}
const nowMS = time[0]?.attributes.datetime.value.match(/^PT(\d+)M(\d+)S/);
const now = parseInt(nowMS?.[1]) * 60 + parseInt(nowMS?.[2]);
const maxMS = time[1]?.attributes.datetime.value.match(/^PT(\d+)M(\d+)S/);
const max = parseInt(maxMS?.[1]) * 60 + parseInt(maxMS?.[2]);
if (now && max && now >= max) {
location.href = document.querySelector('div.com-vod-VODNextProgramInfo__next a').attributes.href.value;
}
};
const changeLink = () => {
const links = document.querySelectorAll('.com-content-list-ContentListEpisodeItem__link');
links.forEach(function(element) {
const before = element.getAttribute('href')
if (!before.includes('?')) {
element.setAttribute('href', before + '?next=true');
element.addEventListener('click', (event) => {
event.stopImmediatePropagation();
});
}
})
}
const intervalID = setInterval(() => {
closeNextProgramInfo();
clickNextProgramInfo();
changeLink();
}, 1000);
})();