您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ニコニコ動画のランキングにNG機能を追加
当前为
// ==UserScript== // @name Nico Nico Ranking NG // @namespace http://userscripts.org/users/121129 // @description ニコニコ動画のランキングにNG機能を追加 // @match http://www.nicovideo.jp/ranking* // @version 20 // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant GM_addStyle // @license MIT License // @noframes // @run-at document-start // ==/UserScript== ;(function() { 'use strict' var array = [].slice.call.bind([].slice) var noop = function() {} var toUpperCase = ''.toUpperCase.call.bind(''.toUpperCase) var getter = function(propName) { return function() { return this[propName] } } var always = function(v) { return function() { return v } } var not = function(fn) { return function() { return !fn.apply(null, arguments) } } var curry = (function() { var applyOrRebind = function(func, arity, args) { var passed = args.concat(array(arguments, 3)).slice(0, arity) return arity === passed.length ? func.apply(null, passed) : applyOrRebind.bind(null, func, arity, passed) } return function(func) { return applyOrRebind.bind(null, func, func.length, []) } })() var eq = curry(function(a, b) { return a === b }) var prop = curry(function(propName, obj) { return obj[propName] }) var callMethod = curry(function(methodName, args, obj) { return obj[methodName].apply(obj, args) }) var include = curry(function(array, elem) { return array.indexOf(elem) >= 0 }) var includeUpperCase = curry(function(array, elem) { return include(array, elem.toUpperCase()) }) var compose = function() { var args = array(arguments) var first = args.pop() return function() { return args.reduceRight(function(v, f) { return f(v) }, first.apply(null, arguments)) } } var removeAllChild = function(parent) { while (parent.firstChild) parent.removeChild(parent.firstChild) } var removeFromParent = function(elem) { var p = elem.parentNode if (p) p.removeChild(elem) } var ancestorAnchor = function(elem) { for (var a = elem; a; a = a.parentNode) if (a.tagName === 'A') return a return null } var elem = (function() { var setter = function(mapName) { return function() { if (arguments.length === 1) { var o = arguments[0] || {} Object.keys(o).forEach(function(k) { this[mapName][k] = o[k] }, this) } else if (arguments.length === 2) { this[mapName][arguments[0]] = arguments[1] } return this } } var attr = function(elem) { var l = arguments.length if (l === 3) { elem.setAttribute(arguments[1], arguments[2]) } else if (l === 2) { var o = arguments[1] || {} Object.keys(o).forEach(function(k) { var v = o[k] if (['string', 'number'].indexOf(typeof v) >= 0) { elem.setAttribute(k, o[k]) } else { elem[k] = Boolean(v) } }) } return elem } var css = function(elem) { var l = arguments.length if (l === 3) { elem.style.setProperty(arguments[1], arguments[2], null) } else if (l === 2) { var o = arguments[1] || {} Object.keys(o).forEach(function(k) { elem.style.setProperty(k, o[k], null) }) } return elem } var add = function(elem) { var children = [].concat.apply([], [].slice.call(arguments, 1)) var d = elem.ownerDocument var f = d.createDocumentFragment() children.map(function(c) { return c.nodeType ? c : d.createTextNode(c) }).forEach(f.appendChild.bind(f)) elem.appendChild(f) return elem } var Builder = function(tagName) { this.tagName = tagName this.attrMap = Object.create(null) this.cssMap = Object.create(null) this.handlers = [] this.children = [] } Builder.prototype.attr = setter('attrMap') Builder.prototype.css = setter('cssMap') Builder.prototype.on = function(type, handler, capture) { this.handlers.push({ type: type, handler: handler, capture: Boolean(capture), }) return this } Builder.prototype.add = function() { this.children = [].concat.apply(this.children, arguments) return this } Builder.prototype.new = function(doc) { doc = doc || document var result = this.tagName ? doc.createElement(this.tagName) : doc.createDocumentFragment() attr(result, this.attrMap) css(result, this.cssMap) add(result, this.children) this.handlers.forEach(function(h) { result.addEventListener(h.type, h.handler, h.capture) }) return result } var elem = function(tagName) { return new Builder(tagName) } elem.attr = attr elem.css = css elem.add = add return elem })() var updateMovies = function(movies, stores) { var visitedMovieIds = stores.visitedMovieIds.get() var ngMovieIds = stores.ngMovieIds.get() var ngTitles = stores.ngTitles.get() movies.forEach(function(movie) { movie.setVisitedIfInclude(visitedMovieIds) movie.setNgIdIfInclude(ngMovieIds) movie.setNgTitleIfInclude(ngTitles) }) } var commonCssText = function() { return [ '#nrn-config-button {', ' text-decoration: underline;', ' cursor: pointer;', '}', '.nrn-popup {', ' display: none;', ' position: absolute;', ' top: 10px;', ' right: 0px;', ' padding: 3px;', ' color: #999;', ' background-color: rgb(105, 105, 105);', '}', '.nrn-popup span {', ' color: white;', '}', '.nrn-popup span:hover {', ' text-decoration: underline;', ' cursor: pointer;', '}', '.nrn-display-none {', ' display: none;', '}', '.nrn-matched-ng-title {', ' color: white;', ' background-color: fuchsia;', '}', '.nrn-movie-info-container .nrn-movie-info-p {', ' margin-top: 4px;', ' line-height: 1.5em;', '}', '.nrn-tag-ng-button,', '.nrn-contributor-ng-button {', ' cursor: pointer;', '}', ].join('\n') } var Movie = (function() { var setIfIncludeId = function(targetProp, viewMethod) { return function(ids) { var b = include(ids, this._id) if (this[targetProp] !== b) { this[targetProp] = b this._views.forEach(callMethod(viewMethod, [this])) } } } var setNgContributorIfInclude = function(ids) { var ng = include(ids, this._id) if (this._ng !== ng) { this._ng = ng this._views.forEach(callMethod('updateNgContributor', [this._movie])) } } var equalArray = function(a1, a2) { if (a1.length !== a2.length) return false for (var i = 0; i < a1.length; i++) if (a1[i] !== a2[i]) return false return true } var compressU3000 = function(str) { return str.replace(/\u3000{2,}/g, '\u3000') } var Contributor = function(id, name, views, movie) { this._id = id this._name = name this._ng = false this._views = views this._movie = movie } Contributor.prototype.id = getter('_id') Contributor.prototype.name = getter('_name') Contributor.prototype.isNg = getter('_ng') Contributor.prototype.isUser = always(false) Contributor.prototype.isChannel = always(false) Contributor.prototype.isNull = function() { return this === Contributor.null } Contributor.prototype.setNgUserIfInclude = noop Contributor.prototype.setNgChannelIfInclude = noop Contributor.null = new Contributor(-1, '', null, null) var User = function() { Contributor.apply(this, arguments) } User.prototype = Object.create(Contributor.prototype) User.prototype.isUser = always(true) User.prototype.setNgUserIfInclude = setNgContributorIfInclude var Channel = function() { Contributor.apply(this, arguments) } Channel.prototype = Object.create(Contributor.prototype) Channel.prototype.isChannel = always(true) Channel.prototype.setNgChannelIfInclude = setNgContributorIfInclude var Movie = function(id, title, view) { this._id = id this._title = title this._views = [view] this._visited = false this._ngId = false this._ngTitle = false this._matchedNgTitle = '' this._tags = [] this._ngTags = [] this._error = '' this._description = '' this._contributor = Contributor.null } Movie.prototype.id = getter('_id') Movie.prototype.title = getter('_title') Movie.prototype.isVisited = getter('_visited') Movie.prototype.setVisitedIfInclude = setIfIncludeId('_visited', 'updateVisited') Movie.prototype.isNgId = getter('_ngId') Movie.prototype.setNgIdIfInclude = setIfIncludeId('_ngId', 'updateNgId') Movie.prototype.isNgTitle = getter('_ngTitle') Movie.prototype.matchedNgTitle = getter('_matchedNgTitle') Movie.prototype._matchedNgTitleIfInclude = function(ngTitles) { var matched = ngTitles.filter( includeUpperCase(this._title.toUpperCase())) return matched.length ? matched[0] : '' } Movie.prototype._viewUpdateMethodName = function(newMatchedNgTitle) { var newNgTitle = Boolean(newMatchedNgTitle) if (this._ngTitle !== newNgTitle) { return 'updateNgTitle' } if (this._ngTitle && newNgTitle && this._matchedNgTitle !== newMatchedNgTitle) { return 'updateMatchedNgTitle' } return '' } Movie.prototype.setNgTitleIfInclude = function(ngTitles) { var matchedNgTitle = this._matchedNgTitleIfInclude(ngTitles) var updateMethodName = this._viewUpdateMethodName(matchedNgTitle) this._ngTitle = Boolean(matchedNgTitle) this._matchedNgTitle = matchedNgTitle if (updateMethodName) { this._views.forEach(callMethod(updateMethodName, [this])) } } Movie.prototype.isNG = function() { return this._ngId || this._ngTitle || Boolean(this._ngTags.length) || this._contributor.isNg() } Movie.prototype.getTags = getter('_tags') Movie.prototype.getNgTags = getter('_ngTags') Movie.prototype.setNgTagsIfMatch = function(ngTags) { var newNgTags = ngTags.filter( includeUpperCase(this._tags.map(toUpperCase))) if (!equalArray(this._ngTags, newNgTags)) { this._ngTags = newNgTags this._views.forEach(callMethod('updateNgTags', [this])) } } Movie.prototype.hasNgTag = function(tag) { return this._ngTags.map(toUpperCase).some(eq(tag.toUpperCase())) } Movie.prototype.setNgUserIfInclude = function(ids) { this._contributor.setNgUserIfInclude(ids) } Movie.prototype.setNgChannelIfInclude = function(ids) { this._contributor.setNgChannelIfInclude(ids) } Movie.prototype.hasMovieInfo = function() { return Boolean(this._tags.length) || this._contributor !== Contributor.null } Movie.prototype.contributor = getter('_contributor') Movie.prototype._newContributorBy = function(thumbInfo) { var c = thumbInfo.contributor switch (c.type) { case 'user': return new User(c.id, c.name, this._views, this) case 'channel': return new Channel(c.id, c.name, this._views, this) default: throw new Error(c.type) } } Movie.prototype.getError = getter('_error') Movie.prototype.getDescription = getter('_description') Movie.prototype.setThumbInfo = function(thumbInfo) { if (thumbInfo.error) { this._error = thumbInfo.error } else { this._description = compressU3000(thumbInfo.description) this._tags = thumbInfo.tags this._contributor = this._newContributorBy(thumbInfo) if (this._title !== thumbInfo.title) { this.setNgTitleIfInclude([]) this._title = thumbInfo.title this._views.forEach(callMethod('updateTitle', [this])) this._titleUpdated = true } } this._views.forEach(callMethod('updateThumbInfo', [this])) } Movie.prototype.setGetThumbInfoDone = function() { this._getThumbInfoDone = true this._views.forEach(callMethod('updateGetThumbInfoDone', [this])) } Movie.prototype.addView = function(view) { this._views.push(view) if (this.isVisited()) view.updateVisited(this) if (this.isNgId()) view.updateNgId(this) if (this._titleUpdated) view.updateTitle(this) if (this.isNgTitle()) view.updateNgTitle(this) if (!this._getThumbInfoDone) return view.updateThumbInfo(this) if (this._tags.length) view.updateNgTags(this) if (this.contributor().isNg()) view.updateNgContributor(this) view.updateGetThumbInfoDone(this) } return Movie })() var ArrayStore = (function() { var defaultInclude = include var defaultFilter = function(array, elems) { return array.filter(not(include(elems))) } var value = function(elem) { return typeof elem === 'object' ? elem.value : elem } var ArrayStore = function(obj) { this._get = obj.get this._set = obj.set this._clear = obj.clear this._key = obj.key this._defaultValue = JSON.stringify(obj.defaultValue) this._include = obj.include || defaultInclude this._filter = obj.filter || defaultFilter this._cache = null this.changed = function() {} } ArrayStore.prototype.get = function() { var v = this._get(this._key, this._defaultValue) return this._cache = JSON.parse(v).map(value) } ArrayStore.prototype.getWithTextIfHas = function() { return JSON.parse(this._get(this._key, this._defaultValue)) } ArrayStore.prototype.cacheOrGet = function() { return this._cache === null ? (this._cache = this.get()) : this._cache } ArrayStore.prototype._setValue = function(value) { this._set(this._key, JSON.stringify(value)) this._cache = null } ArrayStore.prototype.clear = function() { this._clear(this._key) this._cache = null this.changed(this) } ArrayStore.prototype.add = function(elem, text) { var array = this.getWithTextIfHas() if (this._include(array.map(value), elem)) return false this._setValue(array.concat(text ? {value: elem, text: text} : elem)) this.changed(this) return true } ArrayStore.prototype.remove = function(/* ...elem */) { var a = this.getWithTextIfHas() var filtered = this._filter(a.map(value) , [].concat.apply([], arguments)) if (a.length === filtered.length) return this._setValue(a.filter(function(e) { return filtered.indexOf(value(e)) >= 0 })) this.changed(this) } return ArrayStore })() var Store = (function() { var Store = function(obj) { this._get = obj.get this._set = obj.set this._key = obj.key this._defaultValue = obj.defaultValue this.changed = function() {} } Store.prototype.get = function() { return this._get(this._key, this._defaultValue) } Store.prototype._setValue = function(value) { this._set(this._key, value) } Store.prototype.set = function(value) { if (value === this.get()) return this._setValue(value) this.changed(this) } Store.newStores = function() { var includeCaseInsensitive = function(array, elem) { return includeUpperCase(array.map(toUpperCase), elem) } var filterCaseInsensitive = function(array, elems) { return array.filter(not(includeUpperCase(elems.map(toUpperCase)))) } var o = function(key, defVal, json, caseInsensitive) { return { get: GM_getValue, set: GM_setValue, clear: GM_deleteValue, key: key, defaultValue: defVal, json: json, include: caseInsensitive ? includeCaseInsensitive : undefined, filter: caseInsensitive ? filterCaseInsensitive : undefined, } } return { visitedMovieViewMode: new Store(o('visitedMovieViewMode', 'reduce')), visibleContributorType: new Store(o('visibleContributorType', 'all')), openNewWindow: new Store(o('openNewWindow', true)), useGetThumbInfo: new Store(o('useGetThumbInfo', true)), movieInfoTogglable: new Store(o('movieInfoTogglable', true)), seamlessRankingNumber: new Store(o('seamlessRankingNumber', true)), descriptionTogglable: new Store(o('descriptionTogglable', true)), requestingNext: new Store(o('requestingNext', true)), popupVisible: new Store(o('popupVisible', true)), movingUp: new Store(o('movingUp', true)), visitedMovieIds: new ArrayStore(o('visitedMovies', [], true)), ngMovieIds: new ArrayStore(o('ngMovies', [], true)), ngTitles: new ArrayStore(o('ngTitles', [], true, true)), ngTags: new ArrayStore(o('ngTags', [], true, true)), ngUserIds: new ArrayStore(o('ngUserIds', [], true)), ngChannelIds: new ArrayStore(o('ngChannelIds', [], true)), } } return Store })() var GetThumbInfo = (function() { var parseTags = function(tags) { return [].map.call(tags, prop('textContent')) } var contributor = function(rootElem, type, id, name) { return { type: type, id: parseInt(rootElem.querySelector(id).textContent, 10), name: rootElem.querySelector(name).textContent, } } var user = function(rootElem) { return contributor(rootElem , 'user' , 'thumb > user_id' , 'thumb > user_nickname') } var channel = function(rootElem) { return contributor(rootElem , 'channel' , 'thumb > ch_id' , 'thumb > ch_name') } var parseContributor = function(rootElem) { var userId = rootElem.querySelector('thumb > user_id') return userId ? user(rootElem) : channel(rootElem) } var parseThumbInfo = function(rootElem) { return { description: rootElem.querySelector('thumb > description').textContent, tags: parseTags(rootElem.querySelectorAll('thumb > tags > tag')), contributor: parseContributor(rootElem), title: rootElem.querySelector('thumb > title').textContent, } } var parseError = function(rootElem) { var t = rootElem.querySelector('error > code').textContent switch (t) { case 'DELETED': return {error: '削除された動画'} case 'NOT_FOUND': return {error: '見つからない、または無効な動画'} case 'COMMUNITY': return {error: 'コミュニティ限定動画'} default: return {error: 'エラーコード: ' + t} } } var parseResText = function(resText) { try { var d = new DOMParser().parseFromString(resText, 'application/xml') var r = d.documentElement var status = r.getAttribute('status') switch (status) { case 'ok': return parseThumbInfo(r) case 'fail': return parseError(r) default: return {error: 'ステータス: ' + status} } } catch (e) { return {error: 'パースエラー: ' + e.message} } } var GetThumbInfo = function(request, movies, stores, concurrent) { this._request = request this._movies = movies.slice() this._concurrent = concurrent this._stores = stores this._requestCount = 0 } GetThumbInfo.prototype._onerror = function(movie) { this._requestCount-- this._requestNextMovie() movie.setThumbInfo({error: 'エラー'}) movie.setGetThumbInfoDone() } GetThumbInfo.prototype._ontimeout = function(movie, retried) { if (retried) { this._requestCount-- this._requestNextMovie() movie.setThumbInfo({error: 'タイムアウト'}) movie.setGetThumbInfoDone() } else { this._requestMovie(movie, true) } } GetThumbInfo.prototype._setNg = function(movie) { movie.setNgTagsIfMatch(this._stores.ngTags.cacheOrGet()) movie.setNgUserIfInclude(this._stores.ngUserIds.cacheOrGet()) movie.setNgChannelIfInclude(this._stores.ngChannelIds.cacheOrGet()) movie.setNgTitleIfInclude(this._stores.ngTitles.cacheOrGet()) } GetThumbInfo.prototype._onload = function(movie, res) { this._requestCount-- this._requestNextMovie() if (res.status === 200) { var thumbInfo = parseResText(res.responseText) movie.setThumbInfo(thumbInfo) if (!thumbInfo.error) this._setNg(movie) } else { movie.setThumbInfo({error: res.statusText}) } movie.setGetThumbInfoDone() } GetThumbInfo.prototype._requestMovie = function(movie, retry) { this._request({ method: 'GET', url: 'http://ext.nicovideo.jp/api/getthumbinfo/' + movie.id(), timeout: 5000, onload: this._onload.bind(this, movie), onerror: this._onerror.bind(this, movie), ontimeout: this._ontimeout.bind(this, movie, retry), }) } GetThumbInfo.prototype._requestNextMovie = function() { var m = this._movies.shift() if (!m) return this._requestMovie(m) this._requestCount++ } GetThumbInfo.prototype.request = function(movies) { ;[].push.apply(this._movies, movies || []) var space = this._concurrent - this._requestCount var c = Math.min(this._movies.length, space) for (var i = 0; i < c; i++) this._requestNextMovie() } return GetThumbInfo })() var ViewMode = function() {} ViewMode.get = function(name) { switch (name) { case ViewMode.DoNothing.prototype.name(): return new ViewMode.DoNothing() case ViewMode.Reduce.prototype.name(): return new ViewMode.Reduce() case ViewMode.Hide.prototype.name(): return new ViewMode.Hide() default: throw new Error(name) } } ViewMode.prototype.isDoNothingMode = function() { return this instanceof ViewMode.DoNothing } ViewMode.prototype.isHideMode = function() { return this instanceof ViewMode.Hide } ViewMode.prototype.isReduceMode = function() { return this instanceof ViewMode.Reduce } ViewMode.prototype.hasSameName = function(viewMode) { return this.name() === viewMode.name() } ViewMode.DoNothing = function() {} ViewMode.DoNothing.prototype = Object.create(ViewMode.prototype) ViewMode.DoNothing.prototype.name = always('doNothing') ViewMode.DoNothing.prototype.restoreViewMode = noop ViewMode.DoNothing.prototype.setViewMode = noop ViewMode.Hide = function() {} ViewMode.Hide.prototype = Object.create(ViewMode.prototype) ViewMode.Hide.prototype.name = always('hide') ViewMode.Hide.prototype.restoreViewMode = function(movieView) { movieView.setVisible(true) } ViewMode.Hide.prototype.setViewMode = function(movieView) { movieView.setVisible(false) } ViewMode.Reduce = function() {} ViewMode.Reduce.prototype = Object.create(ViewMode.prototype) ViewMode.Reduce.prototype.name = always('reduce') ViewMode.Reduce.prototype.restoreViewMode = function(movieView) { var r = movieView.root r.classList.remove('nrn-reduce') this.restoreThumb(r.querySelector('.thumb')) } ViewMode.Reduce.prototype.setViewMode = function(movieView) { var r = movieView.root r.classList.add('nrn-reduce') this.halfThumb(r.querySelector('.thumb')) } ViewMode.Reduce.prototype.halfThumb = function(thumb) { if (!thumb) return if (thumb.style.width) { var w = this.srcThumbWidth = parseInt(thumb.style.width) var t = this.srcThumbMarginTop = parseInt(thumb.style.marginTop) thumb.style.width = parseInt(w / 2) + 'px' thumb.style.marginTop = parseInt(t / 2) + 'px' } else { thumb.style.width = '100%' } } ViewMode.Reduce.prototype.restoreThumb = function(thumb) { if (!thumb) return if (this.srcThumbWidth) { thumb.style.width = this.srcThumbWidth + 'px' thumb.style.marginTop = this.srcThumbMarginTop + 'px' } else { thumb.style.width = '' } } var ViewModeStore = function(store) { this._store = store } ViewModeStore.prototype.get = function() { return ViewMode.get(this._store.get()) } ViewModeStore.prototype.set = function(viewMode) { this._store.set(viewMode.name()) } var Model = (function() { var movieUpdater = function(model, setter) { return function(store) { model._movies.forEach(function(m) { setter.call(m, store.get()) }) } } var onChanged = function(store, callback) { store.changed = callback return store } var Model = function(movies, view, stores) { this._movies = movies this._view = view this._stores = stores this._ngMovieVisible = false this._useGetThumbInfo = stores.useGetThumbInfo this._requestingNext = stores.requestingNext this._popupVisible = stores.popupVisible this._visitedMovieViewMode = new ViewModeStore( onChanged(stores.visitedMovieViewMode , view.updateVisitedMovieViewMode.bind(view))) this._visibleContributorType = onChanged(stores.visibleContributorType , view.updateVisibleContributorType.bind(view)) this._openNewWindow = onChanged(stores.openNewWindow , view.updateNewWindowOpen.bind(view)) this._seamlessRankingNumber = onChanged(stores.seamlessRankingNumber , view.updateSeamlessRankingNumber.bind(view)) this._movieInfoTogglable = onChanged(stores.movieInfoTogglable , view.updateMovieInfoTogglable.bind(view)) this._descriptionTogglable = onChanged(stores.descriptionTogglable , view.updateDescriptionTogglable.bind(view)) this._movingUp = onChanged(stores.movingUp, view.updateMovingUp.bind(view)) var p = Movie.prototype this._visitedMovieIds = onChanged(stores.visitedMovieIds , movieUpdater(this, p.setVisitedIfInclude)) this._ngMovieIds = onChanged(stores.ngMovieIds, movieUpdater(this, p.setNgIdIfInclude)) this._ngTitles = onChanged(stores.ngTitles, movieUpdater(this, p.setNgTitleIfInclude)) this._ngTags = onChanged(stores.ngTags, movieUpdater(this, p.setNgTagsIfMatch)) this._ngUserIds = onChanged(stores.ngUserIds, movieUpdater(this, p.setNgUserIfInclude)) this._ngChannelIds = onChanged(stores.ngChannelIds , movieUpdater(this, p.setNgChannelIfInclude)) } Model.prototype.movies = getter('_movies') Model.prototype.stores = getter('_stores') Model.prototype.movieById = function(id) { var a = this._movies.filter(compose(eq(id), callMethod('id', []))) return a.length ? a[0] : null } Model.prototype.isNgMovieVisible = getter('_ngMovieVisible') Model.prototype.setNgMovieVisible = function(ngMovieVisible) { if (this._ngMovieVisible !== ngMovieVisible) { this._ngMovieVisible = ngMovieVisible this._view.updateNgMovieVisible() } } Model.prototype.visitedMovieViewMode = getter('_visitedMovieViewMode') Model.prototype.visibleContributorType = getter('_visibleContributorType') Model.prototype.openNewWindow = getter('_openNewWindow') Model.prototype.useGetThumbInfo = getter('_useGetThumbInfo') Model.prototype.requestingNext = getter('_requestingNext') Model.prototype.popupVisible = getter('_popupVisible') Model.prototype.seamlessRankingNumber = getter('_seamlessRankingNumber') Model.prototype.movieInfoTogglable = getter('_movieInfoTogglable') Model.prototype.descriptionTogglable = getter('_descriptionTogglable') Model.prototype.movingUp = getter('_movingUp') Model.prototype.visitedMovieIds = getter('_visitedMovieIds') Model.prototype.ngMovieIds = getter('_ngMovieIds') Model.prototype.ngTitles = getter('_ngTitles') Model.prototype.ngTags = getter('_ngTags') Model.prototype.ngUserIds = getter('_ngUserIds') Model.prototype.ngChannelIds = getter('_ngChannelIds') Model.prototype._isVisible = function(movie) { return !movie.isNG() || this._ngMovieVisible } Model.prototype._isHiddenByVisitedHideMode = function(movie) { return movie.isVisited() && this.visitedMovieViewMode().get().isHideMode() } Model.prototype._isHiddenByContributorType = function(movie) { if (movie.contributor().isNull()) return false var t = this.visibleContributorType().get() return (t === 'channel' && !movie.contributor().isChannel()) || (t === 'user' && !movie.contributor().isUser()) } Model.prototype._isHidden = function(movie) { return !this._isVisible(movie) || this._isHiddenByVisitedHideMode(movie) || this._isHiddenByContributorType(movie) } Model.prototype._isReduced = function(movie) { return this._isVisible(movie) && movie.isVisited() && this.visitedMovieViewMode().get().isReduceMode() } Model.prototype.movieViewMode = function(movie) { if (this._isHidden(movie)) return new ViewMode.Hide() if (this._isReduced(movie)) return new ViewMode.Reduce() return new ViewMode.DoNothing() } Model.prototype.sortMoviesByVisible = function(movies) { var self = this return (movies || this._movies).map(function(movie, i) { return { rank: i, movie: movie } }).sort(function(a, b) { var aIsHidden = self._isHidden(a.movie) var bIsHidden = self._isHidden(b.movie) if (!aIsHidden && bIsHidden) return -1 if (aIsHidden && !bIsHidden) return 1 if (a.rank < b.rank) return -1 if (a.rank > b.rank) return 1 return 0 }).map(prop('movie')) } Model.prototype.addMovies = function (movies) { this._movies = this._movies.concat(movies) } return Model })() var MovieView = (function() { var newParagraph = function(doc) { return elem('p') .attr('class', 'font12 nrn-movie-info-p') .add([].slice.call(arguments, 1)) .new(doc) } var newActionButton = function(klass, doc) { return elem('span').attr('class', klass).add('[+]').new(doc) } var OPEN_TOGGLE_TEXT = '開く▼' var CLOSE_TOGGLE_TEXT = '閉じる▲' var newMovieInfoToggleButton = function(doc) { return elem('span') .attr('class', 'count nrn-movie-info-toggle') .add(elem('span') .attr('class', 'value nrn-movie-info-toggle-button') .add(OPEN_TOGGLE_TEXT) .new(doc)) .new(doc) } var newDescriptionToggleButton = function(doc) { return elem('span') .attr('class', 'nrn-desc-toggle-button') .add(OPEN_TOGGLE_TEXT) .new(doc) } var newDescriptionToggleP = function(toggle, doc) { return elem('p') .attr('class', 'nrn-desc-toggle-p') .add(toggle) .new(doc) } var decodeHtmlCharRef = (function() { var e = document.createElement('span') return function(text) { e.innerHTML = text return e.textContent } })() var newTagAnchor = function(tag, doc) { return elem('a') .attr({ href: 'http://www.nicovideo.jp/tag/' + tag, target: '_blank', class: 'nrn-movie-tag-link', }) .add(decodeHtmlCharRef(tag)) .new(doc) } var newTagSpan = function(anchor, button) { return elem('span') .attr('class', 'nrn-movie-tag') .add(anchor, button) .new(anchor.ownerDocument) } var setTagActionAndStyle = function(tagView, ng) { tagView.anchor.classList[ng ? 'add' : 'remove']('nrn-movie-ng-tag-link') tagView.button.textContent = ng ? '[x]' : '[+]' } var newContributorA = function(doc) { return elem('a') .attr({'target': '_blank', 'class': 'nrn-contributor-link'}) .new(doc) } var Description = function(movieView) { this.movieView = movieView this.expanded = false this.hasBeenSet = false this.p = elem('p').attr('class', 'nrn-desc-p').new(movieView.view.doc) this.toggle = newDescriptionToggleButton(movieView.view.doc) this.toggleP = newDescriptionToggleP(this.toggle, movieView.view.doc) } Description.prototype.container = function() { return this.movieView.root.querySelector('.itemDescription') } Description.prototype.isTruncated = function() { return this.container().textContent.slice(-3) === '...' } Description.prototype.set = function(description) { this.p.textContent = description var e = this.container() removeAllChild(e) e.classList.add('nrn-desc') e.appendChild(this.p) this.hasBeenSet = true } Description.prototype.addToggle = function() { this.container().appendChild(this.toggleP) } Description.prototype.removeToggle = function() { removeFromParent(this.toggleP) } Description.prototype.setExpanded = function(expanded) { this.expanded = expanded this.p.classList[expanded ? 'add' : 'remove']('nrn-desc-p-close') this.toggleP .classList[expanded ? 'remove' : 'add']('nrn-desc-toggle-p-open') this.toggle.textContent = expanded ? CLOSE_TOGGLE_TEXT : OPEN_TOGGLE_TEXT } Description.prototype.linkify = (function() { var re = /(sm|so|nm|co|ar|im|lv|mylist\/|watch\/|user\/)(?:\d+)/g var type2href = { sm: 'http://www.nicovideo.jp/watch/', so: 'http://www.nicovideo.jp/watch/', nm: 'http://www.nicovideo.jp/watch/', co: 'http://com.nicovideo.jp/community/', ar: 'http://ch.nicovideo.jp/article/', im: 'http://seiga.nicovideo.jp/seiga/', lv: 'http://live.nicovideo.jp/watch/', 'mylist/': 'http://www.nicovideo.jp/', 'watch/': 'http://www.nicovideo.jp/', 'user/': 'http://www.nicovideo.jp/', } return function() { var descElem = this.hasBeenSet ? this.p : this.container() var text = descElem.textContent var builder = elem() var lastIndex = 0 for (var r; r = re.exec(text);) { builder.add([ text.slice(lastIndex, r.index), elem('a') .attr({target: '_blank', href: type2href[r[1]] + r[0]}) .add(r[0]) .new(), ]) lastIndex = re.lastIndex } var df = builder.add(text.slice(lastIndex)).new() df.normalize() removeAllChild(descElem) descElem.appendChild(df) } })() var MovieInfo = function(movieView) { this.movieView = movieView this.visible = true var d = movieView.view.doc this.toggle = newMovieInfoToggleButton(d) this.toggleButton = this.toggle.firstChild this.container = elem('div') .attr('class', 'nrn-movie-info-container').new(d) this.tagP = newParagraph(d) this.contributorType = elem('span').add('ユーザー: ').new() this.contributorA = newContributorA(d) this.ngContributorButton = newActionButton('nrn-contributor-ng-button', d) this.contributorP = newParagraph(d , this.contributorType , this.contributorA , this.ngContributorButton) this.tag2view = Object.create(null) } MovieInfo.prototype.setParagraphsVisible = function(visible) { ;[this.tagP, this.contributorP].forEach(function(p) { p.classList[visible ? 'remove' : 'add']('nrn-display-none') }) } MovieInfo.prototype.getItemDataElem = function() { return this.movieView.root.querySelector('.itemData') } MovieInfo.prototype.updateVisible = function() { var b = this.toggle b.childNodes[0].textContent = this.visible ? CLOSE_TOGGLE_TEXT : OPEN_TOGGLE_TEXT this.setParagraphsVisible(this.visible) } MovieInfo.prototype.setVisible = function(visible) { this.visible = visible this.updateVisible() } MovieInfo.prototype.addToggle = function() { var b = this.toggle if (!b.parentNode) { var e = this.getItemDataElem() e.classList.add('nrn-movie-info-toggle-container') e.appendChild(b) } this.setVisible(false) } MovieInfo.prototype.removeToggle = function() { var b = this.toggle if (b.parentNode) { var e = this.getItemDataElem() e.removeChild(b) e.classList.remove('nrn-movie-info-toggle-container') } this.setVisible(true) } MovieInfo.prototype.getItemContent = function() { return this.movieView.root.querySelector('.itemContent') } MovieInfo.prototype.addContainerToItemContent = function() { var d = this.container if (!d.parentNode) this.getItemContent().appendChild(d) } MovieInfo.prototype.setTags = function(tags) { var d = this.movieView.view.doc var t2v = this.tag2view var p = this.tagP tags.forEach(function(tag) { var a = newTagAnchor(tag, d) var b = newActionButton('nrn-tag-ng-button', d) elem.add(p, newTagSpan(a, b), ' ') t2v[tag] = { anchor: a, button: b } }) this.container.appendChild(p) this.addContainerToItemContent() } MovieInfo.prototype.updateByNgTags = function(ngTags) { var t2v = this.tag2view var tags = Object.keys(t2v) var upperNgTags = ngTags.map(toUpperCase) tags.filter(not(includeUpperCase(upperNgTags))).forEach(function(tag) { setTagActionAndStyle(t2v[tag], false) }) tags.filter(includeUpperCase(upperNgTags)).forEach(function(tag) { setTagActionAndStyle(t2v[tag], true) }) } MovieInfo.prototype.setContributor = function(contributor) { this.contributorType.textContent = contributor.isUser() ? 'ユーザー: ' : 'チャンネル: ' var preHref = contributor.isUser() ? 'http://www.nicovideo.jp/user/' : 'http://ch.nicovideo.jp/channel/ch' this.contributorA.href = preHref + contributor.id() this.contributorA.textContent = contributor.name() this.container.appendChild(this.contributorP) this.addContainerToItemContent() } MovieInfo.prototype.updateContributorByNg = function(ng) { var method = ng ? 'add' : 'remove' this.contributorA.classList[method]('nrn-ng-contributor-link') this.ngContributorButton.textContent = ng ? '[x]' : '[+]' } var Title = function(a) { this.a = a } Title.prototype.update = function(movie) { var a = this.a if (!movie.isNgTitle()) { a.textContent = a.textContent return } removeAllChild(a) var m = movie.matchedNgTitle() var t = movie.title() var i = t.toUpperCase().indexOf(m.toUpperCase()) if (i !== 0) elem.add(a, t.substring(0, i)) elem.add(a, elem('span') .attr('class', 'nrn-matched-ng-title') .add(t.substring(i, i + m.length)) .new(a.ownerDocument)) if (i + m.length !== t.length) elem.add(a, t.substring(i + m.length)) } Title.prototype.setLineThrough = function(lineThrough) { this.a.classList[lineThrough ? 'add' : 'remove']('nrn-ng-movie-title') } Title.prototype.setTitle = function(title) { this.a.textContent = title } var MovieView = function(view, titleAnchor, root) { this.view = view this.root = root this.title = new Title(titleAnchor) this.viewMode = new ViewMode.DoNothing() this.movieInfo = this.newMovieInfo(this) this.description = new Description(this) this.errorElem = this.newErrorElem(view.doc) } MovieView.MovieInfo = MovieInfo MovieView.prototype.newMovieInfo = function () { return new MovieInfo(this) } MovieView.prototype.newErrorElem = function(doc) { return elem('li').attr('class', 'count').css('color', 'red').new(doc) } MovieView.prototype.setVisible = function(visible) { this.root.classList[visible ? 'remove' : 'add']('nrn-display-none') } MovieView.prototype.getMovieDataElem = function() { return this.root.querySelector('ul.list') } MovieView.prototype.setViewModeIfDiff = function(viewMode) { if (this.viewMode.hasSameName(viewMode)) return false this.viewMode.restoreViewMode(this) viewMode.setViewMode(this) this.viewMode = viewMode return true } MovieView.prototype.updateViewMode = function(movie) { var m = this.view.model.movieViewMode(movie) if (this.setViewModeIfDiff(m)) this.view.requestLoadingLazyImages() } MovieView.prototype.updateVisited = function(movie) { this.updateViewMode(movie) this.view.updatePopupMenu(movie) } MovieView.prototype.updateNgId = function(movie) { this.updateViewMode(movie) this.title.setLineThrough(movie.isNgId()) this.view.updatePopupMenu(movie) } MovieView.prototype.updateMatchedNgTitle = function(movie) { this.title.update(movie) } MovieView.prototype.updateNgTitle = function(movie) { this.updateViewMode(movie) this.updateMatchedNgTitle(movie) this.view.updatePopupMenu(movie) } MovieView.prototype._isMovieInfoTogglable = function () { return this.view.model.movieInfoTogglable().get() } MovieView.prototype.updateTags = function(movie) { this.movieInfo.setTags(movie.getTags()) if (this._isMovieInfoTogglable()) { this.movieInfo.addToggle(movie) } } MovieView.prototype.updateNgTags = function(movie) { this.updateViewMode(movie) this.movieInfo.updateByNgTags(movie.getNgTags()) } MovieView.prototype.updateDescription = function(movie) { var d = this.description if (d.isTruncated()) { d.set(movie.getDescription()) var togglable = this.view.model.descriptionTogglable().get() d.setExpanded(!togglable) if (togglable) d.addToggle() } d.linkify() } MovieView.prototype.updateError = function(movie) { this.errorElem.textContent = movie.getError() this.getMovieDataElem().appendChild(this.errorElem) } MovieView.prototype.updateContributor = function(movie) { this.movieInfo.setContributor(movie.contributor()) if (this._isMovieInfoTogglable()) { this.movieInfo.addToggle(movie) } } MovieView.prototype.updateNgContributor = function(movie) { this.updateViewMode(movie) this.movieInfo.updateContributorByNg(movie.contributor().isNg()) } MovieView.prototype.updateThumbInfo = function(movie) { if (movie.getError()) { this.updateError(movie) } else { this.updateTags(movie) this.updateContributor(movie) this.updateDescription(movie) this.updateViewMode(movie) } } MovieView.prototype.updateTitle = function(movie) { this.title.setTitle(movie.title()) } MovieView.prototype.updateGetThumbInfoDone = function() { this.root.classList.add('nrn-getThumbInfoDone') } return MovieView })() var SideMovieView = (function(_super) { var SideMovieInfo = function(movieView) { _super.MovieInfo.call(this, movieView) } SideMovieInfo.prototype = Object.create(_super.MovieInfo.prototype) SideMovieInfo.prototype.getItemDataElem = function() { return this.movieView.root } SideMovieInfo.prototype.getItemContent = function() { return this.movieView.root } SideMovieInfo.prototype.removeToggle = noop var SideMovieView = function(view, titleAnchor, root) { _super.call(this, view, titleAnchor, root) } SideMovieView.SideMovieInfo = SideMovieInfo SideMovieView.prototype = Object.create(_super.prototype) SideMovieView.prototype.newMovieInfo = function () { return new SideMovieInfo(this) } SideMovieView.prototype.newErrorElem = function(doc) { return elem('div').css('color', 'red').new(doc) } SideMovieView.prototype.getMovieDataElem = function() { return this.root } SideMovieView.prototype._isMovieInfoTogglable = always(true) SideMovieView.prototype.updateDescription = noop return SideMovieView })(MovieView) var LazyImageLoader = (function() { var INTERVAL = 125 var LazyImageLoader = function(doc) { this._doc = doc this._requested = false this._requestedInInterval = false } LazyImageLoader.prototype._lazyImages = function() { return [].slice.call(this._doc.querySelectorAll('img.thumb.jsLazyImage')) } LazyImageLoader.prototype._lazyImagesInView = function() { return this._lazyImages().filter(this._isInView.bind(this)) } LazyImageLoader.prototype._isInView = function(elem) { var r = elem.getBoundingClientRect() if (!(r.width && r.height)) return false var h = this._doc.defaultView.innerHeight return (r.top >= 0 && r.top < h) || (r.bottom > 0 && r.bottom <= h) } LazyImageLoader.prototype._load = function() { this._lazyImagesInView().forEach(function(img) { img.src = img.dataset.original img.dataset.original = '' img.classList.remove('jsLazyImage') }) } LazyImageLoader.prototype._loadIfRequestedInInterval = function() { if (this._requestedInInterval) { this._requestedInInterval = false this._load() setTimeout(this._loadIfRequestedInInterval.bind(this), INTERVAL) } else { this._requested = false } } LazyImageLoader.prototype.request = function() { if (this._requested) { this._requestedInInterval = true return } this._requested = true setTimeout(this._load.bind(this)) setTimeout(this._loadIfRequestedInInterval.bind(this), INTERVAL) } return LazyImageLoader })() var NicoChart = (function() { var url = function (o) { var t = o.type === 'all' ? '' : o.type var p = o.from === 101 ? '' : 'page=2&' return `http://now.nicochart.jp/hourly/${t}?${p}rank=-101` } var parseVideoInfo = function (e) { var t = function (selector) { var n = e.querySelector(selector) return n ? n.textContent : '' } return { point: e.parentNode.querySelector('.video-chart .point').textContent, contributionDay: e.querySelector('.first-retrieve').firstChild.nodeValue, movieLength: t('.length'), viewCount: t('.view em'), resCount: t('.res em'), mylistCount: t('.mylist em'), url: e.parentNode.querySelector('.thumbnail-image a').href, thumbURL: e.parentNode.querySelector('.thumbnail-image a img').title, title: t('.title'), description: t('.description-summary'), comment: t('.last-res-body'), fresh: Boolean(e.querySelector('.first-retrieve.new')), } } var parse = function (text, from) { var d = new DOMParser().parseFromString(text, 'text/html') var infos = d.querySelectorAll('#result .video-info') if (infos.length === 0) { throw new Error('"#result .video-info" query return empty') } return array(infos) .map(parseVideoInfo) .map(function (o) { o.rank = from++ return o }) } var NicoChart = function(request, ok, fail) { this._request = request this._ok = ok this._fail = fail } NicoChart.prototype._onload = function (from, r) { if (r.status === 200) { try { this._ok(parse(r.responseText, from)) } catch (e) { this._fail(e.message) } } else { this._fail(r.status + ' ' + r.statusText) } } NicoChart.prototype._onerror = function (message) { this._fail(message) } NicoChart.prototype.request = function (o) { if (['all', 'view', 'res', 'mylist'].indexOf(o.type) === -1) { throw new Error('o.type: ' + o.type) } if ([101, 201].indexOf(o.from) === -1) { throw new Error('o.from: ' + o.from) } this._request({ method: 'GET', timeout: 30000, url: url(o), onload: this._onload.bind(this, o.from), onerror: this._onerror.bind(this, 'エラー'), ontimeout: this._onerror.bind(this, 'タイムアウト'), }) } return NicoChart })() var ConfigDialog = (function() { var opt = function(val, text, selected) { return elem('option').attr({value: val, selected: selected}).add(text) } var textOpt = function(e) { return typeof e === 'object' ? opt(e.value, e.value + ',' + e.text) : opt(e, e) } var action = function(targetOption) { var noEmptyStr = function(v) { return v !== '' } var isPositiveInt = function(v) { var i = parseInt(v) return !isNaN(i) && i > 0 } var toInt = function(v) { return parseInt(v) } var errMessage = '1以上の整数を入力して下さい。\n' return { 'ng-movie-id': { get: function(model) { return model.ngMovieIds().get() }, getWithTextIfHas: function(model) { return model.ngMovieIds().getWithTextIfHas() }, add: function(model, val) { return model.ngMovieIds().add(val) }, remove: function(model, vals) { model.ngMovieIds().remove(vals) }, removeAll: function(model) { model.ngMovieIds().clear() }, valid: noEmptyStr, errMessage: '', url: function(v) { return 'http://www.nicovideo.jp/watch/' + v }, }, 'ng-title': { get: function(model) { return model.ngTitles().get() }, getWithTextIfHas: function(model) { return model.ngTitles().getWithTextIfHas() }, add: function(model, val) { return model.ngTitles().add(val) }, remove: function(model, vals) { model.ngTitles().remove(vals) }, removeAll: function(model) { model.ngTitles().clear() }, valid: noEmptyStr, errMessage: '', url: function(v) { return 'http://www.nicovideo.jp/search/' + v }, }, 'ng-tag': { get: function(model) { return model.ngTags().get() }, getWithTextIfHas: function(model) { return model.ngTags().getWithTextIfHas() }, add: function(model, val) { return model.ngTags().add(val) }, remove: function(model, vals) { model.ngTags().remove(vals) }, removeAll: function(model) { model.ngTags().clear() }, valid: noEmptyStr, errMessage: '', url: function(v) { return 'http://www.nicovideo.jp/tag/' + v }, }, 'ng-user-id': { get: function(model) { return model.ngUserIds().get() }, getWithTextIfHas: function(model) { return model.ngUserIds().getWithTextIfHas() }, add: function(model, val) { return model.ngUserIds().add(parseInt(val)) }, remove: function(model, vals) { model.ngUserIds().remove(vals.map(toInt)) }, removeAll: function(model) { model.ngUserIds().clear() }, valid: isPositiveInt, errMessage: errMessage, url: function(v) { return 'http://www.nicovideo.jp/user/' + v }, }, 'ng-channel-id': { get: function(model) { return model.ngChannelIds().get() }, getWithTextIfHas: function(model) { return model.ngChannelIds().getWithTextIfHas() }, add: function(model, val) { return model.ngChannelIds().add(parseInt(val)) }, remove: function(model, vals) { model.ngChannelIds().remove(vals.map(toInt)) }, removeAll: function(model) { model.ngChannelIds().clear() }, valid: isPositiveInt, errMessage: errMessage, url: function(v) { return 'http://ch.nicovideo.jp/ch' + v }, }, 'visited-movie-id': { get: function(model) { return model.visitedMovieIds().get() }, getWithTextIfHas: function(model) { return model.visitedMovieIds().getWithTextIfHas() }, add: function(model, val) { return model.visitedMovieIds().add(val) }, remove: function(model, vals) { model.visitedMovieIds().remove(vals) }, removeAll: function(model) { model.visitedMovieIds().clear() }, valid: noEmptyStr, errMessage: '', url: function(v) { return 'http://www.nicovideo.jp/watch/' + v }, }, }[targetOption.value] } var newBackground = function(zIndex) { return elem('div') .css({ 'background-color': 'black', opacity: '0.5', 'z-index': String(zIndex), position: 'fixed', top: '0px', left: '0px', width: '100%', height: '100%', }) .new() } var initCheckbox = function(checkbox, checked, changeListener) { checkbox.checked = checked checkbox.addEventListener('change', changeListener) } var byId = function(id) { return function() { return this.doc.getElementById(id) } } var ConfigDialog = function(model, doc) { this.model = model this.doc = doc this.background = newBackground(ConfigDialog.Z_INDEX - 1) this.iframe = null this.initListSelect() this.getTargetSelect() .addEventListener('change', this.updateList.bind(this)) this.getAddButton() .addEventListener('click', this.addPromptResult.bind(this)) this.getRemoveButton() .addEventListener('click', this.removeSelectedItems.bind(this)) this.getRemoveAllButton() .addEventListener('click', this.removeAll.bind(this)) this.getOpenButton() .addEventListener('click', this.openSelectedItems.bind(this)) this.getCloseButton() .addEventListener('click', this.remove.bind(this)) this.updateButtonDisabled() initCheckbox(this.getNewWindowOpenCheckbox() , this.model.openNewWindow().get() , this.updateNewWindowOpen.bind(this)) initCheckbox(this.getUseGetThumbInfoCheckbox() , this.model.useGetThumbInfo().get() , this.updateUseGetThumbInfo.bind(this)) initCheckbox(this.getSeamlessRankingNumberCheckbox() , this.model.seamlessRankingNumber().get() , this.updateSeamlessRankingNumber.bind(this)) initCheckbox(this.getRequestingNextCheckbox() , this.model.requestingNext().get() , this.updateRequestingNext.bind(this)) initCheckbox(this.getMovieInfoTogglableCheckbox() , this.model.movieInfoTogglable().get() , this.updateMovieInfoTogglable.bind(this)) initCheckbox(this.getDescriptionTogglableCheckbox() , this.model.descriptionTogglable().get() , this.updateDescriptionTogglable.bind(this)) initCheckbox(this.getPopupVisibleCheckbox() , this.model.popupVisible().get() , this.updatePopupVisible.bind(this)) } ConfigDialog.Z_INDEX = 10000 ConfigDialog.srcdoc = `<!doctype html> <html><head><style> html { margin: 0 auto; max-width: 30em; height: 100%; line-height: 1.5em; } body { height: 100%; margin: 0; display: flex; flex-direction: column; justify-content: center; } .dialog { overflow: auto; padding: 8px; background-color: white; } p { margin: 0; } .listButtonsWrap { display: flex; } .listButtonsWrap .list { flex: auto; } .listButtonsWrap .list select { width: 100%; } .listButtonsWrap .buttons { flex: none; display: flex; flex-direction: column; } .listButtonsWrap .buttons input { margin-bottom: 5px; } .sideComment { margin-left: 2em; } .dialogBottom { text-align: center; } .scriptInfo { text-align: right; } </style></head><body> <div class=dialog> <p><select id=target> <option value=ng-movie-id>NG動画ID</option> <option value=ng-title selected>NGタイトル</option> <option value=ng-tag>NGタグ</option> <option value=ng-user-id>NGユーザーID</option> <option value=ng-channel-id>NGチャンネルID</option> <option value=visited-movie-id>閲覧済み動画ID</option> </select></p> <div class=listButtonsWrap> <p class=list><select multiple size=10 id=list></select></p> <p class=buttons> <input type=button value=追加 id=addButton> <input type=button value=削除 disabled id=removeButton> <input type=button value=全削除 disabled id=removeAllButton> <input type=button value=開く disabled id=openButton> </p> </div> <p><label><input type=checkbox id=newWindowOpen>動画を別窓で開く</label></p> <p><label><input type=checkbox id=useGetThumbInfo>動画情報を取得する</label></p> <p><label><input type=checkbox id=seamlessRankingNumber>表示されている動画で順位を数える</label></p> <p><label><input type=checkbox id=requestingNext>カテゴリ合算毎時ランキングの 101 位以降を取得する</label></p> <p class=sideComment><small>取得元: <a target=_blank href=http://www.nicochart.jp/>ニコニコチャート</a></small></p> <p><label><input type=checkbox id=popupVisible>ポップアップを表示する</label></p> <fieldset> <legend>表示・非表示の切り替えボタン</legend> <p><label><input type=checkbox id=movieInfoTogglable>タグ、ユーザー、チャンネル</label></p> <p><label><input type=checkbox id=descriptionTogglable>動画説明</label></p> </fieldset> <p class=dialogBottom><input type=button value=閉じる id=closeButton></p> <p class=scriptInfo><small><a href=https://gf.qytechs.cn/ja/scripts/880-nico-nico-ranking-ng target=_blank>Nico Nico Ranking NG</a></small></p> </div> </body></html>` ConfigDialog.show = function(model, callback) { var f = document.createElement('iframe') f.srcdoc = ConfigDialog.srcdoc f.addEventListener('load', function() { var d = new ConfigDialog(model, f.contentDocument) d.setup(f) if (callback) callback(d) }) document.body.appendChild(f) } ConfigDialog.prototype.getNewWindowOpenCheckbox = byId('newWindowOpen') ConfigDialog.prototype.getUseGetThumbInfoCheckbox = byId('useGetThumbInfo') ConfigDialog.prototype.getSeamlessRankingNumberCheckbox = byId('seamlessRankingNumber') ConfigDialog.prototype.getRequestingNextCheckbox = byId('requestingNext') ConfigDialog.prototype.getMovieInfoTogglableCheckbox = byId('movieInfoTogglable') ConfigDialog.prototype.getDescriptionTogglableCheckbox = byId('descriptionTogglable') ConfigDialog.prototype.getPopupVisibleCheckbox = byId('popupVisible') ConfigDialog.prototype.getAddButton = byId('addButton') ConfigDialog.prototype.getRemoveButton = byId('removeButton') ConfigDialog.prototype.getRemoveAllButton = byId('removeAllButton') ConfigDialog.prototype.getOpenButton = byId('openButton') ConfigDialog.prototype.getCloseButton = byId('closeButton') ConfigDialog.prototype.getTargetSelect = byId('target') ConfigDialog.prototype.getListSelect = byId('list') ConfigDialog.prototype.initListSelect = function() { var l = this.getListSelect() elem.add(l, this.model.ngTitles().get().map(function(t) { return opt(t, t).new(this.doc) }, this)) l.addEventListener('change', this.updateButtonDisabled.bind(this)) } ConfigDialog.prototype.setup = function(iframe) { document.body.appendChild(this.background) this.iframe = elem.css(iframe, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', 'z-index': ConfigDialog.Z_INDEX, }) } ConfigDialog.prototype.remove = function() { var f = this.iframe if (f && f.parentNode) f.parentNode.removeChild(f) var b = this.background if (b.parentNode) b.parentNode.removeChild(b) } ConfigDialog.prototype.selectedTargetOption = function() { return this.getTargetSelect().options[this.getTargetSelect().selectedIndex] } ConfigDialog.prototype.addPromptResult = function() { var o = this.selectedTargetOption() var a = action(o) var r = null do { r = window.prompt((r ? '"' + r + '"は登録済みです。\n' : '') + o.text , r || '') if (r === null) return while (!a.valid(r)) { r = window.prompt(a.errMessage + o.text, r) if (r === null) return } } while (!a.add(this.model, r)) this.getListSelect().appendChild(opt(r, r).new(this.doc)) this.updateButtonDisabled() } ConfigDialog.prototype.removeSelectedItems = function() { var opts = [].slice.call(this.getListSelect().selectedOptions) opts.forEach(function(o) { o.parentNode.removeChild(o) }) var selected = this.selectedTargetOption() action(selected).remove(this.model , opts.map(function(o) { return o.value })) this.updateButtonDisabled() } ConfigDialog.prototype.removeAll = function() { var o = this.selectedTargetOption() if (!window.confirm('すべての"' + o.text + '"を削除しますか?')) return action(o).removeAll(this.model) ;[].slice.call(this.getListSelect().options).forEach(function(o) { o.parentNode.removeChild(o) }) this.updateButtonDisabled() } ConfigDialog.prototype.openSelectedItems = function() { var a = action(this.selectedTargetOption()) ;[].forEach.call(this.getListSelect().selectedOptions, function(o) { GM_openInTab(a.url(o.value)) }) } ConfigDialog.prototype.updateList = function() { ;[].slice.call(this.getListSelect().options).forEach(function(o) { o.parentNode.removeChild(o) }) var o = this.selectedTargetOption() action(o).getWithTextIfHas(this.model).forEach(function(v) { this.getListSelect().appendChild(textOpt(v).new(this.doc)) }, this) this.updateButtonDisabled() } ConfigDialog.prototype.updateButtonDisabled = function() { this.getRemoveAllButton().disabled = !this.getListSelect().options.length var disabled = this.getListSelect().selectedIndex === -1 this.getRemoveButton().disabled = disabled this.getOpenButton().disabled = disabled } ConfigDialog.prototype.updateNewWindowOpen = function() { this.model.openNewWindow().set(this.getNewWindowOpenCheckbox().checked) } ConfigDialog.prototype.updateSeamlessRankingNumber = function () { this.model.seamlessRankingNumber() .set(this.getSeamlessRankingNumberCheckbox().checked) } ConfigDialog.prototype.updateUseGetThumbInfo = function() { this.model.useGetThumbInfo() .set(this.getUseGetThumbInfoCheckbox().checked) } ConfigDialog.prototype.updateRequestingNext = function() { this.model.requestingNext().set(this.getRequestingNextCheckbox().checked) } ConfigDialog.prototype.updateMovieInfoTogglable = function() { this.model.movieInfoTogglable() .set(this.getMovieInfoTogglableCheckbox().checked) } ConfigDialog.prototype.updateDescriptionTogglable = function() { this.model.descriptionTogglable() .set(this.getDescriptionTogglableCheckbox().checked) } ConfigDialog.prototype.updatePopupVisible = function() { this.model.popupVisible().set(this.getPopupVisibleCheckbox().checked) } return ConfigDialog })() var GinzaView = (function() { var bindClassContains = function(elem) { return elem.classList.contains.bind(elem.classList) } var checkbox = function(doc, clickCallback, checked) { return elem('input') .attr({type: 'checkbox', checked: checked}) .on('click', clickCallback) .new(doc) } var popupMenuButton = function(text, klass, doc) { return elem('span').attr('class', klass).add(text).new(doc) } var movieNgButtonText = function(ng) { return ng ? 'NG解除' : 'NG登録' } var newMovieNgButton = function(doc) { return popupMenuButton(movieNgButtonText(false) , 'nrn-movie-ng-button' , doc) } var titleNgButtonText = function(ng) { return ng ? 'NGタイトル削除' : 'NGタイトル追加' } var newTitleNgButton = function(doc) { return popupMenuButton(titleNgButtonText(false) , 'nrn-title-ng-button' , doc) } var visitButtonText = function(ng) { return ng ? '未閲覧' : '閲覧済み' } var newVisitButton = function(doc) { return popupMenuButton(visitButtonText(false), 'nrn-visit-button', doc) } var newContributorTypeSelect = function(doc, changeListener) { return elem('select') .add([ elem('option').attr({value: 'all', selected: true}).add('全部').new(doc), elem('option').attr('value', 'user').add('ユーザー').new(doc), elem('option').attr('value', 'channel').add('チャンネル').new(doc), ]) .on('change', changeListener) .new(doc) } var newVisitedMovieViewModeSelect = function(doc, changeListener) { return elem('select') .add([ elem('option').attr({value: 'reduce', selected: true}).add('縮小').new(doc), elem('option').attr('value', 'hide').add('非表示').new(doc), elem('option').attr('value', 'doNothing').add('通常表示').new(doc), ]) .on('change', changeListener) .new(doc) } var newPopupMenu = function(doc) { return elem('div') .attr('class', 'nrn-popup') .add([ newVisitButton(doc), ' | ', newMovieNgButton(doc), ' | ', newTitleNgButton(doc), ]) .new(doc) } var containsClass = curry(function(className, elem) { return elem.classList.contains(className) }) var contributionDay = function (nicoChartDay) { var r = /(\d{4})年(\d{2})月(\d{2})日 (\d{2}):(\d{2}):\d{2}/ .exec(nicoChartDay) return `${r[1]}/${r[2]}/${r[3]} ${r[4]}:${r[5]}` } var hourlyRegExp = /^\/ranking\/(fav|view|res|mylist)\/hourly\/all/ var newIdToMovie = function(movies) { return (movies || []).reduce(function(o, m) { o[m.id()] = m return o }, {}) } var sideSelector = function(s) { return '.column.sub .ranking' + ' .videoRcolumn:not(.niconews):not(.blomaga):not(.seiga):not(.ichiba)' + ' ' + s } var GinzaView = function(doc) { doc = doc || document this.doc = doc this.visitedMovieViewModeSelect = newVisitedMovieViewModeSelect( doc, this._setVisitedMovieViewMode.bind(this)) this.contributorTypeSelect = newContributorTypeSelect( doc, this._setVisibleContributorType.bind(this)) this.ngMovieVisibleCheckbox = checkbox(doc, this._setNgMovieVisible.bind(this)) this.configButton = this._newConfigButton() this.popup = newPopupMenu(doc) this.idToMovieView = Object.create(null) this.lazyImageLoader = new LazyImageLoader(doc) this.rankingIds = Object.create(null) } GinzaView.prototype.setModel = function(model) { this.model = model this.updateVisitedMovieViewMode() this.updateNewWindowOpen() this.updateMovieInfoTogglable() this.updateSeamlessRankingNumber() this.updateVisibleContributorType() } GinzaView.prototype._newConfigButton = function() { return elem('span') .attr('id', 'nrn-config-button') .on('click', this.showConfigDialog.bind(this)) .add('設定') .new(this.doc) } GinzaView.prototype._getMovieImages = function() { return array(this.doc.querySelectorAll('img.thumb')) } GinzaView.prototype._getMovieAnchors = function() { return this._getMovieImages() .map(prop('parentNode')) .concat(array(this.doc.querySelectorAll(sideSelector('.itemThumbWrap')))) .concat(this._getWatchAnchors()) .concat(this._getSideWatchAnchors()) } GinzaView.prototype._getWatchAnchors = function(root) { root = root || this.doc return array(root.querySelectorAll('p.itemTitle.ranking a')) } GinzaView.prototype._getSideWatchAnchors = function() { return array(this.doc.querySelectorAll(sideSelector('.itemTitle a'))) } GinzaView.prototype._isMovieRoot = function(elem) { return ['item', 'videoRanking'].every(bindClassContains(elem)) } GinzaView.prototype._getMovieRoot = function(child) { for (var e = child; e && e.classList; e = e.parentNode) { if (this._isMovieRoot(e)) return e } return null } GinzaView.prototype._newMovieView = function(titleAnchor) { return new MovieView(this, titleAnchor, this._getMovieRoot(titleAnchor)) } GinzaView.prototype.movieIdInHRef = function(href) { var execResult = /watch\/([^/?]+)/.exec(href) return execResult ? execResult[1] : null } GinzaView.prototype._addMovieViewById = function(id, view) { var v = this.idToMovieView[id] if (Array.isArray(v)) v.push(view) else if (v) this.idToMovieView[id] = [v, view] else this.idToMovieView[id] = view } GinzaView.prototype._getMovieViewsById = function(id) { var v = this.idToMovieView[id] return Array.isArray(v) ? v : [v] } GinzaView.prototype._getAllMovieViews = function() { return Object.keys(this.idToMovieView) .map(this._getMovieViewsById.bind(this)) .reduce(function(a, v) { ;[].push.apply(a, v) return a }, []) } GinzaView.prototype.getMovies = function(root, movies) { var result = [] var idToMovie = newIdToMovie(movies) this._getWatchAnchors(root).forEach(function(a) { var id = this.movieIdInHRef(a.getAttribute('href')) this.rankingIds[id] = true var movieView = this._newMovieView(a) this._addMovieViewById(id, movieView) if (idToMovie[id]) { idToMovie[id].addView(movieView) } else { result.push(new Movie(id, a.textContent, movieView)) } }, this) return result } GinzaView.prototype._getSideMovieRoot = function(child) { for (var e = child; e && e.classList; e = e.parentNode) { if (e.classList.contains('item')) return e } return null } GinzaView.prototype.getSideMovies = function(movies) { var result = [] var idToMovie = newIdToMovie(movies) this._getSideWatchAnchors().forEach(function(a) { var id = this.movieIdInHRef(a.getAttribute('href')) var movieView = new SideMovieView(this, a, this._getSideMovieRoot(a)) this._addMovieViewById(id, movieView) if (idToMovie[id]) { idToMovie[id].addView(movieView) } else { result.push(new Movie(id, a.textContent, movieView)) } }, this) return result } GinzaView.prototype._newAddedController = function() { return this.doc.createDocumentFragment() } GinzaView.prototype._newControllers = function() { return elem('div') .add([ elem('label').add('閲覧済みの動画を', this.visitedMovieViewModeSelect).new(this.doc), ' | ', elem('label').add('投稿者', this.contributorTypeSelect).new(this.doc), ' | ', elem('label').add(this.ngMovieVisibleCheckbox, ' NG動画を表示') .new(this.doc), ' | ', this._newAddedController(), this.configButton, ]) .new(this.doc) } GinzaView.prototype.addControllers = function() { var mainDiv = this.doc.querySelector('div.contentBody.video') mainDiv.insertBefore(this._newControllers(), mainDiv.firstChild) } GinzaView.prototype.updateNewWindowOpen = function() { var f = this.model.openNewWindow().get() ? callMethod('setAttribute', ['target', '_blank']) : callMethod('removeAttribute', ['target']) this._getMovieAnchors().forEach(f) } GinzaView.prototype.updateViewMode = function(movie) { this._getMovieViewsById(movie.id()) .forEach(callMethod('updateViewMode', [movie])) } GinzaView.prototype.updateVisitedMovieViewMode = function() { this.visitedMovieViewModeSelect.value = this.model.visitedMovieViewMode().get().name() this.model.movies().forEach(this.updateViewMode, this) } GinzaView.prototype.updateNgMovieVisible = function() { this.ngMovieVisibleCheckbox.checked = this.model.isNgMovieVisible() this.model.movies().forEach(this.updateViewMode, this) } GinzaView.prototype.updateVisibleContributorType = function() { this.contributorTypeSelect.value = this.model.visibleContributorType().get() this.model.movies().forEach(this.updateViewMode, this) } GinzaView.prototype.toggleDescriptionExpanded = function(event) { var movieId = this.movieIdByComponent(event.target) this._getMovieViewsById(movieId) .filter(function(v) { return v.description.toggle === event.target }) .forEach(function(v) { var d = v.description d.setExpanded(!d.expanded) }) } GinzaView.prototype.updateDescriptionTogglable = function() { var togglable = this.model.descriptionTogglable().get() var methodName = togglable ? 'addToggle' : 'removeToggle' this._getAllMovieViews() .filter(compose(prop('hasBeenSet'), prop('description'))) .forEach(function(view) { view.description[methodName]() view.description.setExpanded(!togglable) }) } GinzaView.prototype._addMovieInfoToggleButton = function(movie) { this._getMovieViewsById(movie.id()) .map(prop('movieInfo')) .forEach(callMethod('addToggle', [])) } GinzaView.prototype._removeMovieInfoToggleButton = function(movie) { this._getMovieViewsById(movie.id()) .map(prop('movieInfo')) .forEach(callMethod('removeToggle', [])) } GinzaView.prototype.toggleMovieInfoVisible = function(event) { var movieId = this.movieIdByComponent(event.target) this._getMovieViewsById(movieId) .filter(function(v) { return v.movieInfo.toggleButton === event.target }) .forEach(function(v) { v.movieInfo.setVisible(!v.movieInfo.visible) }) } GinzaView.prototype.updateMovieInfoTogglable = function() { var f = this.model.movieInfoTogglable().get() ? this._addMovieInfoToggleButton : this._removeMovieInfoToggleButton this.model.movies() .filter(callMethod('hasMovieInfo', [])) .forEach(f, this) } GinzaView.prototype._setVisitedMovieViewMode = function() { this.model.visitedMovieViewMode() .set(ViewMode.get(this.visitedMovieViewModeSelect.value)) } GinzaView.prototype._setVisibleContributorType = function() { this.model.visibleContributorType() .set(this.contributorTypeSelect.value) } GinzaView.prototype._setNgMovieVisible = function() { this.model.setNgMovieVisible(this.ngMovieVisibleCheckbox.checked) } GinzaView.prototype.showConfigDialog = function() { ConfigDialog.show(this.model) } GinzaView.prototype.requestLoadingLazyImages = function() { this.lazyImageLoader.request() } GinzaView.prototype.movieIdByComponent = function(elem) { var r = this._getMovieRoot(elem) if (r) return r.dataset.id var sr = this._getSideMovieRoot(elem) if (!sr) return '' var a = sr.querySelector('.itemTitle a') return a ? this.movieIdInHRef(a.href) : '' } GinzaView.prototype.movieIdByPopup = function() { return this.movieIdByComponent(this.popup) } GinzaView.prototype.tagByTagNgButton = function(tagNgButton) { return tagNgButton.previousSibling.textContent } GinzaView.prototype.isMovieNgButton = containsClass('nrn-movie-ng-button') GinzaView.prototype.isTitleNgButton = containsClass('nrn-title-ng-button') GinzaView.prototype.isVisitButton = containsClass('nrn-visit-button') GinzaView.prototype.isTagNgButton = containsClass('nrn-tag-ng-button') GinzaView.prototype.isContributorNgButton = containsClass('nrn-contributor-ng-button') GinzaView.prototype.isTitleAnchor = function(elem) { var a = ancestorAnchor(elem) return Boolean(a) && Boolean(a.parentNode) && Boolean(a.parentNode.classList) && a.parentNode.classList.contains('itemTitle') && a.hostname === 'www.nicovideo.jp' && Boolean(this.movieIdByComponent(a)) } GinzaView.prototype.isThumbAnchor = function(elem) { var a = ancestorAnchor(elem) return Boolean(a) && a.classList.contains('itemThumbWrap') && a.hostname === 'www.nicovideo.jp' && Boolean(this.movieIdByComponent(a)) } GinzaView.prototype.isMovieInfoToggle = containsClass('nrn-movie-info-toggle-button') GinzaView.prototype.isDescriptionToggle = containsClass('nrn-desc-toggle-button') GinzaView.prototype.contentBody = function() { return this.doc.querySelector('.container.columns .inner') } GinzaView.prototype.updatePopupMenu = function(movie) { var p = this.popup if (!p.parentNode) return if (this.movieIdByComponent(p) !== movie.id()) return p.childNodes[0].textContent = visitButtonText(movie.isVisited()) p.childNodes[2].textContent = movieNgButtonText(movie.isNgId()) p.childNodes[4].textContent = titleNgButtonText(movie.isNgTitle()) } GinzaView.prototype.addPopupMenuTo = function(elem) { var r = this._getMovieRoot(elem) if (!r) return r.appendChild(this.popup) this.updatePopupMenu(this.model.movieById(this.movieIdByComponent(elem))) } GinzaView.prototype.removePopupMenu = function() { removeFromParent(this.popup) } GinzaView.prototype.updateSeamlessRankingNumber = function () { var b = this.model.seamlessRankingNumber().get() var e = this.doc.getElementById('nrn-seamlessRankingNumberStyle') if (b && !e) { var s = this.doc.createElement('style') s.id = 'nrn-seamlessRankingNumberStyle' s.textContent = this._getSeamlessRankingNumberCssText() this.doc.head.appendChild(s) } else if (!b && e) { e.remove() } } GinzaView.prototype._convertNicoChartTime = function(t) { var r = /((\d+)時間)?((\d{1,2})分)?((\d{1,2})秒)?/.exec(t) if (!(r && (r[1] || r[3] || r[5]))) return t var result = '' if (r[1]) result += r[2] + ':' if (r[3]) { result += (r[1] && r[4].length === 1 ? '0' : '') + r[4] } else { result += r[1] ? '00' : '0' } result += ':' if (r[5]) { result += (r[6].length === 1 ? '0' : '') + r[6] } else { result += '00' } return result } GinzaView.prototype.createMovieRoots = function (objs) { var div = this.doc.createElement('div') div.innerHTML = objs.map(function (o) { var id = this.movieIdInHRef(o.url) return `<li class="item videoRanking nrn-fromNicoChart" data-video-item data-enable-uad="1" data-id="${id}"> <div class="rankingNumWrap"> <p class="rankingNum">${o.rank}</p> <p class="rankingPt">+${o.point}</p> </div> <div data-video-comments style="display: none;"> <p class="adComment" data-video-comments-inner></p> </div> <div class="videoList01Wrap"> <p class="itemTime${o.fresh ? ' new' : ''}"> <span>${contributionDay(o.contributionDay)}</span> 投稿</p> <div class="itemThumbBox"><div class="itemThumb" data-video-thumbnail data-id="${id}"><a href="/watch/${id}" class="itemThumbWrap" data-link data-href="/watch/${id}"><img src="http://res.nimg.jp/images/noimage.png" title="" alt="" class="noImage"><img class="jsLazyImage thumb" src="http://res.nimg.jp/images/1x1.gif" data-original="${o.thumbURL}" alt="${o.title}" data-thumbnail></a></div><span class="videoLength">${this._convertNicoChartTime(o.movieLength)}</span></div> </div> <div class="itemContent"> <p class="itemTitle ranking"> <a title="${o.title}" href="watch/${id}" data-href="watch/${id}">${o.title}</a> </p> <div class="wrap"> <p class="itemDescription ranking">${o.description}</p> <p class="itemComment ranking">${o.comment}</p> </div> <div class="itemData"> <ul class="list"> <li class="count view">再生<span class="value">${o.viewCount}</span></li> <li class="count comment">コメ<span class="value">${o.resCount}</span></li> <li class="count mylist">マイ<span class="value"><a href="/mylistcomment/video/${id}">${o.mylistCount}</a></span></li> <li class="count ads" style="display: none;" data-uad-point-outer>宣伝<span class="value"><a href="http://uad.nicovideo.jp/ads/?vid=so27132526&video_rank" data-uad-point>0</a></span></li> </ul> </div> </div> </li>` }, this).join('') return div } GinzaView.prototype._getSeamlessRankingNumberCssText = function () { var e = this.doc.querySelector('.rankingNum') var n = e ? parseInt(e.textContent) - 1 : 0 return `body { counter-increment: ranking ${n}; } .video .item.videoRanking { counter-increment: ranking; } .videoList01 .item.videoRanking .rankingNumWrap .rankingNum { font-size: 0; } .videoList01 .item.videoRanking .rankingNumWrap .rankingNum::after { content: counter(ranking, decimal); font-size: 40px; line-height: 1.3; }` } GinzaView.prototype.getCssText = function () { return commonCssText() + '\n' + [ '.item.videoRanking:hover .nrn-popup {', ' display: inherit;', '}', '.nrn-reduce .rankingPt,', '.nrn-reduce .itemTime,', '.nrn-reduce .wrap,', '.nrn-reduce .itemData,', '.nrn-reduce .nrn-movie-info-container {', ' display: none;', '}', '.column.sub .nrn-reduce .wrap,', '.column.sub .nrn-reduce .itemData,', '.column.sub .nrn-reduce .nrn-movie-info-container {', ' display: initial;', '}', '.nrn-reduce .rankingNum {', ' font-size: 150%;', '}', '.videoList01 .nrn-reduce .videoList01Wrap {', ' width: 80px;', '}', '.videoList01 .nrn-reduce .itemContent .itemTitle.ranking {', ' width: auto;', '}', '.video .nrn-reduce .itemThumbBox,', '.video .nrn-reduce .itemThumbBox .itemThumb,', '.video .nrn-reduce .itemThumbBox .itemThumb .itemThumbWrap {', ' width: 80px;', ' height: 45px;', '}', '.nrn-contributor-link {', ' color: rgb(51, 51, 51);', '}', '.nrn-ng-contributor-link {', ' color: white;', ' background-color: fuchsia;', '}', '.nrn-movie-tag-link {', ' color: rgb(51, 51, 51);', '}', '.nrn-movie-ng-tag-link {', ' color: white;', ' background-color: fuchsia;', '}', '.nrn-desc-toggle-button {', ' cursor: pointer;', ' text-decoration: underline;', '}', '.nrn-desc-toggle-p {', ' text-align: right;', '}', '.nrn-desc-toggle-p-open {', ' position: absolute;', ' top: 0;', ' right: 0;', '}', '.nrn-desc-p {', ' width: 400px;', ' height: 14px;', '}', '.nrn-desc-p.nrn-desc-p-close {', ' width: 440px;', ' height: auto;', '}', '.videoList01 .itemContent .itemDescription.ranking.nrn-desc {', ' height: auto;', ' position: relative;', '}', '.nrn-movie-info-toggle {', ' position: absolute;', ' top: 0;', ' right: 0;', '}', '.video .itemData .count .value.nrn-movie-info-toggle-button {', ' cursor: pointer;', ' text-decoration: underline;', ' padding: 0;', '}', '.column.sub .nrn-movie-info-toggle {', ' top: initial;', ' bottom: 0;', ' text-decoration: underline;', ' cursor: pointer;', '}', '.nrn-movie-info-toggle-container {', ' position: relative;', ' width: 440px;', '}', '.nrn-movie-tag {', ' white-space: nowrap;', ' margin-right: 0.5em;', '}', '.nrn-ng-movie-title {', ' text-decoration: line-through;', '}', ].join('\n') + ` .item.videoRanking.nrn-fromNicoChart .videoList01Wrap { text-align: center; } .item.videoRanking.nrn-fromNicoChart .itemThumbBox { display: inline-block; } .item.videoRanking.nrn-fromNicoChart .itemThumbBox, .item.videoRanking.nrn-fromNicoChart .itemThumbBox .itemThumb, .item.videoRanking.nrn-fromNicoChart .itemThumbBox .itemThumb .itemThumbWrap, .item.videoRanking.nrn-fromNicoChart .itemThumbBox .itemThumb .itemThumbWrap .thumb { height: 100px; width: 130px; } .item.videoRanking.nrn-fromNicoChart .itemThumbBox .itemThumb .itemThumbWrap .thumb { max-height: 100px; } .item.videoRanking.nrn-fromNicoChart.nrn-reduce .videoList01Wrap { margin-left: 15px; } .item.videoRanking.nrn-fromNicoChart.nrn-reduce .itemThumbBox, .item.videoRanking.nrn-fromNicoChart.nrn-reduce .itemThumbBox .itemThumb, .item.videoRanking.nrn-fromNicoChart.nrn-reduce .itemThumbBox .itemThumb .itemThumbWrap, .item.videoRanking.nrn-fromNicoChart.nrn-reduce .itemThumbBox .itemThumb .itemThumbWrap .thumb { height: 50px; width: 80px; } .item.videoRanking, .column.sub .ranking .videoRcolumn:not(.niconews):not(.blomaga):not(.seiga):not(.ichiba) .item { visibility: hidden; } .item.videoRanking.nrn-getThumbInfoDone, .column.sub .ranking .videoRcolumn:not(.niconews):not(.blomaga):not(.seiga):not(.ichiba) .item.nrn-getThumbInfoDone { visibility: inherit; } ` } GinzaView.prototype.isHourlyAll = function () { return hourlyRegExp.test(this.doc.location.pathname) } GinzaView.prototype.getSortType = function () { var r = hourlyRegExp.exec(this.doc.location.pathname) if (r && r[1]) return r[1] === 'fav' ? 'all' : r[1] throw new Error(r && r[1]) } GinzaView.prototype.requestToNicoChart = function (getThumbInfo, from) { from = from || 101 var progress = elem('p').add(from + ' 位以降を取得中...').new(this.doc) this.contentBody().appendChild(progress) new NicoChart(GM_xmlhttpRequest, function (objs) { progress.remove() var div = this.createMovieRoots(objs.filter(function (o) { return !this.rankingIds[this.movieIdInHRef(o.url)] }, this)) var movies = this.getMovies(div, this.model.movies()) updateMovies(movies, this.model.stores()) this.model.addMovies(movies) var target = this.doc.querySelector('.contentBody.video > .list') array(div.childNodes).forEach(function (c) { target.appendChild(c) }) this.updateNewWindowOpen() if (getThumbInfo) { getThumbInfo.request(this.model.sortMoviesByVisible(movies)) } else { movies.forEach(callMethod('setGetThumbInfoDone', [])) } if (from === 101) { this.requestToNicoChart(getThumbInfo, 201) } }.bind(this), function (message) { progress.style.color = 'red' progress.innerHTML = `<a href="http://www.nicochart.jp/" target=_blank>ニコニコチャート</a>からの取得に失敗しました(${message})` }).request({type: this.getSortType(), from: from}) } GinzaView.prototype.updateMovingUp = noop GinzaView.prototype.moveUp = noop return GinzaView })() var MatrixMovieView = (function(_super) { var MatrixMovieInfo = function(movieView) { _super.MovieInfo.call(this, movieView) } MatrixMovieInfo.prototype = Object.create(_super.MovieInfo.prototype) MatrixMovieInfo.prototype.getItemDataElem = function() { return this.movieView.root } MatrixMovieInfo.prototype.getItemContent = function() { return this.movieView.root } var MatrixMovieView = function(view, titleAnchor, root) { _super.call(this, view, titleAnchor, root) this.sourceRow = root.parentNode.rowIndex this.sourceCol = root.cellIndex } MatrixMovieView.MatrixMovieInfo = MatrixMovieInfo MatrixMovieView.prototype = Object.create(_super.prototype) MatrixMovieView.prototype.newMovieInfo = function () { return new MatrixMovieInfo(this) } MatrixMovieView.prototype.newErrorElem = function(doc) { return elem('div').css('color', 'red').new(doc) } MatrixMovieView.prototype.isVisible = function() { return !this.root.classList.contains('nrn-hidden') } MatrixMovieView.prototype.setVisible = function(visible) { this.root.classList[visible ? 'remove' : 'add']('nrn-hidden') this.view.requestMovingUp() } MatrixMovieView.prototype.getMovieDataElem = function() { return this.root } MatrixMovieView.prototype.currentRow = function() { return this.root.parentNode.rowIndex } MatrixMovieView.prototype._isMovieInfoTogglable = always(true) MatrixMovieView.prototype.updateDescription = noop return MatrixMovieView })(MovieView) var MatrixView = (function (_super) { var colToRows = function(idToMovieView) { return Object.keys(idToMovieView).map(function(id) { return idToMovieView[id] }).reduce(function(o, v) { var a = o[v.sourceCol] if (a) a.push(v); else o[v.sourceCol] = [v] return o }, {}) } var visibleComparator = function(a, b) { var av = a.isVisible() var bv = b.isVisible() if (av && !bv) return -1 if (!av && bv) return 1 if (a.sourceRow < b.sourceRow) return -1 if (a.sourceRow > b.sourceRow) return 1 return 0 } var sourceRowComparator = function(a, b) { if (a.sourceRow < b.sourceRow) return -1 if (a.sourceRow > b.sourceRow) return 1 return 0 } var rowToCols = function(colToRows) { return Object.keys(colToRows).sort().map(function(col) { return colToRows[col] }).reduce(function(o, rows) { return rows.reduce(function(o, r, i) { var a = o[i] if (a) a.push(r); else o[i] = [r] return o }, o) }, []) } var currentRowComparator = function(a, b) { if (a.currentRow() < b.currentRow()) return -1 if (a.currentRow() > b.currentRow()) return 1 return 0 } var diffRowToCols = function(current, fresh) { var result = [] for (var i = 0; i < current.length; i++) { var cols = current[i] var diff = [] for (var j = 0; j < cols.length; j++) { var f = fresh[i][j] if (cols[j].root === f.root) { diff.push(null) } else { diff.push(f) } } result.push(diff) } return result } var MatrixView = function () { _super.apply(this, arguments) this.movingUpCheckbox = elem('input') .attr('type', 'checkbox') .on('change', this._setMovingUp.bind(this)) .new(this.doc) this._movingUpRequested = false } MatrixView.prototype._newMovingUpCheckbox = function() { return elem('input').attr('type', 'checkbox').new(this.doc) } MatrixView.is = function (location) { return location.pathname.startsWith('/ranking/matrix') } MatrixView.prototype = Object.create(_super.prototype) MatrixView.prototype.setModel = function(model) { _super.prototype.setModel.call(this, model) this.movingUpCheckbox.checked = model.movingUp().get() } MatrixView.prototype.contentBody = function() { return this.doc.querySelector('.column.main') } MatrixView.prototype._getWatchAnchors = function() { return array(this.doc.querySelectorAll('.itemTitle > a')) } MatrixView.prototype._isMovieRoot = function(elem) { return elem.tagName === 'TD' } MatrixView.prototype._newMovieView = function(root) { return new MatrixMovieView(this, root, this._getMovieRoot(root)) } MatrixView.prototype.addControllers = function() { var c = this._newControllers() c.style.marginTop = '10px' var b = this.contentBody() b.insertBefore(c, b.firstChild) } MatrixView.prototype.isTitleAnchor = function(elem) { var a = ancestorAnchor(elem) return Boolean(a) && Boolean(a.parentNode) && a.parentNode.classList.contains('itemTitle') } MatrixView.prototype.isThumbAnchor = function(elem) { var a = ancestorAnchor(elem) return Boolean(a) && a.classList.contains('itemThumbWrap') } MatrixView.prototype.getCssText = function () { return commonCssText() + '\n' + [ 'td:hover .nrn-popup {', ' display: inherit;', ' z-index: 9999;', '}', '.top20 .bg_grade_0 a.nrn-contributor-link:link {', ' color: black !important;', '}', '.top20 .bg_grade_0 a.nrn-contributor-link:link:hover {', ' background-color: initial;', '}', '.top20 .bg_grade_0 a.nrn-ng-contributor-link:link {', ' color: white !important;', ' background-color: fuchsia;', '}', '.top20 .bg_grade_0 a.nrn-movie-tag-link:link {', ' color: black !important;', '}', '.top20 .bg_grade_0 a.nrn-movie-tag-link:link:hover {', ' background-color: initial;', '}', '.top20 .bg_grade_0 a.nrn-movie-ng-tag-link:link {', ' color: white !important;', ' background-color: fuchsia;', '}', '.nrn-movie-info-toggle-button {', ' cursor: pointer;', ' text-decoration: underline;', '}', 'table.top20 tr td .nrn-movie-info-toggle-container {', ' background-position: top;', '}', '.nrn-movie-info-toggle {', ' display: block;', ' text-align: right;', ' margin-top: 5px;', '}', '.nrn-movie-info-container .nrn-movie-info-p {', ' text-align: left;', '}', '.nrn-movie-tag {', ' display: block;', '}', '.nrn-tag-ng-button,', '.nrn-contributor-ng-button {', ' white-space: nowrap;', '}', 'td[data-video-item].nrn-reduce .itemThumbBox,', 'td[data-video-item].nrn-reduce .itemThumbBox .itemThumb {', ' width: 50px;', ' height: 38px;', ' margin: 0;', '}', '.top20 .itemTitle a.nrn-ng-movie-title {', ' text-decoration: line-through;', '}', 'td[data-video-item] {', ' position: relative;', ' visibility: hidden;', '}', 'td[data-video-item].nrn-getThumbInfoDone {', ' visibility: inherit;', '}', 'td[data-video-item].nrn-getThumbInfoDone.nrn-hidden {', ' visibility: hidden;', '}', '.nrn-hidden > .nrn-movie-info-container,', '.nrn-hidden > .nrn-movie-info-toggle {', ' display: none;', '}', ].join('\n') } MatrixView.prototype.updateMovieInfoTogglable = noop MatrixView.prototype.isHourlyAll = always(false) MatrixView.prototype._newAddedController = function() { return elem() .add([ elem('label').add(this.movingUpCheckbox, '上に詰める').new(this.doc), ' | ', ]) .new(this.doc) } MatrixView.prototype._setMovingUp = function() { this.model.movingUp().set(this.movingUpCheckbox.checked) } MatrixView.prototype.updateMovingUp = function() { if (this.model.movingUp().get()) { this.moveUp() } else { this.unmoveUp() } } MatrixView.prototype._sortedRowToCols = function(comparator) { var c2r = colToRows(this.idToMovieView) Object.keys(c2r).forEach(function(c) { c2r[c].sort(comparator) }) return rowToCols(c2r) } MatrixView.prototype._addToMatrixTable = function() { var doc = this.doc var rows = doc.querySelector('.top20 table').rows return function(cols, i) { var row = rows[1 + i] cols.forEach(function(col, i) { if (!col) return if (col.root.parentNode) { col.root.parentNode.replaceChild(doc.createElement('td'), col.root) } row.replaceChild(col.root, row.cells[i + 1]) }) } } MatrixView.prototype._relocate = function(comparator) { var current = this._sortedRowToCols(currentRowComparator) var fresh = this._sortedRowToCols(comparator) diffRowToCols(current, fresh).forEach(this._addToMatrixTable()) } MatrixView.prototype.moveUp = function() { this._relocate(visibleComparator) } MatrixView.prototype.unmoveUp = function() { this._relocate(sourceRowComparator) } MatrixView.prototype.requestMovingUp = function() { if (this._movingUpRequested) return this._movingUpRequested = true setTimeout(function() { this._movingUpRequested = false if (this.model.movingUp().get()) this.moveUp() }.bind(this)) } MatrixView.prototype.getSideMovies = function() { return [] } return MatrixView })(GinzaView) var Controller = (function() { var movieByTarget = function(fn) { return function(target) { var movieId = this._view.movieIdByComponent(target) fn.call(this, this._model.movieById(movieId), target) } } var method = function(b) { return b ? 'remove' : 'add' } var Controller = function(model, view) { this._model = model this._view = view } Controller.prototype._addVisitedId = movieByTarget(function(movie) { this._model.visitedMovieIds().add(movie.id(), movie.title()) }) Controller.prototype._changeNgIds = movieByTarget(function(movie) { if (movie.isNgId()) { this._model.ngMovieIds().remove(movie.id()) } else { this._model.ngMovieIds().add(movie.id(), movie.title()) } }) Controller.prototype._promptUntilAdd = function(movie) { var r = null do { var msg = (r ? '"' + r + '"は登録済みです。\n' : '') + 'NGタイトルを入力' r = prompt(msg, r ? r : movie.title()) } while (r && !this._model.ngTitles().add(r)) } Controller.prototype._changeNgTitles = movieByTarget(function(movie) { if (movie.isNgTitle()) { this._model.ngTitles().remove(movie.matchedNgTitle()) } else { this._promptUntilAdd(movie) } }) Controller.prototype._changeVisistedIds = movieByTarget(function(movie) { if (movie.isVisited()) { this._model.visitedMovieIds().remove(movie.id()) } else { this._model.visitedMovieIds().add(movie.id(), movie.title()) } }) Controller.prototype._changeNgTags = movieByTarget(function(movie, target) { var tag = this._view.tagByTagNgButton(target) this._model.ngTags()[method(movie.hasNgTag(tag))](tag) }) Controller.prototype._changeNgContributors = movieByTarget(function(movie) { var c = movie.contributor() var n = c.isUser() ? 'ngUserIds' : 'ngChannelIds' var s = this._model[n]() if (c.isNg()) { s.remove(c.id()) } else { s.add(c.id(), c.name()) } }) Controller.prototype.clickCallback = function(event) { var t = event.target if (this._view.isThumbAnchor(t) || this._view.isTitleAnchor(t)) { this._addVisitedId(t) } else if (this._view.isMovieNgButton(t)) { this._changeNgIds(t) } else if (this._view.isTitleNgButton(t)) { this._changeNgTitles(t) } else if (this._view.isVisitButton(t)) { this._changeVisistedIds(t) } else if (this._view.isTagNgButton(t)) { this._changeNgTags(t) } else if (this._view.isContributorNgButton(t)) { this._changeNgContributors(t) } else if (this._view.isMovieInfoToggle(t)) { this._view.toggleMovieInfoVisible(event) } else if (this._view.isDescriptionToggle(t)) { this._view.toggleDescriptionExpanded(event) } } Controller.prototype.mouseOverCallback = function(event) { if (!this._model.popupVisible().get()) return var targetId = this._view.movieIdByComponent(event.target) var popupId = this._view.movieIdByPopup() if (targetId === popupId) return if (targetId) { this._view.addPopupMenuTo(event.target) } else { this._view.removePopupMenu() } } return Controller })() var main = function() { var view = MatrixView.is(window.location) ? new MatrixView() : new GinzaView() GM_addStyle(view.getCssText()) document.addEventListener('DOMContentLoaded', function() { var movies = view.getMovies() ;[].push.apply(movies, view.getSideMovies(movies)) var stores = Store.newStores() var model = new Model(movies, view, stores) view.setModel(model) view.addControllers() updateMovies(movies, stores) var body = view.contentBody() var ctrl = new Controller(model, view) body.addEventListener('click', ctrl.clickCallback.bind(ctrl)) body.addEventListener('mouseover', ctrl.mouseOverCallback.bind(ctrl)) window.addEventListener('scroll', view.requestLoadingLazyImages.bind(view)) var getThumbInfo = null if (stores.useGetThumbInfo.get()) { getThumbInfo = new GetThumbInfo(GM_xmlhttpRequest , model.sortMoviesByVisible() , stores , 5) getThumbInfo.request() } else { movies.forEach(callMethod('setGetThumbInfoDone', [])) } if (stores.requestingNext.get() && view.isHourlyAll()) { view.requestToNicoChart(getThumbInfo) } }) } main() })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址