SmartNicorepo

ニコレポの「投稿」以外をデフォルトで折りたたむ

  1. // ==UserScript==
  2. // @name SmartNicorepo
  3. // @namespace https://github.com/segabito/
  4. // @description ニコレポの「投稿」以外をデフォルトで折りたたむ
  5. // @include *://www.nicovideo.jp/my*
  6. // @include *://www.nicovideo.jp/my/*
  7. // @include *://www.nicovideo.jp/user/*
  8. // @include *://www.nicovideo.jp/my/fav/user
  9. // @include *://www.nicovideo.jp/mylist/*
  10. // @version 3.1.11
  11. // @grant none
  12. // @noframes
  13. // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js
  14. // ==/UserScript==
  15.  
  16. (function(window) {
  17. const document = window.document;
  18. const monkey =
  19. (function() {
  20. var $ = window.jQuery;
  21.  
  22. function addStyle(styles, id) {
  23. let elm = document.createElement('style');
  24. elm.type = 'text/css';
  25. if (id) { elm.id = id; }
  26.  
  27. let text = styles.toString();
  28. text = document.createTextNode(text);
  29. elm.appendChild(text);
  30. document.head.append(elm);
  31. return elm;
  32. }
  33.  
  34. const COMMON_CSS = `
  35. [data-follow-container] {
  36. display: none;
  37. }
  38. `;
  39. addStyle(COMMON_CSS);
  40.  
  41. const __nicorepocss__ = (`
  42. .nicorepo .log.log-upload {
  43. background: #ffe;
  44. }
  45.  
  46. #nicorepo .timeline > .log .log-target-thumbnail {
  47. width: auto; height: auto; margin-left: -30px;
  48. }
  49.  
  50. #nicorepo .log .log-target-thumbnail img,
  51. #nicorepo .log.log-live .live_program,
  52. #nicorepo .log.log-kiriban .video,
  53. #nicorepo .log.log-ranking .video,
  54. #nicorepo .log.log-uad .video,
  55. #nicorepo .log.log-mylist .video,
  56. #nicorepo .log.log-mylist .seiga_illust,
  57. #nicorepo .log.log-mylist .manga_episode,
  58. #nicorepo .log.log-upload .video,
  59. #nicorepo .log.log-upload .seiga_illust,
  60. #nicorepo .log.log-upload .seiga_image,
  61. #nicorepo .log.log-upload .manga_episode,
  62. #nicorepo .log.log-upload .chblog
  63. {
  64. height: auto !important;
  65. width: 130px !important;
  66. margin-top: 0px;
  67. margin-bottom: 0 !important;
  68. margin-left: 0 !important;
  69. }
  70. #nicorepo .timeline > .log {
  71. max-height: 500px;
  72. transition: max-height 0.2s ease-in-out 0.4s;
  73. overflow: auto;
  74. }
  75.  
  76. #nicorepo .timeline > .log {
  77. max-height: 22px;
  78. overflow: hidden;
  79. }
  80.  
  81. #nicorepo.show-upload .timeline > .log.log-upload,
  82. #nicorepo.show-mylist .timeline > .log.log-mylist,
  83. #nicorepo.show-live .timeline > .log.log-live,
  84. #nicorepo.show-uad .timeline > .log.log-uad,
  85. #nicorepo.show-kiriban .timeline > .log.log-kiriban,
  86. #nicorepo.show-ranking .timeline > .log.log-ranking,
  87. #nicorepo.show-other .timeline > .log.log-other,
  88. #nicorepo .timeline > .log:hover {
  89. max-height: 500px !important;
  90. overflow: hidden;
  91. transition: max-height 0.4s ease-in-out 0.4s;
  92. }
  93.  
  94. #nicorepo.hiderepo:not(.show-upload) .timeline > .log.log-upload,
  95. #nicorepo.hiderepo:not(.show-mylist) .timeline > .log.log-mylist,
  96. #nicorepo.hiderepo:not(.show-live) .timeline > .log.log-live,
  97. #nicorepo.hiderepo:not(.show-uad) .timeline > .log.log-uad,
  98. #nicorepo.hiderepo:not(.show-kiriban) .timeline > .log.log-kiriban,
  99. #nicorepo.hiderepo:not(.show-ranking) .timeline > .log.log-ranking,
  100. #nicorepo.hiderepo:not(.show-other) .timeline > .log.log-other {
  101. display: none !important;
  102. }
  103.  
  104.  
  105. .smartNicorepoToolbar {
  106. display: inline-block;
  107. position: fixed;
  108. padding: 0 4px;
  109. border-radius: 4px 4px 0 0;
  110. bottom: 0;
  111. z-index: 1000;
  112. background: rgba(128, 128, 128, 0.9);
  113. box-shadow: 0 0 4px #000;
  114. user-select: none;
  115. /*transform: translate(28px, 0);*/
  116. width: 722px;
  117. text-align: center;
  118. }
  119. .smartNicorepoToolbar .categoryCheckLabel {
  120. display: inline-block;
  121. min-width: 64px;
  122. margin: 4px;
  123. padding: 2px;
  124. background: #fff;
  125. border-radius: 4px;
  126. border: 1px solid #888;
  127. text-align: center;
  128. }
  129. .smartNicorepoToolbar .categoryCheckLabel.active {
  130. background: #ccf;
  131. }
  132.  
  133. .smartNicorepoToolbar .toggle-hiderepo {
  134. border: none;
  135. background: #ccc;
  136. color: black;
  137. }
  138. .smartNicorepoToolbar .toggle-hiderepo.active {
  139. background: #ccc;
  140. }
  141. .smartNicorepoToolbar .toggle-hiderepo:before {
  142. content: '${'\\002610'}';
  143. display: inline-block;
  144. margin-right: 4px;
  145. margin-left: 4px;
  146. transform: scale(1.8);
  147. }
  148. .smartNicorepoToolbar .toggle-hiderepo.active:before {
  149. content: '${'\\002611'}';
  150. }
  151.  
  152. .toggleUpload {
  153. position: absolute;
  154. top: 0px;
  155. right: 16px;
  156. font-weight: bolder;
  157. cursor: pointer;
  158. color: #888;
  159. padding: 6px 8px;
  160. z-index: 1000;
  161. }
  162. .toggleUpload.bottom {
  163. top: auto; right: 32px; bottom: 32px;
  164. }
  165. .show-upload .toggleUpload {
  166. color: red;
  167. }
  168. .toggleUpload:after {
  169. content: ': OFF';
  170. }
  171. .show-upload .toggleUpload:after {
  172. content: ': ON';
  173. }
  174.  
  175. .togglePagerize {
  176. position: fixed;
  177. bottom: 0;
  178. right: 0;
  179. color: #888;
  180. font-weight: bolder;
  181. cursor: pointer;
  182. border: 2px solid #666;
  183. }
  184. .togglePagerize.enable {
  185. color: red;
  186. }
  187. .togglePagerize:after {
  188. content: ': OFF';
  189. }
  190. .togglePagerize.enable:after {
  191. content: ': ON';
  192. }
  193. `).trim();
  194.  
  195.  
  196. const __favusercss__ = (`
  197.  
  198. #favUser .outer.updating {
  199. }
  200. #favUser .outer.updating * {
  201. cursor: wait;
  202. }
  203. #favUser .outer.done .showNicorepo {
  204. display: none;
  205. }
  206.  
  207. #favUser .nicorepo {
  208. color: #800;
  209. clear: both;
  210. margin-bottom: 24px;
  211. }
  212. #favUser .uploadVideoList, #favUser .seigaUserPage {
  213. font-size: 80%;
  214. margin-left: 16px;
  215. }
  216.  
  217. #favUser .nicorepo.fail {
  218. color: #800;
  219. clear: both;
  220. margin-left: 64px;
  221. }
  222.  
  223.  
  224. #favUser .nicorepo.success {
  225. padding: 8px;
  226. overflow: auto;
  227. border: 1px inset;
  228. max-height: 300px;
  229. }
  230.  
  231. .nicorepo .log-target-thumbnail,
  232. .nicorepo .log-target-info {
  233. display: inline-block;
  234. vertical-align: middle;
  235. }
  236. .nicorepo .log-target-thumbnail .imageContainer {
  237. width: 64px;
  238. height: 48px;
  239. background-color: #fff;
  240. background-size: contain;
  241. background-repeat: no-repeat;
  242. background-position: center;
  243. transition: 0.2s width ease 0.4s, 0.2s height ease 0.4s;
  244. }
  245. .nicorepo .log-target-thumbnail .imageContainer:hover {
  246. width: 128px;
  247. height: 96px;
  248. }
  249. .nicorepo .log-target-info .time {
  250. display: block;
  251. font-size: 80%;
  252. color: black;
  253. }
  254. .nicorepo .log-target-info .logComment {
  255. display: block;
  256. font-size: 80%;
  257. color: black;
  258. }
  259. .nicorepo .log-target-info .logComment:before {
  260. content: '「';
  261. }
  262. .nicorepo .log-target-info .logComment:after {
  263. content: '」';
  264. }
  265. .nicorepo .log-target-info a {
  266. display: inline-block;
  267. min-width: 100px;
  268. }
  269. .nicorepo .log-target-info a:hover {
  270. background: #ccf;
  271. }
  272.  
  273.  
  274. .nicorepo .log.log-user-video-round-number-of-view-counter {
  275. display: none;
  276. }
  277.  
  278. .nicorepo .log-content {
  279. margin: 4px 8px;
  280. position: relative;
  281. }
  282. .nicorepo .log-footer {
  283. position: absolute;
  284. top: 0;
  285. left: 138px;
  286. }
  287. .nicorepo .log-footer a {
  288. font-size: 80%;
  289. color: black;
  290. }
  291.  
  292. .nicorepo .log .time:after {
  293. background: #888;
  294. color: #fff;
  295. border-radius: 4px;
  296. display: inline-block;
  297. padding: 2px 4px;
  298. }
  299. .nicorepo .log.log-user-register-chblog .time:after,
  300. .nicorepo .log.log-upload .time:after,
  301. .nicorepo .log.log-user-seiga-image-upload .time:after {
  302. content: '投稿';
  303. background: #866;
  304. }
  305.  
  306. .nicorepo .log.log-user-mylist-add-blomaga .time:after,
  307. .nicorepo .log.log-user-mylist-add .time:after {
  308. content: 'マイリスト';
  309. }
  310. .nicorepo .log.log-user-live-broadcast .time:after {
  311. content: '放送';
  312. }
  313. .nicorepo .log.log-user-seiga-image-clip .time:after {
  314. content: 'クリップ';
  315. }
  316. .nicorepo .log.log-user-video-review .time:after {
  317. content: 'レビュー';
  318. }
  319. .nicorepo .log.log-user-uad-advertise .time:after {
  320. content: '広告';
  321. }
  322.  
  323. .nicorepo .log.log-upload {
  324. background: #ffe;
  325. }
  326.  
  327. .nicorepo .log.log-upload .log-target-thumbnail,
  328. .nicorepo .log.log-user-seiga-image-upload .log-target-thumbnail {
  329. }
  330. .nicorepo .log.log-upload .video,
  331. .nicorepo .log.log-user-seiga-image-upload .seiga_image {
  332. }
  333.  
  334.  
  335. .nicorepo .log-author,
  336. .nicorepo .log-body,
  337. .nicorepo .log-res,
  338. .nicorepo .log-comment,
  339. .nicorepo .log-footer {
  340. display: none !important;
  341. }
  342. `).trim();
  343.  
  344. const __large_thumbnail_css__ = (`
  345.  
  346. .largeThumbnailLink {
  347. display: inline-block;
  348. }
  349.  
  350. .largeThumbnailLink::after {
  351. content: "";
  352. position: fixed;
  353. bottom: -280px;
  354. width: 360px;
  355. height: 270px;
  356. left: 24px;
  357. opacity: 0;
  358. background-color: #000;
  359. background-size: contain;
  360. background-repeat: no-repeat;
  361. background-position: center center;
  362. background-image: var(--thumbnail-image);
  363. transition:
  364. bottom 0.5s ease 0.5s,
  365. z-index 0.5s ease,
  366. box-shadow 0.5s ease 0.5s,
  367. opacity 0.5s ease 0.5s;
  368. z-index: 100000;
  369. pointer-events: none;
  370. }
  371.  
  372. #PAGEBODY .largeThumbnailLink::after {
  373. left: auto;
  374. right: 24px;
  375. }
  376.  
  377. .largeThumbnailLink:hover::after {
  378. position: fixed;
  379. bottom: 32px;
  380. opacity: 1;
  381. box-shadow: 4px 4px 4px #333;
  382. transition:
  383. bottom 0.2s ease,
  384. z-index 0.2s ease,
  385. box-shadow 0.2s ease,
  386. opacity 0.2s ease;
  387. z-index: 100100;
  388. }
  389.  
  390. #mylist .articleBody .myContList li.SYS_box_item .thumbContainer img,
  391. #SYS_page_items .thumbContainer img.video,
  392. #video .articleBody .outer .thumbContainer img.video {
  393. max-width: unset;
  394. max-height: unset;
  395. object-fit: cover;
  396. width: 160px;
  397. height: 90px;
  398. }
  399. `).trim();
  400.  
  401. const __tagrepocss__ = (`
  402. .newVideoChannel .post-item,
  403. .newVideoUser .post-item {
  404. {* background: #ffe;*}
  405. }
  406.  
  407. .newLiveChannel .post-item,
  408. .newLiveUser .post-item {
  409. background: #eee;
  410. }
  411.  
  412. .newVideoUser .contents-thumbnail img.largeThumbnail,
  413. .newVideoOfficial .contents-thumbnail img.largeThumbnail,
  414. .newVideoChannel .contents-thumbnail img.largeThumbnail {
  415. margin-top: -21px;
  416. }
  417.  
  418. `).trim();
  419.  
  420. const failedUrl = {};
  421. const initializeLargeThumbnail = function(type, container, selector) {
  422. console.log('%cinitializeLargeThumbnail: type=%s', 'background: lightgreen;', type);
  423. addStyle(__large_thumbnail_css__);
  424.  
  425. // 大サムネが存在する最初の動画ID。 ソースはちゆ12歳
  426. // ※この数字以降でもごく稀に例外はある。
  427. const threthold = 16371888;
  428. const hasLargeThumbnail = videoId => {
  429. let cid = videoId.substr(0, 2);
  430. let fid = videoId.substr(2) * 1;
  431. if (cid !== 'sm' && fid < 35000000) { return false; }
  432.  
  433. if (fid < threthold) { return false; }
  434.  
  435. return true;
  436. };
  437.  
  438. const onLoadImageError = e => {
  439. let target = e.target;
  440. let src = target.src.replace('.L', '');
  441. target.classList.add('large-thumbnail-fail');
  442.  
  443. failedUrl[src] = true;
  444. if (target.src !== src) {
  445. target.src = src;
  446. }
  447. };
  448.  
  449. const each = v => {
  450. let href = (v.href || '').toString();
  451. if (!href ||
  452. v.hostname !== 'www.nicovideo.jp' ||
  453. href.indexOf('/watch/') < 0) {
  454. return;
  455. }
  456.  
  457. let videoId;
  458. if (/^.+(s[mo]\d+).*$/.test(href)) {
  459. videoId = href.replace(/^.+(s[mo]\d+).*$/, '$1');
  460. } else {
  461. let img = v.querySelector('img');
  462. if (!img || !img.src) { return; }
  463. if (!/smile\?i=(\d+)/.test(img.src)) { return; }
  464. videoId = 'sm' + RegExp.$1;
  465. }
  466.  
  467. if (!hasLargeThumbnail(videoId)) {
  468. return;
  469. }
  470.  
  471. let thumbnail = v.querySelector('img');
  472. // console.log('thumbnail', v.querySelector('img'));
  473. if (!thumbnail || thumbnail.classList.contains('large-thumbnail-fail')) { return; }
  474. let src = thumbnail.getAttribute('src') || '';
  475. let org = thumbnail.dataset.original || '';
  476. let attrName = org ? 'data-original' : 'src';
  477. src = org ? org : src;
  478. let url = src.replace(/\.[LM]$/, '') + '.L';
  479.  
  480. if (failedUrl[src] || failedUrl[url]) { return; }
  481.  
  482. if (!src || !(src.match(/\/smile\?i=/) || src.match(/\/thumbnails\/[\d]+\/[\d]+\.[\d]+/))) {
  483. return;
  484. }
  485. // console.log('each', videoId, src, v, !src || src.match(/\.M/) || !src.match('/smile?i='));
  486.  
  487. thumbnail.addEventListener('error', onLoadImageError, {once: true, passive: true});
  488. thumbnail.addEventListener('load', () => {
  489. if (!thumbnail.src.match(/\.L$/)) { return; }
  490. v.classList.add('largeThumbnailLink');
  491. v.style.setProperty('--thumbnail-image', `url(${url})`);
  492. thumbnail.classList.add('largeThumbnail', videoId);
  493. thumbnail.removeEventListener('error', onLoadImageError);
  494. }, {once: true});
  495. v.dataset.loadingThumbnail = JSON.stringify({id: videoId, url});
  496. thumbnail.setAttribute(attrName, url);
  497. return {id: videoId, url};
  498. };
  499.  
  500. const update = _.debounce(() => {
  501. Array.from(document.querySelectorAll(selector)).map(each);
  502. }, 500);
  503.  
  504. update();
  505.  
  506. const mutationObserver = new window.MutationObserver(mutations => {
  507. if (mutations.some(mutation => mutation.addedNodes.length)) {
  508. update(mutations.target);
  509. }
  510. });
  511. mutationObserver.observe(
  512. document.querySelector(container),
  513. {childList: true, characterData: false, attributes: false, subtree: true}
  514. );
  515. };
  516.  
  517. const initializeSeigaThumbnail = function(type, container, selector) {
  518. console.log('%cinitializeSeigaThumbnail: type=%s', 'background: lightgreen;', type, container, selector);
  519.  
  520. const onLoadImageError = e => {
  521. console.warn('%c large thumbnail load error!', '', e);
  522.  
  523. let target = e.target;
  524. target.classList.add('large-thumbnail-fail');
  525. target.src = target.src.replace(/i$/, 'z');
  526. };
  527.  
  528. const each = v => {
  529. let href = v.href || '';
  530. if (!href || href.indexOf('/seiga/im') < 0) {
  531. return;
  532. }
  533.  
  534. let seigaId = href.replace(/^.+(im\d+).*$/, '$1');
  535.  
  536. let thumbnail = v.querySelector('img');
  537. if (!thumbnail || thumbnail.classList.contains('large-thumbnail-fail')) { return; }
  538. let src = thumbnail.getAttribute('src') || '';
  539. let org = thumbnail.dataset.original || '';
  540. let attrName = org ? 'data-original' : 'src';
  541. src = org ? org : src;
  542. let url = src.replace(/z$/, 'i');
  543.  
  544. if (!src || !src.match(/thumb\/\d+z$/)) { return; }
  545.  
  546. thumbnail.addEventListener('error', onLoadImageError, {once: true, passive: true});
  547. thumbnail.addEventListener('load', () => {
  548. if (!thumbnail.src.match(/i$/)) { return; }
  549. v.classList.add('largeThumbnailLink');
  550. v.style.setProperty('--thumbnail-image', `url(${url})`);
  551. thumbnail.classList.add('largeThumbnail', seigaId);
  552. thumbnail.removeEventListener('error', onLoadImageError);
  553. }, {once: true});
  554. thumbnail.setAttribute(attrName, url);
  555.  
  556. return {id: seigaId, url};
  557. };
  558.  
  559. let update = _.debounce(() => {
  560. Array.from(document.querySelectorAll(selector)).map(each)
  561. }, 500);
  562.  
  563. update();
  564.  
  565. const mutationObserver = new window.MutationObserver(mutations => {
  566. if (mutations.some(mutation => mutation.addedNodes.length)) {
  567. update();
  568. }
  569. });
  570. mutationObserver.observe(
  571. document.querySelector(container),
  572. {childList: true, characterData: false, attributes: false, subtree: true}
  573. );
  574. };
  575.  
  576. const updateTimelineItemClass = () => {
  577. // 「投稿」のクラスをつける
  578. let query = '.NicorepoTimelineItem:not(.is-named) .log-body strong';
  579. Array.from(document.querySelectorAll(query)).forEach(elm => {
  580. const log = elm.closest('.NicorepoTimelineItem');
  581. if (!log) { return; }
  582. log.classList.add('log-upload');
  583. log.classList.add('is-named');
  584. });
  585.  
  586. // TODO: 多言語対応
  587. const uploadReg = /(^チャンネル.*に(動画|記事)が追加されました。$|コミュニティ.*?に動画を追加しました。$|投稿しました。$)/;
  588. const mylistReg = /(に(動画|ブロマガ)を登録しました。$|イラストをクリップしました。$|^.*?さんが .*? にマンガ .*? を登録しました。$|マンガをお気に入りしました。$)/;
  589.  
  590. const liveReg = /(生放送を(開始|予約)しました。$|生放送が開始されました。$|生放送が予約されました。$)/;
  591.  
  592. const uadReg = /ニコニ広告(で宣伝)?しました。 >広告履歴を確認$/;
  593.  
  594. const kiribanReg = /(再生を達成しました。$)/;
  595.  
  596. const rankingReg = /(位を達成しました。$)/;
  597.  
  598.  
  599. // それ以外のカテゴリ。 文言から判別するしかない
  600. query = '.NicorepoTimelineItem:not(.is-named) .log-body';
  601. Array.from(document.querySelectorAll(query)).forEach(elm => {
  602. const text = (elm.textContent || '').trim();
  603. const item = elm.closest('.NicorepoTimelineItem');
  604.  
  605. if (uploadReg.test(text)) {
  606. item.classList.add('log-upload');
  607. } else if (mylistReg.test(text)) {
  608. item.classList.add('log-mylist');
  609. } else if (liveReg.test(text)) {
  610. item.classList.add('log-live');
  611. } else if (uadReg.test(text)) {
  612. item.classList.add('log-uad');
  613. } else if (kiribanReg.test(text)) {
  614. item.classList.add('log-kiriban');
  615. } else if (rankingReg.test(text)) {
  616. item.classList.add('log-ranking');
  617. } else {
  618. item.classList.add('log-other');
  619. }
  620. item.classList.add('is-named');
  621. });
  622.  
  623. Array.from(document.querySelectorAll('.nicorepo a[href*="_topic="]')).forEach((a) => {
  624. a.href = a.href.replace(/\?.*?$/, '');
  625. });
  626. };
  627.  
  628. const initializeToolbar = (config, parentNodeSelector) => {
  629. const nicorepo = document.querySelector('#nicorepo');
  630. const container = document.createElement('div');
  631. container.className = 'smartNicorepoToolbar';
  632.  
  633. const createToggle = function(elm, confName, className) {
  634. return function(isChecked) {
  635. if (typeof isChecked === 'boolean') {
  636. nicorepo.classList.toggle(className, isChecked);
  637. } else {
  638. nicorepo.classList.toggle(className);
  639. }
  640.  
  641. const v = nicorepo.classList.contains(className);
  642. elm.classList.toggle('active', v);
  643. config.set(confName, v);
  644. };
  645. };
  646.  
  647. const createItem = function(text, titleText) {
  648. const label = document.createElement('label');
  649. label.className = 'categoryCheckLabel';
  650. if (titleText) { label.title = titleText; }
  651.  
  652. const span = document.createElement('span');
  653. span.textContent = text;
  654. label.appendChild(span);
  655. return label;
  656. };
  657.  
  658. const upload = createItem('投稿', '動画・静画・ブログなど');
  659. const toggleUpload = createToggle(upload, 'showUpload', 'show-upload');
  660. upload.addEventListener('click', toggleUpload);
  661. toggleUpload(!!config.get('showUpload'));
  662. container.appendChild(upload);
  663.  
  664. const mylist = createItem('マイリスト', 'マイリスト・クリップなど');
  665. const toggleMylist = createToggle(mylist, 'showMylist', 'show-mylist');
  666. mylist.addEventListener('click', toggleMylist);
  667. toggleMylist(!!config.get('showMylist'));
  668. container.appendChild(mylist);
  669.  
  670. const live = createItem('生放送', '開始・予約など');
  671. const toggleLive = createToggle(live, 'showLive', 'show-live');
  672. live.addEventListener('click', toggleLive);
  673. toggleLive(!!config.get('showLive'));
  674. container.appendChild(live);
  675.  
  676. const uad = createItem('宣伝', 'ニコニ広告');
  677. const toggleUad = createToggle(uad, 'showUad', 'show-uad');
  678. uad.addEventListener('click', toggleUad);
  679. toggleUad(!!config.get('showUad'));
  680. container.appendChild(uad);
  681.  
  682. const kiriban = createItem('再生', '2525再生など');
  683. const toggleKiriban = createToggle(kiriban, 'showKiriban', 'show-kiriban');
  684. kiriban.addEventListener('click', toggleKiriban);
  685. toggleKiriban(!!config.get('showKiriban'));
  686. container.appendChild(kiriban);
  687.  
  688. const ranking = createItem('ランキング', '');
  689. const toggleRanking = createToggle(ranking, 'showRanking', 'show-ranking');
  690. ranking.addEventListener('click', toggleRanking);
  691. toggleRanking(!!config.get('showRanking'));
  692. container.appendChild(ranking);
  693.  
  694. const other = createItem('その他', '');
  695. const toggleOther = createToggle(other, 'showOther', 'show-other');
  696. other.addEventListener('click', toggleOther);
  697. toggleOther(!!config.get('showOther'));
  698. container.appendChild(other);
  699.  
  700. const hiderepo = createItem('閉じてるのを消す', '');
  701. hiderepo.classList.add('toggle-hiderepo');
  702. const toggleHiderepo = createToggle(hiderepo, 'hiderepo', 'hiderepo');
  703. hiderepo.addEventListener('click', toggleHiderepo);
  704. toggleHiderepo(!!config.get('hiderepo'));
  705. container.appendChild(hiderepo);
  706.  
  707.  
  708. setTimeout(() => {
  709. const parentNode = document.querySelector(parentNodeSelector);
  710. if (!parentNode) { return; }
  711. parentNode.appendChild(container);
  712. }, 3000);
  713. };
  714.  
  715.  
  716. window.SmartNicorepo = {
  717. model: {},
  718. util: {},
  719. initialize: function() {
  720. this.initializeUserConfig();
  721. if (location.pathname === '/my/fav/user') {
  722. this.initializeFavUser();
  723. } else
  724. if (location.pathname.indexOf('/my/tagrepo/') === 0) {
  725. this.initializeTagrepo();
  726. } else {
  727. this.initializeNicorepo();
  728. this.initializeAutoPageRize();
  729. }
  730.  
  731. this.initializeOther();
  732. // jQuery._data(jQuery(window).get(0), 'events')
  733. },
  734. initializeUserConfig: function() {
  735. var prefix = 'SmartNicorepo_';
  736. var conf = {
  737. showUpload: true,
  738. showMylist: false,
  739. showLive: false,
  740. showUad: false,
  741. showKiriban: false,
  742. showRanking: false,
  743. showOther: false,
  744. autoPagerize: true,
  745. hiderepo: false
  746. };
  747.  
  748. this.config = {
  749. get: function(key) {
  750. try {
  751. if (window.localStorage.hasOwnProperty(prefix + key) || localStorage[prefix + key] !== undefined) {
  752. return JSON.parse(window.localStorage.getItem(prefix + key));
  753. }
  754. return conf[key];
  755. } catch (e) {
  756. return conf[key];
  757. }
  758. },
  759. set: function(key, value) {
  760. //console.log('%cupdate config {"%s": "%s"}', 'background: cyan', key, value);
  761. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  762. }
  763. };
  764. },
  765. initializeNicorepo: function() {
  766. addStyle(__nicorepocss__, 'nicorepoCss');
  767.  
  768. let config = this.config;
  769.  
  770. let app = document.querySelector('#MyPageNicorepoApp, #UserPageNicorepoApp');
  771. if (!app) { return; }
  772.  
  773. const mutationObserver = new window.MutationObserver((mutations) => {
  774. let isAdded = false;
  775. mutations.forEach(mutation => {
  776. if (mutation.addedNodes && mutation.addedNodes.length > 0) {
  777. isAdded = true;
  778. }
  779. });
  780. if (isAdded) { updateTimelineItemClass(); }
  781. });
  782.  
  783. updateTimelineItemClass();
  784.  
  785. mutationObserver.observe(
  786. app,
  787. {childList: true, characterData: false, attributes: false, subtree: true}
  788. );
  789.  
  790. initializeToolbar(config, '#MyPageNicorepoApp, #UserPageNicorepoApp');
  791. },
  792. initializeTagrepo: function() {
  793. console.log('%cinitializeTagrepo', 'background: lightgreen;');
  794. addStyle(__tagrepocss__, 'tagrepoCss');
  795. },
  796. initializeFavUser: function() {
  797. addStyle(__favusercss__, 'favUserCss');
  798.  
  799. $('.posRight .arrow').each(function(i, elm) {
  800. var $elm = $(elm), $lnk = $elm.clone();
  801. $lnk
  802. .html('<span></span> ニコレポを表示&nbsp;')
  803. .addClass('showNicorepo');
  804. $elm.before($lnk);
  805. });
  806.  
  807. $('.outer .section a').each(function(i, elm) {
  808. var $elm = $(elm), href = $elm.attr('href');
  809. if (href.match(/\/(\d+)$/)) {
  810. var userId = RegExp.$1;
  811. var $video = $('<a class="uploadVideoList">動画一覧</a>')
  812. .attr('href', '/user/' + userId + '/video');
  813. var $seiga = $('<a class="seigaUserPage">静画一覧</a>')
  814. .attr('href', '//seiga.nicovideo.jp/user/illust/' + userId);
  815. $elm.after($seiga).after($video);
  816. }
  817. });
  818.  
  819. var getClearBusy = function($elm) {
  820. return function() {
  821. $elm.removeClass('updating').addClass('done');
  822. };
  823. };
  824.  
  825. $('#favUser .showNicorepo').off().on('click', $.proxy(function(e) {
  826. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) {
  827. return;
  828. }
  829. e.preventDefault();
  830. e.stopPropagation();
  831. var $elm = $(e.target);
  832. var userId = $elm.attr('data-nico-nicorepolistid');
  833. if (!userId) { return; }
  834. var $outer = $elm.closest('.outer');
  835. if ($outer.hasClass('updating')) {
  836. return;
  837. }
  838.  
  839. var clearBusy = getClearBusy($outer);
  840. $outer.addClass('updating');
  841. window.setTimeout(clearBusy, 3000);
  842.  
  843. this.loadNicorepo(userId, $outer).then(clearBusy, clearBusy);
  844.  
  845. }, this));
  846. },
  847. initializeAutoPageRize: function() {
  848. let config = this.config;
  849. let $button = $('<button class="togglePagerize">自動読込</button>');
  850. let timer = null;
  851.  
  852. var onButtonClick = () => {
  853. toggle();
  854. updateView();
  855. };
  856. var toggle = () => {
  857. this._isAutoPagerizeEnable = !this._isAutoPagerizeEnable;
  858. config.set('autoPagerize', this._isAutoPagerizeEnable);
  859. if (this._isAutoPagerizeEnable) {
  860. bind();
  861. } else {
  862. unbind();
  863. }
  864. };
  865. let updateView = () => {
  866. $button.toggleClass('enable', this._isAutoPagerizeEnable);
  867. };
  868. let onWindowScroll = _.debounce(this._onWindowScroll.bind(this), 100);
  869. let bind = () => {
  870. window.addEventListener('scroll', onWindowScroll, {passive: true});
  871. timer = window.setInterval(this._autoPagerize.bind(this), 5000);
  872. };
  873. let unbind = () => {
  874. window.removeEventListener('scroll', onWindowScroll);
  875. window.clearInterval(timer);
  876. };
  877.  
  878.  
  879. $button.click(onButtonClick);
  880. $('body').append($button);
  881.  
  882. this._isAutoPagerizeEnable = config.get('autoPagerize');
  883. if (this._isAutoPagerizeEnable) { bind(); }
  884.  
  885. updateView();
  886. },
  887. initializeOther: function() {
  888. window.resizeSidebarHeight = () => {};
  889. if (window.Nico && window.Nico.FollowManager) {
  890. window.Nico.FollowManager.resizeAdjust =
  891. window.Nico.FollowManager.scrollAdjust = () => {};
  892. }
  893. },
  894. _onWindowScroll: function() {
  895. this._autoPagerize();
  896. },
  897. _autoPagerize: function() {
  898. if (!this._isAutoPagerizeEnable) { return; }
  899. //let ver = document.querySelector('#MyPageNicorepoApp') ? 'new' : 'old';
  900.  
  901. // TODO: IntersectionObserverつかえ
  902. let nextPage = //this._nextPage ||
  903. document.querySelector('#MyPageNicorepoApp .next-page, #nicorepo .next-page');
  904. if (!nextPage) { return; }
  905. //this._nextPage = nextPage;
  906.  
  907. let rect = nextPage.getBoundingClientRect();
  908. let isLoading = nextPage.classList.contains('loading');
  909. let isScrollIn = window.innerHeight - rect.top > 0;
  910. //console.info('?', isLoading, isScrollIn, window.innerHeight - rect.top);
  911. if (isScrollIn && !isLoading) {
  912. (document.querySelector('#nicorepo .next-page-link') || {click: _.noop}).click();
  913. }
  914. },
  915. loadNicorepo: function(userId, $container) {
  916. // http://www.nicovideo.jp/user/[userId]/top?innerPage=1
  917. var url = '//www.nicovideo.jp/user/' + userId + '/top?innerPage=1';
  918.  
  919. var fail = function(msg) {
  920. var $fail = $('<div class="nicorepo fail">' + msg + '</div>');
  921. $container.append($fail);
  922. autoScrollIfNeed($fail);
  923. };
  924.  
  925. // ニコレポが画面の一番下よりはみ出していたら見える位置までスクロール
  926. var autoScrollIfNeed = function($target) {
  927. var
  928. scrollTop = $('html').scrollTop(),
  929. targetOffset = $target.offset(),
  930. clientHeight = $(window).innerHeight(),
  931. clientBottom = scrollTop + clientHeight,
  932. targetBottom = targetOffset.top + $target.outerHeight();
  933.  
  934. if (targetBottom > clientBottom) {
  935. $('html').animate({
  936. scrollTop: scrollTop + $target.outerHeight()
  937. }, 500);
  938. }
  939. };
  940.  
  941. var success = function($dom, $logBody) {
  942. var $result = $('<div class="nicorepo success" />');
  943. var $img = $logBody.find('img'), $log = $logBody.find('.log');
  944. $img.each(function() {
  945. var $this = $(this), $parent = $this.parent();
  946. var lazyImg = $this.attr('data-original');
  947. if (lazyImg) {
  948. var $imageContainer = $('<div class="imageContainer"/>');
  949. $imageContainer.css('background-image', 'url(' + lazyImg + ')');
  950. $this.before($imageContainer);
  951. $this.remove();
  952. }
  953. if (window.WatchItLater) {
  954. var href = $parent.attr('href');
  955. if (href) {
  956. $parent.attr('href', href.replace('//www.nicovideo.jp/watch/', '//nico.ms/'));
  957. }
  958. }
  959. });
  960. $logBody.each(function() {
  961. var $this = $(this), time = $this.find('time:first').text(), logComment = $this.find('.log-comment').text();
  962.  
  963. $this.find('.log-target-info>*:first')
  964. .before($('<span class="time">' + time + '</span>'));
  965. if (logComment) {
  966. $this.find('.log-target-info')
  967. .append($('<span class="logComment">' + logComment + '</span>'));
  968. }
  969. });
  970.  
  971. $result.append($logBody);
  972. $container.append($result);
  973. $result.scrollTop(0);
  974.  
  975. autoScrollIfNeed($result);
  976. };
  977.  
  978. return $.ajax({
  979. url: url,
  980. timeout: 30000
  981. }).then(
  982. function(resp) {
  983. var
  984. $dom = $(resp),
  985. // 欲しいのはそのユーザーの「行動」なので、
  986. // xx再生やスタンプみたいなのはいらない
  987. $logBody = $dom.find('.log:not(.log-user-video-round-number-of-view-counter):not(.log-user-action-stamp):not(.log-user-live-video-introduced)');
  988. if ($logBody.length < 1) {
  989. fail('ニコレポが存在しないか、取得に失敗しました');
  990. } else {
  991. success($dom, $logBody);
  992. }
  993. },
  994. function() {
  995. fail('ニコレポの取得に失敗しました');
  996. });
  997. },
  998. loadFavUserList: function() {
  999. var def = new $.Deferred();
  1000. // このAPIのupdate_timeが期待していた物と違ったのでボツ
  1001. // create_timeとupdate_timeはどちらも同じ値が入ってるだけだった。(なんのためにあるんだ?)
  1002. //
  1003. $.ajax({
  1004. url: '//www.nicovideo.jp/api/watchitem/list',
  1005. timeout: 30000,
  1006. complete: function(resp) {
  1007. var json;
  1008. try {
  1009. json = JSON.parse(resp.responseText);
  1010. } catch (e) {
  1011. console.log('%c parse error: ', 'background: #f88', e);
  1012. return def.reject('json parse error');
  1013. }
  1014.  
  1015. if (json.status !== 'ok') {
  1016. console.log('%c status error: ', 'background: #f88', json.status);
  1017. return def.reject('status error', json.status);
  1018. }
  1019. return def.resolve(json.watchitem);
  1020. },
  1021. error: function(req, status, thrown) {
  1022. if (status === 'parsererror') {
  1023. return;
  1024. }
  1025. console.log('%c ajax error: ' + status, 'background: #f88', thrown);
  1026. return def.reject(status);
  1027. }
  1028. });
  1029. return def.promise();
  1030. }
  1031.  
  1032. };
  1033.  
  1034.  
  1035. window.SmartNicorepo.model.WatchItem = function() { this.initialize.apply(this, arguments); };
  1036. window.SmartNicorepo.model.WatchItem.prototype = {
  1037. initialize: function(seed) {
  1038. this._seed = seed;
  1039. this.itemType = seed.item_type || '1';
  1040. this.itemId = seed.item_id || '';
  1041. if (typeof seed.item_data === 'object') {
  1042. var data = seed.item_data;
  1043. this.userId = data.id;
  1044. this.nickname = data.nickname;
  1045. this.thumbnailUrl = data.thumbnail_url;
  1046. }
  1047. var now = (new Date()).getTime();
  1048. this.createTime = new Date(seed.create_time ? seed.create_time * 1000 : now);
  1049. this.updateTime = new Date(seed.update_time ? seed.update_time * 1000 : now);
  1050. }
  1051. };
  1052.  
  1053. window.SmartNicorepo.model.WatchItemList = function() { this.initialize.apply(this, arguments); };
  1054. window.SmartNicorepo.model.WatchItemList.prototype = {
  1055. initialize: function(watchItems) {
  1056. this._seed = watchItems;
  1057. this._items = {};
  1058. this._itemArray = [];
  1059. for (var i = 0, len = watchItems.length; i < len; i++) {
  1060. var item = new window.SmartNicorepo.model.WatchItem(watchItems[i]);
  1061. this._items[item.userId] = item;
  1062. this._itemArray.push(item);
  1063. }
  1064. },
  1065. getItem: function(userId) {
  1066. return this._items[userId];
  1067. },
  1068. getSortedItems: function() {
  1069. var result = this._itemArray.concat();
  1070. result.sort(function(a, b) {
  1071. return (a.updateTime < b.updateTime) ? 1 : -1;
  1072. });
  1073. return result;
  1074. }
  1075. };
  1076.  
  1077. var removeQuery = function(container) {
  1078. const reg = /(^\?nicorepo|ref=)/;
  1079. $(container + ' a').each((i, a) => {
  1080. const search = a.search || '';
  1081. if (reg.test(search)) {
  1082. a.search = '';
  1083. }
  1084. });
  1085. };
  1086.  
  1087.  
  1088. window.Nico.onReady(function() {
  1089. console.log('%cNico.onReady', 'background: lightgreen;');
  1090. if (location.pathname.indexOf('/my/top') === 0 || document.querySelector('#nicorepo')) {
  1091. initializeLargeThumbnail('nicorepo', '#nicorepo',
  1092. '.log-target-thumbnail a[href*="/watch/"]:not(.largeThumbnailLink)');
  1093. initializeSeigaThumbnail('nicorepo', '#nicorepo',
  1094. '.log-target-thumbnail a:not(.largeThumbnailLink)');
  1095. //initializeSeigaThumbnail('nicorepo', '.nicorepo', '.log-target-thumbnail a[href*=/seiga/im]:not(.largeThumbnailLink)');
  1096.  
  1097. } else
  1098. if (location.pathname.indexOf('/my/mylist') === 0) {
  1099. initializeLargeThumbnail('mylist', '#mylist', '.thumbContainer a:not(.largeThumbnailLink)');
  1100. } else
  1101. if (location.pathname.indexOf('/my/video') === 0) {
  1102. initializeLargeThumbnail('video', '#video', '.thumbContainer a:not(.largeThumbnailLink)');
  1103. } else
  1104. if (location.pathname.indexOf('/mylist') === 0) {
  1105. initializeLargeThumbnail('openMylist', '#PAGEBODY', '.SYS_box_item a:not(.watch):not(.largeThumbnailLink)');
  1106. } else
  1107. if (location.pathname.match(/\/user\/\d+\/video/)) {
  1108. initializeLargeThumbnail('video', '#video', '.thumbContainer a:not(.largeThumbnailLink)');
  1109. } else
  1110. if (location.pathname.match(/\/user\/\d+(\/top|)$/)) {
  1111. initializeLargeThumbnail('nicorepo', '.nicorepo', '.log-target-thumbnail a[href*="/watch/"]:not(.largeThumbnailLink)');
  1112. initializeSeigaThumbnail('nicorepo', '.nicorepo',
  1113. '.log-target-thumbnail a:not(.largeThumbnailLink)');
  1114. } else
  1115. if (location.pathname.match(/\/my\/tagrepo\//)) {
  1116. initializeLargeThumbnail('tagrepo', '#tagrepo', '.newVideoUser .contents-thumbnail a[href*="nicovideo.jp/watch/sm"]:not(.largeThumbnailLink)');
  1117. }
  1118. });
  1119.  
  1120. if (location.pathname.indexOf('/mylist') < 0) {
  1121. window.SmartNicorepo.initialize();
  1122. }
  1123.  
  1124. }); // end of monkey
  1125.  
  1126. let gm = document.createElement('script');
  1127. gm.id = 'smartNicorepoScript';
  1128. gm.setAttribute("type", "text/javascript");
  1129. gm.setAttribute("charset", "UTF-8");
  1130. gm.appendChild(document.createTextNode("(" + monkey + ")(window)"));
  1131. document.body.appendChild(gm);
  1132.  
  1133. })(window.unsafeWindow || window);