Advanced Browser Hosts File (User Managed)

Mimics hosts file behavior with user-managed redirections via local storage.

  1. // ==UserScript==
  2. // @name Advanced Browser Hosts File (User Managed)
  3. // @description Mimics hosts file behavior with user-managed redirections via local storage.
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.1
  6. // @author s3b
  7. // @license MIT
  8. // @match *://*/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addStyle
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // --- CONFIGURATION ---
  19. const STORAGE_KEY = 'browserHostsRules';
  20. const MANAGEMENT_TRIGGER_KEY = 'Alt+Shift+H'; // Keyboard shortcut to open management UI
  21. const MANAGEMENT_URL_PATH = '/_tmredirect'; // A unique path to trigger the management UI
  22. // --- END CONFIGURATION ---
  23.  
  24. let hostsRules = {}; // Will be loaded from localStorage
  25.  
  26. // --- UI Styles ---
  27. GM_addStyle(`
  28. #tmHostsManagerOverlay {
  29. position: fixed;
  30. top: 0;
  31. left: 0;
  32. width: 100%;
  33. height: 100%;
  34. background-color: rgba(0, 0, 0, 0.7);
  35. display: flex;
  36. justify-content: center;
  37. align-items: center;
  38. z-index: 99999;
  39. font-family: sans-serif;
  40. color: #333;
  41. }
  42. #tmHostsManagerPanel {
  43. background-color: #fff;
  44. padding: 25px;
  45. border-radius: 8px;
  46. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
  47. max-width: 600px;
  48. width: 90%;
  49. max-height: 90%;
  50. overflow-y: auto;
  51. position: relative;
  52. }
  53. #tmHostsManagerPanel h2 {
  54. margin-top: 0;
  55. color: #0056b3;
  56. border-bottom: 2px solid #eee;
  57. padding-bottom: 10px;
  58. margin-bottom: 20px;
  59. }
  60. #tmHostsManagerPanel label {
  61. display: block;
  62. margin-bottom: 5px;
  63. font-weight: bold;
  64. color: #555;
  65. }
  66. #tmHostsManagerPanel input[type="text"] {
  67. width: calc(100% - 22px);
  68. padding: 10px;
  69. margin-bottom: 15px;
  70. border: 1px solid #ddd;
  71. border-radius: 4px;
  72. box-sizing: border-box;
  73. }
  74. #tmHostsManagerPanel button {
  75. background-color: #007bff;
  76. color: white;
  77. padding: 10px 20px;
  78. border: none;
  79. border-radius: 5px;
  80. cursor: pointer;
  81. font-size: 16px;
  82. margin-right: 10px;
  83. transition: background-color 0.2s ease;
  84. }
  85. #tmHostsManagerPanel button:hover {
  86. background-color: #0056b3;
  87. }
  88. #tmHostsManagerPanel button.delete {
  89. background-color: #dc3545;
  90. }
  91. #tmHostsManagerPanel button.delete:hover {
  92. background-color: #c82333;
  93. }
  94. #tmHostsManagerPanel button.close {
  95. background-color: #6c757d;
  96. }
  97. #tmHostsManagerPanel button.close:hover {
  98. background-color: #5a6268;
  99. }
  100. #tmHostsManagerPanel #rulesList {
  101. margin-top: 20px;
  102. border-top: 1px solid #eee;
  103. padding-top: 20px;
  104. }
  105. #tmHostsManagerPanel .rule-item {
  106. display: flex;
  107. align-items: center;
  108. padding: 10px 0;
  109. border-bottom: 1px dashed #eee;
  110. }
  111. #tmHostsManagerPanel .rule-item:last-child {
  112. border-bottom: none;
  113. }
  114. #tmHostsManagerPanel .rule-item span {
  115. flex-grow: 1;
  116. word-break: break-all;
  117. }
  118. #tmHostsManagerPanel .rule-item .actions {
  119. margin-left: 15px;
  120. display: flex;
  121. gap: 5px;
  122. }
  123. #tmHostsManagerPanel .rule-item .actions button {
  124. padding: 5px 10px;
  125. font-size: 14px;
  126. margin-right: 0;
  127. }
  128. #tmHostsManagerPanel .info-text {
  129. font-size: 0.9em;
  130. color: #666;
  131. margin-bottom: 15px;
  132. background-color: #f9f9f9;
  133. border-left: 4px solid #007bff;
  134. padding: 10px;
  135. border-radius: 4px;
  136. }
  137. #tmHostsManagerPanel .close-button-top {
  138. position: absolute;
  139. top: 10px;
  140. right: 15px;
  141. font-size: 24px;
  142. font-weight: bold;
  143. color: #aaa;
  144. cursor: pointer;
  145. line-height: 1;
  146. padding: 0 5px;
  147. }
  148. #tmHostsManagerPanel .close-button-top:hover {
  149. color: #666;
  150. }
  151. `);
  152.  
  153. // --- Core Functions ---
  154.  
  155. async function loadRules() {
  156. try {
  157. const storedRules = await GM_getValue(STORAGE_KEY, '{}');
  158. hostsRules = JSON.parse(storedRules);
  159. } catch (e) {
  160. console.error('Tampermonkey Hosts: Failed to load rules from localStorage', e);
  161. hostsRules = {}; // Reset if parsing fails
  162. }
  163. }
  164.  
  165. async function saveRules() {
  166. try {
  167. await GM_setValue(STORAGE_KEY, JSON.stringify(hostsRules));
  168. console.log('Tampermonkey Hosts: Rules saved.');
  169. } catch (e) {
  170. console.error('Tampermonkey Hosts: Failed to save rules to localStorage', e);
  171. }
  172. }
  173.  
  174. function addRule(source, target) {
  175. source = source.trim().toLowerCase();
  176. target = target.trim();
  177. if (source && target) {
  178. hostsRules[source] = target;
  179. saveRules();
  180. renderRules(); // Re-render UI if it's open
  181. return true;
  182. }
  183. return false;
  184. }
  185.  
  186. function deleteRule(source) {
  187. if (confirm(`Are you sure you want to delete the rule for "${source}"?`)) {
  188. delete hostsRules[source];
  189. saveRules();
  190. renderRules(); // Re-render UI if it's open
  191. }
  192. }
  193.  
  194. function clearRuleInputs() {
  195. document.getElementById('tmSourceInput').value = '';
  196. document.getElementById('tmTargetInput').value = '';
  197. }
  198.  
  199. // --- UI Management ---
  200.  
  201. function createManagementUI() {
  202. if (document.getElementById('tmHostsManagerOverlay')) {
  203. // UI already exists
  204. return;
  205. }
  206.  
  207. const overlay = document.createElement('div');
  208. overlay.id = 'tmHostsManagerOverlay';
  209. overlay.innerHTML = `
  210. <div id="tmHostsManagerPanel">
  211. <span class="close-button-top" id="tmCloseManagerBtnTop">&times;</span>
  212. <h2>Browser Hosts Manager</h2>
  213. <div class="info-text">
  214. Add rules to redirect websites. Use 'BLOCK' as the target URL to redirect to <code>about:blank</code> (effectively blocking).
  215. <br>Rules are matched against the hostname (e.g., <code>www.google.com</code>) or its subdomains.
  216. </div>
  217. <div>
  218. <label for="tmSourceInput">Source Hostname (e.g., youtube.com, www.facebook.com):</label>
  219. <input type="text" id="tmSourceInput" placeholder="Enter source domain (e.g., badsite.com)" required>
  220. </div>
  221. <div>
  222. <label for="tmTargetInput">Target URL (e.g., https://newsite.com, or BLOCK):</label>
  223. <input type="text" id="tmTargetInput" placeholder="Enter target URL or 'BLOCK'" required>
  224. </div>
  225. <button id="tmAddRuleBtn">Add/Update Rule</button>
  226. <button id="tmCloseManagerBtnBottom" class="close">Close</button>
  227.  
  228. <div id="rulesList">
  229. <h3>Current Rules:</h3>
  230. <div id="tmRulesContainer">
  231. </div>
  232. </div>
  233. </div>
  234. `;
  235. document.body.appendChild(overlay);
  236.  
  237. // Event Listeners
  238. document.getElementById('tmAddRuleBtn').addEventListener('click', () => {
  239. const source = document.getElementById('tmSourceInput').value;
  240. const target = document.getElementById('tmTargetInput').value;
  241. if (addRule(source, target)) {
  242. clearRuleInputs();
  243. } else {
  244. alert('Please provide both a source hostname and a target URL.');
  245. }
  246. });
  247.  
  248. document.getElementById('tmCloseManagerBtnTop').addEventListener('click', closeManagementUI);
  249. document.getElementById('tmCloseManagerBtnBottom').addEventListener('click', closeManagementUI);
  250.  
  251. // Allow clicking outside the panel to close it
  252. overlay.addEventListener('click', (event) => {
  253. if (event.target === overlay) {
  254. closeManagementUI();
  255. }
  256. });
  257.  
  258. renderRules();
  259. }
  260.  
  261. function renderRules() {
  262. const rulesContainer = document.getElementById('tmRulesContainer');
  263. if (!rulesContainer) return; // UI not open
  264.  
  265. rulesContainer.innerHTML = ''; // Clear previous rules
  266.  
  267. if (Object.keys(hostsRules).length === 0) {
  268. rulesContainer.innerHTML = '<p>No rules added yet. Add your first rule above!</p>';
  269. return;
  270. }
  271.  
  272. for (const source in hostsRules) {
  273. if (hostsRules.hasOwnProperty(source)) {
  274. const target = hostsRules[source];
  275. const ruleItem = document.createElement('div');
  276. ruleItem.classList.add('rule-item');
  277. ruleItem.innerHTML = `
  278. <span><b>${source}</b> &rarr; ${target === 'BLOCK' ? '<span style="color:red; font-weight:bold;">BLOCK</span>' : target}</span>
  279. <div class="actions">
  280. <button class="delete" data-source="${source}">Delete</button>
  281. </div>
  282. `;
  283. rulesContainer.appendChild(ruleItem);
  284.  
  285. ruleItem.querySelector('.delete').addEventListener('click', (event) => {
  286. deleteRule(event.target.dataset.source);
  287. });
  288. }
  289. }
  290. }
  291.  
  292. function openManagementUI() {
  293. createManagementUI();
  294. document.getElementById('tmHostsManagerOverlay').style.display = 'flex';
  295. renderRules(); // Ensure rules are up-to-date when opening
  296. }
  297.  
  298. function closeManagementUI() {
  299. const overlay = document.getElementById('tmHostsManagerOverlay');
  300. if (overlay) {
  301. overlay.style.display = 'none';
  302. }
  303. }
  304.  
  305. // --- Main Script Logic ---
  306.  
  307. async function initializeAndRun() {
  308. await loadRules(); // Load rules asynchronously at script start
  309.  
  310. const currentHostname = window.location.hostname;
  311. const currentPath = window.location.pathname;
  312.  
  313. // Check for management UI trigger URL
  314. if (currentPath === MANAGEMENT_URL_PATH) {
  315. // Prevent the original page from loading
  316. document.documentElement.innerHTML = ''; // Clear existing content
  317. document.head.innerHTML = ''; // Clear head content
  318. document.title = 'Browser Hosts Manager'; // Set a temporary title
  319. openManagementUI();
  320. // Stop further script execution for the main page logic
  321. return;
  322. }
  323.  
  324. // Check for redirection rules
  325. for (const domain in hostsRules) {
  326. if (hostsRules.hasOwnProperty(domain)) {
  327. // More robust check: exact match or currentHostname ends with '.' + domain
  328. // This covers www.example.com for example.com, but not example.com for an-example.com
  329. if (currentHostname === domain || currentHostname.endsWith('.' + domain)) {
  330. const redirectTarget = hostsRules[domain];
  331.  
  332. if (redirectTarget === 'BLOCK') {
  333. // Redirect to about:blank to "block" the site
  334. window.location.replace('about:blank');
  335. // Prevent the original page from loading further
  336. throw new Error('Site blocked by Tampermonkey script.');
  337. } else {
  338. // Redirect to a specific URL
  339. window.location.replace(redirectTarget);
  340. // Prevent the original page from loading further
  341. throw new Error('Site redirected by Tampermonkey script.');
  342. }
  343. }
  344. }
  345. }
  346. }
  347.  
  348. // Run the initialization and main logic
  349. initializeAndRun();
  350.  
  351. // Add keyboard shortcut listener for convenience
  352. document.addEventListener('keydown', (e) => {
  353. const [modifier, key] = MANAGEMENT_TRIGGER_KEY.split('+');
  354. if (e.altKey === (modifier === 'Alt') &&
  355. e.shiftKey === (modifier === 'Shift') &&
  356. e.ctrlKey === (modifier === 'Control') &&
  357. e.key.toUpperCase() === key.toUpperCase()) {
  358. e.preventDefault(); // Prevent default browser action
  359. openManagementUI();
  360. }
  361. });
  362.  
  363. // Option to open management UI from Tampermonkey's menu
  364. // This part is more for a context menu or similar, Tampermonkey doesn't have a direct GM_registerMenuCommand
  365. // However, the keyboard shortcut or the specific URL will serve the purpose.
  366.  
  367. })();