RSROC Event Details

Extract event details and display them on the calendar page

当前为 2025-05-24 提交的版本,查看 最新版本

// ==UserScript==
// @name RSROC Event Details
// @namespace http://tampermonkey.net/
// @version 0.5
// @description Extract event details and display them on the calendar page
// @author Cheng Hsien Tsou
// @match https://www.rsroc.org.tw/action/*
// @grant none
// @license MIT
// ==/UserScript==

/*
這個script使用來擷取RSROC網站上的活動資訊,並將教育積分時數顯示在活動頁面上。
產生google calendar的連結,並在滑鼠懸停時顯示活動內容和聯絡資訊的tooltip。
*/


// Immediately-invoked function expression (IIFE) to encapsulate the script
(function() {
    'use strict';

    // Function to fetch event details from a given URL
    async function fetchEventDetails(url) {
        try {
            // Fetch the HTML content of the event page
            const response = await fetch(url);
            const html = await response.text();
            // Parse the HTML string into a DOM document
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');

            // Initialize variables to store extracted details
            let educationPoints = '';
            let recognizedHours = '';
            let eventDateTime = '';
            let eventLocation = '';
            let eventTitle = '';
            let eventContent = '';
            let contactInfo = '';

            // Select all table rows within the article content
            const rows = doc.querySelectorAll('.articleContent table tr');
            // Iterate over each row to find specific details
            for (const row of rows) {
                const th = row.querySelector('th');
                if (th) {
                    // Check the header text to identify the data
                    if (th.innerText.includes('活動日期')) {
                        const td = row.querySelector('td');
                        if (td) eventDateTime = td.innerText.trim();
                    }
                     if (th.innerText.includes('主辦單位')) {
                         // Extract event title from caption
                         const caption = doc.querySelector('.tableContent caption');
                         if (caption) eventTitle = caption.innerText.trim();
                    }
                    if (th.innerText.includes('活動地點')) {
                        const td = row.querySelector('td');
                        if (td) eventLocation = td.innerText.trim();
                    }
                     if (th.innerText.includes('活動內容')) {
                        const td = row.querySelector('td');
                        if (td) eventContent = td.innerText.trim();
                    }
                    if (th.innerText.includes('教育積點')) {
                        const td = row.querySelector('td');
                        if (td) educationPoints = td.innerText.replace('放射診斷科專科醫師', '').trim();
                    }
                    if (th.innerText.includes('認定時數')) {
                        const td = row.querySelector('td');
                        if (td) recognizedHours = td.innerText.trim();
                    }
                     if (th.innerText.includes('聯絡資訊')) {
                        const td = row.querySelector('td');
                        if (td) contactInfo = td.innerText.trim();
                    }
                }
            }
            // Return an object containing the extracted details
            return { educationPoints, recognizedHours, eventDateTime, eventLocation, eventTitle, eventContent, contactInfo };
        } catch (error) {
            // Log any errors during fetching or parsing
            console.error('Error fetching event details:', error);
            // Return default values in case of an error
            return { educationPoints: 'N/A', recognizedHours: 'N/A', eventDateTime: '', eventLocation: '', eventTitle: 'Event', eventContent: '', contactInfo: '' };
        }
    }

    // Function to format the date and time string for Google Calendar
    function formatGoogleCalendarDate(dateTimeString) {
        // Expected format: YYYY/MM/DD 星期X HH:MM ~ HH:MM
        // Use regex to extract date and time components
        const parts = dateTimeString.match(/(\d{4})\/(\d{2})\/(\d{2}).*?(\d{2}):(\d{2})\s*~*\s*(\d{0,2})*:*(\d{0,2})/);
        if (!parts) return null; // Return null if the format doesn't match

        // Extract components from regex parts
        const year = parts[1];
        const month = parts[2];
        const day = parts[3];
        const startHour = parts[4];
        const startMinute = parts[5];
        const endHour = parts[6] || startHour; // Assume same hour if end hour is missing
        const endMinute = parts[7] || startMinute; // Assume same minute if end minute is missing

        // Google Calendar format: YYYYMMDDTHHMMSS/YYYYMMDDTHHMMSS
        const start = `${year}${month}${day}T${startHour}${startMinute}00`;
        const end = `${year}${month}${day}T${endHour}${endMinute}00`;

        // Return the formatted date string
        return `${start}/${end}`;
    }

    // Main function to add event details and Google Calendar links to the page
    async function addEventDetailsToCalendar() {
        // Select all event links on the page
        const eventLinks = document.querySelectorAll('.eventLink');

        // Create tooltip element for displaying event content and contact info on hover
        const tooltip = document.createElement('div');
        tooltip.style.cssText = `
            position: absolute;
            background-color: #fff;
            border: 1px solid #ccc;
            padding: 10px;
            z-index: 1000;
            display: none;
            font-size: 0.9em;
            color: #333;
            max-width: 300px;
            word-wrap: break-word;
            pointer-events: none; /* Allow clicks to pass through */
        `;
        document.body.appendChild(tooltip);

        // Iterate over each event link
        for (const link of eventLinks) {
            const url = link.href;
            const eventDiv = link.querySelector('.event');
            const eventText = eventDiv.innerText;

            // Fetch details for the current event
            const details = await fetchEventDetails(url);

            // Store fetched details as data attributes on the eventDiv for later use (e.g., tooltip)
            eventDiv.dataset.eventContent = details.eventContent;
            eventDiv.dataset.contactInfo = details.contactInfo;
            eventDiv.dataset.eventTitle = details.eventTitle;
            eventDiv.dataset.eventDateTime = details.eventDateTime;
            eventDiv.dataset.eventLocation = details.eventLocation;

            // Check if there are education points, recognized hours, or event date/time to display
            if (details.educationPoints !== 'N/A' || details.recognizedHours !== 'N/A' || details.eventDateTime) {
                // Create a div to display more information
                const moreInfoDiv = document.createElement('div');
                moreInfoDiv.classList.add('moreinfo');
                moreInfoDiv.style.fontSize = '0.8em';
                moreInfoDiv.style.color = 'gray';
                // Display education points and recognized hours
                moreInfoDiv.innerHTML = `${details.educationPoints}<br/>時數: ${details.recognizedHours}`;

                // If event date/time is available, create a Google Calendar link
                if (details.eventDateTime) {
                    const googleCalendarDate = formatGoogleCalendarDate(details.eventDateTime);
                    if (googleCalendarDate) {
                        const googleCalendarLink = document.createElement('a');
                        // Construct the details for the calendar event
                        const calendarDetails = `活動內容: ${details.eventContent}\n\n聯絡資訊: ${details.contactInfo}\n\n原始連結: ${url}`;
                        // Create the Google Calendar URL with event details
                        googleCalendarLink.href = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(details.eventTitle)}&dates=${googleCalendarDate}&details=${encodeURIComponent(calendarDetails)}&location=${encodeURIComponent(details.eventLocation)}`;
                        googleCalendarLink.target = '_blank'; // Open link in a new tab
                        googleCalendarLink.innerText = '📅'; // Calendar emoji as link text
                        googleCalendarLink.style.marginLeft = '5px'; // Add some spacing
                        moreInfoDiv.appendChild(googleCalendarLink); // Append the link to the info div
                    }
                }

                // Append the more info div to the event div
                eventDiv.appendChild(moreInfoDiv);
            }

            // Add hover event listeners to the event div for showing/hiding the tooltip
            eventDiv.addEventListener('mouseover', (event) => {
                // Get event content and contact info from data attributes
                const content = event.target.dataset.eventContent || '無活動內容';
                const contact = event.target.dataset.contactInfo || '無聯絡資訊';
                // Set the tooltip content
                tooltip.innerHTML = `<strong>活動內容:</strong><br>${content}<br><br><strong>聯絡資訊:</strong><br>${contact}`;

                // Position the tooltip below the hovered element
                const rect = event.target.getBoundingClientRect();
                tooltip.style.left = `${rect.left + window.scrollX}px`;
                tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`; // 5px below the element
                tooltip.style.display = 'block'; // Show the tooltip
            });

            eventDiv.addEventListener('mouseout', () => {
                tooltip.style.display = 'none'; // Hide the tooltip on mouse out
            });
        }
    }

    // Run the script after the page has fully loaded
    window.addEventListener('load', addEventDetailsToCalendar);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址