// ==UserScript==
// @name あにまん掲示板用
// @namespace
http://tampermonkey.net/
// @version 0.1
// @description スレ、レス、削除済レスからの安価をミュートする
// @author You
// @match
https://bbs.animanch.com/*
// @require
http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==
// スレミュートの最大数
const COOKIE_THREAD_MUTE_MAX_COUNT = 120;
// スレミュートのcookie寿命(秒)
const COOKIE_THREAD_MUTE_MAX_AGE = 60*60*24*365;
// スレミュートのcookieのkey
const COOKIE_THREAD_MUTE_KEY = "threadMute";
// スレミュートのcookieのpath
const COOKIE_THREAD_MUTE_PATH = "/";
// スレミュート用ボタンのid
const ELEMENT_ID_THREAD_MUTE_BUTTON = "threadMuteButton";
// スレミュート用ボタンのclass
const ELEMENT_CLASS_THREAD_MUTE_BUTTON = "threadMuteButton";
// レスミュートの最大数
const COOKIE_RES_MUTE_MAX_COUNT = 100;
// レスミュートのcookie寿命(秒)
const COOKIE_RES_MUTE_MAX_AGE = 60*60*24*365;
// レスミュートのcookieのkey
// 修正前のkeyと同じ値(resMute)だと修正後のミュート設定値が修正前の設定値で上書きされてしまうため別名にする
const COOKIE_RES_MUTE_KEY = "resMute2";
// レスミュートのcookieのpath
const COOKIE_RES_MUTE_PATH = "/";
// レスミュート用ボタンのclass
const ELEMENT_CLASS_RES_MUTE_BUTTON = "resMuteButton";
// カテゴリミュートのcookieのkey
// key自体はsetting[mute]だがgetCookieValueByKey()で取得するためにsetting\\[mute\\]と定義
const COOKIE_CATEGORY_MUTE = "setting\\[mute\\]"
// レス自動更新の監視
var observer = new MutationObserver(function(){
// レス自動更新で取得したレスにもミュートボタンを設定
createResMuteButton();
// hなしurlリンク化
replaceUrl();
});
// レス自動更新の監視の設定
const config = {
childList: true,
subtree:true
};
$(function(){
// ミュート対象のスレを非表示
let cookieValue = getCookieValueByKey(COOKIE_THREAD_MUTE_KEY);
removeMuteTargetThread(cookieValue)
// 各スレのスレタイ横にミュートボタン表示
createThreadMuteButton(cookieValue);
// スレミュートのcookieを更新(max-ageを延長)
updateCookie(COOKIE_THREAD_MUTE_KEY, cookieValue,COOKIE_THREAD_MUTE_MAX_AGE,COOKIE_THREAD_MUTE_PATH);
// スレのサムネ画像の表示サイズを調整
$("#recommends a img,#mainThread a img").css('height',$("#recommends a img,#mainThread a img").css('width'));
// カテゴリ選択ドロップダウン内のミュート対象のカテゴリを非表示
// URIエンコードされてるのでdecode + 処理に不要な文字を削除
cookieValue = decodeURI(getCookieValueByKey(COOKIE_CATEGORY_MUTE)).replace(/(\[)|(\])|"/g,"").replace(/%2C/g,",");
// removeCategoryDropDownItem(cookieValue);
// ミュート対象のレスを非表示
cookieValue = getCookieValueByKey(COOKIE_RES_MUTE_KEY);
removeMuteTargetRes(cookieValue);
// レスミュートのcookieを更新(max-ageを延長)
updateCookie(COOKIE_RES_MUTE_KEY, cookieValue,COOKIE_RES_MUTE_MAX_AGE,COOKIE_RES_MUTE_PATH);
// 表示しているのが各スレの画面でなければ以降の処理は不要のためreturn
if(!document.URL.match(/board/g)) {
// 自作CSSであにまん掲示板内のbodyを非表示にしているユーザ用(※してないユーザには特に影響なし)
$("body").css("visibility","visible");
return;
}
// 各レスにレスミュートボタン表示
createResMuteButton();
// 削除済レスからの安価削除
deleteResLink();
// hなしurlリンク化
replaceUrl();
// 自作CSSであにまん掲示板内のbodyを非表示にしているユーザ用(※してないユーザには特に影響なし)
$("body").css("visibility","visible");
// レス自動更新(id="reslist"の要素の変更)を監視
observer.observe(document.getElementById("reslist"),config);
});
// スレミュートボタン追加関数
function createThreadMuteButton(muteTargets) {
// スレミュートボタン(★)
let muteButton = "";
if(document.URL.match(/board/g)) {
// 各スレの画面を表示している場合
muteButton = "<button id='" + ELEMENT_ID_THREAD_MUTE_BUTTON + "'>★</button>";
$("#threadTitle").find(".shareBtns").append(muteButton);
// 初期状態ではスレミュートボタンの色は青
$("#"+ELEMENT_ID_THREAD_MUTE_BUTTON).css("color", "#00f");
// スレミュートボタンにクリックイベント設定
// ※ボタンに直接onclick="changeMuteFlag()"やるとエラーになったためここで設定
$("#"+ELEMENT_ID_THREAD_MUTE_BUTTON).on('click', function() {
changeThreadMuteFlag(getThreadId(), $(this));
});
let muteTargetArray = muteTargets.split(",");
for(let muteTarget of muteTargetArray) {
if(muteTarget == getThreadId()) {
// ミュート対象スレ表示時のスレミュートボタンの色は赤
$("#"+ELEMENT_ID_THREAD_MUTE_BUTTON).css("color", "#f00");
break;
}
}
}
muteButton = "<a class='" + ELEMENT_CLASS_THREAD_MUTE_BUTTON + "' href='javascript:void(0);'>★</a>";
// スレ一覧内の各スレへのリンクにスレミュートボタンを設定
$("#recommends,#mainThread").find("a[href^='
https://bbs.animanch.com/board']").append(muteButton);
// 個人設定→自分の書き込み の各スレへのリンクにスレミュートボタンを設定
$("#myres").find(".thtitle").find("a[href^='
https://bbs.animanch.com/board']").prepend(muteButton);
// 初期状態ではスレミュートボタンの色は青
$("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("color", "#00f");
// ボタンのstyle設定(スレ一覧用)
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("display", "inline-block");
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("background-color", "#fff");
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("position", "absolute");
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("top", "0");
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("left", "0")
$("#recommends,#mainThread").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("width", "1.1em");
// ボタンのstyle設定(個人設定→自分の書き込み)用
$("#myres").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("display", "inline");
$("#myres").find("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).css("background-color", "#fff");
// スレミュートボタンにクリックイベント設定
// ※ボタンに直接onclick="changeMuteFlag()"やるとエラーになったためここで設定
$("."+ELEMENT_CLASS_THREAD_MUTE_BUTTON).on('click', function() {
changeThreadMuteFlag($(this).parent().prop("href").replace(/^.*\/board\/([0-9]*)\//,"$1"), $(this));
});
}
// スレミュート状態切替関数
function changeThreadMuteFlag(threadId, mutoButton){
let muteCookieValue = getCookieValueByKey(COOKIE_THREAD_MUTE_KEY);
let regExp = new RegExp("^"+threadId+"$|^" + threadId + "[^0-9]|[^0-9]"+threadId,'g')
if (!muteCookieValue.match(regExp)) {
// muteCookieValueがregExpとmatchしない場合、対象スレはミュート対象未登録のため新規登録
// ミュート確認して「いいえ」なら何もせずreturn
if(!window.confirm("スレをミュートしますか?")) {
return;
}
if (muteCookieValue == "") {
// muteCookieValueが空文字の場合、そもそもmuteのcookieが未登録のためスレIDをそのまま設定
muteCookieValue = threadId;
} else {
// muteCookieValueが空文字以外の場合、既存のmuteがあるためその末尾に「,[スレID]」を設定
muteCookieValue = muteCookieValue + "," + threadId;
while(muteCookieValue.split(",").length > COOKIE_THREAD_MUTE_MAX_COUNT) {
// muteCookieValueに設定されているミュート対象の数がCOOKIE_THREAD_MUTE_MAX_COUNTより大きい場合、
// muteCookieValueの先頭に設定されている(=最も古く設定された)ミュート対象を削除する
muteCookieValue=muteCookieValue.replace(/^[^,]*,/g,"");
}
}
updateCookie(COOKIE_THREAD_MUTE_KEY, muteCookieValue,COOKIE_THREAD_MUTE_MAX_AGE,COOKIE_THREAD_MUTE_PATH);
mutoButton.css("color", "#f00");
} else {
// ミュート確認して「いいえ」なら何もせずreturn
if(!window.confirm("スレをミュート解除しますか?")) {
return;
}
updateCookie(COOKIE_THREAD_MUTE_KEY, muteCookieValue.replace(regExp,""),COOKIE_THREAD_MUTE_MAX_AGE,COOKIE_THREAD_MUTE_PATH);
mutoButton.css("color", "#00f");
}
}
// ミュート対象スレ削除関数
function removeMuteTargetThread(muteTargets) {
// ミュート対象が存在しない場合、処理不要のためreturn
if(muteTargets == "") {
return;
}
let x= muteTargets.replaceAll(",","|");
let regExp = new RegExp("<a[^>]*?href[^>]*?/(" +x+")/[^>]*?>.*?</a>","ig");
// オススメ
if($("#recommends").length > 0) {
$("#recommends").html($("#recommends").html().replace(regExp,""));
}
// スレ一覧
if($("#mainThread").length > 0) {
$("#mainThread").html($("#mainThread").html().replace(regExp,""));
}
// 個人設定→自分の書き込み
if($("#myres").length > 0) {
// 個人設定→自分の書き込みは要素の形式が異なるためregExpを再設定
// スレタイのリンク
regExp = new RegExp("<p[^>]*list-group-item[^>]*><a[^>]*?href[^>]*?/(" + x + ")/[^>]*?>.*?</a>.*?</p>","ig");
$("#myres").html($("#myres").html().replace(regExp,""));
// レスのリンク
regExp = new RegExp("<li[^>]*list-group-item[^>]*><div[^>]*resheader[^>]*><a[^>]*?href[^>]*?/(" + x + ")/[^>]*?>.*?</a>.*?</li>","ig");
$("#myres").html($("#myres").html().replace(regExp,""));
}
/*
let muteTargetArray = muteTargets.split(",");
for(let muteTarget of muteTargetArray) {
if(muteTarget != "") {
// ミュート対象が設定されている場合、対象スレへのリンクを非表示
// スレ一覧用
$("#recommends,#mainThread").find("[href^='
https://bbs.animanch.com/board/"+muteTarget + "/']" ).remove();
// 個人設定→自分の書き込み用
$("#myres").find("[href^='
https://bbs.animanch.com/board/"+muteTarget + "/']" ).closest(".list-group-item").remove();
}
}
*/
}
// 削除済レスからの安価削除関数
function deleteResLink() {
// 削除済のレスを取得
// 削除済のレスはclassに"resbody"と"disabled"が設定されている
// レスの一覧(id=resList)の子孫からそれが設定されている要素をfind()すれば
// 削除済のレスを取得できる
$("#resList").find(".resbody.disabled").each(function(index) {
// 削除されたレス番号を取得
// 各レスはid="res[レス番号]"が設定されているため、
// "res"部分(数字ではない部分)を削除することでレス番号が取得できる
let resId = parseInt($(this).parent().attr("id").replace(/[^0-9]/g,""));
// 削除済のレスからつけられた安価を取得
// レスの一覧(id=resList)の子孫から安価の親要素にあたる要素(class="reply")を取得し、
// 更につけられた安価の中でもhref="#red[削除されたレス番号]"をfind()すれば
// 削除済のレスからつけられた安価のみ取得できる
$("#resList").find(".reply").find("a[href='#res" + resId+"']").each(function(index) {
// 削除されたレスからの安価の親要素を取得
let parent = $(this).parent();
// 削除されたレスからの安価をremove()
$(this).remove();
// 削除されたレスからの安価をremove()後、他に安価がついていなければ
// 安価の親要素自体をremove()
// 安価の親要素が複数の安価(class="reslink")を持っていれば
// 削除されたレスからの安価をremove()後もparent.find(".reslink").lengthは1以上のため
// parent.remove()は実行されない
if(parent.find(".reslink").length==0) {
parent.remove();
}
});
});
}
// レスミュートボタン追加関数
function createResMuteButton() {
// 表示しているのが各スレの画面でなければ何もしない
if(!document.URL.match(/board/g)) {
return;
}
// レスミュートボタンを各レスに追加
let muteButton = " <button class='" + ELEMENT_CLASS_RES_MUTE_BUTTON + " badge'>ミュート</button>";
$(".resheader").each(function(index) {
if($(this).find("."+ELEMENT_CLASS_RES_MUTE_BUTTON).length == 0) {
if($(this).parent().find(".resbody.disabled").length==0) {
$(this).append(muteButton);
$(this).find("."+ELEMENT_CLASS_RES_MUTE_BUTTON).on('click', function() {
changeResMuteFlag($(this));
});
}
}
});
// レスミュートボタンの色は緑
$("."+ELEMENT_CLASS_RES_MUTE_BUTTON).css("background-color","#060");
}
// レスミュート状態切替関数
function changeResMuteFlag(obj){
// ミュート確認して「いいえ」なら何もせずreturn
if(!window.confirm("レスをミュートしますか?")) {
return;
}
let muteCookieValue = getCookieValueByKey(COOKIE_RES_MUTE_KEY);
let resId = $(obj).parent().find(".resnumber").text();
let muteTarget = getThreadId() + "/" + resId;
let regExp = new RegExp("^"+muteTarget+"$|^" + muteTarget + "[^0-9]|[^0-9]"+muteTarget,'g')
if (!muteCookieValue.match(regExp)) {
// muteCookieValueがregExpとmatchしない場合、対象レスはミュート対象未登録のため新規登録
if (muteCookieValue == "") {
// muteCookieValueが空文字の場合、そもそもmuteのcookieが未登録のため対象レスをそのまま設定
muteCookieValue = muteTarget;
} else {
// muteCookieValueが空文字以外の場合、既存のmuteがあるためその末尾に「,[スレID]/[レスId]」を追加
muteCookieValue = muteCookieValue + "," + muteTarget;
while (muteCookieValue.split(",").length > COOKIE_RES_MUTE_MAX_COUNT) {
// muteCookieValueに設定されているミュート対象の数がCOOKIE_RES_MUTE_MAX_COUNTより大きい場合、
// muteCookieValueの先頭に設定されている(=最も古く設定された)ミュート対象を削除する
muteCookieValue=muteCookieValue.replace(/^[0-9\/]*,/g,"");
}
}
updateCookie(COOKIE_RES_MUTE_KEY, muteCookieValue,COOKIE_RES_MUTE_MAX_AGE,COOKIE_RES_MUTE_PATH);
} else {
alert("ミュート済です");
}
removeMuteTargetRes(getCookieValueByKey(COOKIE_RES_MUTE_KEY)) ;
}
// ミュート対象レス削除関数
function removeMuteTargetRes(muteTargets) {
let muteTargetArray = muteTargets.split(",");
let threadId = getThreadId();
for(let muteTarget of muteTargetArray) {
// 表示中のスレとミュート対象レスのスレが一致しないかつ個人設定画面でければ何もせずcontinue
if(!muteTarget.match(new RegExp("^" + threadId + "/")) && !document.URL.match(/setting/g)) {
continue;
}
let muteTargetThreadId = muteTarget.split("/")[0];
let muteTargetResId = muteTarget.split("/")[1];
// 表示中のスレがミュート対象と一致する場合、ミュート対象レスを取得しミュートにする
// スレ用
// ※サーバ側で削除済のレスと同じ外見を設定
if(document.URL.match(new RegExp("/board/" + threadId + "/"))) {
let obj = $("#res" + muteTargetResId);
$(obj).find(".resheader").find("button").remove();
$(obj).find(".resname").html("二次元好きの匿名さん");
$(obj).find(".resbody").addClass("disabled");
$(obj).find(".resbody").html("<p>このレスは削除されています</p>");
}
// 個人設定→自分の書き込み用
// 単純にremove()
if (document.URL.match(/setting/g)) {
$("#myres").find("[href^='
https://bbs.animanch.com/board/"+muteTargetThreadId + "/1/?res=" + muteTargetResId + "']" ).closest(".list-group-item").remove();
// スレへのリンクがひとつしかない=レスが全てミュートされてるのにスレタイのリンクだけ残っている状態であるため
// スレタイのリンクも消す
if ($("#myres").find("[href^='
https://bbs.animanch.com/board/"+muteTargetThreadId +"/']").length == 1) {
$("#myres").find("[href^='
https://bbs.animanch.com/board/"+muteTargetThreadId +"/']").closest(".list-group-item").remove()
}
}
}
deleteResLink();
}
// cookie取得関数
function getCookieValueByKey(key) {
let cookieValue = "";
// cookieを配列として取得
let cookiesArray = document.cookie.split(';');
for(let cookie of cookiesArray){
// cookieは[key=value]の値で取得されるため、"="でsplitしてkeyとvalueを分離
let cookieArray = cookie.split('=');
// name="mute"のcookieがあったら値を取得してbreak;
// ※cookieは半角スペース+keyで登録されてるらしい
if( cookieArray[0].match(new RegExp("^ *"+ key))){
cookieValue = cookieArray[1];
break;
}
}
return cookieValue;
}
// cookie更新関数
function updateCookie(key,value,maxAge,path) {
document.cookie = key +"=" + value + ";max-age=" + maxAge + ";path=" + path;
}
// スレid取得関数
function getThreadId() {
let regExp = new RegExp("^.*/board/([0-9]*)/.*");
return document.URL.replace(regExp,"$1");
}
// hなしurlリンク化
function replaceUrl() {
// hなしurl文字列の正規表現
let regExp = new RegExp("([^h])(ttps*:\/\/[^ < ]*)","ig");
// レスの中にあるhなしurl文字列に該当する部分をリンクに置換
$(".resbody").each(function (index) {
if($(this).html().match(regExp)) {
$(this).html($(this).html().replace(regExp,"$1<a href='h$2' target='_blank'>h$2</a>"));
}
});
}
// ミュート対象カテゴリをカテゴリ選択ドロップダウントから削除
function removeCategoryDropDownItem(cookieValue) {
let muteTargets = cookieValue.split(",");
// ミュート対象のカテゴリをカテゴリ選択ドロップダウンからremove()
for(let target of muteTargets) {
$(".dropdown-menu").find("a[href='/category" + target + "/']").closest("li").remove();
}
}