Greasy Fork is available in English.

Feed Thomas Reminder

Feed reminder overlay with 3 snooze modes (1hr @ hh:02, 10s, 24hr), sync, Open Sans, Snooze button, and visual mode indicators. Updated 1hr mode to resume 2 minutes past the hour. 🐾🐈🕒⏱️📅🧭🔕

  1. // ==UserScript==
  2. // @name Feed Thomas Reminder
  3. // @namespace https://farmrpg.com/
  4. // @version 2.8
  5. // @description Feed reminder overlay with 3 snooze modes (1hr @ hh:02, 10s, 24hr), sync, Open Sans, Snooze button, and visual mode indicators. Updated 1hr mode to resume 2 minutes past the hour. 🐾🐈🕒⏱️📅🧭🔕
  6. // @author Clientcoin
  7. // @match *://*/*
  8. // @icon https://farmrpg.com/favicon.ico
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addValueChangeListener
  12. // @grant GM_removeValueChangeListener
  13. // @license Unlicense
  14. // @homepage https://farmrpg.com/
  15. // @supportURL https://farmrpg.com/
  16. // @run-at document-end
  17. // ==/UserScript==
  18.  
  19. (async function () {
  20. 'use strict';
  21.  
  22. const FORCE_SHOW_NEXT_LOAD = false;
  23.  
  24. const ID = 'feed-thomas-box';
  25. const SCALE_KEY = 'feedThomasScale';
  26. const STORAGE_KEY = 'feedThomasHiddenUntil';
  27. const MODE_KEY = 'feedThomasMode';
  28. const FORCE_KEY = 'feedThomasForceFlag';
  29.  
  30. let scale = parseFloat(localStorage.getItem(SCALE_KEY)) || 1.0;
  31. let mode = parseInt(await GM_getValue(MODE_KEY, 0), 10);
  32. let hiddenUntil = parseInt(await GM_getValue(STORAGE_KEY, 0), 10) || 0;
  33. let clockInterval = null;
  34.  
  35. const fontLink = document.createElement('link');
  36. fontLink.href = 'https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap';
  37. fontLink.rel = 'stylesheet';
  38. document.head.appendChild(fontLink);
  39.  
  40. if (FORCE_SHOW_NEXT_LOAD) {
  41. await GM_setValue(FORCE_KEY, true);
  42. }
  43. const forceOverride = await GM_getValue(FORCE_KEY, false);
  44. if (forceOverride) {
  45. hiddenUntil = 0;
  46. await GM_setValue(FORCE_KEY, false);
  47. }
  48.  
  49. function shouldShowBox() {
  50. return Date.now() >= hiddenUntil;
  51. }
  52.  
  53. async function handleSnooze() {
  54. const now = new Date();
  55. let next;
  56. if (mode === 1) {
  57. next = new Date(now);
  58. const remainder = 10 - (now.getSeconds() % 10);
  59. next.setSeconds(now.getSeconds() + remainder, 0);
  60. } else if (mode === 2) {
  61. next = new Date(now);
  62. next.setDate(now.getDate() + 1);
  63. } else {
  64. next = new Date(now);
  65. next.setMinutes(2, 0, 0); // set to next hour @ 02:00
  66. next.setHours(next.getHours() + 1);
  67. }
  68. await GM_setValue(STORAGE_KEY, next.getTime());
  69. }
  70.  
  71. function createBox() {
  72. if (document.getElementById(ID)) return;
  73.  
  74. const box = document.createElement('div');
  75. box.id = ID;
  76. box.title = 'Click to snooze until next hour +2min, 10s or 24h depending on mode';
  77. Object.assign(box.style, {
  78. position: 'fixed',
  79. top: '10px',
  80. right: '10px',
  81. zIndex: '999999',
  82. backgroundColor: 'blue',
  83. color: 'yellow',
  84. fontSize: `${scale}em`,
  85. padding: '10px',
  86. borderRadius: '5px',
  87. cursor: 'pointer',
  88. display: 'flex',
  89. flexDirection: 'column',
  90. alignItems: 'center',
  91. textAlign: 'center',
  92. fontFamily: "'Open Sans', sans-serif",
  93. lineHeight: '1.1'
  94. });
  95.  
  96. const text = document.createElement('div');
  97. text.textContent = 'Feed Thomas';
  98. text.style.fontSize = '1em';
  99. box.appendChild(text);
  100.  
  101. const clock = document.createElement('div');
  102. clock.style.fontSize = '0.8em';
  103. clock.style.marginTop = '4px';
  104. box.appendChild(clock);
  105.  
  106. const date = document.createElement('div');
  107. date.style.fontSize = '0.75em';
  108. box.appendChild(date);
  109.  
  110. updateClockAndDate(clock, date);
  111. clockInterval = setInterval(() => updateClockAndDate(clock, date), 1000);
  112.  
  113. const controls = document.createElement('div');
  114. controls.style.display = 'flex';
  115. controls.style.gap = '6px';
  116. controls.style.marginTop = '6px';
  117.  
  118. const minusBtn = document.createElement('button');
  119. minusBtn.textContent = '-';
  120. minusBtn.title = 'Decrease size';
  121. styleControlButton(minusBtn);
  122. minusBtn.style.width = '28px';
  123. minusBtn.onclick = (e) => {
  124. e.stopPropagation();
  125. scale = Math.max(0.5, scale - 0.1);
  126. localStorage.setItem(SCALE_KEY, scale.toFixed(2));
  127. document.getElementById(ID).style.fontSize = `${scale}em`;
  128. };
  129.  
  130. const plusBtn = document.createElement('button');
  131. plusBtn.textContent = '+';
  132. plusBtn.title = 'Increase size';
  133. styleControlButton(plusBtn);
  134. plusBtn.style.width = '28px';
  135. plusBtn.onclick = (e) => {
  136. e.stopPropagation();
  137. scale = Math.min(3.0, scale + 0.1);
  138. localStorage.setItem(SCALE_KEY, scale.toFixed(2));
  139. document.getElementById(ID).style.fontSize = `${scale}em`;
  140. };
  141.  
  142. controls.appendChild(minusBtn);
  143. controls.appendChild(plusBtn);
  144. box.appendChild(controls);
  145.  
  146. const modeBtn = document.createElement('button');
  147. updateModeButtonStyle(modeBtn, mode);
  148. modeBtn.title = 'Click to cycle reminder interval (1hr, 10s, 24hr)';
  149. modeBtn.onclick = async (e) => {
  150. e.stopPropagation();
  151. mode = (mode + 1) % 3;
  152. await GM_setValue(MODE_KEY, mode);
  153. };
  154. box.appendChild(modeBtn);
  155.  
  156. const snoozeBtn = document.createElement('button');
  157. snoozeBtn.textContent = 'Snooze';
  158. snoozeBtn.title = 'Manually snooze this reminder';
  159. Object.assign(snoozeBtn.style, {
  160. marginTop: '4px',
  161. fontSize: '11px'
  162. });
  163. styleControlButton(snoozeBtn);
  164. snoozeBtn.onclick = async (e) => {
  165. e.stopPropagation();
  166. await handleSnooze();
  167. };
  168. box.appendChild(snoozeBtn);
  169.  
  170. box.onclick = async () => {
  171. await handleSnooze();
  172. };
  173.  
  174. document.body.appendChild(box);
  175. }
  176.  
  177. function styleControlButton(btn) {
  178. btn.style.background = 'yellow';
  179. btn.style.color = 'blue';
  180. btn.style.border = 'none';
  181. btn.style.cursor = 'pointer';
  182. btn.style.padding = '2px 6px';
  183. btn.style.fontWeight = 'bold';
  184. btn.style.borderRadius = '4px';
  185. btn.style.fontFamily = "'Open Sans', sans-serif";
  186. }
  187.  
  188. function updateModeButtonStyle(btn, modeValue) {
  189. const labels = ['Mode: 1hr', 'Mode: 10s', 'Mode: 24hr'];
  190. btn.textContent = labels[modeValue];
  191. btn.style.marginTop = '6px';
  192. btn.style.fontSize = '10px';
  193.  
  194. styleControlButton(btn);
  195.  
  196. if (modeValue === 1) {
  197. btn.style.opacity = '0.6';
  198. } else if (modeValue === 2) {
  199. btn.style.background = 'blue';
  200. btn.style.color = 'yellow';
  201. btn.style.opacity = '1.0';
  202. } else {
  203. btn.style.opacity = '1.0';
  204. btn.style.background = 'yellow';
  205. btn.style.color = 'blue';
  206. }
  207. }
  208.  
  209. function updateClockAndDate(clockEl, dateEl) {
  210. const now = new Date();
  211. const timeStr = now.toLocaleTimeString([], {
  212. hour: '2-digit',
  213. minute: '2-digit',
  214. second: '2-digit'
  215. });
  216.  
  217. const year = now.getFullYear();
  218. const month = now.toLocaleString('default', { month: 'short' });
  219. const day = String(now.getDate()).padStart(2, '0');
  220. const weekday = now.toLocaleString('default', { weekday: 'short' });
  221. const dateStr = `${year}-${month}-${day} (${weekday})`;
  222.  
  223. clockEl.textContent = timeStr;
  224. dateEl.textContent = dateStr;
  225. }
  226.  
  227. function checkAndShowBox() {
  228. const box = document.getElementById(ID);
  229. if (shouldShowBox()) {
  230. if (!box) createBox();
  231. } else if (box) {
  232. clearInterval(clockInterval);
  233. box.remove();
  234. }
  235. }
  236.  
  237. GM_addValueChangeListener(STORAGE_KEY, (_, __, newValue) => {
  238. hiddenUntil = parseInt(newValue, 10);
  239. checkAndShowBox();
  240. });
  241.  
  242. GM_addValueChangeListener(MODE_KEY, (_, __, newValue) => {
  243. mode = parseInt(newValue, 10);
  244. const box = document.getElementById(ID);
  245. if (box) {
  246. const btn = [...box.querySelectorAll('button')]
  247. .find(b => b.textContent.startsWith('Mode:'));
  248. if (btn) {
  249. updateModeButtonStyle(btn, mode);
  250. }
  251. }
  252. });
  253.  
  254. function init() {
  255. checkAndShowBox();
  256. setInterval(checkAndShowBox, 2000);
  257. }
  258.  
  259. if (document.readyState === 'loading') {
  260. document.addEventListener('DOMContentLoaded', init);
  261. } else {
  262. init();
  263. }
  264. })();