TolyanHammerKombat

Игра поймай Толяна Хамстеркомбата

  1. // ==UserScript==
  2. // @name TolyanHammerKombat
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.6
  5. // @description Игра поймай Толяна Хамстеркомбата
  6. // @author Marina Khamsterkombat
  7. // @match https://vk.com/*
  8. // @icon https://i.imgur.com/9hJJv6a.png
  9. // @grant none
  10. // @license MIT
  11. // @run-at document-idle
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Настройки скрипта
  18. const config = {
  19. targets: ["356761121", "252323336", "1035510061", "1032968837", "1042305865", "1023730354", "867502146", "744130756", "649621677", "1047382637", "894118832"],
  20. commentText: '🐹🐹🐹',
  21. notice: {
  22. text: '🚫🐹🔨 Попался, Хомяк! 🔨🐹🚫',
  23. textColor: '#71AAEB',
  24. threshold: 1.0
  25. },
  26. hammer: {
  27. image: {
  28. url: 'https://i.imgur.com/9hJJv6a.png',
  29. size: { widthPx: 50, heightPx: 50 },
  30. offset: { leftPx: 8, topPx: 40 }
  31. },
  32. sound: {
  33. url: 'https://myinstants.com/media/sounds/fnaf-12-3-freddys-nose-sound.mp3',
  34. volume: 0.1,
  35. timeoutMs: 500,
  36. },
  37. animation: { durationMs: 200 }
  38. },
  39. attribute: {
  40. name: 'data-hammerkombat',
  41. hidden: 'hidden'
  42. }
  43. };
  44.  
  45. // Создает элемент уведомления
  46. const createNotice = () => {
  47. const notice = document.createElement('div');
  48. notice.className = 'hammerkombat-notice';
  49.  
  50. const text = document.createElement('h2');
  51. text.className = 'hammerkombat-notice-text';
  52. text.textContent = config.notice.text;
  53.  
  54. notice.append(text);
  55. return notice;
  56. }
  57.  
  58. // Создает молоток в позиции клика
  59. const createHammer = (x, y) => {
  60. const { image } = config.hammer
  61.  
  62. const hammer = document.createElement('img');
  63. hammer.className = 'hammerkombat-cursor';
  64. hammer.src = image.url;
  65.  
  66. Object.assign(hammer.style, {
  67. left: `${x - image.offset.leftPx}px`,
  68. top: `${y - image.offset.topPx}px`,
  69. width: `${image.size.widthPx}px`,
  70. height: `${image.size.heightPx}px`
  71. });
  72.  
  73. document.body.append(hammer);
  74. return hammer;
  75. }
  76.  
  77. // Воспроизводит звук удара
  78. const playSound = () => {
  79. const { sound } = config.hammer
  80.  
  81. const audio = new Audio(sound.url);
  82. audio.volume = sound.volume;
  83. audio.play().catch(() => {});
  84. setTimeout(() => audio.remove(), sound.timeoutMs);
  85. }
  86.  
  87. // Настраивает обработчик клика на аватар
  88. const setupAvatarClick = (element) => {
  89. const avatar = element.querySelector('a.AvatarRich');
  90. if (!avatar) return;
  91.  
  92. avatar.style.cursor = 'crosshair';
  93. avatar.style.userSelect = 'none';
  94. avatar.removeAttribute('href');
  95.  
  96. avatar.addEventListener('click', (event) => {
  97. const hammer = createHammer(event.clientX, event.clientY);
  98. setTimeout(() => hammer.remove(), config.hammer.animation.durationMs);
  99. playSound();
  100. });
  101. }
  102.  
  103. // Проверяет автора поста/коммента
  104. const handleAuthor = (element, field) => {
  105. const authorId = element?.dataset?.[field];
  106. if (!authorId) return false;
  107. if (!config.targets.includes(authorId)) return false;
  108.  
  109. element.setAttribute(config.attribute.name, config.attribute.hidden);
  110. return true;
  111. };
  112.  
  113. // Заменяет содержание поста
  114. const replacePostContent = (post) => {
  115. const content = post.querySelector('div.wall_text');
  116. if (!content) return;
  117.  
  118. content.style.setProperty('display', 'none');
  119. content.before(createNotice());
  120. };
  121.  
  122. // Проверяет и обрабатывает пост
  123. const processPost = (post) => {
  124. if (!handleAuthor(post, 'postAuthorId')) return;
  125.  
  126. // Заменяем содержание поста
  127. replacePostContent(post);
  128.  
  129. // Настраиваем молоточек на аватарке
  130. setupAvatarClick(post);
  131. };
  132.  
  133. // Проверяет и обрабатывает комментарий
  134. const processComment = (comment) => {
  135. if (!handleAuthor(comment, 'answeringId')) return;
  136.  
  137. // Заменяем содержание комментария
  138. const replyText = comment.querySelector('div.wall_reply_text');
  139. if (replyText) replyText.textContent = config.commentText;
  140.  
  141. // Настраиваем молоточек на аватарке
  142. setupAvatarClick(comment);
  143. };
  144.  
  145. // Проверяет все посты и комментарии на странице
  146. const checkContent = () => {
  147. document.querySelectorAll(`.post:not([${config.attribute.name}])`).forEach(processPost);
  148. document.querySelectorAll(`.replies_list div:not([${config.attribute.name}])`).forEach(processComment);
  149. };
  150.  
  151. // Добавляет стили на страницу
  152. const addStyles = () => {
  153. const style = document.createElement('style');
  154. style.type = 'text/css';
  155.  
  156. style.textContent = `
  157. .hammerkombat-notice {
  158. position: relative;
  159. text-align: center;
  160. padding: 16px;
  161. margin-top: 8px;
  162. }
  163. .hammerkombat-notice-text {
  164. color: ${config.notice.textColor};
  165. font-weight: bold;
  166. text-transform: uppercase;
  167. user-select: none;
  168. }
  169. .hammerkombat-cursor {
  170. position: fixed;
  171. pointer-events: none;
  172. transform-origin: bottom right;
  173. animation: hammer-hit ${config.hammer.animation.durationMs}ms ease-out;
  174. }
  175. @keyframes hammer-hit {
  176. 0% { transform: rotate(0deg); }
  177. 50% { transform: rotate(-30deg); }
  178. 100% { transform: rotate(0deg); }
  179. }
  180. `;
  181.  
  182. document.head.append(style);
  183. }
  184.  
  185. // Инициализация скрипта
  186. function init() {
  187. addStyles();
  188. window.addEventListener('load', checkContent);
  189. new MutationObserver(checkContent).observe(document.body, { childList: true, subtree: true });
  190. }
  191.  
  192. // Запуск скрипта
  193. init();
  194. })();