// ==UserScript== // @name Append Tag Searching Tub // @name:ja niconico タグ検索タブを追加 // @namespace http://loda.jp/script/ // @id niconico-adds-search-tab-347021 // @version 4.1.1 // @description Adds "Keyword", "Tags", "My List", "Images" and "Live" search tabs to all of the Niconico search boxes. // @description:ja 『niconico』各サービスの検索窓について、「キーワード」「タグ」「マイリスト」「静画」「生放送」検索タブが5つとも含まれるように補完する // @match http://www.nicovideo.jp/* // @match http://seiga.nicovideo.jp/* // @match http://live.nicovideo.jp/* // @match http://watch.live.nicovideo.jp/* // @match http://com.nicovideo.jp/* // @match http://blog.nicovideo.jp/en_info/* // @match http://tw.blog.nicovideo.jp/* // @match http://info.nicovideo.jp/psvita/en/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @domain www.nicovideo.jp // @domain seiga.nicovideo.jp // @domain live.nicovideo.jp // @domain watch.live.nicovideo.jp // @domain com.nicovideo.jp // @domain blog.nicovideo.jp // @domain tw.blog.nicovideo.jp // @domain info.nicovideo.jp // @run-at document-start // @icon data:image/vnd.microsoft.icon;base64,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 // @author 100の人 // @homepage https://greasyfork.org/scripts/268 // @license Creative Commons Attribution 4.0 International Public License; http://creativecommons.org/licenses/by/4.0/ // @downloadURL none // ==/UserScript== (function () { 'use strict'; polyfill(); // L10N Gettext.setLocalizedTexts({ 'en': { 'キーワード': 'Keyword', '動画をキーワードで検索': 'Search Video by Keyword', 'タグ': 'Tags', '動画をタグで検索': 'Search Video by Tag', 'マイリスト': 'My List', 'マイリストを検索': 'Search My List', '静画': 'Images', '静画を検索': 'Search Images', '生放送': 'Live', '番組を探す': 'Search Live Program', '関連コミュニティ': 'Related Communities', 'このコミュニティを登録した人はこんなコミュニティも登録しています': 'People Who Joined This Community Also Joined', 'マンガ': 'Comics', }, 'zh': { 'キーワード': '關鍵字', '動画をキーワードで検索': '', 'タグ': '標籤', '動画をタグで検索': '', 'マイリスト': '我的清單', 'マイリストを検索': '搜尋我的清單', '静画': '靜畫', '静画を検索': '搜尋靜畫', '生放送': '生放送', '番組を探す': '搜尋節目', '関連コミュニティ': '相關社群', 'このコミュニティを登録した人はこんなコミュニティも登録しています': '加入此社群的人也有加入這些社群', 'マンガ': '漫畫', }, }); /** * タグ検索ページにおける検索窓の最大幅 (CSSの単位を含む)。 * @constant {string} */ var MAX_SEARCH_BOX_WIDTH = '268px'; /** * 追加したタブバーから新しいタブで検索結果を開いたとき、選択中のタブを元に戻す遅延時間 (ミリ秒)。 * @constant {number} */ var CURRENT_TAB_RESTORATION_DELAY = 1000; /** * 表示しているページのパスにスラッシュを前置し連結したもの。 * @type {Object} */ var pathname = window.location.pathname; /** * 表示しているページの種類。 * @type {string} */ var pageType; // ページの種類を取得 switch (window.location.host) { case 'www.nicovideo.jp': if (pathname === '/') { // 総合トップページ pageType = 'top'; } else if (pathname.startsWith('/search/')) { // 動画キーワード検索ページ pageType = 'videoSearch'; } else if (pathname.startsWith('/mylist_search')) { // マイリスト検索ページ pageType = 'mylist'; } else if (/^\/(?:(?:tag|related_tag|watch|mylist)\/|(?:recent|newarrival|hotlist|video_top|openlist|playlist|recommendations|video_catalog)(?:\/|$))/.test(pathname)) { // 動画タグ検索ページと原宿プレイヤーのページ pageType = 'tag'; } else if (pathname.startsWith('/user/')) { // ユーザーページ pageType = 'user'; } break; case 'seiga.nicovideo.jp': pageType = pathname.startsWith('/search/') // 静画検索ページ ? 'imageSearch' // 静画ページ : 'image'; break; case 'live.nicovideo.jp': case 'watch.live.nicovideo.jp': pageType = pathname.startsWith('/search') // 生放送検索ページ ? 'liveSearch' // 生放送ページ : 'live'; break; case 'com.nicovideo.jp': if (/^\/[^/]+\/co[0-9]+(?:\/|$)/.test(pathname)) { // コミュニティ詳細ページ pageType = 'community'; } break; case 'info.nicovideo.jp': if (pathname.startsWith('/psvita/en/')) { // 英語版PS Vita紹介ページ startScript(prepare, function (parent) { return parent.localName === 'body'; }, function (target) { return target.id === 'header'; }, function () { return document.getElementById('header'); }, { isTargetParent: function (parent) { return parent.localName === 'html'; }, isTarget: function (target) { return target.localName === 'body'; }, }); } return; case 'blog.nicovideo.jp': // 英語版ニコニコインフォ pageType = 'info_en'; break; case 'tw.blog.nicovideo.jp': // 台湾版ニコニコインフォ pageType = 'info_tw'; break; } // 上部メニューが追加されるまで待機 var targetParentIdFirefox, isTargetFirefox; switch (pageType) { case 'imageSearch': case 'image': isTargetFirefox = function (target) { return target.id === 'wrapper'; }; break; case 'liveSearch': targetParentIdFirefox = 'body_header'; break; case 'info_en': targetParentIdFirefox = 'container-inner'; break; case 'info_tw': targetParentIdFirefox = 'header'; break; } startScript(prepare, function (parent) { return parent.classList.contains('siteHeaderGlovalNavigation'); }, function (target) { return target.id === 'siteHeaderLeftMenu'; }, function () { return document.getElementById('siteHeaderLeftMenu'); }, { isTargetParent: targetParentIdFirefox ? function (parent) { return parent.id === targetParentIdFirefox; } : function (parent) { return parent.localName === 'body'; }, isTarget: isTargetFirefox || function (target) { return target.id === 'siteHeader'; }, }); /** * ページの種類別に、実行する関数を切り替える。 */ function prepare() { // ニコニコ生放送ではlang属性値が常にja-JPのため、ニコニコ動画へのリンク文字によって、ページの言語を判定する var textVideo = document.querySelector('[href^="http://www.nicovideo.jp/video_top"],[href^="http://nicovideo.jp/video_top"]').textContent; if (textVideo.indexOf('Video') !== -1) { // Firefox には String#includes が実装されていない Gettext.setLocale('en'); } else if (textVideo.indexOf('動畫') !== -1) { // Firefox には String#includes が実装されていない Gettext.setLocale('zh'); } if (!document.querySelector(pageType === 'imageSearch' ? '#siteHeader [href="/?header"], #siteHeader [href="/"]' : '#siteHeader [href^="http://seiga.nicovideo.jp/"], #globalNav [href^="http://seiga.nicovideo.jp/"]')) { // ヘッダに静画へのリンクが無ければ // 生放送へのリンクを取得 var itemLive = document.querySelector('#siteHeader [href^="http://live.nicovideo.jp/"], #globalNav [href^="http://live.nicovideo.jp/"]').parentNode; // 生放送リンクの複製 var item = itemLive.cloneNode(true); // リンク文字を変更 (item.getElementsByTagName('span')[0] || item.getElementsByTagName('a')[0]).textContent = _('静画'); // アドレスを変更 item.getElementsByTagName('a')[0].host = 'seiga.nicovideo.jp'; // ヘッダに静画へのリンクを追加 itemLive.parentNode.insertBefore(item, itemLive); } switch (pageType) { case 'videoSearch': // 動画キーワード startScript(addTagSearchTabAboveSearchBox, function (parent) { return parent.classList.contains('formSearch'); }, function (target) { return target.id === 'search_united_form'; }, function () { return document.getElementById('search_united_form'); }, { isTargetParent: function (parent) { return parent.localName === 'body'; }, isTarget: function (target) { return target.localName === 'section'; }, }); break; case 'mylist': // マイリスト startScript(addTagSearchTabAboveSearchBox, function (parent) { return parent.id === 'form_search'; }, function (target) { return target.id === 'search_united_form'; }, function () { return document.getElementById('search_united_form'); }, { isTargetParent: function (parent) { return parent.id === 'PAGEMAIN'; }, isTarget: function (target) { return target.id === 'PAGEBODY'; }, }); break; case 'top': // トップページ startScript(addTagSearchButtonToTopPage, function (parent) { return parent.id === 'searchFormInner'; }, function (target) { return target.id === 'searchForm'; }, function () { return document.getElementById('searchForm'); }, { isTargetParent: function (parent) { return parent.id === 'main_container' || parent.localName === 'body'; }, isTarget: function (target) { return target.id === 'searchFormWrap'; }, }); break; case 'imageSearch': // 静画キーワード startScript(addTagSearchTabAboveSearchBox, function (parent) { return parent.id === 'usearch_form'; }, function (target) { return target.id === 'usearch_form_input'; }, function () { return document.getElementById('usearch_form_input'); }, { isTargetParent: function (parent) { return parent.id === 'wrapper'; }, isTarget: function (target) { return target.id === 'main'; }, }); break; case 'image': // 静画 startScript(careteTabsBarToSearchBox, function (parent) { return parent.id === 'head_search_form'; }, function (target) { return target.id === 'search_button'; }, function () { return document.getElementById('search_button'); }, { isTargetParent: function (parent) { return parent.id === 'header_block'; }, isTarget: function () { return true; }, }); break; case 'liveSearch': // 生放送キーワード startScript(addTagSearchTabAboveSearchBox, function (parent) { return parent.id === 'search_input_block'; }, function (target) { return target.id === 'search_form'; }, function () { return document.getElementById('search_form'); }); break; case 'live': // 生放送 startScript(careteTabsBarToSearchBox, function (parent) { return parent.classList.contains('search_program'); }, function (target) { return target.classList.contains('search_word'); }, function () { return document.getElementsByClassName('search_word')[0]; }, { isTargetParent: function (parent) { return parent.localName === 'body'; }, isTarget: function (target) { return target.id === 'page_header'; }, }); break; case 'tag': if (document.doctype.publicId) { // 原宿プレイヤーの動画ページ、オススメ動画ページ等 startScript(addOtherServiceTabsAboveSearchBox, function (parent) { return parent.id === 'search_tab'; }, function (target) { return target.id === 'target_m'; }, function () { return document.getElementById('target_m'); }, { isTargetParent: function (parent) { return parent.id === 'PAGEMAIN'; }, isTarget: function (target) { return target.id === 'PAGEBODY'; }, }); } else { // 動画タグ startScript(addOtherServiceTabsAboveSearchBox, function (parent) { return parent.classList.contains('videoSearchOption'); }, function (target) { return target.classList.contains('optMylist'); }, function () { return document.getElementsByClassName('optMylist')[0]; }, { isTargetParent: function (parent) { return parent.localName === 'body'; }, isTarget: function (target) { return target.localName === 'header'; }, }); return; } break; case 'user': // ユーザー startScript(addImageLinkToUserPageMenu, function (parent) { return parent.localName === 'body'; }, function (target) { return target.classList.contains('optionOuter'); }, function () { return document.getElementsByClassName('optionOuter')[0]; }); break; case 'community': // コミュニティ startScript(addRelatedCommunitiesLink, function (parent) { return parent.localName === 'body'; }, function (target) { return target.id === 'site-body'; }, function () { return document.getElementById('site-body'); }); break; } } /** * 各サービスのキーワード検索ページの検索窓に、動画の「タグ」検索タブを追加する。 */ function addTagSearchTabAboveSearchBox() { // マイリスト検索タブの取得 var mylistTab = document.querySelector('.tab_table td:nth-of-type(2), #search_frm_a a:nth-of-type(2), .search_tab_list li:nth-of-type(2), .seachFormA a:nth-of-type(2)'); // マイリスト検索タブの複製 var tagTab = mylistTab.cloneNode(true); // タブ名を変更 var anchor = tagTab.tagName.toLowerCase() === 'a' ? tagTab : tagTab.getElementsByTagName('a')[0]; var tabNameNode = anchor.getElementsByTagName('div'); tabNameNode = (tabNameNode.length > 0 ? tabNameNode[0].firstChild : anchor.firstChild); tabNameNode.data = _('タグ') + (pageType === 'liveSearch' ? '(' : ' ( '); // クラス名を変更・動画件数をリセット var searchCount = tagTab.querySelector('strong, span'); switch (pageType) { case 'videoSearch': searchCount.classList.remove('more'); break; case 'mylist': searchCount.style.removeProperty('color'); break; case 'imageSearch': searchCount.classList.remove('search_value_em'); searchCount.classList.add('search_value'); break; case 'liveSearch': searchCount.classList.remove('Redtxt'); break; } searchCount.textContent = '-'; if (searchCount.id) { // 生放送 searchCount.id = 'search_count_tag'; } // 検索語句を取得 var searchWordsPattern = /(?:\/(?:search|tag|mylist_search)\/|[?&]keyword=)([^?]+)/g; var result = window.location.href.match(searchWordsPattern); var searchWords = result ? searchWordsPattern.exec(result[pageType === 'liveSearch' ? result.length - 1 : 0])[1] : ''; // タグが付いた動画件数を取得・表示 if (searchWords) { GM_xmlhttpRequest({ method: 'GET', url: 'http://www.nicovideo.jp/tag/' + searchWords, onload: function (response) { var responseDocument = new DOMParser().parseFromString(response.responseText, 'text/html'); var total = responseDocument.querySelector('.tagCaption .dataValue .num').textContent; var trimmedThousandsSep = total.replace(/,/g, ''); if (trimmedThousandsSep >= 100) { // 動画件数が100件を超えていれば switch (pageType) { case 'videoSearch': searchCount.classList.add('more'); break; case 'mylist': searchCount.style.color = '#CC0000'; break; case 'imageSearch': searchCount.classList.remove('search_value'); searchCount.classList.add('search_value_em'); break; case 'liveSearch': searchCount.classList.add('Redtxt'); break; } } switch (pageType) { case 'mylist': searchCount.textContent = ' ' + total + ' '; break; case 'videoSearch': case 'imageSearch': searchCount.textContent = total; break; case 'liveSearch': searchCount.textContent = trimmedThousandsSep; break; } } }); } // 非アクティブタブを取得 var inactiveTab = document.querySelector('.tab_0, .tab1, .search_tab_list a:not(.active)'); // クラス名を変更 anchor.className = inactiveTab.className; // アドレスを変更 anchor.href = 'http://www.nicovideo.jp/tag/' + searchWords + inactiveTab.search; // タグ検索タブを追加 mylistTab.parentNode.insertBefore(tagTab, mylistTab); if (pageType === 'liveSearch') { mylistTab.parentNode.insertBefore(new Text(' '), mylistTab); } else if (inactiveTab.classList.contains('tab1')) { // GINZAバージョン mylistTab.parentNode.insertBefore(tagTab.previousSibling.cloneNode(true), mylistTab); } } /** * ニコニコ動画の上部に表示されている検索窓に、「静画」「生放送」を検索するタブを追加する。 */ function addOtherServiceTabsAboveSearchBox() { // スタイルの設定 document.head.insertAdjacentHTML('beforeend', ''); // タブリストの取得 var mylistTab = document.querySelector('#target_m, .optMylist'); var tabList = mylistTab.parentNode; // タブの複製・追加 [ { type: 'image', title: _('静画を検索'), uri: 'http://seiga.nicovideo.jp/search', text: _('静画'), }, { type: 'live', title: _('番組を探す'), uri: 'http://live.nicovideo.jp/search', text: _('生放送'), }, ].forEach(function (option) { var tab = mylistTab.cloneNode(true); if (mylistTab.classList.contains('optMylist')) { // GINZAバージョン tab.classList.remove('optMylist'); tab.classList.add('opt' + option.type[0].toUpperCase() + option.type.slice(1)); tab.dataset.type = option.type; tab.getElementsByTagName('a')[0].textContent = option.text; } else { // 原宿バージョン tab.id = 'target_' + option.type[0]; tab.title = option.title; tab.setAttribute('onclick', tab.getAttribute('onclick').replace(/'.+?'/, '\'' + option.uri + '\'')); tab.textContent = option.text; } tabList.appendChild(tab); }); if (mylistTab.classList.contains('optMylist')) { // GINZAバージョン var script = document.createElement('script'); script.text = '(' + (function () { eval('Nico.Navigation.HeaderSearch.Controller.search = ' + Nico.Navigation.HeaderSearch.Controller.search.toString().replace(/(switch.+?{.+?)(})/, '$1; break;' + 'case "image":' + 'd = "http://seiga.nicovideo.jp/search/" + e; break;' + 'case "live":' + 'd = "http://live.nicovideo.jp/search/" + e; break;' + '$2')); }).toString() + ')();'; document.head.appendChild(script); } } /** * 静画・生放送の上部に表示されている検索窓に、「動画キーワード」「動画タグ」「マイリスト」「静画」「生放送」を検索するタブバーを設置する。 */ function careteTabsBarToSearchBox() { // スタイルの設定 document.head.insertAdjacentHTML('beforeend', ''); /** * 静画検索のtargetパラメータの値。 * @type {string} */ var imageSearchParamValue = 'illust'; /** * 静画検索タブの補足情報・プレースホルダー。 * @type {string} */ var imageSearchPlaceholder = 'イラストを検索'; var form = document.querySelector('[action$="search"]'); var textField = form[pageType === 'image' ? 'q' : 'keyword']; if (pageType === 'image') { // 静画の場合 var pathnameParts = document.querySelector('#logo > h1 > a').pathname.split('/'); switch (pathnameParts[1]) { case 'manga': imageSearchParamValue = 'manga'; break; case 'book': imageSearchParamValue = pathnameParts[2] === 'r18' ? 'book_r18' : 'book'; break; } imageSearchPlaceholder = textField.defaultValue; } form.insertAdjacentHTML('afterbegin', '