Nicorenizer

動画クリックで一時停止/再開 ダブルクリックでフルスクリーン切換え

  1. // ==UserScript==
  2. // @name Nicorenizer
  3. // @namespace https://github.com/segabito/
  4. // @description 動画クリックで一時停止/再開 ダブルクリックでフルスクリーン切換え
  5. // @include http://www.nicovideo.jp/watch/*
  6. // @version 0.2.1
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. // TODO:
  11. // ダブルクリック時にフルスクリーンにする設定を無効・ブラウザ全体・モニター全体から選べるようにする
  12.  
  13. // ver 0.1.2
  14. // - Watch It Laterと併用時、動画選択画面でのダブルクリックでフルスクリーンにならないのを修正
  15.  
  16. // ver 0.1.0 最初のバージョン
  17.  
  18. (function() {
  19. var monkey = (function(){
  20. 'use strict';
  21.  
  22.  
  23. if (!window.WatchApp || !window.WatchJsApi) {
  24. return;
  25. }
  26.  
  27. var FullScreen = {
  28. now: function() {
  29. if (document.fullScreenElement || document.mozFullScreen || document.webkitIsFullScreen) {
  30. return true;
  31. }
  32. return false;
  33. },
  34. request: function(target) {
  35. var elm = typeof target === 'string' ? document.getElementById(target) : target;
  36. if (!elm) { return; }
  37. if (elm.requestFullScreen) {
  38. elm.requestFullScreen();
  39. } else if (elm.webkitRequestFullScreen) {
  40. elm.webkitRequestFullScreen();
  41. } else if (elm.mozRequestFullScreen) {
  42. elm.mozRequestFullScreen();
  43. }
  44. },
  45. cancel: function() {
  46. if (!this.now()) { return; }
  47.  
  48. if (document.cancelFullScreen) {
  49. document.cancelFullScreen();
  50. } else if (document.webkitCancelFullScreen) {
  51. document.webkitCancelFullScreen();
  52. } else if (document.mozCancelFullScreen) {
  53. document.mozCancelFullScreen();
  54. }
  55. }
  56. };
  57.  
  58.  
  59. window.Nicorenizer = {};
  60.  
  61. window.WatchApp.mixin(window.Nicorenizer, {
  62. initialize: function() {
  63. this._watchInfoModel = require('watchapp/model/WatchInfoModel').getInstance();//window.WatchApp.ns.init.CommonModelInitializer.watchInfoModel;
  64. var PlayerInitializer = require('watchapp/init/PlayerInitializer');
  65. this._playerAreaConnector = PlayerInitializer.playerAreaConnector;
  66. this._nicoPlayerConnector = PlayerInitializer.nicoPlayerConnector;
  67. this._playerScreenMode = PlayerInitializer.playerScreenMode;
  68. this._playlist = require('watchapp/init/PlaylistInitializer').playlist;
  69. this._videoExplorer = require('watchapp/init/VideoExplorerInitializer').videoExplorer;
  70. this._watchInfoModel = require('watchapp/model/WatchInfoModel').getInstance();
  71.  
  72. this._vastStatus = this._nicoPlayerConnector.vastStatus;
  73.  
  74. this.initializeUserConfig();
  75. this.initializeSettingPanel();
  76. this.initializeShield();
  77. this.initializePlayerEvent();
  78.  
  79. this.initializePlayerApp();
  80.  
  81. this.initializeCss();
  82. },
  83. addStyle: function(styles, id) {
  84. var elm = document.createElement('style');
  85. elm.type = 'text/css';
  86. if (id) { elm.id = id; }
  87.  
  88. var text = styles.toString();
  89. text = document.createTextNode(text);
  90. elm.appendChild(text);
  91. var head = document.getElementsByTagName('head');
  92. head = head[0];
  93. head.appendChild(elm);
  94. return elm;
  95. },
  96. initializeCss: function() {
  97. var __css__ = (function() {/*
  98.  
  99. #nicorenaiShield {
  100. display: none;
  101. position: absolute;
  102. top: 0;
  103. left: 0;
  104. right: 0;
  105. bottom: 85px;
  106. z-index: 9950;
  107. cursor: none;
  108. }
  109.  
  110. #nicorenaiShield.disable, #nicorenaiShield.vast, #nicorenaiShield.disableTemp {
  111. display: none !important;
  112. }
  113.  
  114. #nicorenaiShield.debug {
  115. background: red; opacity: 0.5;
  116. }
  117.  
  118. #nicorenaiShield.initialized {
  119. display: block;
  120. }
  121.  
  122. #nicorenaiShield.showCursor {
  123. cursor: crosshair; {* 現在有効である事をわかりやすくするためにcrosshair。本当はオリジナルのカーソルを用意したいところ *}
  124. }
  125.  
  126. body.setting_panel #nicorenaiShield, body.videoErrorOccurred #nicorenaiShield,
  127. body.setting_panel #nicorenaiShield, body.videoErrorOccurred #nicorenaiShieldToggle {
  128. display: none;
  129. }
  130.  
  131. body.videoExplorer #content:not(.w_adjusted) #nicorenaiShield {
  132. {* 動画選択画面ではクリックで解除させるために邪魔なので消す *}
  133. {* ただしWatch It Lterの検索モードでは有効にする *}
  134. display: none;
  135. }
  136.  
  137. #nicorenaiShieldToggle {
  138. position: absolute;
  139. z-index: 9951;
  140. top: 10px;
  141. left: 10px;
  142. border-color: blue;
  143. opacity: 0;
  144. cursor: pointer;
  145. transition: opacity 0.5s ease;
  146. padding: 4px 8px;
  147. }
  148.  
  149. #nicorenaiShieldToggle.disable, #nicorenaiShieldToggle.disableTemp {
  150. border-color: black;
  151. }
  152.  
  153. #nicorenaiShieldToggle.debug {
  154. opacity: 1 !important;
  155. }
  156.  
  157. #nicorenaiShieldToggle.initialized:hover, #nicorenaiShieldToggle.show, #nicorenaiShieldToggle.disableTemp {
  158. opacity: 1;
  159. transition: none;
  160. }
  161.  
  162. #nicorenaiShieldToggle:after {
  163. content: ':ON';
  164. }
  165. #nicorenaiShieldToggle.disable:after, #nicorenaiShieldToggle.disableTemp:after {
  166. content: ':OFF';
  167. }
  168.  
  169. #nicorenizerSettingPanel {
  170. position: fixed;
  171. bottom: 2000px; right: 8px;
  172. z-index: -1;
  173. width: 500px;
  174. background: #f0f0f0; border: 1px solid black;
  175. padding: 8px;
  176. transition: bottom 0.4s ease-out;
  177. }
  178. #nicorenizerSettingPanel.open {
  179. display: block;
  180. bottom: 8px;
  181. box-shadow: 0 0 8px black;
  182. z-index: 10000;
  183. }
  184. #nicorenizerSettingPanel .close {
  185. position: absolute;
  186. cursor: pointer;
  187. right: 8px; top: 8px;
  188. }
  189. #nicorenizerSettingPanel .panelInner {
  190. background: #fff;
  191. border: 1px inset;
  192. padding: 8px;
  193. min-height: 300px;
  194. overflow-y: scroll;
  195. max-height: 500px;
  196. }
  197. #nicorenizerSettingPanel .panelInner .item {
  198. border-bottom: 1px dotted #888;
  199. margin-bottom: 8px;
  200. padding-bottom: 8px;
  201. }
  202. #nicorenizerSettingPanel .panelInner .item:hover {
  203. background: #eef;
  204. }
  205. #nicorenizerSettingPanel .windowTitle {
  206. font-size: 150%;
  207. }
  208. #nicorenizerSettingPanel .itemTitle {
  209. }
  210. #nicorenizerSettingPanel label {
  211.  
  212. }
  213. #nicorenizerSettingPanel small {
  214. color: #666;
  215. }
  216. #nicorenizerSettingPanel .expert {
  217. margin: 32px 0 16px;
  218. font-size: 150%;
  219. background: #ccc;
  220. }
  221.  
  222. {* Chromeの不具合対策 *}
  223. body.full_with_browser {
  224. width: 100%;
  225. }
  226.  
  227. #nicorenaiShield .previous,
  228. #nicorenaiShield .next {
  229. position: absolute;
  230. display: none;
  231. top: 0;
  232. width: 48px;
  233. height: 100%;
  234. opacity: 0.8;
  235. }
  236.  
  237. #nicorenaiShield.hasPrevious .previous,
  238. #nicorenaiShield.hasNext .next {
  239. display: block;
  240. }
  241.  
  242.  
  243. #nicorenaiShield .previous .button,
  244. #nicorenaiShield .next .button{
  245. position: absolute;
  246. top: 0;
  247. bottom: 0;
  248. left: 0;
  249. width: 40px;
  250. height: 64px;
  251. margin: auto;
  252. box-sizing: border-box;
  253. font-size: 28px;
  254. cursor: pointer;
  255. background-color: #333;
  256. color: #fff;
  257. border: 2px solid #ccc;
  258. opacity: 0;
  259. transition: opacity 0.4s linear;
  260. user-select: none;
  261. }
  262.  
  263. #nicorenaiShield .previous:hover .button,
  264. #nicorenaiShield .next:hover .button {
  265. opacity: 1;
  266. }
  267.  
  268. #nicorenaiShield .previous .button:active,
  269. #nicorenaiShield .next .button:active {
  270. background: #888;
  271. }
  272.  
  273.  
  274. #nicorenaiShield .previous {
  275. left: 0;
  276. }
  277. #nicorenaiShield .next {
  278. right: 0;
  279. }
  280.  
  281. #nicorenaiShield .previous .button {
  282. }
  283. #nicorenaiShield .next .button {
  284. right: 0;
  285. left: auto;
  286. }
  287.  
  288.  
  289. body.full_with_browser #nicoplayerContainer #nicoplayerContainerInner {
  290. margin-top: 0 !important;
  291. margin-bottom: -76px !important;
  292. }
  293. body.full_with_browser.showControl #nicoplayerContainer #nicoplayerContainerInner {
  294. margin-top: 0 !important;
  295. margin-bottom: 0 !important;
  296. }
  297.  
  298.  
  299.  
  300. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  301.  
  302. this.addStyle(__css__, 'NicorenizerCss');
  303. },
  304. initializeUserConfig: function() {
  305. var prefix = 'Nicorenizer_';
  306. var conf = {
  307. fullScreenType: 'browser', // none, browser, monitor
  308. togglePlay: true
  309. };
  310. this.config = {
  311. get: function(key) {
  312. try {
  313. if (window.localStorage.hasOwnProperty(prefix + key)) {
  314. return JSON.parse(window.localStorage.getItem(prefix + key));
  315. }
  316. return conf[key];
  317. } catch (e) {
  318. return conf[key];
  319. }
  320. },
  321. set: function(key, value) {
  322. window.localStorage.setItem(prefix + key, JSON.stringify(value));
  323. }
  324. };
  325. },
  326. initializeSettingPanel: function() {
  327. var $menu = $('<li class="nicorenizerSettingMenu"><a href="javascript:;" title="Nicorenizerの設定変更">Nicorenizer設定</a></li>');
  328. var $panel = $('<div id="nicorenizerSettingPanel" />');//.addClass('open');
  329. var $button = $('<button class="toggleSetting playerBottomButton">設定</botton>');
  330.  
  331. $button.on('click', function(e) {
  332. e.stopPropagation(); e.preventDefault();
  333. $panel.toggleClass('open');
  334. });
  335.  
  336. var config = this.config;
  337. $menu.find('a').on('click', function() { $panel.toggleClass('open'); });
  338.  
  339. var __tpl__ = (function() {/*
  340. <div class="panelHeader">
  341. <h1 class="windowTitle">Nicorenizerの設定</h1>
  342. <button class="close" title="閉じる">×</button>
  343. </div>
  344. <div class="panelInner">
  345. <div class="item" data-setting-name="togglePlay" data-menu-type="radio">
  346. <h3 class="itemTitle">画面クリックで一時停止/再生</h3>
  347. <label><input type="radio" value="true" >する</label>
  348. <label><input type="radio" value="false">しない</label>
  349. </div>
  350.  
  351. <div class="item" data-setting-name="fullScreenType" data-menu-type="radio">
  352. <h3 class="itemTitle">ダブルクリック時のフルスクリーン</h3>
  353. <label><input type="radio" value="&quot;browser&quot;" >ブラウザ全体</label>
  354. <label><input type="radio" value="&quot;monitor&quot;">モニター全体</label>
  355. <label><input type="radio" value="&quot;none&quot;">切り換えない</label>
  356. </div>
  357. </div>
  358. */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1].replace(/\{\*/g, '/*').replace(/\*\}/g, '*/');
  359. $panel.html(__tpl__);
  360. $panel.find('.item').on('click', function(/* e */) {
  361. var $this = $(this);
  362. var settingName = $this.attr('data-setting-name');
  363. var value = JSON.parse($this.find('input:checked').val());
  364. console.log('seting-name', settingName, 'value', value);
  365. config.set(settingName, value);
  366. }).each(function(/* e */) {
  367. var $this = $(this);
  368. var settingName = $this.attr('data-setting-name');
  369. var value = config.get(settingName);
  370. $this.addClass(settingName);
  371. $this.find('input').attr('name', settingName).val([JSON.stringify(value)]);
  372. });
  373. $panel.find('.close').click(function() {
  374. $panel.removeClass('open');
  375. });
  376.  
  377.  
  378. $('#playerAlignmentArea').append($button);
  379. $('#siteHeaderRightMenuFix').after($menu);
  380. $('body').append($panel);
  381.  
  382.  
  383. },
  384. _initializeDom: function() {
  385. var $prev = $('<div class="previous"><button class="button" data-click-command="previous" title="前の動画">&lt;</button></div>');
  386. var $next = $('<div class="next"><button class="button" data-click-command="next" title="次の動画">&gt;</button></div>');
  387. this._$shield = $('<div id="nicorenaiShield" class="hasPrevious hasNext"></div>');
  388. this._$toggle = $('<button id="nicorenaiShieldToggle" title="クリックで無効化ON/OFF">シールド</botton>');
  389. this._$shield.append($prev).append($next);
  390.  
  391. $('#external_nicoplayer').after(this._$shield).after(this._$toggle);
  392. return this._$shield;
  393. },
  394. initializeShield: function() {
  395. var videoExplorer = this._videoExplorer;
  396. var nicoPlayer = $("#external_nicoplayer")[0];
  397. var toggleMonitorFull = $.proxy(this.toggleMonitorFull, this);
  398. var toggleDisable = $.proxy(this.toggleDisable, this);
  399. var execCommand = $.proxy(this.execCommand, this);
  400. var config = this.config;
  401.  
  402. this._initializeDom();
  403. var $shield = this._$shield;
  404. var $toggle = this._$toggle;
  405.  
  406. var click = function(e) {
  407. // TODO: YouTubeみたいに中央に停止/再生マーク出す?
  408. if (e.button !== 0) { return; }
  409. if (!config.get('togglePlay')) { return; }
  410. var $target = $(e.target);
  411. var cmd = $target.attr('data-click-command');
  412. if (cmd) {
  413. e.preventDefault();
  414. e.stopPropagation();
  415. execCommand(cmd);
  416. return;
  417. }
  418. var status = nicoPlayer.ext_getStatus();
  419. if (status === 'playing') {
  420. execCommand('stop');
  421. } else {
  422. execCommand('play');
  423. }
  424. };
  425.  
  426. var dblclick = function(e) {
  427. if (e.button !== 0) return;
  428. e.preventDefault(); e.stopPropagation();
  429. var fullScreenType = config.get('fullScreenType');
  430. if (fullScreenType === 'none') { return; }
  431.  
  432. if (videoExplorer.isOpen()) {
  433. videoExplorer.changeState(false);
  434. if (fullScreenType === 'monitor') {
  435. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  436. toggleMonitorFull(true);
  437. } else {
  438. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  439. }
  440. } else
  441. if ($('body').hasClass('full_with_browser')) {
  442. window.WatchJsApi.player.changePlayerScreenMode('notFull');
  443. toggleMonitorFull(false);
  444. } else {
  445. if (fullScreenType === 'monitor') {
  446. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  447. toggleMonitorFull(true);
  448. } else {
  449. window.WatchJsApi.player.changePlayerScreenMode('browserFull');
  450. }
  451. }
  452. };
  453.  
  454. var cursorHideTimer = null;
  455. var mousemove = function() {
  456. $shield.addClass('showCursor');
  457. if (cursorHideTimer) {
  458. window.clearTimeout(cursorHideTimer);
  459. cursorHideTimer = null;
  460. }
  461. cursorHideTimer = window.setTimeout(function() {
  462. $shield.off('mousemove', mousemove);
  463. $shield.removeClass('showCursor');
  464. window.setTimeout(function() { $shield.on('mousemove', mousemove); }, 500);
  465. }, 1500);
  466. };
  467.  
  468. var mousedown = function(e) {
  469. if (e.button === 0) return;
  470. // 左ボタン以外でクリックされたら5秒間だけシールドを解除するよ
  471. e.preventDefault(); e.stopPropagation();
  472.  
  473. $shield.addClass('disableTemp');
  474. $toggle.addClass('disableTemp');
  475.  
  476. $toggle
  477. .css('opacity', 1)
  478. .animate({'opacity': 0.3}, 5000, function() { $toggle.css('opacity', ''); });
  479. window.setTimeout(function() {
  480. $toggle.removeClass('disableTemp');
  481. $shield.removeClass('disableTemp');
  482. $toggle.css('opacity', '');
  483. }, 5000);
  484. };
  485.  
  486. $shield
  487. .on('click' , click)
  488. .on('dblclick', dblclick)
  489. .on('mousedown', mousedown)
  490. .on('mousemove', mousemove);
  491.  
  492. $toggle
  493. .on('click', toggleDisable);
  494.  
  495. },
  496. _initializeScreenMode: function() {
  497. var playerScreenMode = this._playerScreenMode;
  498. var lastScreenMode = '';
  499. var $nicoplayerContainer = $('#nicoplayerContainer');
  500. var toggleMonitorFull = $.proxy(this.toggleMonitorFull, this);
  501.  
  502. var onPlayerContainerMouseMove = _.debounce(function(e) {
  503. var bottom = $nicoplayerContainer.innerHeight() - e.clientY;
  504. $('body').toggleClass('showControl', bottom < 100);
  505. }, 100);
  506.  
  507. var onScreenModeChange = function(sc) {
  508. var mode = sc.mode;
  509. if (lastScreenMode === 'browserFull' && mode !== 'browserFull') {
  510. toggleMonitorFull(false);
  511. }
  512. lastScreenMode = mode;
  513.  
  514. if (mode === 'browserFull') {
  515. $nicoplayerContainer.on('mousemove', onPlayerContainerMouseMove);
  516. } else {
  517. $nicoplayerContainer.off('mousemove', onPlayerContainerMouseMove);
  518. }
  519. };
  520.  
  521. playerScreenMode.addEventListener('change', onScreenModeChange);
  522. },
  523. initializePlayerEvent: function() {
  524. var playerAreaConnector = this._playerAreaConnector;
  525. var watchInfoModel = this._watchInfoModel;
  526. var playlist = this._playlist;
  527. var $shield = this._$shield;
  528. var $toggle = this._$toggle;
  529. var toggleDisable = $.proxy(this.toggleDisable, this);
  530.  
  531. this._initializeScreenMode();
  532.  
  533. // 最初に再生開始されるまでは表示しない。 ローカルストレージ~が出たときにクリックできるようにするため。
  534. // でも自動再生にしてると詰む。
  535. playerAreaConnector.addEventListener(
  536. 'onVideoStarted', function() {
  537. $shield.addClass('initialized');
  538. $toggle.addClass('initialized');
  539. toggleDisable(false);
  540. }
  541. );
  542.  
  543. // 再生後メニューがクリックできないのも困るので無効化する
  544. playerAreaConnector.addEventListener(
  545. 'onVideoEnded', function() {
  546. toggleDisable(true, true);
  547. }
  548. );
  549.  
  550. playerAreaConnector.addEventListener(
  551. 'onVideoSeeked', function(vpos/*, b, c*/) {
  552. // もう一度再生する場合など
  553. if (parseInt(vpos, 10) === 0) toggleDisable(false);
  554. }
  555. );
  556.  
  557. var hasPreviousVideo = function() {
  558. console.log('%chasPreviousVideo', 'background: cyan;', playlist.getPlayingIndex(), 0);
  559. return playlist.getPlayingIndex() > 0;
  560. };
  561.  
  562. var hasNextVideo = function() {
  563. console.log('%chasNextVideo', 'background: cyan;', playlist.getPlayingIndex(), playlist.getItems().length);
  564. return playlist.getPlayingIndex() < playlist.getItems().length - 1;
  565. };
  566.  
  567. var onWatchInfoModelReset = function() {
  568. $shield
  569. .toggleClass('hasPrevious', hasPreviousVideo())
  570. .toggleClass('hasNext', hasNextVideo());
  571. };
  572. watchInfoModel.addEventListener('reset', onWatchInfoModelReset);
  573. if (watchInfoModel.initialized) {
  574. window.setTimeout(function() { onWatchInfoModelReset(); }, 0);
  575. }
  576.  
  577. // 動画広告まわり
  578. var vastStatus = this._vastStatus;
  579. vastStatus.addEventListener('linearStart', function() {
  580. $shield.addClass('vast');
  581. });
  582. vastStatus.addEventListener('linearEnd', function() {
  583. $shield.removeClass('vast');
  584. });
  585.  
  586. },
  587. initializePlayerApp: function() {
  588. // 実装が漏れててエラーが出てるっぽいのを修正
  589. // フルスクリーン時に動画プレイヤー以外にフォーカスがある時に出る
  590. var np = window.PlayerApp.ns.player.Nicoplayer.getInstance();
  591. var ep = $('#external_nicoplayer')[0];
  592. if (!np.ext_getVolume) {
  593. np.ext_getVolume = function() { return ep.ext_getVolume(); };
  594. }
  595. if (!np.ext_setVolume) {
  596. np.ext_setVolume = function(v) { ep.ext_setVolume(v); };
  597. }
  598. if (!np.ext_getStatus) {
  599. np.ext_getStatus = function() { return ep.ext_getStatus(); };
  600. }
  601. },
  602. toggleMonitorFull: function(v) {
  603. var now = FullScreen.now();
  604. if (now || v === false) {
  605. FullScreen.cancel();
  606. } else if (!now || v === true) {
  607. FullScreen.request(document.body);
  608. }
  609. },
  610. toggleDisable: function(f, showButtonTemporary) {
  611. var $shield = this._$shield;
  612. var $toggle = this._$toggle;
  613.  
  614. var isDisable = $toggle.toggleClass('disable', f).hasClass('disable');
  615. $shield.toggleClass('disable', isDisable);
  616.  
  617. if (showButtonTemporary) { // 状態が変わった事を通知するために一時的に表示する
  618. $toggle.addClass('show');
  619. window.setTimeout(function() { $toggle.removeClass('show'); }, 2000);
  620. }
  621. },
  622. execCommand: function(cmd) {
  623. var nicoPlayerConnector = this._nicoPlayerConnector;
  624. console.log('%cexec command: %s', 'background: cyan;', cmd);
  625. switch (cmd) {
  626. case 'previous':
  627. nicoPlayerConnector.playPreviousVideo();
  628. break;
  629. case 'next':
  630. nicoPlayerConnector.playNextVideo();
  631. break;
  632. case 'stop':
  633. nicoPlayerConnector.stopVideo();
  634. break;
  635. case 'play':
  636. nicoPlayerConnector.playVideo();
  637. break;
  638. default:
  639. break;
  640. }
  641. }
  642. });
  643.  
  644. if (window.PlayerApp) {
  645. (function() {
  646. var watchInfoModel = require('watchapp/model/WatchInfoModel').getInstance();
  647. if (watchInfoModel.initialized) {
  648. window.Nicorenizer.initialize();
  649. } else {
  650. var onReset = function() {
  651. watchInfoModel.removeEventListener('reset', onReset);
  652. window.setTimeout(function() {
  653. watchInfoModel.removeEventListener('reset', onReset);
  654. window.Nicorenizer.initialize();
  655. }, 0);
  656. };
  657. watchInfoModel.addEventListener('reset', onReset);
  658. }
  659. })();
  660. }
  661.  
  662.  
  663. });
  664.  
  665. var script = document.createElement("script");
  666. script.id = "NicorenizerLoader";
  667. script.setAttribute("type", "text/javascript");
  668. script.setAttribute("charset", "UTF-8");
  669. script.appendChild(document.createTextNode("(" + monkey + ")()"));
  670. document.body.appendChild(script);
  671.  
  672. })();