Convert between NIS, USD, EUR, GBP and other currencies with real-time rates. Right-click any amount to convert currencies.
Size
14.0 KB
Version
1.1.2
Created
Feb 5, 2026
Updated
12 days ago
1// ==UserScript==
2// @name Multi-Currency Converter with Context Menu
3// @description Convert between NIS, USD, EUR, GBP and other currencies with real-time rates. Right-click any amount to convert currencies.
4// @version 1.1.2
5// @match https://*/*
6// @match http://*/*
7// @icon https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
8// @grant GM.getValue
9// @grant GM.setValue
10// @grant GM.xmlhttpRequest
11// @grant GM.registerMenuCommand
12// ==/UserScript==
13(function() {
14 'use strict';
15
16 // Configuration
17 const API_URL = 'https://api.exchangerate-api.com/v4/latest/';
18 const CACHE_DURATION = 3600000; // 1 hour in milliseconds
19 const SUPPORTED_CURRENCIES = ['NIS', 'USD', 'EUR', 'GBP', 'JPY', 'CHF', 'CAD', 'AUD', 'CNY', 'INR'];
20
21 // Currency symbols mapping
22 const CURRENCY_SYMBOLS = {
23 'NIS': '₪',
24 'USD': '$',
25 'EUR': '€',
26 'GBP': '£',
27 'JPY': '¥',
28 'CHF': 'CHF',
29 'CAD': 'C$',
30 'AUD': 'A$',
31 'CNY': '¥',
32 'INR': '₹'
33 };
34
35 let exchangeRates = {};
36 let converterPanel = null;
37 let selectedText = '';
38 let selectedCurrency = '';
39
40 // Fetch exchange rates
41 async function fetchExchangeRates(baseCurrency = 'NIS') {
42 try {
43 const cacheKey = `rates_${baseCurrency}`;
44 const cached = await GM.getValue(cacheKey);
45
46 if (cached) {
47 const data = JSON.parse(cached);
48 if (Date.now() - data.timestamp < CACHE_DURATION) {
49 console.log('Using cached exchange rates');
50 return data.rates;
51 }
52 }
53
54 console.log('Fetching fresh exchange rates...');
55 const response = await GM.xmlhttpRequest({
56 method: 'GET',
57 url: API_URL + baseCurrency,
58 responseType: 'json'
59 });
60
61 const rates = response.response.rates;
62 await GM.setValue(cacheKey, JSON.stringify({
63 rates: rates,
64 timestamp: Date.now()
65 }));
66
67 return rates;
68 } catch (error) {
69 console.error('Error fetching exchange rates:', error);
70 return null;
71 }
72 }
73
74 // Convert currency
75 function convertCurrency(amount, fromCurrency, toCurrency, rates) {
76 if (fromCurrency === toCurrency) return amount;
77
78 // Convert to base currency (NIS) first, then to target
79 const amountInBase = amount / rates[fromCurrency];
80 return amountInBase * rates[toCurrency];
81 }
82
83 // Detect currency from text
84 function detectCurrency(text) {
85 const symbolMap = {
86 '₪': 'NIS',
87 '$': 'USD',
88 '€': 'EUR',
89 '£': 'GBP',
90 '¥': 'JPY',
91 '₹': 'INR'
92 };
93
94 for (const [symbol, currency] of Object.entries(symbolMap)) {
95 if (text.includes(symbol)) {
96 return currency;
97 }
98 }
99
100 // Check for currency codes
101 for (const currency of SUPPORTED_CURRENCIES) {
102 if (text.toUpperCase().includes(currency)) {
103 return currency;
104 }
105 }
106
107 return null;
108 }
109
110 // Extract number from text
111 function extractNumber(text) {
112 // Remove currency symbols and letters, keep numbers, dots, and commas
113 const cleaned = text.replace(/[^\d.,]/g, '');
114 // Replace comma with dot for decimal
115 const normalized = cleaned.replace(',', '.');
116 const number = parseFloat(normalized);
117 return isNaN(number) ? null : number;
118 }
119
120 // Create converter panel UI
121 function createConverterPanel() {
122 const panel = document.createElement('div');
123 panel.id = 'currency-converter-panel';
124 panel.style.cssText = `
125 position: fixed;
126 top: 50%;
127 left: 50%;
128 transform: translate(-50%, -50%);
129 background: white;
130 border: 2px solid #007bff;
131 border-radius: 12px;
132 padding: 25px;
133 box-shadow: 0 8px 32px rgba(0,0,0,0.3);
134 z-index: 999999;
135 min-width: 400px;
136 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
137 color: #333;
138 `;
139
140 panel.innerHTML = `
141 <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
142 <h3 style="margin: 0; color: #007bff; font-size: 20px;">Currency Converter</h3>
143 <button id="close-converter" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 30px; height: 30px;">×</button>
144 </div>
145
146 <div style="margin-bottom: 20px;">
147 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #555;">Amount:</label>
148 <input type="number" id="amount-input" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box;" placeholder="Enter amount">
149 </div>
150
151 <div style="display: flex; gap: 15px; margin-bottom: 20px; align-items: center;">
152 <div style="flex: 1;">
153 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #555;">From:</label>
154 <select id="from-currency" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 16px; cursor: pointer;">
155 ${SUPPORTED_CURRENCIES.map(c => `<option value="${c}">${c} ${CURRENCY_SYMBOLS[c]}</option>`).join('')}
156 </select>
157 </div>
158
159 <button id="swap-currencies" style="margin-top: 28px; background: #007bff; color: white; border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; font-size: 18px; transition: transform 0.2s;" title="Swap currencies">⇄</button>
160
161 <div style="flex: 1;">
162 <label style="display: block; margin-bottom: 8px; font-weight: 600; color: #555;">To:</label>
163 <select id="to-currency" style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 6px; font-size: 16px; cursor: pointer;">
164 ${SUPPORTED_CURRENCIES.map(c => `<option value="${c}">${c} ${CURRENCY_SYMBOLS[c]}</option>`).join('')}
165 </select>
166 </div>
167 </div>
168
169 <div id="conversion-results" style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px; min-height: 60px;">
170 <div style="text-align: center; color: #666;">Enter an amount to convert</div>
171 </div>
172
173 <div style="display: flex; gap: 10px;">
174 <button id="convert-btn" style="flex: 1; background: #007bff; color: white; border: none; padding: 14px; border-radius: 6px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s;">Convert</button>
175 <button id="refresh-rates" style="background: #28a745; color: white; border: none; padding: 14px 20px; border-radius: 6px; font-size: 16px; cursor: pointer; transition: background 0.2s;" title="Refresh exchange rates">🔄</button>
176 </div>
177
178 <div id="rate-info" style="margin-top: 15px; text-align: center; font-size: 12px; color: #888;"></div>
179 `;
180
181 document.body.appendChild(panel);
182 return panel;
183 }
184
185 // Show converter panel
186 async function showConverterPanel(amount = '', fromCurrency = 'NIS', toCurrency = 'USD') {
187 if (!converterPanel) {
188 converterPanel = createConverterPanel();
189 setupPanelListeners();
190 }
191
192 converterPanel.style.display = 'block';
193
194 const amountInput = document.getElementById('amount-input');
195 const fromSelect = document.getElementById('from-currency');
196 const toSelect = document.getElementById('to-currency');
197
198 amountInput.value = amount;
199 fromSelect.value = fromCurrency;
200 toSelect.value = toCurrency;
201
202 // Load rates and convert if amount is provided
203 exchangeRates = await fetchExchangeRates('NIS');
204 if (amount) {
205 performConversion();
206 }
207 }
208
209 // Setup panel event listeners
210 function setupPanelListeners() {
211 document.getElementById('close-converter').addEventListener('click', () => {
212 converterPanel.style.display = 'none';
213 });
214
215 document.getElementById('swap-currencies').addEventListener('click', () => {
216 const fromSelect = document.getElementById('from-currency');
217 const toSelect = document.getElementById('to-currency');
218 const temp = fromSelect.value;
219 fromSelect.value = toSelect.value;
220 toSelect.value = temp;
221 performConversion();
222 });
223
224 document.getElementById('convert-btn').addEventListener('click', performConversion);
225
226 document.getElementById('amount-input').addEventListener('keypress', (e) => {
227 if (e.key === 'Enter') {
228 performConversion();
229 }
230 });
231
232 document.getElementById('refresh-rates').addEventListener('click', async () => {
233 const btn = document.getElementById('refresh-rates');
234 btn.textContent = '⏳';
235 btn.disabled = true;
236
237 // Clear cache
238 for (const currency of SUPPORTED_CURRENCIES) {
239 await GM.setValue(`rates_${currency}`, '');
240 }
241
242 exchangeRates = await fetchExchangeRates('NIS');
243 btn.textContent = '🔄';
244 btn.disabled = false;
245 performConversion();
246 });
247
248 // Close on outside click
249 converterPanel.addEventListener('click', (e) => {
250 if (e.target === converterPanel) {
251 converterPanel.style.display = 'none';
252 }
253 });
254 }
255
256 // Perform conversion
257 async function performConversion() {
258 const amount = parseFloat(document.getElementById('amount-input').value);
259 const fromCurrency = document.getElementById('from-currency').value;
260 const toCurrency = document.getElementById('to-currency').value;
261 const resultsDiv = document.getElementById('conversion-results');
262 const rateInfo = document.getElementById('rate-info');
263
264 if (!amount || isNaN(amount)) {
265 resultsDiv.innerHTML = '<div style="text-align: center; color: #dc3545;">Please enter a valid amount</div>';
266 return;
267 }
268
269 if (!exchangeRates) {
270 exchangeRates = await fetchExchangeRates('NIS');
271 }
272
273 const result = convertCurrency(amount, fromCurrency, toCurrency, exchangeRates);
274 const rate = exchangeRates[toCurrency] / exchangeRates[fromCurrency];
275
276 resultsDiv.innerHTML = `
277 <div style="text-align: center;">
278 <div style="font-size: 14px; color: #666; margin-bottom: 8px;">
279 ${amount.toLocaleString()} ${CURRENCY_SYMBOLS[fromCurrency]} ${fromCurrency}
280 </div>
281 <div style="font-size: 28px; font-weight: bold; color: #007bff;">
282 ${result.toFixed(2)} ${CURRENCY_SYMBOLS[toCurrency]}
283 </div>
284 <div style="font-size: 16px; color: #333; margin-top: 4px;">
285 ${toCurrency}
286 </div>
287 </div>
288 `;
289
290 rateInfo.textContent = `Exchange rate: 1 ${fromCurrency} = ${rate.toFixed(4)} ${toCurrency}`;
291 }
292
293 // Context menu for selected text
294 document.addEventListener('mouseup', (e) => {
295 const selection = window.getSelection();
296 selectedText = selection.toString().trim();
297
298 if (selectedText) {
299 const amount = extractNumber(selectedText);
300 const currency = detectCurrency(selectedText);
301
302 if (amount !== null) {
303 selectedCurrency = currency || 'USD';
304 }
305 }
306 });
307
308 // Listen for context menu
309 document.addEventListener('contextmenu', async (e) => {
310 if (selectedText) {
311 const amount = extractNumber(selectedText);
312 const currency = detectCurrency(selectedText);
313
314 if (amount !== null && currency) {
315 // Store for context menu action
316 await GM.setValue('contextAmount', amount.toString());
317 await GM.setValue('contextCurrency', currency);
318 }
319 }
320 });
321
322 // Register menu commands
323 GM.registerMenuCommand('Open Currency Converter', () => {
324 showConverterPanel();
325 });
326
327 GM.registerMenuCommand('Convert Selected to NIS', async () => {
328 const amount = parseFloat(await GM.getValue('contextAmount', '0'));
329 const currency = await GM.getValue('contextCurrency', 'USD');
330
331 if (amount && currency !== 'NIS') {
332 showConverterPanel(amount, currency, 'NIS');
333 }
334 });
335
336 GM.registerMenuCommand('Convert Selected to USD', async () => {
337 const amount = parseFloat(await GM.getValue('contextAmount', '0'));
338 const currency = await GM.getValue('contextCurrency', 'NIS');
339
340 if (amount) {
341 showConverterPanel(amount, currency, 'USD');
342 }
343 });
344
345 GM.registerMenuCommand('Convert Selected to EUR', async () => {
346 const amount = parseFloat(await GM.getValue('contextAmount', '0'));
347 const currency = await GM.getValue('contextCurrency', 'NIS');
348
349 if (amount) {
350 showConverterPanel(amount, currency, 'EUR');
351 }
352 });
353
354 // Add keyboard shortcut (Ctrl+Shift+C)
355 document.addEventListener('keydown', (e) => {
356 if (e.ctrlKey && e.shiftKey && e.key === 'C') {
357 e.preventDefault();
358 showConverterPanel();
359 }
360 });
361
362 // Initialize
363 async function init() {
364 console.log('Multi-Currency Converter initialized');
365 // Pre-fetch rates
366 exchangeRates = await fetchExchangeRates('NIS');
367 }
368
369 init();
370})();