SmartNicorepo

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

当前为 2015-07-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. // @version 2.1.3
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. var monkey =
  14. (function() {
  15. var $ = window.jQuery;
  16.  
  17. function addStyle(styles, id) {
  18. var elm = document.createElement('style');
  19. elm.type = 'text/css';
  20. if (id) { elm.id = id; }
  21.  
  22. var text = styles.toString();
  23. text = document.createTextNode(text);
  24. elm.appendChild(text);
  25. var head = document.getElementsByTagName('head');
  26. head = head[0];
  27. head.appendChild(elm);
  28. return elm;
  29. }
  30.  
  31. var __nicorepocss__ = (function() {/*
  32. .nicorepo .log.log-user-video-upload {
  33. background: #ffe;
  34. }
  35. .nicorepo .log.log-user-video-upload .log-target-thumbnail ,.nicorepo .log.log-user-seiga-image-upload .log-target-thumbnail {
  36. width: auto; margin-left: -30px;
  37. }
  38. .nicorepo .log.log-user-video-upload .video , .nicorepo .log.log-user-seiga-image-upload .seiga_image {
  39. height: auto !important; width: 130px !important; margin-top: 0px;
  40. margin-bottom: 0 !important; margin-left: 0 !important;
  41. }
  42. #nicorepo .timeline > .log {
  43. max-height: 500px;
  44. transition: max-height 0.4s ease-in-out;
  45. }
  46. #nicorepo.show-upload-only .timeline > .log:not(.log-user-video-upload):not(.log-user-seiga-image-upload):not(.log-user-register-chblog) {
  47. max-height: 22px;
  48. overflow: hidden;
  49. }
  50. #nicorepo .timeline > .log:not(.log-user-video-upload):not(.log-user-seiga-image-upload):not(.log-user-register-chblog):hover {
  51. max-height: 500px;
  52. overflow: hidden;
  53. transition: max-height 0.4s ease-in-out 0.8s;
  54. }
  55. .toggleUpload {
  56. position: absolute; top: 32px; right: 32px; font-weight: bolder; cursor: pointer; color: #888; padding: 8px;
  57. z-index: 1000;
  58. box-shadow: 2px 2px 2px #333;
  59. }
  60. .toggleUpload.bottom {
  61. top: auto; right: 32px; bottom: 32px;
  62. }
  63. .show-upload-only .toggleUpload {
  64. color: red;
  65. }
  66. .toggleUpload:after {
  67. content: ': OFF';
  68. }
  69. .show-upload-only .toggleUpload:after {
  70. content: ': ON';
  71. }
  72.  
  73. .togglePagerize {
  74. position: fixed;
  75. bottom: 0;
  76. right: 0;
  77. color: #888;
  78. font-weight: bolder;
  79. cursor: pointer;
  80. border: 2px solid #666;
  81. }
  82. .togglePagerize.enable {
  83. color: red;
  84. }
  85. .togglePagerize:after {
  86. content: ': OFF';
  87. }
  88. .togglePagerize.enable:after {
  89. content: ': ON';
  90. }
  91.  
  92. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  93.  
  94. var __favusercss__ = (function() {/*
  95.  
  96. #favUser .outer.updating {
  97. }
  98. #favUser .outer.updating * {
  99. cursor: wait;
  100. }
  101. #favUser .outer.done .showNicorepo {
  102. display: none;
  103. }
  104.  
  105. #favUser .nicorepo {
  106. color: #800;
  107. clear: both;
  108. margin-bottom: 24px;
  109. }
  110. #favUser .uploadVideoList, #favUser .seigaUserPage {
  111. font-size: 80%;
  112. margin-left: 16px;
  113. }
  114.  
  115. #favUser .nicorepo.fail {
  116. color: #800;
  117. clear: both;
  118. margin-left: 64px;
  119. }
  120.  
  121.  
  122. #favUser .nicorepo.success {
  123. padding: 8px;
  124. overflow: auto;
  125. border: 1px inset;
  126. max-height: 300px;
  127. }
  128.  
  129. .nicorepo .log-target-thumbnail,
  130. .nicorepo .log-target-info {
  131. display: inline-block;
  132. vertical-align: middle;
  133. }
  134. .nicorepo .log-target-thumbnail .imageContainer {
  135. width: 64px;
  136. height: 48px;
  137. background-color: #fff;
  138. background-size: contain;
  139. background-repeat: no-repeat;
  140. background-position: center;
  141. transition: 0.2s width ease 0.4s, 0.2s height ease 0.4s;
  142. }
  143. .nicorepo .log-target-thumbnail .imageContainer:hover {
  144. width: 128px;
  145. height: 96px;
  146. }
  147. .nicorepo .log-target-info .time {
  148. display: block;
  149. font-size: 80%;
  150. color: black;
  151. }
  152. .nicorepo .log-target-info .logComment {
  153. display: block;
  154. font-size: 80%;
  155. color: black;
  156. }
  157. .nicorepo .log-target-info .logComment:before {
  158. content: '「';
  159. }
  160. .nicorepo .log-target-info .logComment:after {
  161. content: '」';
  162. }
  163. .nicorepo .log-target-info a {
  164. display: inline-block;
  165. min-width: 100px;
  166. }
  167. .nicorepo .log-target-info a:hover {
  168. background: #ccf;
  169. }
  170.  
  171.  
  172. .nicorepo .log.log-user-video-round-number-of-view-counter {
  173. display: none;
  174. }
  175.  
  176. .nicorepo .log-content {
  177. margin: 4px 8px;
  178. position: relative;
  179. }
  180. .nicorepo .log-footer {
  181. position: absolute;
  182. top: 0;
  183. left: 138px;
  184. }
  185. .nicorepo .log-footer a {
  186. font-size: 80%;
  187. color: black;
  188. }
  189.  
  190. .nicorepo .log .time:after {
  191. background: #888;
  192. color: #fff;
  193. border-radius: 4px;
  194. display: inline-block;
  195. padding: 2px 4px;
  196. }
  197. .nicorepo .log.log-user-register-chblog .time:after,
  198. .nicorepo .log.log-user-video-upload .time:after,
  199. .nicorepo .log.log-user-seiga-image-upload .time:after {
  200. content: '投稿';
  201. background: #866;
  202. }
  203.  
  204. .nicorepo .log.log-user-mylist-add-blomaga .time:after,
  205. .nicorepo .log.log-user-mylist-add .time:after {
  206. content: 'マイリスト';
  207. }
  208. .nicorepo .log.log-user-live-broadcast .time:after {
  209. content: '放送';
  210. }
  211. .nicorepo .log.log-user-seiga-image-clip .time:after {
  212. content: 'クリップ';
  213. }
  214. .nicorepo .log.log-user-video-review .time:after {
  215. content: 'レビュー';
  216. }
  217. .nicorepo .log.log-user-uad-advertise .time:after {
  218. content: '広告';
  219. }
  220.  
  221. .nicorepo .log.log-user-video-upload {
  222. background: #ffe;
  223. }
  224.  
  225. .nicorepo .log.log-user-video-upload .log-target-thumbnail,
  226. .nicorepo .log.log-user-seiga-image-upload .log-target-thumbnail {
  227. }
  228. .nicorepo .log.log-user-video-upload .video,
  229. .nicorepo .log.log-user-seiga-image-upload .seiga_image {
  230. }
  231.  
  232.  
  233. .nicorepo .log-author,
  234. .nicorepo .log-body,
  235. .nicorepo .log-res,
  236. .nicorepo .log-comment,
  237. .nicorepo .log-footer {
  238. display: none !important;
  239. }
  240. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  241.  
  242. window.SmartNicorepo = {
  243. model: {},
  244. util: {},
  245. initialize: function() {
  246. this.initializeUserConfig();
  247. if (location.pathname === '/my/fav/user') {
  248. this.initializeFavUser();
  249. } else {
  250. this.initializeNicorepo();
  251. this.initializeAutoPageRize();
  252. }
  253. },
  254. initializeUserConfig: function() {
  255. var prefix = 'SmartNicorepo_';
  256. var conf = {
  257. showUploadOnly: false,
  258. autoPagerize: true
  259. };
  260.  
  261. this.config = {
  262. get: function(key) {
  263. try {
  264. if (window.localStorage.hasOwnProperty(prefix + key)) {
  265. return JSON.parse(window.localStorage.getItem(prefix + key));
  266. }
  267. return conf[key];
  268. } catch (e) {
  269. return conf[key];
  270. }
  271. },
  272. set: function(key, value) {
  273. //console.log('%cupdate config {"%s": "%s"}', 'background: cyan', key, value);
  274. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  275. }
  276. };
  277. },
  278. initializeNicorepo: function() {
  279. addStyle(__nicorepocss__, 'nicorepoCss');
  280.  
  281. var config = this.config;
  282. var toggle = $.proxy(function() {
  283. $nicorepo.toggleClass('show-upload-only');
  284. config.set('showUploadOnly', $nicorepo.hasClass('show-upload-only'));
  285. }, this);
  286.  
  287. var $nicorepo = $('#nicorepo').dblclick(toggle);
  288. var $button = $('<button class="toggleUpload">投稿だけ表示</button>').click(toggle);
  289.  
  290. $nicorepo.toggleClass('show-upload-only', config.get('showUploadOnly'));
  291.  
  292.  
  293. $('.timeline>*:first').before($button);
  294. $('.timeline>*:last').before($button.clone(true).addClass('bottom'));
  295. },
  296. initializeFavUser: function() {
  297. addStyle(__favusercss__, 'favUserCss');
  298. // this.loadFavUserList()
  299. // .then($.proxy(function(watchitems) {
  300. // console.log('%c ok:', 'background: #8f8;', watchitems.length);
  301. //
  302. // this._itemList = new window.SmartNicorepo.model.WatchItemList(watchitems);
  303. //
  304. // console.log('item list', this._itemList.getSortedItems());
  305. //
  306. // }, this));
  307. $('.posRight .arrow').each(function(i, elm) {
  308. var $elm = $(elm), $lnk = $elm.clone();
  309. $lnk
  310. .html('<span></span> ニコレポを表示&nbsp;')
  311. .addClass('showNicorepo');
  312. $elm.before($lnk);
  313. });
  314.  
  315. $('.outer .section a').each(function(i, elm) {
  316. var $elm = $(elm), href = $elm.attr('href');
  317. if (href.match(/\/(\d+)$/)) {
  318. var userId = RegExp.$1;
  319. var $video = $('<a class="uploadVideoList">動画一覧</a>')
  320. .attr('href', '/user/' + userId + '/video');
  321. var $seiga = $('<a class="seigaUserPage">静画一覧</a>')
  322. .attr('href', 'http://seiga.nicovideo.jp/user/illust/' + userId);
  323. $elm.after($seiga).after($video);
  324. }
  325. });
  326.  
  327. var getClearBusy = function($elm) {
  328. return function() {
  329. $elm.removeClass('updating').addClass('done');
  330. };
  331. };
  332.  
  333. $('#favUser .showNicorepo').off().on('click', $.proxy(function(e) {
  334. if (e.button !== 0 || e.metaKey || e.shiftKey || e.altKey || e.ctrlKey) {
  335. return;
  336. }
  337. e.preventDefault();
  338. e.stopPropagation();
  339. var $elm = $(e.target);
  340. var userId = $elm.attr('data-nico-nicorepolistid');
  341. if (!userId) { return; }
  342. var $outer = $elm.closest('.outer');
  343. if ($outer.hasClass('updating')) {
  344. return;
  345. }
  346.  
  347. var clearBusy = getClearBusy($outer);
  348. $outer.addClass('updating');
  349. window.setTimeout(clearBusy, 3000);
  350.  
  351. this.loadNicorepo(userId, $outer).then(clearBusy, clearBusy);
  352.  
  353. }, this));
  354. },
  355. initializeAutoPageRize: function() {
  356. var config = this.config;
  357. var $button = $('<button class="togglePagerize">自動読込</button>');
  358. var timer = null;
  359.  
  360. var onButtonClick = function(e) {
  361. toggle();
  362. updateView();
  363. };
  364. var toggle = $.proxy(function() {
  365. this._isAutoPagerizeEnable = !this._isAutoPagerizeEnable;
  366. config.set('autoPagerize', this._isAutoPagerizeEnable);
  367. if (this._isAutoPagerizeEnable) {
  368. bind();
  369. } else {
  370. unbind();
  371. }
  372. }, this);
  373. var updateView = $.proxy(function() {
  374. $button.toggleClass('enable', this._isAutoPagerizeEnable);
  375. }, this);
  376. var onWindowScroll = _.debounce($.proxy(this._onWindowScroll, this), 100);
  377. var bind = $.proxy(function() {
  378. $(window).on('scroll', onWindowScroll);
  379. timer = window.setInterval($.proxy(this._autoPagerize, this), 1000);
  380. }, this);
  381. var unbind = $.proxy(function() {
  382. $(window).off('scroll', onWindowScroll);
  383. window.clearInterval(timer);
  384. }, this);
  385.  
  386.  
  387. $button.click(onButtonClick);
  388. $('body').append($button);
  389.  
  390. this._isAutoPagerizeEnable = config.get('autoPagerize');
  391. if (this._isAutoPagerizeEnable) { bind(); }
  392.  
  393. updateView();
  394.  
  395. },
  396. _onWindowScroll: function() {
  397. this._autoPagerize();
  398. },
  399. _autoPagerize: function() {
  400. if (!this._isAutoPagerizeEnable) { return; }
  401.  
  402. // TODO: キャッシュする
  403. var $nextPage = $('.next-page');
  404. var $window = $(window);
  405.  
  406. var isLoading = function() {
  407. return $nextPage.hasClass('loading');
  408. };
  409.  
  410. var isScrollIn = function() {
  411. var bottom =
  412. $window.scrollTop() + $window.innerHeight() - $nextPage.offset().top;
  413. return bottom > 100;
  414. };
  415.  
  416. if (isScrollIn() && !isLoading()) {
  417. this._$nextPage = null;
  418. $nextPage.find('.next-page-link').click();
  419. }
  420. },
  421. loadNicorepo: function(userId, $container) {
  422. // http://www.nicovideo.jp/user/[userId]/top?innerPage=1
  423. var url = 'http://www.nicovideo.jp/user/' + userId + '/top?innerPage=1';
  424.  
  425. var fail = function(msg) {
  426. var $fail = $('<div class="nicorepo fail">' + msg + '</div>');
  427. $container.append($fail);
  428. autoScrollIfNeed($fail);
  429. };
  430.  
  431. // ニコレポが画面の一番下よりはみ出していたら見える位置までスクロール
  432. var autoScrollIfNeed = function($target) {
  433. var
  434. scrollTop = $('html').scrollTop(),
  435. targetOffset = $target.offset(),
  436. clientHeight = $(window).innerHeight(),
  437. clientBottom = scrollTop + clientHeight,
  438. targetBottom = targetOffset.top + $target.outerHeight();
  439.  
  440. if (targetBottom > clientBottom) {
  441. $('html').animate({
  442. scrollTop: scrollTop + $target.outerHeight()
  443. }, 500);
  444. }
  445. };
  446.  
  447. var success = function($dom, $logBody) {
  448. var $result = $('<div class="nicorepo success" />');
  449. var $img = $logBody.find('img'), $log = $logBody.find('.log');
  450. $img.each(function() {
  451. var $this = $(this), $parent = $this.parent();
  452. var lazyImg = $this.attr('data-original');
  453. if (lazyImg) {
  454. var $imageContainer = $('<div class="imageContainer"/>');
  455. $imageContainer.css('background-image', 'url(' + lazyImg + ')');
  456. $this.before($imageContainer);
  457. $this.remove();
  458. }
  459. if (window.WatchItLater) {
  460. var href = $parent.attr('href');
  461. if (href) {
  462. $parent.attr('href', href.replace('http://www.nicovideo.jp/watch/', 'http://nico.ms/'));
  463. }
  464. }
  465. });
  466. $logBody.each(function() {
  467. var $this = $(this), time = $this.find('time:first').text(), logComment = $this.find('.log-comment').text();
  468.  
  469. $this.find('.log-target-info>*:first')
  470. .before($('<span class="time">' + time + '</span>'));
  471. if (logComment) {
  472. $this.find('.log-target-info')
  473. .append($('<span class="logComment">' + logComment + '</span>'));
  474. }
  475. });
  476.  
  477. $result.append($logBody);
  478. $container.append($result);
  479. $result.scrollTop(0);
  480.  
  481. autoScrollIfNeed($result);
  482. };
  483.  
  484. return $.ajax({
  485. url: url,
  486. timeout: 30000
  487. }).then(
  488. function(resp) {
  489. var
  490. $dom = $(resp),
  491. // 欲しいのはそのユーザーの「行動」なので、
  492. // xx再生やスタンプみたいなのはいらない
  493. $logBody = $dom.find('.log:not(.log-user-video-round-number-of-view-counter):not(.log-user-action-stamp):not(.log-user-live-video-introduced)');
  494. if ($logBody.length < 1) {
  495. fail('ニコレポが存在しないか、取得に失敗しました');
  496. } else {
  497. success($dom, $logBody);
  498. }
  499. },
  500. function() {
  501. fail('ニコレポの取得に失敗しました');
  502. });
  503.  
  504. },
  505. loadFavUserList: function() {
  506. var def = new $.Deferred();
  507. // このAPIのupdate_timeが期待していた物と違ったのでボツ
  508. // create_timeとupdate_timeはどちらも同じ値が入ってるだけだった。(なんのためにあるんだ?)
  509. //
  510. $.ajax({
  511. url: 'http://www.nicovideo.jp/api/watchitem/list',
  512. timeout: 30000,
  513. complete: function(resp) {
  514. var json;
  515. try {
  516. json = JSON.parse(resp.responseText);
  517. } catch (e) {
  518. console.log('%c parse error: ', 'background: #f88', e);
  519. return def.reject('json parse error');
  520. }
  521.  
  522. if (json.status !== 'ok') {
  523. console.log('%c status error: ', 'background: #f88', json.status);
  524. return def.reject('status error', json.status);
  525. }
  526. return def.resolve(json.watchitem);
  527. },
  528. error: function(req, status, thrown) {
  529. if (status === 'parsererror') {
  530. return;
  531. }
  532. console.log('%c ajax error: ' + status, 'background: #f88', thrown);
  533. return def.reject(status);
  534. }
  535. });
  536. return def.promise();
  537. }
  538.  
  539. };
  540.  
  541.  
  542. window.SmartNicorepo.model.WatchItem = function() { this.initialize.apply(this, arguments); };
  543. window.SmartNicorepo.model.WatchItem.prototype = {
  544. initialize: function(seed) {
  545. this._seed = seed;
  546. this.itemType = seed.item_type || '1';
  547. this.itemId = seed.item_id || '';
  548. if (typeof seed.item_data === 'object') {
  549. var data = seed.item_data;
  550. this.userId = data.id;
  551. this.nickname = data.nickname;
  552. this.thumbnailUrl = data.thumbnail_url;
  553. }
  554. var now = (new Date()).getTime();
  555. this.createTime = new Date(seed.create_time ? seed.create_time * 1000 : now);
  556. this.updateTime = new Date(seed.update_time ? seed.update_time * 1000 : now);
  557. }
  558. };
  559.  
  560. window.SmartNicorepo.model.WatchItemList = function() { this.initialize.apply(this, arguments); };
  561. window.SmartNicorepo.model.WatchItemList.prototype = {
  562. initialize: function(watchItems) {
  563. this._seed = watchItems;
  564. this._items = {};
  565. this._itemArray = [];
  566. for (var i = 0, len = watchItems.length; i < len; i++) {
  567. var item = new window.SmartNicorepo.model.WatchItem(watchItems[i]);
  568. this._items[item.userId] = item;
  569. this._itemArray.push(item);
  570. }
  571. },
  572. getItem: function(userId) {
  573. return this._items[userId];
  574. },
  575. getSortedItems: function() {
  576. var result = this._itemArray.concat();
  577. result.sort(function(a, b) {
  578. return (a.updateTime < b.updateTime) ? 1 : -1;
  579. });
  580. return result;
  581. }
  582. };
  583.  
  584. window.SmartNicorepo.initialize();
  585.  
  586. }); // end of monkey
  587.  
  588. var gm = document.createElement('script');
  589. gm.id = 'smartNicorepoScript';
  590. gm.setAttribute("type", "text/javascript");
  591. gm.setAttribute("charset", "UTF-8");
  592. gm.appendChild(document.createTextNode("(" + monkey + ")(window)"));
  593. document.body.appendChild(gm);
  594.  
  595. })();