BestSubscriber

Organiza tus suscripciones por juego y da like automáticamente a los vídeos que veas

  1. // ==UserScript==
  2. // @name BestSubscriber
  3. // @namespace https://greasyfork.org/es/scripts/15251-bestsubscriber
  4. // @version 0.2
  5. // @description Organiza tus suscripciones por juego y da like automáticamente a los vídeos que veas
  6. // @author DonNadie
  7. // @match https://*.youtube.com/feed/subscriptions
  8. // @match https://*.youtube.com/*
  9. // @match https://*.youtube.com/watch?v=*
  10. // @grant none
  11. // @require https://code.jquery.com/jquery-latest.js
  12. // @require https://cdn.jsdelivr.net/sortable/1.4.2/Sortable.min.js
  13. // @require https://greasyfork.org/scripts/15246-micro-templating/code/Micro%20templating.js?version=95830
  14. // @require https://greasyfork.org/scripts/1003-wait-for-key-elements/code/Wait%20for%20key%20elements.js?
  15. version=49342
  16. // @noframes
  17. // ==/UserScript==
  18. /* jshint -W097 */
  19. 'use strict';
  20.  
  21. var BestSubscriber = function ()
  22. {
  23. var gameList,
  24. debugMode = false,
  25. skipWords = [
  26. 'gameplay',
  27. 'episode',
  28. 'episodio',
  29. 'ps4',
  30. 'xbox 360',
  31. 'xbox one',
  32. 'xbox',
  33. 'directo',
  34. ];
  35.  
  36. String.prototype.capitalizeFirstLetter = function() {
  37. return this.charAt(0).toUpperCase() + this.slice(1);
  38. };
  39. String.prototype.friendly = function () {
  40. var str = this;
  41. if (typeof max == "undefined") max = 32;
  42. var a_chars = new Array(
  43. new Array("a",/[áàâãªÁÀÂÃ]/g),
  44. new Array("e",/[éèêÉÈÊ]/g),
  45. new Array("i",/[íìîÍÌÎ]/g),
  46. new Array("o",/[òóôõºÓÒÔÕ]/g),
  47. new Array("u",/[úùûÚÙÛ]/g),
  48. new Array("c",/[çÇ]/g),
  49. new Array("n",/[Ññ]/g)
  50. );
  51. // Replace vowel with accent without them
  52. for(var i=0;i<a_chars.length;i++)
  53. str = str.replace(a_chars[i][1],a_chars[i][0]);
  54. // first replace whitespace by -, second remove repeated - by just one, third turn in low case the chars,
  55. // fourth delete all chars which are not between a-z or 0-9, fifth trim the string and
  56. // the last step truncate the string to 32 chars
  57. return str.replace(/\s+/g,'-').toLowerCase().replace(/[^a-z0-9\-]/g, '').replace(/\-{2,}/g,'-').replace(/(^\s*)|(\s*$)/g, '').substr(0, max);
  58. };
  59.  
  60. var init = function ()
  61. {
  62. var k,
  63. game;
  64.  
  65. gameList = getConfig();
  66. setupPanel();
  67.  
  68. // detect AJAX navigation
  69. var observer = new MutationObserver(function(mutations) {
  70. // We observe the title, so our key elements in body may not be loaded yet
  71. setTimeout(function () {
  72. firePageActions();
  73. }, 100);
  74. });
  75.  
  76. observer.observe(document.querySelector('title'), { childList: true});
  77.  
  78. firePageActions();
  79. }
  80.  
  81. var firePageActions = function ()
  82. {
  83. if (location.pathname.indexOf("feed/subscriptions") !== -1 && $('.best-subscriber-section').length == 0) {
  84. setupSubscriptionFeed();
  85. }
  86. else if (location.pathname.indexOf("watch") !== -1)
  87. {
  88. log("Inside a video");
  89. if ($(".video-stream.html5-main-video").length == 0) {
  90. waitForKeyElements (".video-stream.html5-main-video", function () {
  91. setTimeout(autoLike, 750);
  92. });
  93. } else {
  94. setTimeout(autoLike, 750);
  95. }
  96. }
  97. }
  98.  
  99. var getConfig = function ()
  100. {
  101. var config = localStorage.bestSubscriber;
  102.  
  103. if (config === undefined) {
  104. // default config
  105. return [{
  106. name: 'gta',
  107. display: false,
  108. },
  109. {
  110. name: 'hurtworld',
  111. display: true,
  112. },
  113. {
  114. name: 'h1z1',
  115. display: true,
  116. },
  117. {
  118. name: 'rust',
  119. display: true,
  120. }];
  121. }
  122.  
  123. return JSON.parse(config);
  124. }
  125.  
  126. var setConfig = function (config)
  127. {
  128. if (config === undefined || !Array.isArray(config)) {
  129. return;
  130. }
  131.  
  132. localStorage.bestSubscriber = JSON.stringify(config);
  133. }
  134.  
  135. var updateGame = function (name, key, val)
  136. {
  137. var k,
  138. list = getConfig();
  139.  
  140. for (k in list) {
  141. if (list[k].name.friendly() == name) {
  142. list[k][key] = val;
  143. setConfig(list);
  144. break;
  145. }
  146. }
  147. }
  148.  
  149. var setupPanel = function ()
  150. {
  151. $('#yt-masthead-user').append('<button class="yt-uix-button yt-uix-button-default" data-action="toggle-config-panel" style="margin-left: 20px">BestSubcriber</button>');
  152.  
  153. $('#masthead-positioner').prepend(parseTemplate(function () {/*
  154. <div id="best-subscriber-config">
  155. <h3>Orden:</h3>
  156. <ul>
  157. <%for (var i=0; i<list.length; i++){ %>
  158. <li><span data-name><%=list[i].name%></span> <span class="bs-delete-button" data-action="delete-game" title="Quitar de la lista">x</span></li>
  159. <% } %>
  160. </ul>
  161. <div>
  162. <label>
  163. <input type="text" name="new-game-name" placeholder="GTA 5">
  164. </label>
  165. <button data-action="add-game" class="add-button" title="Añadir a la lista">+</button>
  166. </div>
  167. <button data-action="save-config" class="yt-uix-button yt-uix-button-default bs-save-button">Guardar cambios</button>
  168. </div>
  169. <style>
  170. #best-subscriber-config {
  171. display: none;
  172. position: fixed;
  173. background: white;
  174. padding: 10px;
  175. border: 1px solid;
  176. left: 89%;
  177. top: 44%;
  178. z-index: 1999999999; // set by Youtube..
  179. }
  180. #best-subscriber-config li {
  181. cursor: all-scroll;
  182. border: 1px dashed;
  183. padding: 5px;
  184. }
  185. #best-subscriber-config li:hover {
  186. background: #F8F8F8;
  187. }
  188. #best-subscriber-config .bs-save-button {
  189. margin-top: 10px;
  190. float: right;
  191. }
  192. #best-subscriber-config .bs-delete-button {
  193. font-weight: bold;
  194. float: right;
  195. cursor: pointer;
  196. color: red;
  197. }
  198. #best-subscriber-config input {
  199. margin-top: 5px;
  200. }
  201. #best-subscriber-config .add-button {
  202. color: green;
  203. font-weight: bold;
  204. cursor: pointer;
  205. }
  206. </style>
  207. */}, {list: gameList.slice().reverse()})); // revert the arary to display it in the right order in config panel
  208.  
  209. //$("#best-subscriber-config ul").sortable();
  210. Sortable.create($("#best-subscriber-config ul")[0]);
  211.  
  212. $('[data-action="toggle-config-panel"]').on("click", function () {
  213. $("#best-subscriber-config").toggle();
  214. });
  215.  
  216. $('input[name="new-game-name"]').on("keydown", function(e) {
  217. if (e.keyCode == 13) {
  218. e.preventDefault();
  219.  
  220. addGame();
  221. }
  222. });
  223.  
  224. $('[data-action="add-game"]').on("click", addGame);
  225.  
  226. $('[data-action="delete-game"]').on("click", function () {
  227. var game = $(this).closest("[data-name]").text();
  228.  
  229. deleteGame(game);
  230. $(this).closest("li").remove();
  231. });
  232.  
  233. $('[data-action="save-config"]').on("click", function () {
  234. var k,
  235. list = [],
  236. tmpList = {},
  237. oldList = getConfig();
  238.  
  239. for (k in oldList) {
  240. tmpList[oldList[k].name] = oldList[k];
  241. }
  242.  
  243. $('#best-subscriber-config li span[data-name]').each(function () {
  244. var name = $(this).text();
  245. list.push(tmpList[name]);
  246. });
  247. list.reverse();
  248.  
  249. setConfig(list);
  250. location.reload();
  251. });
  252. }
  253. var setupSubscriptionFeed = function () {
  254. // create the categories (games)
  255. for (k in gameList)
  256. {
  257. game = gameList[k];
  258. $('#browse-items-primary ol.section-list').prepend(parseTemplate(function () {
  259. /*
  260. <li id="game-<%=game.name.friendly() %>" class="best-subscriber-section">
  261. <ol class="item-section">
  262. <li>
  263. <div class="feed-item-container browse-list-item-container yt-section-hover-container compact-shelf shelf-item branded-page-box clearfix">
  264. <div class="feed-item-dismissable">
  265. <div class="shelf-title-table">
  266. <div class="shelf-title-row">
  267. <h2 class="branded-page-module-title shelf-title-cell"><span class="branded-page-module-title-text"><%=game.name.capitalizeFirstLetter()%></span></h2>
  268. <div class="menu-container shelf-title-cell">
  269. <div class="yt-uix-menu-container feed-item-action-menu">
  270. <ul class="yt-uix-menu-top-level-button-container">
  271. <li class="yt-uix-menu-top-level-button yt-uix-menu-top-level-flow-button">
  272. <button data-action="toggle" data-game="<%=game.name.friendly()%>" class="yt-uix-button yt-uix-button-size-default yt-uix-button-opacity yt-uix-button-empty yt-uix-button-has-icon yt-uix-tooltip" onclick=";return false;" title="Mostrar/Ocultar">
  273. <span class="yt-uix-button-icon-wrapper">
  274. <span class="yt-uix-button-icon yt-sprite yt-uix-expander-arrow" <% if (game.display){ %>style="transform: rotate(180deg);"<% } %>>
  275. </span>
  276. </span>
  277. </button>
  278. </li>
  279. </ul>
  280. </div>
  281. </div>
  282. </div>
  283. </div>
  284. <div class="multirow-shelf">
  285. <ul class="shelf-content" data-section="videos" <% if (!game.display){ %>style="display:none"<% } %>>
  286.  
  287. </ul>
  288. </div>
  289. </div>
  290. <div class="feed-item-dismissal-notices"></div>
  291. </div>
  292. </li>
  293. </ol>
  294. </li>
  295. */}, { game : game }));
  296. }
  297.  
  298.  
  299. // move the videos
  300. $('.yt-shelf-grid-item').each(function () {
  301. var regex,
  302. title = $(".yt-lockup-title a", this).text();
  303.  
  304. for (k in gameList)
  305. {
  306. game = gameList[k];
  307. regex = new RegExp(game.name, "i");
  308.  
  309.  
  310. if (title.search(regex) !== -1) {
  311. $('#game-' + game.name.friendly() + ' [data-section="videos"]').append($(this));
  312. }
  313. }
  314. });
  315.  
  316. // show/hide categories
  317. $('[data-action="toggle"]').on("click", function () {
  318. var game = $(this).data("game"),
  319. isVisible = false,
  320. list = getConfig(),
  321. $videosContainer = $('#game-' + game + ' [data-section="videos"]');
  322.  
  323. $videosContainer.toggle();
  324. isVisible = $videosContainer.is(":visible")
  325.  
  326. if (isVisible) {
  327. $('.yt-uix-expander-arrow', this).css("transform", "rotate(180deg)");
  328. } else {
  329. $('.yt-uix-expander-arrow', this).css("transform", "");
  330. }
  331.  
  332. updateGame(game, "display", isVisible);
  333. });
  334. }
  335.  
  336. var autoLike = function ()
  337. {
  338. log("Liking");
  339. var $likeButton = $('.like-button-renderer-like-button-unclicked'),
  340. eventObject;
  341.  
  342. // is already liked or user is not subscribed
  343. if (!$likeButton.is(":visible") || !$('.yt-uix-button-subscribed-branded').is(":visible")) {
  344. log("not clicking");
  345. return;
  346. }
  347.  
  348. eventObject = document.createEvent('MouseEvents');
  349.  
  350. eventObject.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  351. $likeButton[0].dispatchEvent(eventObject);
  352. }
  353.  
  354. var deleteGame = function (name)
  355. {
  356. var k,
  357. list = getConfig();
  358.  
  359. for (k in list) {
  360. if (list[k].name == name) {
  361. list = list.splice(k, 1);
  362. break;
  363. }
  364. }
  365. setConfig(list);
  366. }
  367.  
  368. var addGame = function ()
  369. {
  370. var game = $('input[name="new-game-name"]').val(),
  371. list = getConfig();
  372.  
  373. if (game.length < 2) {
  374. return;
  375. }
  376.  
  377. $('input[name="new-game-name"]').val("");
  378. $('#best-subscriber-config ul').append('<li><span data-name>' + game + '</span> <span class="bs-delete-button" data-action="delete-game" title="Quitar de la lista">x</span></li>');
  379.  
  380. list.push({
  381. name: game,
  382. display: true
  383. });
  384.  
  385. setConfig(list);
  386. }
  387.  
  388. var parseTemplate = function(f, data)
  389. {
  390. var html = f.toString().replace(/^[^\/]+\/\*!?/, '').replace(/\*\/[^\/]+$/, '');
  391.  
  392. if (typeof data != 'undefined') {
  393. return _tmpl(html, data);
  394. } else {
  395. return html;
  396. }
  397. }
  398.  
  399. // Not finished, suggests game names for the list for the first time
  400. var suggestGameNames = function ()
  401. {
  402. $('.yt-shelf-grid-item').each(function () {
  403. var title = $(".yt-lockup-title a", this).text(),
  404. publisher = $(".yt-lockup-byline a", this).text();
  405.  
  406. console.log(title.replace(publisher, ''));
  407. });
  408. }
  409.  
  410. var log = function () {
  411. if (debugMode) {
  412. console.log.apply(console, arguments);
  413. }
  414. }
  415.  
  416. return {
  417. init : init,
  418. };
  419. }();
  420.  
  421.  
  422. $(document).ready(function() {
  423. BestSubscriber.init();
  424. });