Input Text Logger

Track continuous text input on websites and save to Tampermonkey's local storage

  1. // ==UserScript==
  2. // @name Input Text Logger
  3. // @namespace https://github.com/putsan
  4. // @author @putsan.bsky.social
  5. // @version 0.1.2
  6. // @description Track continuous text input on websites and save to Tampermonkey's local storage
  7. // @icon https://github.com/putsan/Tampermonkey_scripts/blob/1b111563ad358762c1611a7e1c48544cd4fcf833/resources/icons/image_2023-12-27_22-31-58.png?raw=true
  8. // @include *
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. "use strict";
  15.  
  16. // --- Variables Initialization ---
  17. let currentText = "";
  18. let lastDomain = "";
  19. let lastTimestamp = "";
  20. let inactivityTimer;
  21.  
  22. // --- Styles for the Popup ---
  23. const popupStyle = `
  24. position: fixed;
  25. bottom: 100px;
  26. right: 10px;
  27.  
  28. display: none;
  29. flex-direction: column;
  30. gap: 10px;
  31. width: 200px;
  32. padding: 10px;
  33.  
  34. background: white;
  35. border: 1px solid black;
  36. z-idex: 999;
  37. `;
  38.  
  39. // --- External CSS Library Link (Pure.css) ---
  40. const pureCssLink = document.createElement('link');
  41. pureCssLink.href = 'https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css';
  42. pureCssLink.rel = 'stylesheet';
  43. pureCssLink.type = 'text/css';
  44.  
  45. // Додавання створеного тегу до секції head веб-сторінки
  46. document.head.appendChild(pureCssLink);
  47.  
  48.  
  49. // --- Function to Save Data ---
  50. /**
  51. * Saves text, domain, and timestamp to Tampermonkey's local storage.
  52. * @param {string} newText - The new text to save.
  53. * @param {string} newDomain - The domain from which the text was captured.
  54. * @param {string} newTimestamp - The timestamp when the text was saved.
  55. */
  56. function saveData(newText, newDomain, newTimestamp) {
  57. // Отримання існуючих даних
  58. let existingData = GM_getValue("savedText", []);
  59. console.log(23, "до", GM_getValue("savedText"));
  60.  
  61. // Додавання нових даних
  62. existingData.unshift({
  63. text: newText,
  64. domain: newDomain,
  65. timestamp: newTimestamp,
  66. });
  67.  
  68. // Збереження оновлених даних
  69. GM_setValue("savedText", existingData);
  70. console.log(31, "після", GM_getValue("savedText"));
  71. }
  72.  
  73. // --- Inactivity Timer Reset Function ---
  74. function resetInactivityTimer() {
  75. clearTimeout(inactivityTimer);
  76. inactivityTimer = setTimeout(() => {
  77. if (currentText.length > 0) {
  78. saveData(currentText, lastDomain, lastTimestamp);
  79. currentText = "";
  80. }
  81. }, 10000); // 10 секунд неактивності
  82. }
  83.  
  84. document.addEventListener("input", function (event) {
  85. const target = event.target;
  86. if (
  87. (target.tagName === "INPUT" && target.type !== "password") ||
  88. target.tagName === "TEXTAREA"
  89. ) {
  90. const domain = window.location.hostname;
  91. const timestamp = new Date().toISOString();
  92.  
  93. if (domain !== lastDomain) {
  94. if (currentText.length > 0) {
  95. saveData(currentText, lastDomain, lastTimestamp);
  96. }
  97. currentText = target.value;
  98. lastDomain = domain;
  99. lastTimestamp = timestamp;
  100. } else {
  101. currentText = target.value;
  102. }
  103. resetInactivityTimer();
  104. }
  105. });
  106.  
  107. // Обробник події натискання клавіші
  108. document.addEventListener("keydown", function (event) {
  109. if (event.key === "Enter") {
  110. const domain = window.location.hostname;
  111. const timestamp = new Date().toISOString();
  112.  
  113. if (currentText.length > 0) {
  114. saveData(currentText, domain, timestamp);
  115. currentText = "";
  116. }
  117. }
  118. });
  119.  
  120. window.addEventListener("blur", function () {
  121. if (currentText.length > 0) {
  122. saveData(currentText, lastDomain, lastTimestamp);
  123. currentText = "";
  124. }
  125. });
  126.  
  127. window.addEventListener("beforeunload", function () {
  128. if (currentText.length > 0) {
  129. saveData(currentText, lastDomain, lastTimestamp);
  130. }
  131. });
  132.  
  133. // --- Function to Create Popup ---
  134. function createPopup() {
  135. const popupHTML = `
  136. <div id="tmPopup" class="pure-u-1-3" style="${popupStyle}">
  137. <button id="viewTextBtn" class="pure-button pure-button-primary">Переглянути текст</button>
  138. <button id="deleteDataBtn" class="button-warning pure-button">Очистити дані</button>
  139. </div>
  140. `;
  141.  
  142. document.body.insertAdjacentHTML("beforeend", popupHTML);
  143.  
  144. // Обробник для кнопки перегляду тексту
  145. document.getElementById("viewTextBtn").addEventListener("click", function () {
  146. const savedData = GM_getValue("savedText", []);
  147. const newTab = window.open();
  148. const head = newTab.document.head;
  149. const link = newTab.document.createElement('link');
  150. link.rel = 'stylesheet';
  151. link.href = 'https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css';
  152. head.appendChild(link);
  153.  
  154. // Стилізація вкладки
  155. const style = newTab.document.createElement('style');
  156. style.textContent = `
  157. body { margin: 0; font-family: sans-serif; background-color: #e7e7e7; }
  158. .card { margin-bottom: 15px; padding: 15px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0, 0, 0, .1); }
  159. .domain { color: #333; font-size: 14px; }
  160. .timestamp { color: #666; font-size: 12px; }
  161. .text { margin-top: 10px; }
  162. `;
  163. head.appendChild(style);
  164.  
  165. const body = newTab.document.body;
  166.  
  167. // Групування даних за доменом
  168. const dataByDomain = savedData.reduce((acc, { text, domain, timestamp }) => {
  169. if (!acc[domain]) {
  170. acc[domain] = [];
  171. }
  172. acc[domain].push({ text, timestamp });
  173. return acc;
  174. }, {});
  175.  
  176. // Функція для генерації кольору на основі домену
  177. function stringToColor(str) {
  178. let hash = 0;
  179. for (let i = 0; i < str.length; i++) {
  180. hash = str.charCodeAt(i) + ((hash << 5) - hash);
  181. }
  182. let colour = '#';
  183. for (let i = 0; i < 3; i++) {
  184. let value = (hash >> (i * 8)) & 0xFF;
  185. colour += ('00' + value.toString(16)).substr(-2);
  186. }
  187. return colour;
  188. }
  189.  
  190. // Створення карточок з даними
  191. Object.keys(dataByDomain).forEach(domain => {
  192. const entries = dataByDomain[domain];
  193. entries.forEach(({ text, timestamp }) => {
  194. const card = newTab.document.createElement('div');
  195. card.className = 'card';
  196. card.style.backgroundColor = stringToColor(domain);
  197.  
  198. const textDiv = newTab.document.createElement('div');
  199. textDiv.className = 'text';
  200. textDiv.textContent = text;
  201. card.appendChild(textDiv);
  202.  
  203. const domainDiv = newTab.document.createElement('div');
  204. domainDiv.className = 'domain';
  205. domainDiv.textContent = `Домен: ${domain}`;
  206. card.appendChild(domainDiv);
  207.  
  208. const timestampDiv = newTab.document.createElement('div');
  209. timestampDiv.className = 'timestamp';
  210. timestampDiv.textContent = `Час: ${new Date(timestamp).toLocaleString()}`;
  211. card.appendChild(timestampDiv);
  212.  
  213. body.appendChild(card);
  214. });
  215. });
  216. });
  217.  
  218.  
  219. document
  220. .getElementById("deleteDataBtn")
  221. .addEventListener("click", function () {
  222. GM_setValue("savedText", []);
  223. });
  224. }
  225.  
  226. // Виклик функції для створення попапа
  227. createPopup();
  228.  
  229. // Створення круглої кнопки
  230. const circleBtnHTML = `
  231. <div id="circleBtn" style="width: 35px; height: 35px; border-radius: 50%; background-color: green; position: fixed; bottom: 100px; right: 10px; z-index: 9999;">
  232. <img src="https://github.com/putsan/Tampermonkey_scripts/blob/1b111563ad358762c1611a7e1c48544cd4fcf833/resources/icons/image_2023-12-27_22-31-58.png?raw=true" style="width: 35px; height: 35px; border-radius: 50%;">
  233. </div>
  234. `;
  235.  
  236. document.body.insertAdjacentHTML("beforeend", circleBtnHTML);
  237.  
  238. const circleBtn = document.getElementById("circleBtn");
  239. const tmPopup = document.getElementById("tmPopup");
  240.  
  241. // Функція для розкриття попапа
  242. function togglePopup() {
  243. if (tmPopup.style.display === "none") {
  244. tmPopup.style.display = "flex";
  245. circleBtn.style.display = "none";
  246. }
  247. }
  248.  
  249. // Функція для закриття попапа
  250. function closePopup() {
  251. tmPopup.style.display = "none";
  252. circleBtn.style.display = "block";
  253. }
  254.  
  255. // Додавання обробника подій
  256. circleBtn.addEventListener("click", togglePopup);
  257. window.addEventListener("click", function (event) {
  258. // Перевірка, чи клік був зроблений поза `circleBtn` та `tmPopup`
  259. if (!circleBtn.contains(event.target) && !tmPopup.contains(event.target)) {
  260. closePopup();
  261. }
  262. });
  263. })();