SmartNicorepo

ニコレポの「投稿」以外をデフォルトで折りたたむ & お気に入りユーザーに最終更新を表示

当前为 2017-06-08 提交的版本,查看 最新版本

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