Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save whiteball/3509e9be6ec67f1c9b79793931754bfb to your computer and use it in GitHub Desktop.
Save whiteball/3509e9be6ec67f1c9b79793931754bfb to your computer and use it in GitHub Desktop.
pixivでの検索結果から、登録したユーザー/タグ(部分一致)を非表示にします。ディスカバリーの表示をフォロー中/非フォローユーザーに限定することもできます。詳しくはdescriptionをご確認ください。
// ==UserScript==
// @name NGユーザー&NGタグ (pixiv 検索結果)
// @namespace https://wb.geo.jp/
// @version 0.2.1
// @description pixivでの検索結果から、登録したユーザー/タグ(部分一致)を非表示にします。ユーザー/タグの登録・削除には設定ダイアログ(キーボードショートカット「Ctrl+.」または各ページ最下部左側のボタンで出現)、または、ブックマークレットを使います。NGユーザー/タグの登録後はページを再読込してください。設定ダイアログとブックマークレットは同時に使うと、設定がうまく保存されない場合があります。片方を使ってからもう片方も使いたいときは、一度ページを再読込してください。(v0.2)ディスカバリーの表示対象をフォロー中ユーザーのみ/非フォローユーザーのみ/すべてから選択して絞り込める機能を追加。絞り込みの結果の件数が少ないと次のおすすめが自動で出てこないことますが、その場合は一旦ページ上部まで戻ってから再び最下部に移動するか、ページをリロードしてください。
// @author しらたま
// @match https://www.pixiv.net/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.pixiv.net
// @updateURL https://gist.github.com/whiteball/3509e9be6ec67f1c9b79793931754bfb/raw/banning_specific_tags_and_users_in_pixiv_search.user.js
// @downloadURL https://gist.github.com/whiteball/3509e9be6ec67f1c9b79793931754bfb/raw/banning_specific_tags_and_users_in_pixiv_search.user.js
// @supportURL https://gist.github.com/whiteball/3509e9be6ec67f1c9b79793931754bfb
// @grant none
// ==/UserScript==
// 下2つのブックマークレットはユーザーページ(https://www.pixiv.net/users/数字のページ)で実行する
// NGユーザー登録時のブックマークレット
// javascript:(function(){const n='mod_ng_users',l=localStorage,e=l.getItem(n)??'',s=new Set(!e.length?[]:e.split(',')),u=/www\.pixiv\.net\/users\/([0-9]+)/.exec(location.href)[1];if(u){l.setItem(n,[...s.add(u)].join(','))}})()
// NGユーザー削除時のブックマークレット
// javascript:(function(){const n='mod_ng_users',l=localStorage,e=l.getItem(n)??'',s=new Set(!e.length?[]:e.split(',')),u=/www\.pixiv\.net\/users\/([0-9]+)/.exec(location.href)[1];if(u&&s.delete(u)){l.setItem(n,[...s].join(','))}})()
// NGタグ登録時のブックマークレット
// javascript:(function(){const n='mod_ng_tags',l=localStorage,e=l.getItem(n)??'',s=new Set(!e.length?[]:e.split(' ')),u=window.prompt('NGタグ(部分一致)に指定する文字列を入力してください。', '');if(u){l.setItem(n,[...s.add(u)].join(' '))}})()
// NGタグ削除時のブックマークレット
// javascript:(function(){const n='mod_ng_tags',l=localStorage,e=l.getItem(n)??'',s=new Set(!e.length?[]:e.split(' ')),u=window.prompt('NGタグから削除する文字列を入力してください。', '');if(u&&s.delete(u)){l.setItem(n,[...s].join(' '))}})()
// 登録したNGユーザーの確認
// javascript:(function(){const n='mod_ng_users',l=localStorage,e=l.getItem(n)??'';window.alert('以下のユーザーがNG登録されています。\n' + (!e.length?[]:e.split(',')).join('\n'))})()
// 登録したNGタグの確認
// javascript:(function(){const n='mod_ng_tags',l=localStorage,e=l.getItem(n)??'';window.alert('以下のタグがNG登録されています。\n' + (!e.length?[]:e.split(' ')).join('\n'))})()
// すべてのNGユーザーを削除
// javascript:(function(){const n='mod_ng_users',r=localStorage.removeItem(n)})()
// すべてのNGタグを削除
// javascript:(function(){const n='mod_ng_tags',r=localStorage.removeItem(n)})()
(function() {
'use strict';
const ngUsersKey = 'mod_ng_users', ngUsersRaw = localStorage.getItem(ngUsersKey) ?? '', ngUsers = new Set(ngUsersRaw.length === 0 ? [] : ngUsersRaw.split(','))
const ngTagsKey = 'mod_ng_tags', ngTagsRaw = localStorage.getItem(ngTagsKey) ?? '', ngTags = ngTagsRaw.length === 0 ? [] : ngTagsRaw.split(' ')
const followUsersKey = 'mod_follow_users', followUsersRaw = localStorage.getItem(followUsersKey) ?? '', followUsers = new Set(followUsersRaw.length === 0 ? [] : followUsersRaw.split(','))
const notFollowUsersKey = 'mod_not_follow_users', notFollowUsersRaw = localStorage.getItem(notFollowUsersKey) ?? '', notFollowUsers = new Set(notFollowUsersRaw.length === 0 ? [] : notFollowUsersRaw.split(','))
const discoveryConfigDisplayKey = 'mod_discovery_config_diplay'
let includeFollowed = true, includeNotFollowed = false
const loadDiscoverySetting = function () {
const display = localStorage.getItem(discoveryConfigDisplayKey) ?? '0'
switch(display) {
case '0':
includeFollowed= true
includeNotFollowed = true
break
case '1':
includeFollowed= true
includeNotFollowed = false
break
case '2':
includeFollowed= false
includeNotFollowed = true
break
default:
includeFollowed= true
includeNotFollowed = true
break
}
}
loadDiscoverySetting()
// 外部接続に割り込み
const originalFetch = window.fetch
window.fetch = async function (resource, init = {}) {
return originalFetch(resource, init).then(async res => {
if (res.url.indexOf('https://www.pixiv.net/ajax/search/') === 0) {
const data = await res.json()
for (const type of ['illust', 'illustManga', 'manga', 'novel']) {
if (data.body[type]) {
// NG処理
// data.body[type].data = data.body[type].data.filter(elem => elem.userId !== undefined && ! (ngUsers.has(elem.userId) || ngTags.some(w => elem.tags.some(e => e.indexOf(w) >= 0))))
data.body[type].data = data.body[type].data.filter(elem => elem.userId === undefined || ! (ngUsers.has(elem.userId) || ngTags.some(w => elem.tags.some(e => e.indexOf(w) >= 0))))
}
}
res.json = async () => data
} else if (res.url.indexOf('https://www.pixiv.net/ajax/discovery/') === 0) {
const data = await res.json()
for (const type of ['illust', 'novel']) {
if (data.body.thumbnails && data.body.thumbnails[type]) {
// NG処理
let list = []
for (const elem of data.body.thumbnails[type]) {
if (followUsers.has(elem.userId)) {
if (includeFollowed) {
list.push(elem)
}
} else if (notFollowUsers.has(elem.userId)) {
if (includeNotFollowed) {
list.push(elem)
}
} else {
const resp = await originalFetch('https://www.pixiv.net/ajax/user/'+elem.userId+'?full=1&lang=ja')
const userData = await resp.json()
if (userData.body.isFollowed) {
followUsers.add(elem.userId)
localStorage.setItem(followUsersKey, [...followUsers].join(','))
if (includeFollowed) {
list.push(elem)
}
} else {
notFollowUsers.add(elem.userId)
localStorage.setItem(notFollowUsersKey, [...notFollowUsers].join(','))
if (includeNotFollowed) {
list.push(elem)
}
}
}
}
data.body.thumbnails[type] = list
}
}
res.json = async () => data
} else if (res.url.indexOf('https://www.pixiv.net/ajax/user/') === 0 && res.url.indexOf('?full=1') > 1) {
const data = await res.json()
if (data.body.isFollowed) {
followUsers.add(data.body.userId)
localStorage.setItem(followUsersKey, [...followUsers].join(','))
} else if (data.body.isFollowed !== undefined) {
notFollowUsers.add(data.body.userId)
localStorage.setItem(notFollowUsersKey, [...notFollowUsers].join(','))
}
res.json = async () => data
}
return res
})
}
document.getElementsByTagName('head')[0].insertAdjacentHTML('beforeend', `<style type="text/css">
#mod-setting-dialog{padding:0;border: black 2px solid;border-radius:5px;}
dialog::backdrop {background-color: rgba(0, 0, 0, 0.4)}
#mod-setting-dialog div.page{margin:10px;}
#mod-setting-dialog p{margin:10px;}
#mod-setting-dialog ul{padding:0;list-style:none;line-height:2rem;text-align:center;}
#mod-setting-dialog ul li a{text-decoration:underline;}
#mod-setting-dialog ul li a:focus-visible{outline:none;}
.dialog-footer{
margin-bottom:5px;
height: 30px;
}
.dialog-footer .close-area{
text-align:center;
width: 100%;
float: left;
margin: 0 auto;
}
</style>`)
document.getElementsByTagName('body')[0].insertAdjacentHTML('beforeend', `<dialog id="mod-setting-dialog">
<div id="mod-dialog-menu" class="page"><p>NGユーザーとNGタグの編集ができます。</p>
<div>
<ul><li><a href="#" id="mod-add-ng-user-link">このページをNGユーザー登録</a></li>
<li><a href="#" id="mod-edit-ng-user-link">NGユーザー編集</a></li>
<li><a href="#" id="mod-edit-ng-tag-link">NGタグ編集</a></li>
<li><a href="#" id="mod-edit-discovery-config-link">ディスカバリー設定</a></li>
</ul>
</div>
<div class="dialog-footer">
<div class="close-area"><button onclick="document.getElementById('mod-setting-dialog').close()">閉じる</button></div>
</div>
</div>
<div id="mod-dialog-edit-user" class="page" style="display:none;"><p>改行区切りでNGユーザーを追加/削除してください。</p>
<div>
<textarea style="width:100%" rows="10" id="mod-dialog-ng-users-editor"></textarea>
</div>
<div class="dialog-footer">
<div class="close-area"><button id="mod-dialog-return-button-1">戻る</button> / <button id="mod-dialog-update-ng-users-button">更新</button></div>
</div>
</div>
<div id="mod-dialog-edit-tag" class="page" style="display:none;"><p>改行区切りでNGタグを追加/削除してください。</p>
<div>
<textarea style="width:100%" rows="10" id="mod-dialog-ng-tags-editor"></textarea>
</div>
<div class="dialog-footer">
<div class="close-area"><button id="mod-dialog-return-button-2">戻る</button> / <button id="mod-dialog-update-ng-tags-button">更新</button></div>
</div>
</div>
<div id="mod-dialog-edit-user" class="page" style="display:none;"><p>改行区切りでNGユーザーを追加/削除してください。</p>
<div>
<textarea style="width:100%" rows="10" id="mod-dialog-ng-users-editor"></textarea>
</div>
<div class="dialog-footer">
<div class="close-area"><button id="mod-dialog-return-button-1">戻る</button> / <button id="mod-dialog-update-ng-users-button">更新</button></div>
</div>
</div>
<div id="mod-dialog-discovery-config" class="page" style="display:none;"><p>ディスカバリーの表示設定</p>
<div>
<div style="margin: 5px">
<label><input type="radio" name="mod-dialog-discovery-config-display" value="0">すべて</label><br>
<label><input type="radio" name="mod-dialog-discovery-config-display" value="1">フォロー中のみ</label><br>
<label><input type="radio" name="mod-dialog-discovery-config-display" value="2">非フォローのみ</label><br><br>
</div>
<div style="margin: 5px">
<button id="mod-dialog-discovery-config-reset">フォローユーザーキャッシュの消去</button>
</div>
</div>
<div class="dialog-footer">
<div class="close-area"><button id="mod-dialog-return-button-3">戻る</button></div>
</div>
</div>
</dialog>
<div style="margin:15px"><button id="mod-show-dialog-button">NG設定ダイアログ</div></div>`)
// 基本ダイアログ
const dialog = document.getElementById("mod-setting-dialog")
const changeDialogState = function () {
if (dialog.open) {
dialog.close()
} else {
dialog.showModal()
}
}
document.getElementsByTagName('body')[0].addEventListener('keyup', function (event) {
if (event.isComposing || event.keyCode === 229) {
return;
}
if (!(event.ctrlKey && event.code === 'Period')) {
return
}
changeDialogState()
})
document.getElementById("mod-show-dialog-button").addEventListener('click', function (event) {
changeDialogState()
})
// 各ページ
const dialogPages = {
menu: document.getElementById("mod-dialog-menu"),
editTag: document.getElementById("mod-dialog-edit-tag"),
editUser: document.getElementById("mod-dialog-edit-user"),
discovery: document.getElementById("mod-dialog-discovery-config")
}
const changePage = function (target) {
for (const page of Object.keys(dialogPages)) {
if (target === page) {
dialogPages[page].style.display = 'block'
} else {
dialogPages[page].style.display = 'none'
}
}
}
// 各処理
document.getElementById("mod-add-ng-user-link").addEventListener('click', function (event) {
const u = /www\.pixiv\.net\/users\/([0-9]+)/.exec(location.href)
if (u) {
localStorage.setItem(ngUsersKey, [...ngUsers.add(u[1])].join(','))
window.alert('NGユーザーを追加しました')
} else {
window.alert('NGユーザーを追加できません。\n現在のページが(https://www.pixiv.net/users/数字のページ)であることを確認してください')
}
event.preventDefault()
return false
})
const userEditor = document.getElementById("mod-dialog-ng-users-editor")
document.getElementById("mod-edit-ng-user-link").addEventListener('click', function (event) {
userEditor.value = [...ngUsers].join('\n')
changePage('editUser')
event.preventDefault()
return false
})
document.getElementById("mod-dialog-update-ng-users-button").addEventListener('click', function () {
const value = userEditor.value.trim()
const temp = new Set(value.length === 0 ? [] : value.split('\n'))
if (temp) {
ngUsers.clear()
if (temp.size > 0) {
temp.forEach(key => ngUsers.add(key))
localStorage.setItem(ngUsersKey, [...ngUsers].join(','))
window.alert('NGユーザーを更新しました')
} else {
if (localStorage.hasOwnProperty(ngUsersKey)) {
localStorage.removeItem(ngUsersKey)
}
window.alert('NGユーザーをすべて削除しました')
}
} else {
window.alert('NGユーザーの更新でエラーが発生しました')
}
})
document.getElementById("mod-dialog-return-button-1").addEventListener('click', function () {
changePage('menu')
})
const tagEditor = document.getElementById("mod-dialog-ng-tags-editor")
document.getElementById("mod-edit-ng-tag-link").addEventListener('click', function (event) {
tagEditor.value = [...ngTags].join('\n')
changePage('editTag')
event.preventDefault()
return false
})
document.getElementById("mod-dialog-update-ng-tags-button").addEventListener('click', function () {
const value = tagEditor.value.trim()
const temp = new Set(value.length === 0 ? [] : value.split('\n'))
if (temp) {
while (ngTags.length > 0) {
ngTags.pop()
}
console.log(temp.size)
if (temp.size > 0) {
temp.forEach(key => ngTags.push(key))
localStorage.setItem(ngTagsKey, ngTags.join(' '))
window.alert('NGタグを更新しました')
} else {
if (localStorage.hasOwnProperty(ngTagsKey)) {
localStorage.removeItem(ngTagsKey)
}
window.alert('NGタグをすべて削除しました')
}
} else {
window.alert('NGタグの更新でエラーが発生しました')
}
})
document.getElementById("mod-dialog-return-button-2").addEventListener('click', function () {
changePage('menu')
})
const discoveryConfigRadio = document.getElementsByName("mod-dialog-discovery-config-display")
document.getElementById("mod-edit-discovery-config-link").addEventListener('click', function (event) {
let value = '0'
if (includeFollowed && ! includeNotFollowed) {
value = '1'
} else if (! includeFollowed && includeNotFollowed) {
value = '2'
}
for (const elem of discoveryConfigRadio) {
if (elem.value === value) {
elem.checked = true
} else {
elem.checked = false
}
}
changePage('discovery')
event.preventDefault()
return false
})
for (const elem of discoveryConfigRadio) {
elem.addEventListener('change', function (event) {
localStorage.setItem(discoveryConfigDisplayKey, event.target.value)
loadDiscoverySetting()
})
}
document.getElementById("mod-dialog-discovery-config-reset").addEventListener('click', function () {
localStorage.removeItem(followUsersKey)
localStorage.removeItem(notFollowUsersKey)
followUsers.clear()
notFollowUsers.clear()
window.alert('フォロー中ユーザーのIDキャッシュを削除しました。')
})
document.getElementById("mod-dialog-return-button-3").addEventListener('click', function () {
changePage('menu')
})
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment