// MIT License // // Copyright (c) 2024 HowMuchRadiation.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // Additional Conditions: // 1. Attribution: Users are encouraged to credit the original authors when using or redistributing this software. // 2. Modification Notices: Any modifications made to the original software should be documented. // 3. Documentation: Clear usage instructions should be provided with any distribution. // 4. Limitations of Liability: The authors are not liable for any damages resulting from the use of this software. // 5. Compliance with Laws: Users must ensure compliance with applicable laws when using this software. // 6. Contribution Guidelines: Contributions are welcome; please follow the provided guidelines. // 7. Contact Information: For questions or support, please contact HowMuchRadiation.com // // THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Note: This widget is not affiliated with GMCmap and operates independently. export default { // Rate limiting variables rlWindow: 60 * 1000, // 1 minute in milliseconds maxReq: 30, reqCounts: {}, async fetch(req) { const clientIP = req.headers.get("cf-connecting-ip") || "unknown"; const now = Date.now(); // Initialize or reset rate limit tracking for the client if (!this.reqCounts[clientIP] || now - this.reqCounts[clientIP].start > this.rlWindow) { this.reqCounts[clientIP] = { count: 1, start: now }; } else { this.reqCounts[clientIP].count++; } // Check if the client exceeds the rate limit if (this.reqCounts[clientIP].count > this.maxReq) { return new Response("Too Many Requests: Please try again later.", { status: 429, headers: { "Retry-After": "60" }, // Suggests a retry after 60 seconds }); } const url = new URL(req.url); const searchParams = new URLSearchParams(); // Normalize query parameters to lowercase for (const [key, value] of url.searchParams) { searchParams.append(key.toLowerCase(), value); } // Check if "param_id" is missing if (!searchParams.has("param_id")) { return this.helpScreen(); } const paramId = searchParams.get("param_id"); const timezone = parseFloat(searchParams.get("timezone")) || 0; const bgColor = searchParams.get("bg_color") || "#f5deb3"; const footer = searchParams.get("footer") || "HowMuchRadiation.com"; const showBorder = searchParams.get("show_border") !== "false"; // Customizable color variables with defaults const colors = { radiationSymbol: searchParams.get("radiation_symbol_color") || "black", headline: searchParams.get("headline_color") || "black", reading: searchParams.get("reading_color") || "red", dateTime: searchParams.get("date_time_color") || "black", }; try { const jsonData = await this.getGMCData(paramId); return this.radiationScreen( jsonData, timezone, bgColor, showBorder, footer, colors ); } catch (error) { return this.errorScreen(bgColor, showBorder, footer); } }, // Fetch data from GMCmap API async getGMCData(paramId) { const jsonUrl = `https://www.gmcmap.com/historyData-plain.asp?Param_ID=${paramId}&n=1`; const response = await fetch(jsonUrl); if (!response.ok) { throw new Error(`Failed to fetch JSON data: ${response.status} ${response.statusText}`); } const jsonData = await response.json(); if (!jsonData || jsonData.length === 0) { throw new Error("No data available from GMCmap."); } return jsonData[0]; // Only need the most recent record }, // Generate Help Screen helpScreen() { const helpText = ` Widget Instructions 1. Add GMCmap ?param_id=1234567890 to the URL (required). 2. Optional: Add &timezone=OFFSET to specify time zone (default: 0). 3. Optional: Add &show_border=false to disable the border (default: border is enabled). 4. Optional: Add color parameters for customization: - radiation_symbol_color: Sets the color of the radiation symbol. - headline_color: Sets the color of the headline text. - reading_color: Sets the color of the radiation reading text. - date_time_color: Sets the color of the date and time text. - bg_color: Sets the background color (default: #f5deb3). Example: ?param_id=1234567890&timezone=-5&bg_color=white Note: This widget depends on gmcmap.com and will not work if the service is down. Disclaimer: This widget is not affiliated with or authorized by gmcmap.com. Use at your own risk. It is offered "as is" without any guarantees of accuracy, reliability, or availability. HowMuchRadiation.com `; return new Response(helpText, { headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" }, }); }, // Generate Error Screen errorScreen(bgColor, showBorder, footer) { const errorSvg = ` ${showBorder ? `` : ""} GMCmap Data unavailable. Check param_id or try later. ${footer} `; return new Response(errorSvg, { headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" }, }); }, // Generate Radiation Screen radiationScreen(data, timezone, bgColor, showBorder, footer, colors) { const { uSv, time, CPM, ACPM } = data; const formattedTime = this.formatTimestamp(time, timezone); const svgContent = ` ${showBorder ? `` : ""} CURRENT RADIATION ${parseFloat(uSv).toFixed(2)} μSv/h | ${CPM} CPM | ${parseFloat(ACPM).toFixed(2)} ACPM ${formattedTime} ${footer} `; return new Response(svgContent, { headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" }, }); }, formatTimestamp(time, timezone) { const utcDate = new Date(); const localDate = new Date(utcDate.getTime() + timezone * 60 * 60 * 1000); return localDate.toLocaleString(); }, };