From 0a3e994e99de320f5535343893d0e60a69ed1777 Mon Sep 17 00:00:00 2001 From: Roslund Date: Thu, 14 Aug 2025 18:32:49 +0000 Subject: [PATCH] deploy: 1b2c015b1ed82c17067aa1a43a57856eafd7797e --- index.xml | 8 +- js/rsvp-tracker.js | 333 +++++++++++++++++++++++++++++++++++++++++++++ meetups/index.html | 24 ++-- sitemap.xml | 2 +- 4 files changed, 350 insertions(+), 17 deletions(-) create mode 100644 js/rsvp-tracker.js diff --git a/index.xml b/index.xml index bb92f80..556fc4a 100644 --- a/index.xml +++ b/index.xml @@ -180,11 +180,11 @@ Detta har gjort WisBlock RAK4631 det självklara valet vid bygget av en solnod.& <h1 class="text-center text-primary">🍻 Meshtastic AW i Stockholm! 🍻</h1> <p class="lead text-center">Träffa likasinnade, snacka LoRa och bygg ut nätverket i Stockholm!</p> <div class="text-center my-4"> -<strong>📍 Plats:</strong> <span>TBD</span><br> -<strong>📅 Datum:</strong> <span>TDB</span><br> -<strong>⏰ Tid:</strong> <span>TDB</span> +<strong>📍 Plats:</strong> <span>Midsommarköket, Svandammsparken (T) Midsommarkransen</span><br> +<strong>📅 Datum:</strong> <span>Tisdag 21 augusti</span><br> +<strong>⏰ Tid:</strong> <span>17:00 (baren öppnar 15:00)</span> </div> -<!--<p class="text-center">Våren närmar sig och det är massvis med trafik i meshen. Det har dessutom tillkommit massvis med nya noder och personer. Vi bjuder därför in till After Work för de som vill träffa likasinnade, snacka LoRa, dela erfarenheter och visa hemmabyggen.</p>Om STHLM-MESHhttps://sthlm-mesh.se/about/Mon, 01 Jan 0001 00:00:00 +0000https://sthlm-mesh.se/about/<link rel="preload" as="image" href="https://sthlm-mesh.se/about/background-sunset_hu_e3de9a257b7b9dc5.jpeg" media="(max-width: 1200px)"> +<p class="text-center">Nu är det dags för en Meshtastic AW i Stockholm igen! Denna gång hoppas vi på bra väder och träffas på baren i Svandammsparken. Kom och träffa likasinnade, snacka LoRa och bygg ut nätverket i Stockholm!</p>Om STHLM-MESHhttps://sthlm-mesh.se/about/Mon, 01 Jan 0001 00:00:00 +0000https://sthlm-mesh.se/about/<link rel="preload" as="image" href="https://sthlm-mesh.se/about/background-sunset_hu_e3de9a257b7b9dc5.jpeg" media="(max-width: 1200px)"> <link rel="preload" as="image" href="https://sthlm-mesh.se/about/background-sunset_hu_91e95a2bc8ac4fa2.jpeg" media="(min-width: 1200px)"> <style> #td-cover-block-0 { diff --git a/js/rsvp-tracker.js b/js/rsvp-tracker.js new file mode 100644 index 0000000..21fc86a --- /dev/null +++ b/js/rsvp-tracker.js @@ -0,0 +1,333 @@ +/** + * RSVP Tracker for Meshtastic Events + * Tracks attendance responses from LoRa mesh messages + */ + +class RSVPTracker { + constructor(eventLabel) { + this.eventLabel = eventLabel; + this.state = { + messages: [], + nodesById: {}, + rsvpResponses: new Map() // nodeId -> latest response + }; + + // RSVP response patterns + this.responsePatterns = { + 'kommer': ['kommer', 'yes', 'ja', 'attending', 'deltar'], + 'kanske': ['kanske', 'maybe', 'unsure', 'oklart', 'tvekar'], + 'kommer inte': ['kommer inte', 'no', 'nej', 'not attending', 'kan inte', 'cannot attend'] + }; + } + + /** + * Parse RSVP message format: "EventLabel - Response" + * Example: "AW 21/8 - Kommer" + */ + parseRSVPMessage(messageText) { + const text = messageText.trim().toLowerCase(); + + // Check if message matches our event pattern + const eventPattern = this.eventLabel.toLowerCase(); + if (!text.includes(eventPattern)) { + return null; + } + + // Extract the response part after the separator + const separatorIndex = text.indexOf(' - '); + if (separatorIndex === -1) { + // Try alternative separators + const altSeparators = [' -', '- ', '-', ':']; + let response = null; + + for (const sep of altSeparators) { + const sepIndex = text.indexOf(sep); + if (sepIndex !== -1 && text.substring(0, sepIndex).includes(eventPattern)) { + response = text.substring(sepIndex + sep.length).trim(); + break; + } + } + + if (!response) return null; + } else { + const response = text.substring(separatorIndex + 3).trim(); + } + + // Determine response type + for (const [responseType, patterns] of Object.entries(this.responsePatterns)) { + for (const pattern of patterns) { + if (text.includes(pattern)) { + return { + type: responseType, + originalText: messageText, + isValid: true + }; + } + } + } + + return null; + } + + /** + * Fetch messages from the API and parse RSVP responses + */ + async fetchRSVPData() { + try { + const response = await fetch('https://map.sthlm-mesh.se/api/v1/text-messages?order=desc&count=500'); + const data = await response.json(); + + // Filter duplicate messages and sort by timestamp + this.state.messages = Array.from( + new Map(data.text_messages.map(msg => [msg.packet_id, msg])).values() + ).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + + // Parse RSVP responses + this.parseRSVPResponses(); + + // Fetch node information for all RSVP responders + await this.fetchNodeInfoForRSVPs(); + + return this.getRSVPSummary(); + + } catch (error) { + console.error('Error fetching RSVP data:', error); + throw error; + } + } + + /** + * Parse all messages for RSVP responses + */ + parseRSVPResponses() { + this.state.rsvpResponses.clear(); + + for (const message of this.state.messages) { + const rsvp = this.parseRSVPMessage(message.text); + if (rsvp) { + // Only keep the latest response from each node + const existingResponse = this.state.rsvpResponses.get(message.from); + if (!existingResponse || new Date(message.created_at) > new Date(existingResponse.timestamp)) { + this.state.rsvpResponses.set(message.from, { + nodeId: message.from, + response: rsvp.type, + originalText: rsvp.originalText, + timestamp: message.created_at, + messageId: message.id + }); + } + } + } + } + + /** + * Fetch node information for all RSVP responders + */ + async fetchNodeInfoForRSVPs() { + const nodeIds = Array.from(this.state.rsvpResponses.keys()); + + for (const nodeId of nodeIds) { + if (!this.state.nodesById[nodeId]) { + try { + const response = await fetch(`https://map.sthlm-mesh.se/api/v1/nodes/${nodeId}`); + const data = await response.json(); + if (data.node) { + this.state.nodesById[nodeId] = data.node; + } + } catch (error) { + console.warn(`Failed to fetch node info for ${nodeId}:`, error); + } + } + } + } + + /** + * Get summary of RSVP responses + */ + getRSVPSummary() { + const summary = { + kommer: [], + kanske: [], + 'kommer inte': [], + total: 0 + }; + + for (const [nodeId, rsvp] of this.state.rsvpResponses) { + const node = this.state.nodesById[nodeId]; + const attendee = { + nodeId: nodeId, + shortName: node?.short_name || '?', + longName: node?.long_name || `!${parseInt(nodeId).toString(16)}`, + response: rsvp.response, + timestamp: rsvp.timestamp, + originalText: rsvp.originalText + }; + + if (summary[rsvp.response]) { + summary[rsvp.response].push(attendee); + } + summary.total++; + } + + // Sort each category by timestamp (most recent first) + Object.keys(summary).forEach(key => { + if (Array.isArray(summary[key])) { + summary[key].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); + } + }); + + return summary; + } + + /** + * Generate HTML for displaying RSVP results + */ + generateRSVPHTML(summary) { + const html = ` +
+

