SmartNicorepo

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

当前为 2019-01-15 提交的版本,查看 最新版本

  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.10
  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. #mylist .articleBody .myContList li.SYS_box_item .thumbContainer img,
  390. #SYS_page_items .thumbContainer img.video,
  391. #video .articleBody .outer .thumbContainer img.video {
  392. max-width: unset;
  393. max-height: unset;
  394. object-fit: cover;
  395. width: 160px;
  396. height: 90px;
  397. }
  398. `).trim();
  399.  
  400. const __tagrepocss__ = (`
  401. .newVideoChannel .post-item,
  402. .newVideoUser .post-item {
  403. {* background: #ffe;*}
  404. }
  405.  
  406. .newLiveChannel .post-item,
  407. .newLiveUser .post-item {
  408. background: #eee;
  409. }
  410.  
  411. .newVideoUser .contents-thumbnail img.largeThumbnail,
  412. .newVideoOfficial .contents-thumbnail img.largeThumbnail,
  413. .newVideoChannel .contents-thumbnail img.largeThumbnail {
  414. margin-top: -21px;
  415. }
  416.  
  417. `).trim();
  418.  
  419. const failedUrl = {};
  420. const initializeLargeThumbnail = function(type, container, selector) {
  421. console.log('%cinitializeLargeThumbnail: type=%s', 'background: lightgreen;', type);
  422. addStyle(__large_thumbnail_css__);
  423.  
  424. // 大サムネが存在する最初の動画ID。 ソースはちゆ12歳
  425. // ※この数字以降でもごく稀に例外はある。
  426. const threthold = 16371888;
  427. const hasLargeThumbnail = videoId => {
  428. let cid = videoId.substr(0, 2);
  429. if (cid !== 'sm') { return false; }
  430.  
  431. let fid = videoId.substr(2) * 1;
  432. if (fid < threthold) { return false; }
  433.  
  434. return true;
  435. };
  436.  
  437. const onLoadImageError = e => {
  438. let target = e.target;
  439. let src = target.src.replace('.L', '');
  440. target.classList.add('large-thumbnail-fail');
  441.  
  442. failedUrl[src] = true;
  443. if (target.src !== src) {
  444. target.src = src;
  445. }
  446. };
  447.  
  448. const each = v => {
  449. let href = (v.href || '').toString();
  450. if (!href ||
  451. v.hostname !== 'www.nicovideo.jp' ||
  452. href.indexOf('/watch/') < 0) {
  453. return;
  454. }
  455.  
  456. let videoId;
  457. if (/^.+(sm\d+).*$/.test(href)) {
  458. videoId = href.replace(/^.+(sm\d+).*$/, '$1');
  459. } else {
  460. let img = v.querySelector('img');
  461. if (!img || !img.src) { return; }
  462. if (!/smile\?i=(\d+)/.test(img.src)) { return; }
  463. videoId = 'sm' + RegExp.$1;
  464. }
  465.  
  466. if (!hasLargeThumbnail(videoId)) {
  467. return;
  468. }
  469.  
  470. let thumbnail = v.querySelector('img');
  471. // console.log('thumbnail', v.querySelector('img'));
  472. if (!thumbnail || thumbnail.classList.contains('large-thumbnail-fail')) { return; }
  473. let src = thumbnail.getAttribute('src') || '';
  474. let org = thumbnail.dataset.original || '';
  475. let attrName = org ? 'data-original' : 'src';
  476. src = org ? org : src;
  477. let url = src.replace('.M', '') + '.L';
  478.  
  479. if (failedUrl[src] || failedUrl[url]) { return; }
  480.  
  481. if (!src || !src.match(/\/smile\?i=/)) {
  482. return;
  483. }
  484. // console.log('each', videoId, src, v, !src || src.match(/\.M/) || !src.match('/smile?i='));
  485.  
  486. thumbnail.addEventListener('error', onLoadImageError, {once: true, passive: true});
  487. thumbnail.addEventListener('load', () => {
  488. if (!thumbnail.src.match(/\.L$/)) { return; }
  489. v.classList.add('largeThumbnailLink');
  490. v.style.setProperty('--thumbnail-image', `url(${url})`);
  491. thumbnail.classList.add('largeThumbnail', videoId);
  492. thumbnail.removeEventListener('error', onLoadImageError);
  493. }, {once: true});
  494. v.dataset.loadingThumbnail = JSON.stringify({id: videoId, url});
  495. thumbnail.setAttribute(attrName, url);
  496. return {id: videoId, url};
  497. };
  498.  
  499. const update = _.debounce(() => {
  500. Array.from(document.querySelectorAll(selector)).map(each);
  501. }, 500);
  502.  
  503. update();
  504.  
  505. const mutationObserver = new window.MutationObserver(mutations => {
  506. if (mutations.some(mutation => mutation.addedNodes.length)) {
  507. update();
  508. }
  509. });
  510. mutationObserver.observe(
  511. document.querySelector(container),
  512. {childList: true, characterData: false, attributes: false, subtree: true}
  513. );
  514. };
  515.  
  516. const initializeSeigaThumbnail = function(type, container, selector) {
  517. console.log('%cinitializeSeigaThumbnail: type=%s', 'background: lightgreen;', type, container, selector);
  518.  
  519. const onLoadImageError = e => {
  520. console.warn('%c large thumbnail load error!', '', e);
  521.  
  522. let target = e.target;
  523. target.classList.add('large-thumbnail-fail');
  524. target.src = target.src.replace(/i$/, 'z');
  525. };
  526.  
  527. const each = v => {
  528. let href = v.href || '';
  529. if (!href || href.indexOf('/seiga/im') < 0) {
  530. return;
  531. }
  532.  
  533. let seigaId = href.replace(/^.+(im\d+).*$/, '$1');
  534.  
  535. let thumbnail = v.querySelector('img');
  536. if (!thumbnail || thumbnail.classList.contains('large-thumbnail-fail')) { return; }
  537. let src = thumbnail.getAttribute('src') || '';
  538. let org = thumbnail.dataset.original || '';
  539. let attrName = org ? 'data-original' : 'src';
  540. src = org ? org : src;
  541. let url = src.replace(/z$/, 'i');
  542.  
  543. if (!src || !src.match(/thumb\/\d+z$/)) { return; }
  544.  
  545. thumbnail.addEventListener('error', onLoadImageError, {once: true, passive: true});
  546. thumbnail.addEventListener('load', () => {
  547. if (!thumbnail.src.match(/i$/)) { return; }
  548. v.classList.add('largeThumbnailLink');
  549. v.style.setProperty('--thumbnail-image', `url(${url})`);
  550. thumbnail.classList.add('largeThumbnail', seigaId);
  551. thumbnail.removeEventListener('error', onLoadImageError);
  552. }, {once: true});
  553. thumbnail.setAttribute(attrName, url);
  554.  
  555. return {id: seigaId, url};
  556. };
  557.  
  558. let update = _.debounce(() => {
  559. Array.from(document.querySelectorAll(selector)).map(each)
  560. }, 500);
  561.  
  562. update();
  563.  
  564. const mutationObserver = new window.MutationObserver(mutations => {
  565. if (mutations.some(mutation => mutation.addedNodes.length)) {
  566. update();
  567. }
  568. });
  569. mutationObserver.observe(
  570. document.querySelector(container),
  571. {childList: true, characterData: false, attributes: false, subtree: true}
  572. );
  573. };
  574.  
  575. const updateTimelineItemClass = () => {
  576. // 「投稿」のクラスをつける
  577. let query = '.NicorepoTimelineItem:not(.is-named) .log-body strong';
  578. Array.from(document.querySelectorAll(query)).forEach(elm => {
  579. const log = elm.closest('.NicorepoTimelineItem');
  580. if (!log) { return; }
  581. log.classList.add('log-upload');
  582. log.classList.add('is-named');
  583. });
  584.  
  585. // TODO: 多言語対応
  586. const uploadReg = /(^チャンネル.*に(動画|記事)が追加されました。$|コミュニティ.*?に動画を追加しました。$|投稿しました。$)/;
  587. const mylistReg = /(に(動画|ブロマガ)を登録しました。$|イラストをクリップしました。$|^.*?さんが .*? にマンガ .*? を登録しました。$|マンガをお気に入りしました。$)/;
  588.  
  589. const liveReg = /(生放送を(開始|予約)しました。$|生放送が開始されました。$|生放送が予約されました。$)/;
  590.  
  591. const uadReg = /ニコニ広告(で宣伝)?しました。 >広告履歴を確認$/;
  592.  
  593. const kiribanReg = /(再生を達成しました。$)/;
  594.  
  595. const rankingReg = /(位を達成しました。$)/;
  596.  
  597.  
  598. // それ以外のカテゴリ。 文言から判別するしかない
  599. query = '.NicorepoTimelineItem:not(.is-named) .log-body';
  600. Array.from(document.querySelectorAll(query)).forEach(elm => {
  601. const text = (elm.textContent || '').trim();
  602. const item = elm.closest('.NicorepoTimelineItem');
  603.  
  604. if (uploadReg.test(text)) {
  605. item.classList.add('log-upload');
  606. } else if (mylistReg.test(text)) {
  607. item.classList.add('log-mylist');
  608. } else if (liveReg.test(text)) {
  609. item.classList.add('log-live');
  610. } else if (uadReg.test(text)) {
  611. item.classList.add('log-uad');
  612. } else if (kiribanReg.test(text)) {
  613. item.classList.add('log-kiriban');
  614. } else if (rankingReg.test(text)) {
  615. item.classList.add('log-ranking');
  616. } else {
  617. item.classList.add('log-other');
  618. }
  619. item.classList.add('is-named');
  620. });
  621.  
  622. Array.from(document.querySelectorAll('.nicorepo a[href*="_topic="]')).forEach((a) => {
  623. a.href = a.href.replace(/\?.*?$/, '');
  624. });
  625. };
  626.  
  627. const initializeToolbar = (config, parentNodeSelector) => {
  628. const nicorepo = document.querySelector('#nicorepo');
  629. const container = document.createElement('div');
  630. container.className = 'smartNicorepoToolbar';
  631.  
  632. const createToggle = function(elm, confName, className) {
  633. return function(isChecked) {
  634. if (typeof isChecked === 'boolean') {
  635. nicorepo.classList.toggle(className, isChecked);
  636. } else {
  637. nicorepo.classList.toggle(className);
  638. }
  639.  
  640. const v = nicorepo.classList.contains(className);
  641. elm.classList.toggle('active', v);
  642. config.set(confName, v);
  643. };
  644. };
  645.  
  646. const createItem = function(text, titleText) {
  647. const label = document.createElement('label');
  648. label.className = 'categoryCheckLabel';
  649. if (titleText) { label.title = titleText; }
  650.  
  651. const span = document.createElement('span');
  652. span.textContent = text;
  653. label.appendChild(span);
  654. return label;
  655. };
  656.  
  657. const upload = createItem('投稿', '動画・静画・ブログなど');
  658. const toggleUpload = createToggle(upload, 'showUpload', 'show-upload');
  659. upload.addEventListener('click', toggleUpload);
  660. toggleUpload(!!config.get('showUpload'));
  661. container.appendChild(upload);
  662.  
  663. const mylist = createItem('マイリスト', 'マイリスト・クリップなど');
  664. const toggleMylist = createToggle(mylist, 'showMylist', 'show-mylist');
  665. mylist.addEventListener('click', toggleMylist);
  666. toggleMylist(!!config.get('showMylist'));
  667. container.appendChild(mylist);
  668.  
  669. const live = createItem('生放送', '開始・予約など');
  670. const toggleLive = createToggle(live, 'showLive', 'show-live');
  671. live.addEventListener('click', toggleLive);
  672. toggleLive(!!config.get('showLive'));
  673. container.appendChild(live);
  674.  
  675. const uad = createItem('宣伝', 'ニコニ広告');
  676. const toggleUad = createToggle(uad, 'showUad', 'show-uad');
  677. uad.addEventListener('click', toggleUad);
  678. toggleUad(!!config.get('showUad'));
  679. container.appendChild(uad);
  680.  
  681. const kiriban = createItem('再生', '2525再生など');
  682. const toggleKiriban = createToggle(kiriban, 'showKiriban', 'show-kiriban');
  683. kiriban.addEventListener('click', toggleKiriban);
  684. toggleKiriban(!!config.get('showKiriban'));
  685. container.appendChild(kiriban);
  686.  
  687. const ranking = createItem('ランキング', '');
  688. const toggleRanking = createToggle(ranking, 'showRanking', 'show-ranking');
  689. ranking.addEventListener('click', toggleRanking);
  690. toggleRanking(!!config.get('showRanking'));
  691. container.appendChild(ranking);
  692.  
  693. const other = createItem('その他', '');
  694. const toggleOther = createToggle(other, 'showOther', 'show-other');
  695. other.addEventListener('click', toggleOther);
  696. toggleOther(!!config.get('showOther'));
  697. container.appendChild(other);
  698.  
  699. const hiderepo = createItem('閉じてるのを消す', '');
  700. hiderepo.classList.add('toggle-hiderepo');
  701. const toggleHiderepo = createToggle(hiderepo, 'hiderepo', 'hiderepo');
  702. hiderepo.addEventListener('click', toggleHiderepo);
  703. toggleHiderepo(!!config.get('hiderepo'));
  704. container.appendChild(hiderepo);
  705.  
  706.  
  707. setTimeout(() => {
  708. const parentNode = document.querySelector(parentNodeSelector);
  709. if (!parentNode) { return; }
  710. parentNode.appendChild(container);
  711. }, 3000);
  712. };
  713.  
  714.  
  715. window.SmartNicorepo = {
  716. model: {},
  717. util: {},
  718. initialize: function() {
  719. this.initializeUserConfig();
  720. if (location.pathname === '/my/fav/user') {
  721. this.initializeFavUser();
  722. } else
  723. if (location.pathname.indexOf('/my/tagrepo/') === 0) {
  724. this.initializeTagrepo();
  725. } else {
  726. this.initializeNicorepo();
  727. this.initializeAutoPageRize();
  728. }
  729.  
  730. this.initializeOther();
  731. // jQuery._data(jQuery(window).get(0), 'events')
  732. },
  733. initializeUserConfig: function() {
  734. var prefix = 'SmartNicorepo_';
  735. var conf = {
  736. showUpload: true,
  737. showMylist: false,
  738. showLive: false,
  739. showUad: false,
  740. showKiriban: false,
  741. showRanking: false,
  742. showOther: false,
  743. autoPagerize: true,
  744. hiderepo: false
  745. };
  746.  
  747. this.config = {
  748. get: function(key) {
  749. try {
  750. if (window.localStorage.hasOwnProperty(prefix + key) || localStorage[prefix + key] !== undefined) {
  751. return JSON.parse(window.localStorage.getItem(prefix + key));
  752. }
  753. return conf[key];
  754. } catch (e) {
  755. return conf[key];
  756. }
  757. },
  758. set: function(key, value) {
  759. //console.log('%cupdate config {"%s": "%s"}', 'background: cyan', key, value);
  760. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  761. }
  762. };
  763. },
  764. initializeNicorepo: function() {
  765. addStyle(__nicorepocss__, 'nicorepoCss');
  766.  
  767. let config = this.config;
  768.  
  769. let app = document.querySelector('#MyPageNicorepoApp, #UserPageNicorepoApp');
  770. if (!app) { return; }
  771.  
  772. const mutationObserver = new window.MutationObserver((mutations) => {
  773. let isAdded = false;
  774. mutations.forEach(mutation => {
  775. if (mutation.addedNodes && mutation.addedNodes.length > 0) {
  776. isAdded = true;
  777. }
  778. });
  779. if (isAdded) { updateTimelineItemClass(); }
  780. });
  781.  
  782. updateTimelineItemClass();
  783.  
  784. mutationObserver.observe(
  785. app,
  786. {childList: true, characterData: false, attributes: false, subtree: true}
  787. );
  788.  
  789. initializeToolbar(config, '#MyPageNicorepoApp, #UserPageNicorepoApp');
  790. },
  791. initializeTagrepo: function() {
  792. console.log('%cinitializeTagrepo', 'background: lightgreen;');
  793. addStyle(__tagrepocss__, 'tagrepoCss');
  794. },
  795. initializeFavUser: function() {
  796. addStyle(__favusercss__, 'favUserCss');
  797.  
  798. $('.posRight .arrow').each(function(i, elm) {
  799. var $elm = $(elm), $lnk = $elm.clone();
  800. $lnk
  801. .html('<span></span> ニコレポを表示&nbsp;')
  802. .addClass('showNicorepo');
  803. $elm.before($lnk);
  804. });
  805.  
  806. $('.outer .section a').each(function(i, elm) {
  807. var $elm = $(elm), href = $elm.attr('href');
  808. if (href.match(/\/(\d+)$/)) {
  809. var userId = RegExp.$1;
  810. var $video = $('<a class="uploadVideoList">動画一覧</a>')
  811. .attr('href', '/user/' + userId + '/video');
  812. var $seiga = $('<a class="seigaUserPage">静画一覧</a>')
  813. .attr('href', '//seiga.nicovideo.jp/user/illust/' + userId);
  814. $elm.after($seiga).after($video);
  815. }
  816. });
  817.  
  818. var getClearBusy = function($elm) {
  819. return function() {
  820. $elm.removeClass('updating').addClass('done');
  821. };
  822. };
  823.  
  824. $('#favUser .showNicorepo').off().on('click', $.proxy(function(e) {
  825. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) {
  826. return;
  827. }
  828. e.preventDefault();
  829. e.stopPropagation();
  830. var $elm = $(e.target);
  831. var userId = $elm.attr('data-nico-nicorepolistid');
  832. if (!userId) { return; }
  833. var $outer = $elm.closest('.outer');
  834. if ($outer.hasClass('updating')) {
  835. return;
  836. }
  837.  
  838. var clearBusy = getClearBusy($outer);
  839. $outer.addClass('updating');
  840. window.setTimeout(clearBusy, 3000);
  841.  
  842. this.loadNicorepo(userId, $outer).then(clearBusy, clearBusy);
  843.  
  844. }, this));
  845. },
  846. initializeAutoPageRize: function() {
  847. let config = this.config;
  848. let $button = $('<button class="togglePagerize">自動読込</button>');
  849. let timer = null;
  850.  
  851. var onButtonClick = () => {
  852. toggle();
  853. updateView();
  854. };
  855. var toggle = () => {
  856. this._isAutoPagerizeEnable = !this._isAutoPagerizeEnable;
  857. config.set('autoPagerize', this._isAutoPagerizeEnable);
  858. if (this._isAutoPagerizeEnable) {
  859. bind();
  860. } else {
  861. unbind();
  862. }
  863. };
  864. let updateView = () => {
  865. $button.toggleClass('enable', this._isAutoPagerizeEnable);
  866. };
  867. let onWindowScroll = _.debounce(this._onWindowScroll.bind(this), 100);
  868. let bind = () => {
  869. window.addEventListener('scroll', onWindowScroll, {passive: true});
  870. timer = window.setInterval(this._autoPagerize.bind(this), 5000);
  871. };
  872. let unbind = () => {
  873. window.removeEventListener('scroll', onWindowScroll);
  874. window.clearInterval(timer);
  875. };
  876.  
  877.  
  878. $button.click(onButtonClick);
  879. $('body').append($button);
  880.  
  881. this._isAutoPagerizeEnable = config.get('autoPagerize');
  882. if (this._isAutoPagerizeEnable) { bind(); }
  883.  
  884. updateView();
  885. },
  886. initializeOther: function() {
  887. window.resizeSidebarHeight = () => {};
  888. if (window.Nico && window.Nico.FollowManager) {
  889. window.Nico.FollowManager.resizeAdjust =
  890. window.Nico.FollowManager.scrollAdjust = () => {};
  891. }
  892. },
  893. _onWindowScroll: function() {
  894. this._autoPagerize();
  895. },
  896. _autoPagerize: function() {
  897. if (!this._isAutoPagerizeEnable) { return; }
  898. //let ver = document.querySelector('#MyPageNicorepoApp') ? 'new' : 'old';
  899.  
  900. // TODO: IntersectionObserverつかえ
  901. let nextPage = //this._nextPage ||
  902. document.querySelector('#MyPageNicorepoApp .next-page, #nicorepo .next-page');
  903. if (!nextPage) { return; }
  904. //this._nextPage = nextPage;
  905.  
  906. let rect = nextPage.getBoundingClientRect();
  907. let isLoading = nextPage.classList.contains('loading');
  908. let isScrollIn = window.innerHeight - rect.top > 0;
  909. //console.info('?', isLoading, isScrollIn, window.innerHeight - rect.top);
  910. if (isScrollIn && !isLoading) {
  911. (document.querySelector('#nicorepo .next-page-link') || {click: _.noop}).click();
  912. }
  913. },
  914. loadNicorepo: function(userId, $container) {
  915. // http://www.nicovideo.jp/user/[userId]/top?innerPage=1
  916. var url = '//www.nicovideo.jp/user/' + userId + '/top?innerPage=1';
  917.  
  918. var fail = function(msg) {
  919. var $fail = $('<div class="nicorepo fail">' + msg + '</div>');
  920. $container.append($fail);
  921. autoScrollIfNeed($fail);
  922. };
  923.  
  924. // ニコレポが画面の一番下よりはみ出していたら見える位置までスクロール
  925. var autoScrollIfNeed = function($target) {
  926. var
  927. scrollTop = $('html').scrollTop(),
  928. targetOffset = $target.offset(),
  929. clientHeight = $(window).innerHeight(),
  930. clientBottom = scrollTop + clientHeight,
  931. targetBottom = targetOffset.top + $target.outerHeight();
  932.  
  933. if (targetBottom > clientBottom) {
  934. $('html').animate({
  935. scrollTop: scrollTop + $target.outerHeight()
  936. }, 500);
  937. }
  938. };
  939.  
  940. var success = function($dom, $logBody) {
  941. var $result = $('<div class="nicorepo success" />');
  942. var $img = $logBody.find('img'), $log = $logBody.find('.log');
  943. $img.each(function() {
  944. var $this = $(this), $parent = $this.parent();
  945. var lazyImg = $this.attr('data-original');
  946. if (lazyImg) {
  947. var $imageContainer = $('<div class="imageContainer"/>');
  948. $imageContainer.css('background-image', 'url(' + lazyImg + ')');
  949. $this.before($imageContainer);
  950. $this.remove();
  951. }
  952. if (window.WatchItLater) {
  953. var href = $parent.attr('href');
  954. if (href) {
  955. $parent.attr('href', href.replace('//www.nicovideo.jp/watch/', '//nico.ms/'));
  956. }
  957. }
  958. });
  959. $logBody.each(function() {
  960. var $this = $(this), time = $this.find('time:first').text(), logComment = $this.find('.log-comment').text();
  961.  
  962. $this.find('.log-target-info>*:first')
  963. .before($('<span class="time">' + time + '</span>'));
  964. if (logComment) {
  965. $this.find('.log-target-info')
  966. .append($('<span class="logComment">' + logComment + '</span>'));
  967. }
  968. });
  969.  
  970. $result.append($logBody);
  971. $container.append($result);
  972. $result.scrollTop(0);
  973.  
  974. autoScrollIfNeed($result);
  975. };
  976.  
  977. return $.ajax({
  978. url: url,
  979. timeout: 30000
  980. }).then(
  981. function(resp) {
  982. var
  983. $dom = $(resp),
  984. // 欲しいのはそのユーザーの「行動」なので、
  985. // xx再生やスタンプみたいなのはいらない
  986. $logBody = $dom.find('.log:not(.log-user-video-round-number-of-view-counter):not(.log-user-action-stamp):not(.log-user-live-video-introduced)');
  987. if ($logBody.length < 1) {
  988. fail('ニコレポが存在しないか、取得に失敗しました');
  989. } else {
  990. success($dom, $logBody);
  991. }
  992. },
  993. function() {
  994. fail('ニコレポの取得に失敗しました');
  995. });
  996. },
  997. loadFavUserList: function() {
  998. var def = new $.Deferred();
  999. // このAPIのupdate_timeが期待していた物と違ったのでボツ
  1000. // create_timeとupdate_timeはどちらも同じ値が入ってるだけだった。(なんのためにあるんだ?)
  1001. //
  1002. $.ajax({
  1003. url: '//www.nicovideo.jp/api/watchitem/list',
  1004. timeout: 30000,
  1005. complete: function(resp) {
  1006. var json;
  1007. try {
  1008. json = JSON.parse(resp.responseText);
  1009. } catch (e) {
  1010. console.log('%c parse error: ', 'background: #f88', e);
  1011. return def.reject('json parse error');
  1012. }
  1013.  
  1014. if (json.status !== 'ok') {
  1015. console.log('%c status error: ', 'background: #f88', json.status);
  1016. return def.reject('status error', json.status);
  1017. }
  1018. return def.resolve(json.watchitem);
  1019. },
  1020. error: function(req, status, thrown) {
  1021. if (status === 'parsererror') {
  1022. return;
  1023. }
  1024. console.log('%c ajax error: ' + status, 'background: #f88', thrown);
  1025. return def.reject(status);
  1026. }
  1027. });
  1028. return def.promise();
  1029. }
  1030.  
  1031. };
  1032.  
  1033.  
  1034. window.SmartNicorepo.model.WatchItem = function() { this.initialize.apply(this, arguments); };
  1035. window.SmartNicorepo.model.WatchItem.prototype = {
  1036. initialize: function(seed) {
  1037. this._seed = seed;
  1038. this.itemType = seed.item_type || '1';
  1039. this.itemId = seed.item_id || '';
  1040. if (typeof seed.item_data === 'object') {
  1041. var data = seed.item_data;
  1042. this.userId = data.id;
  1043. this.nickname = data.nickname;
  1044. this.thumbnailUrl = data.thumbnail_url;
  1045. }
  1046. var now = (new Date()).getTime();
  1047. this.createTime = new Date(seed.create_time ? seed.create_time * 1000 : now);
  1048. this.updateTime = new Date(seed.update_time ? seed.update_time * 1000 : now);
  1049. }
  1050. };
  1051.  
  1052. window.SmartNicorepo.model.WatchItemList = function() { this.initialize.apply(this, arguments); };
  1053. window.SmartNicorepo.model.WatchItemList.prototype = {
  1054. initialize: function(watchItems) {
  1055. this._seed = watchItems;
  1056. this._items = {};
  1057. this._itemArray = [];
  1058. for (var i = 0, len = watchItems.length; i < len; i++) {
  1059. var item = new window.SmartNicorepo.model.WatchItem(watchItems[i]);
  1060. this._items[item.userId] = item;
  1061. this._itemArray.push(item);
  1062. }
  1063. },
  1064. getItem: function(userId) {
  1065. return this._items[userId];
  1066. },
  1067. getSortedItems: function() {
  1068. var result = this._itemArray.concat();
  1069. result.sort(function(a, b) {
  1070. return (a.updateTime < b.updateTime) ? 1 : -1;
  1071. });
  1072. return result;
  1073. }
  1074. };
  1075.  
  1076. var removeQuery = function(container) {
  1077. const reg = /(^\?nicorepo|ref=)/;
  1078. $(container + ' a').each((i, a) => {
  1079. const search = a.search || '';
  1080. if (reg.test(search)) {
  1081. a.search = '';
  1082. }
  1083. });
  1084. };
  1085.  
  1086.  
  1087. window.Nico.onReady(function() {
  1088. console.log('%cNico.onReady', 'background: lightgreen;');
  1089. if (location.pathname.indexOf('/my/top') === 0 || document.querySelector('#nicorepo')) {
  1090. initializeLargeThumbnail('nicorepo', '#nicorepo',
  1091. '.log-target-thumbnail a[href*="/watch/"]:not(.largeThumbnailLink)');
  1092. initializeSeigaThumbnail('nicorepo', '#nicorepo',
  1093. '.log-target-thumbnail a:not(.largeThumbnailLink)');
  1094. //initializeSeigaThumbnail('nicorepo', '.nicorepo', '.log-target-thumbnail a[href*=/seiga/im]:not(.largeThumbnailLink)');
  1095. } else
  1096. if (location.pathname.indexOf('/my/mylist') === 0) {
  1097. initializeLargeThumbnail('mylist', '#mylist', '.thumbContainer a:not(.largeThumbnailLink)');
  1098. } else
  1099. if (location.pathname.indexOf('/my/video') === 0) {
  1100. initializeLargeThumbnail('video', '#video', '.thumbContainer a:not(.largeThumbnailLink)');
  1101. } else
  1102. if (location.pathname.indexOf('/mylist') === 0) {
  1103. initializeLargeThumbnail('openMylist', '#PAGEBODY', '.SYS_box_item a:not(.watch):not(.largeThumbnailLink)');
  1104. } else
  1105. if (location.pathname.match(/\/user\/\d+\/video/)) {
  1106. initializeLargeThumbnail('video', '#video', '.thumbContainer a:not(.largeThumbnailLink)');
  1107. } else
  1108. if (location.pathname.match(/\/user\/\d+(\/top|)$/)) {
  1109. initializeLargeThumbnail('nicorepo', '.nicorepo', '.log-target-thumbnail a[href*="/watch/"]:not(.largeThumbnailLink)');
  1110. initializeSeigaThumbnail('nicorepo', '.nicorepo',
  1111. '.log-target-thumbnail a:not(.largeThumbnailLink)');
  1112. } else
  1113. if (location.pathname.match(/\/my\/tagrepo\//)) {
  1114. initializeLargeThumbnail('tagrepo', '#tagrepo', '.newVideoUser .contents-thumbnail a[href*="nicovideo.jp/watch/sm"]:not(.largeThumbnailLink)');
  1115. }
  1116. });
  1117.  
  1118. if (location.pathname.indexOf('/mylist') < 0) {
  1119. window.SmartNicorepo.initialize();
  1120. }
  1121.  
  1122. }); // end of monkey
  1123.  
  1124. let gm = document.createElement('script');
  1125. gm.id = 'smartNicorepoScript';
  1126. gm.setAttribute("type", "text/javascript");
  1127. gm.setAttribute("charset", "UTF-8");
  1128. gm.appendChild(document.createTextNode("(" + monkey + ")(window)"));
  1129. document.body.appendChild(gm);
  1130.  
  1131. })(window.unsafeWindow || window);