📋 Anmälningsstatus

+

Skicka meddelande över LoRa-meshen: "${this.eventLabel} - [Kommer/Kanske/Kommer inte]"

+ +
+
+
+
+
✅ Kommer (${summary.kommer.length})
+
+
+ ${this.generateAttendeeList(summary.kommer)} +
+
+
+ +
+
+
+
❓ Kanske (${summary.kanske.length})
+
+
+ ${this.generateAttendeeList(summary.kanske)} +
+
+
+ +
+
+
+
❌ Kommer inte (${summary['kommer inte'].length})
+
+
+ ${this.generateAttendeeList(summary['kommer inte'])} +
+
+
+
+ +
+ + Totalt ${summary.total} svar • + Senast uppdaterad: ${new Date().toLocaleString('sv-SE')} + +
+
+ `; + + return html; + } + + /** + * Generate attendee list HTML + */ + generateAttendeeList(attendees) { + if (attendees.length === 0) { + return '

Inga svar än

'; + } + + return attendees.map(attendee => ` +
+
+ ${attendee.shortName.substring(0, 4)} +
+
+ +
+ ${new Date(attendee.timestamp).toLocaleString('sv-SE', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + })} +
+
+
+ `).join(''); + } + + /** + * Get node color based on ID + */ + getNodeColour(nodeId) { + return "#" + (nodeId & 0x00FFFFFF).toString(16).padStart(6, '0'); + } + + /** + * Get example message format + */ + getExampleMessage() { + return `${this.eventLabel} - Kommer`; + } +} + +/** + * Generate container ID from event label + */ +function generateContainerId(eventLabel) { + return 'rsvp-tracker-' + eventLabel.toLowerCase().replace(/[^a-z0-9]/g, '-'); +} + +/** + * Initialize RSVP tracking for a specific event + */ +async function initRSVPTracker(eventLabel) { + const tracker = new RSVPTracker(eventLabel); + const containerId = generateContainerId(eventLabel); + const container = document.getElementById(containerId); + + if (!container) { + console.error(`Container with ID '${containerId}' not found. Make sure you have:
`); + return; + } + + try { + // Show loading state + container.innerHTML = '

Laddar anmälningar...

'; + + // Fetch and display RSVP data + const summary = await tracker.fetchRSVPData(); + container.innerHTML = tracker.generateRSVPHTML(summary); + + // Set up auto-refresh every 60 seconds + setInterval(async () => { + try { + const summary = await tracker.fetchRSVPData(); + container.innerHTML = tracker.generateRSVPHTML(summary); + } catch (error) { + console.error('Error refreshing RSVP data:', error); + } + }, 60000); + + } catch (error) { + container.innerHTML = `
Kunde inte ladda anmälningar: ${error.message}
`; + console.error('Error initializing RSVP tracker:', error); + } +} + +// Export for use in other scripts +window.RSVPTracker = RSVPTracker; +window.initRSVPTracker = initRSVPTracker; diff --git a/meetups/index.html b/meetups/index.html index 7547028..0304a79 100644 --- a/meetups/index.html +++ b/meetups/index.html @@ -1,16 +1,16 @@ Meetups | STHLM-MESH

Kommande Meetups

🍻 Meshtastic AW i Stockholm! 🍻

Träffa likasinnade, snacka LoRa och bygg ut nätverket i Stockholm!

📍 Plats: TBD
📅 Datum: TDB
⏰ Tid: TDB

Tidigare meetups:

  • Datum: 2025-04-08
  • Tid: 17:00 - 22:00
  • Plats: The Bishops Arms, Sundbyberg
  • Event: Facebook

  • Datum: 2024-09-04
  • Tid: 17:00 - 22:00
  • Plats: The Bishops Arms, Sundbyberg
  • Event: Facebook

  • Datum: 2024-05-05
  • Tid: 17:00
  • Plats: Takpark by Urban Deli, Sveavägen 44